express、跨域请求头、jsonp<前端学习笔记>

Express

Express初识

作用

构建Web服务器

对于程序员来说,最常见的两种服务器:

  • W e b 网 站 服 务 器 \color{red}{Web网站服务器} Web:专门对外提供Web 网页资源的服务器
  • A p i 接 口 服 务 器 \color{red}{Api接口服务器} Api:专门对外提供API接口的服务器

使用Express,我可以方便、快速的创建 Web网站 的服务器或 API接口

创建基本的Web服务
const express = require('express')
// 创建web服务器
const app = express()
//调用 app.listen(ip,port,回调函数),启动服务器
app.listen(ip,port,()=>{
    console.log(`express server running at http://${ip}:${port}`)
})


 //ndoejs 下的衍生框架  可以访问ejs文件  ejs动态数据
const express = require('express');
const morgan = require('morgan'); //中间件
const app = express();
//接受post数据
const bodyParser = require("body-parser");

//与Blog路由产生关联
const blogRoutes = require("./routes/BlogRoutes");

const port = process.env.PORT || 8081;
const host = process.env.HOST || "127.0.0.1";



app.set('view engine', "ejs");//声明使用什么文件(寻找)
// app.set("views","my-views");//views 文件夹必须,按此改名



//使用中间件
// app.use((req, res, next)=>{
//     console.log("获得新请求");
//     console.log("host:",req.hostname);
//     console.log("path:",req.path);
//     console.log("method:",req.method);
//     next();//指示下一步,否则一直执行中间件
// })

app.use(express.static("public"));//静态资源文件夹
app.use(morgan("tiny"));

//使用中间件,配置接受内容的中间件,
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

app.get('/',(req, res)=>{
    // res.send("<p>Hello</p>")
    //项目路径root + 当前路径
    // res.sendFile("./views/index.html",{root:__dirname});

    //访问ejs页面  render可传值(对象 数组对象 对象数组 json)
    // const blogs = [
    //     {title:"Vue.js快速学习指南",snippet:"使用范围最广,学习人数最多的框架"},
    //     {title:"React.js快速学习指南",snippet:"使用范围最广,学习人数最多的框架"},
    //     {title:"Node.js快速学习指南",snippet:"使用范围最广,学习人数最多的框架"}
    // ]
    // res.render('index',{title:"首页",blogs});

    //重定向到博客页面
    res.redirect("/blogs");
});



中间件此处无效
// app.use((req, res, next)=>{
//     console.log("只在req和res之间生效");
//     next();//指示下一步,否则一直执行中间件
// })


app.get('/about',(req, res)=>{
    // res.sendFile("./views/about.html",{root:__dirname});
    res.render('about',{title:"关于我们"});
});


// app.get('/about_us',(req, res)=>{
//     res.redirect('/about');
// });

//可定义根路由
app.use("/blogs",blogRoutes);


//中间件 的执行 一定是在req和res之间
//中间件 一定是有一定作用
app.use((req, res)=>{
    // res.status(404).sendFile("./views/404.html",{root:__dirname})
    res.status(404).render('404',{title:"404"});
})

app.listen(port,host,()=>{
    console.log(`服务器运行中  ${host}:${port}`)
});
获取URL中携带的参数查询

通过 req.query 对象,可以访问到客户端通过 查询字符串 的形式,发送到服务器的参数:

app.get('/',(req,res)=>{
    //req.query 默认是一个空对象
    // 客户端使用 ?name=zs&age=20 这种查询字符串形式,发送到服务器的参数
    // 可以通过 req.query 对象访问到:
    // req.query.name == zs
    console.log(req.query)
})
获取URL中的动态参数

通过 req.params 对象,可以访问到URL中,通过 “:” 匹配到的动态参数:

//:id 是动态参数 
app.get('/delete/:id',(req,res)=>{
     // req.params 匹配动态参数(键值对),默认为空对象
    console.log(req.params)
    res.send(req.params)
})
托管静态资源

express.static() 创建一个 静态资源服务器

  • 将public 目录下的图片、CSS文件、JavaScript文件对外开发:
app.use(express.static('public'))
// 不需要写全路径,会自动到静态资源文件夹去寻找,设置多个静态资源文件夹时按顺序查找
  • Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录不会出现在URL中。
    访问public下的 index.html ===> http://localohost:80/index.html
挂载路径前缀

设置通过带有/public 前缀的地址来访问public目录中的文件:

app.use('/public',express.static('public'))

http://localohost:80/public/index.html

Express路由

概念
  • 客户端请求 与 服务器处理函数 之间的映射关系

  • Express中的路由份3部分组成,分别是 请求的类型、请求的URL地址、处理函数

app.METHOD(PATH,HANDLER)
路由匹配过程
  • 按照路由顺序进行匹配,从上到下
  • 请求类型和请求的URL 同时匹配成功,才会方法对应的处理函数
模块化路由
  • 为了 方 便 对 路 由 进 行 模 块 化 管 理 \color{blue}{方便对路由进行模块化管理} 便,Express 不建议将路由直接挂载到app(express服务器实例)上,而是 推 荐 将 路 由 抽 离 为 单 独 的 模 块 \color{red}{推荐将路由抽离为单独的模块}
  • 步骤:
  1. 创建路由模块对应的 .js 文件 (用户自定义模块,专门负责挂载路由)
  2. 调用 express.Router() 函数创建路由的实例对象 (express() 返回服务器实例)
  3. 向路由对象上挂载具体的路由
  4. 使用 module.exports 向外共享路由对象
  5. 使用app.use() 函数注册路由模块 ·
// 路由模块 UserRouters.js

//登录  注册 接口
const express = require('express');
//能使用user创建对应路由
const users = express.Router();
//生成token
const jwt = require("jsonwebtoken");
//密码加密
const bcrypt = require('bcryptjs'); 
//引入模型。与模型关联,实现增删改查
const User = require('../model/User');

//dzw_secret放入环境变量
process.env.SECRET_KEY = "dzw_secret";


//localhost:5000/api/v1/test
users.get("/test", (req, res) => {
    res.send({ msg: "test is working." })
});

//注册接口 调试时 post需要数据
users.post("/register", (req, res) => {
    console.log(req.body);
    let userData = {
        username: req.body.username,
        password: req.body.password
    }
    //注册检查  findAll返回一个数组对象
    User.findOne({ 
        where: { 
            username: req.body.username 
        } 
    }).then((user) => {
        if (!user) {
            //加密
            bcrypt.hash(req.body.password,10,(err,hash)=>{
                userData.password = hash;

                User.create(userData).then((user) => {
                    res.json({ status: user.username + "registered" });
                }).catch((err)=>{
                    res.send("error:"+err);
                });
            });

        } else {
            //用户已存在
            res.json({ error: "User already exists!" });
        }
    }).catch((err) => {
        res.send("error:" + err);
    });
});


//登录接口
users.post("/login", (req,res)=>{
    // console.log(req.body);
    //查询数据
    User.findOne({
        where:{
            username: req.body.username
        }
    }).then((user)=>{
        //查到用户
        if (user){
            //匹配密码  加密匹配compareSync
            if (bcrypt.compareSync(req.body.password,user.password)){
                // 创建cookies
                // res.cookie("loginToken",user.dataValues,{
                //     maxAge:1000 * 60 * 15,
                //     httpOnly:true
                // });
                //生成token 签名
                let token = jwt.sign(user.dataValues,process.env.SECRET_KEY,{
                    expiresIn:1000//过期时间
                })
                res.send(token);
            }else{
                res.send("用户名或密码错误!");
            }
        }else{
            res.status(400).json({error:"User does not exist"})
        }
    }).catch((err)=>{
        res.send("error:"+err);
    });
});

module.exports = users;//给login_server使用

// 登录模块使用路由 login_sever.js

const express = require('express');
const app = express();
const port = process.env.PORT || 5000; //获取服务器的口号
const bodyParser = require('body-parser');//获取POST传递的数据

app.use(bodyParser.json());//获取JSON数据
app.use(bodyParser.urlencoded({extended: false}));//urlencoded数据
//添加路由
app.get('/', (req,res)=>{
    res.send({msg:"Server is working."});
});

const User = require("./routes/UserRouters");
//localhost:5000/api  到Users寻找路由
app.use("/api/v1",User);

app.listen(port,()=>{
    console.log("Server is running on port:"+ port);
});

中间件

特指业务流程中间处理环节

  • 当一个请求到达 Express 的服务器后,可以连续调用多个中间件,从而对这次请求进行预处理
  • Express的中间件,本质上是一个 function 处理函数,中间件的形参列表中必须包含 next 参数。
  • next 表示把流转关系转交给下一个 中间件或路由
全局生效的中间件
  • 客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件
  • 通过调用 app.use(中间件函数)
局部生效的中间件
  • 不使用 app.use()定义的中间件,叫做局部中间件
// 定义中间件函数mw1
const mw1 = function(req,res,next){
    console.log('这是中间件函数')
    next()
}

// 当前路由中生效 中间可加入多个中间件
app.get('/',mw1,functino(req,res){
    res.send('Home Page')
})
中间件的作用
  • 多个中间件之间,共享同一份 req和res。 上游挂载属性,下游访问
注意事项
  1. 在 路由之前 注册中间件
  2. 客户端发送过来的请求,可以连续调用多个 中间件进行处理
  3. 执行完毕中间件的业务代码之后,不要忘记调用 next()函数
  4. 为了防止代码逻辑混乱,调用 next() 函数后不再写额外代码
  5. 多个中间件共享 req和res对象
中间件的分类
  1. 应用级别的中间件

    • 通过 app.use()或 app.get()或app.post() ,绑定到app实例上的中间件,叫做应用级别的中间件(全局中间件,局部中间件)
  2. 路由级别的中间件

    • 绑定到 express.Router()实例上的中间件,叫做路由级别的中间件,绑定到router实例上
  3. 错误级别的中间件

    • 专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
    • 格式: 多一个参数 ==> function(err,req,res,next)。
    • 必须注册在所有路由之后,否则无法捕获错误。
  4. Express内置的中间件

    • 内置3个常用中间件:
    1. e x p r e s s . s t a t i c \color{blue}{express.static} express.static 快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性)
    2. e x p r e s s . j s o n \color{blue}{express.json} express.json 解析JSON格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用 )
    3. e x p r e s s . u r l e n c o d e d \color{blue}{express.urlencoded} express.urlencoded 解析URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
// 配置解析 application/json 格式数据的内置中间件
app.use(express.json())
// 配置解析 application/x-www-form-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({extended:false}))

app.post('/user',(req,res)=>{
    //在服务器,可以使用 req.body 这个属性,来接受客户端发送过来的请求体数据
    // 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
    console.log(req.body)
    res.send("ok")
})
  1. 第三方的中间件
自定义中间件

手动模拟一个类似 express.urlencoded 这样的中间件,来解析POST提交到服务器的表单数据

实现步骤:

  1. 定义中间件
  2. 监听 req 的 data 事件 (触发data事件,证明有数据提交到服务器)
    • 在中间件中,需要监听 req 对象的data事件,来获取客户端发送到服务器的数据
    • 数据量比较大, 客 户 端 会 把 数 据 切 割 后 , 分 批 发 送 到 服 务 器 \color{red}{客户端会把数据切割后,分批发送到服务器} 。所以data事件可能触发多次,每一次触发data事件时, 获 取 到 的 数 据 只 是 完 整 数 据 的 一 部 分 \color{blue}{获取到的数据只是完整数据的一部分} ,需要手动对接收到的数据进行拼接。
    //定义变量存储 客户端发送来的数据
    let str = ''
    //监听 req 对象的 data 事件(客户端发送过来的新的请求体数据)
    req.on('data',(chunk)=>{
        //拼接请求体数据,隐式转换为字符串
        str += chunk
    })
    
  3. 监听 req 的 end 事件 (触发end事件,证明数据发送完毕,服务器端完整接收到post提交的数据)
    • 当请求体数据 接 收 完 毕 \color{red}{接收完毕} 之后,会自动触发 req 的 end 事件。
    • 可以在 req 的 end 事件中, 拿 到 并 处 理 完 整 的 请 求 体 数 据 \color{red}{拿到并处理完整的请求体数据}
    // 监听 req 对象的 end 事件(请求体发送完毕后自动触发)
    req.on('end',()=>{
        // 打印完整的请求体数据
        console.log(str)
        // TODO: 把字符串格式的请求体数据,解析成对象格式
    })
    
  4. 使用 querystring 模块解析请求体数据
  5. 将解析出来的数据对象挂载为 req.body
  6. 将自定义中间件封装为模块
// 解析表单数据的中间件
app.use((req,res,next)=>{

})

CORS跨域资源共享

同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。

使用cors中间件解决跨域问题
// 路由之前
app.use(cors()) //配置中间件
什么时CORS

CORS (Cross-Origin Resource SHaring,跨域资源共享)由一系列 HTTP响应头 组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源

浏览器的 同源安全策略会默认阻止网页“跨域”获取资源,但如果接口服务器配置了CORS相关的HTTP响应头,就可以解决浏览器端的跨域访问限制

服务器响应回的数据被浏览器拦截

CORS的注意事项
  1. CORS 主要在 服务器端进行配置。客户端浏览器无须作任何额外的配置,即可请求开启了CORS的接口
  2. CORS在浏览器中有 兼容性 。只有支持 XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口

CORS 响应头- Access-Control-Allow-Origin

响应头部中可携带一个 Access-Control-Allow-Origin 字段:

 Access-Control-Allow-Origin:<origin> | *

其中,origin 参数的值指定了 允许访问该资源的外域URL

例如,下面的字段值将只允许来自 http://itcast.cn 的请求

res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')

CORS 响应头- Access-Control-Allow-Headers

默认情况下,CORS支持 客户端向服务器 发送如下9个请求头

  • Accept
  • Accept-Language
  • Content-Language
  • DPR
  • Downlink
  • Save-Data
  • Vierport-Width
  • Width
  • Content-Type(值仅限于 text/plain 、 multipart/form-data 、 application/x-www-form-urlencoded 三者之一)

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers额外的请求头进行声明,否则这次请求会失败!

// 允许客户端额外向服务器发送 Content-Type 请求头和 X-Custom-Header 请求头
// 注意:多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers','Content-Type, X-Custom-Header')

CORS 响应头- Access-Control-Allow-Methods

默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。

如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务端,通过 Access-Control-Allow-Methods指明实际请求所允许使用的HTTP方法

// 只允许 POST、GET、DELETE、HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
// 允许所有的HTTP请求方法
res.setHeader(Access-Control-Allow-Methods','*')

简单请求

同时满足以下两大条件的请求:

  1. 请求方式:GET 、POST 、HEAD三者之一
  2. HTTP头部信息 不超过上文提到的9种字段字段:无自定义头部字段

预检请求

只要符合以下任何一个条件,都需要进行预检请求:

  1. 请求方式为GET 、POST 、HEAD之外的请求Method类型
  2. 请求头中包含自定义头部字段
  3. 向服务器发送了application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTINO请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并携带真实数据


简单请求预检请求的区别
  • 简单请求的特点:客户端与服务器之间只会发生一次请求
  • 预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求

JSONP接口

概念与特点

概念:浏览器端通过<script> 标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。

特点

  1. JSONP 不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
  2. JSONP仅支持GET请求,不支持POST、PUT、DELETE等请求。

创建JSONP接口的注意事项

如果项目中已经配置了CORS跨域资源共享,为了防止冲突必须在配置CORS中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS接口:

// 优先创建JSONP 接口【这个接口不会处理成CORS接口】
app.get('/api/jsonp',(req,res)=>{})

// 再配置CORS中间件【后续的所有接口,都会被处理成CORS接口】
app.use(cors())

// 这是一个开启了CORS的接口
app.get('/api/get',(req,res)=>{})

实现JSONP接口步骤
  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过JSONP形式发送给客户端的数据
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行
app.get('/api/jsonps',(req,res)=>{
    // 1. 获取客户端发送过来的回调函数的名字
    const funcName = req.query.callback
    // 2. 得到要通过JSONP 形式发送给客户端的数据
    const data = {name:'zs',age:22}
    // 3. 根据前两步得到的数据,拼接出一个函数调用的字符串
    const scriptStr = `${funcName}(${JSON.stringify(data)})`
    // 4. 把上一步拼接得到的字符串,响应给客户端的 <script>标签进行解析执行
    res.send(scriptStr)
})
在网页中使用jQuery发起JSONP请求

调用$.ajax()函数,提供JSONP的配置选项,从而发起JSONP请求:

$.ajax({
    type:'get',
    url: 'http://127.0.0.1:8181/api/jsonps',
    data:{
        name:1
    },
    // 如果要使用 $.ajax() 发起JSONP请求,必须指定datatype 为 jsonp
    dataType:'jsonp',
    // 发送到服务端的参数名称,默认值为callback
    jsonp:'callback',
    // 自定义的回调函数名称,默认值为jQueryxxx格式
    jsonpCallbaack:'abc',
    success:function(res){
        console.log(res)
    }
})
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值