前端与移动开发----Node.js----express中间件

Node.js

回顾

express是一款快速搭建web服务的一个第三方模块

express是基于http模块进行封装使用的

express既能提供静态资源, 还能提供数据接口

使用方式

首先确保当前工程里已经用npm下载了express模块

const express = require("express");
const app = express();
app.listen(3000);

app.请求方式("请求url", (请求对象, 响应对象) => {})

后台解析前端FormData数据, 需要配合multer模块

express更多用法:

静态资源: app.use(express.static(“静态资源文件夹”)) - 前端可以直接从端口号后直接拼接这下面的路径直接访问

express路由, 其实就是不同的app.请求方式()监测不同的路径, 每一种请求方式+路径 确定一种映射关系和功能

13. 中间件

什么是中间件

在这里插入图片描述

  • 在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节
  • 我们称其中的每一个处理环节就是一个中间件。
  • 这样做的目的既提高了生产效率也保证了可维护性。

每个中间件上一次的输出, 是下一个中间件的输入

中间件的作用

中间件的作用, 就是对请求进行预处理

当一个请求到达Express服务器之后, 可以连续调用多个中间件, 从而对着此次请求进行预处理

在这里插入图片描述

中间件的形式

中间件 - 本质上就是一个 函数

在这里插入图片描述

  • next函数: 多个连续中间件调用的关键, 把流程转交给下一个中间件函数或者路由
    • 中间件函数 带有 next形参
    • 路由 没有next形参, 会调用res.send结束本次请求和响应

在这里插入图片描述

13.0 全局中间件

app.use() - 加载用于处理任何请求的中间件函数

const express = require("express");
const app = express();
app.listen(3000, () => console.log("启动成功, http://localhost:3000"));

// 定义中间件函数 - 函数体
// 给服务器注册全局生效的中间件 app.use()
app.use(function(req, res, next){
    console.log("我是一个中间件函数, 我处理完了");
    next(); // 调用next函数, 才会传递给下一个函数处理(按照编写顺序)
});

// 路由 - 处理完毕 - 返回响应数据
app.get("/api/list", (req, res) => {
    res.send("get请求 /api/list");
})

app.get("/api/book", (req, res) => {
    res.send("get请求 /api/book");
})

// 执行流程:
// 请求过来 -> 从上到下 -> 先执行中间件函数 -> next() -> 向下匹配路由的路径, 如果请求的url是/api/book (第一个路由不匹配, 继续匹配下一个, 如果匹配执行然后res.send结束响应返回数据) / 假如都不匹配则express默认返回一个找不到路径的网页给前端

13.1 全局中间件 - 使用场景

可以把公共的东西, 提到路由以上的中间件函数中处理

// 需求: 我想返回给前端, 现在服务器的系统时间
// 可以提到公共的中间件里来写 - 因为所有的中间件都共享同一个req和 res对象
app.use((req, res, next) => {
    res.nowDate = momentObj.format("YYYY-MM-DD HH:mm:ss");
    // new Date() 获取的是标准时间, 比北京时间少8小时
    next();
})

app.get("/api/getbook", (req, res) => {
    res.send({
        msg: "get方式/api/getbooks被访问了",
        time: res.nowDate
    })
})

app.post("/api/addbook", (req, res) => {
    res.send({
        msg: "post方式/api/addbook",
        time: res.nowDate
    })
})

13.2 全局中间件 - 多个

app.use((req, res, next) => { // 时间处理中间件
    res.nowDate = momentObj.format("YYYY-MM-DD HH:mm:ss");
    next();
})
app.use((req, res, next) => { // 求get参数上的和
    req.thesum = req.query.num1 * 1 + req.query.num2 * 1 + req.query.num3 * 1;
    next();
})

app.get("/api/sum", (req, res) => {
    res.send({
        msg: "get方式/api/getbooks被访问了",
        data: req.thesum,
        time: res.nowDate
    })
})

13.3 局部中间件

  • 只对某个路由生效的中间件, 写在路由的参数2后面(从参数2往后都是函数体)
app.get("/api/list", (req, res, next) => {
    console.log("GET方式/api/list, 中间件被触发");
    next(); // 继续匹配/调用下一个函数
}, (req, res) => {
    res.send("结束本次请求 - GET方式/api/list");
})

app.get("/api/book", (req, res) => {
    res.send("GET方式, 访问了/api/book");
})

13.4 局部中间件 - 使用场景

刚刚的13.2, 如果求和那个, 不传递参数num3, 后台返回一个null, 因为接口的功能都不相同, 所以公共的放在全局中间件, 独有的/几个接口有的, 可以定义一个中间件, 只有这几个接口引用哦这个局部中间件使用的功能方法

封装功能中间件函数 - 例如求参数的和

const getSum = (req, res, next) => {
    req.thesum = req.query.num1 * 1 + req.query.num2 * 1 + req.query.num3 * 1;
    next();
}

app.get("/api/sum", getSum, (req, res) => {
    res.send({
        msg: "帮你把传递来的3个数的和求好了",
        data: req.thesum
    })
})

app.get("/api/list", (req, res) => {
    res.send({
        msg: "访问/api/list",
        data: []
    })
})

// 总结: 谁需要哪个中间件功能就集成起来, 不需要就不引入即可

13.5 局部中间 - 多个

const getSum = (req, res, next) => {
    req.thesum = req.query.num1 * 1 + req.query.num2 * 1 + req.query.num3 * 1;
    next();
}

app.get("/api/sum", getSum, (req, res, next) => {
    console.log("第二个中间被触发");
    next();
}, (req, res) => {
    res.send({
        msg: "帮你把传递来的3个数的和求好了",
        data: req.thesum
    })
})

// 总结: 有多少个, 就一直往某个路由的参数上往后写, 但是最后一个因为这是路由, 得需要结束本次请求, 响应(send)返回内容

中间件的写法总结

  • 中间件 本质就是一个 函数体

  • 公共的写在全局中间件 - 路由之前 用app.use()注册

  • 局部中间件 - 写在路由路径的后面

  • 无论是哪种中间件, 在客户端发过来请求以后, 都可以连续调用多个(按照从上到下, 左到右顺序)中间件处理(不要忘了next()) - next() 在中间件函数最后

  • 多个中间件之间, 共享req和res对象

13.6 错误处理中间件

  • 以前用http编写的项目, 只要后端报错了, 服务器就停止运行了 - 很不好
  • express最后有一个兜底的错误处理中间件, 还是会照常返回一个提示消息, 不会停止服务器(保证服务器连续运行可靠安全)
// 正常的路由
app.get("/api/getbook", (req, res) => {
    res.send("GET方式 获取书籍");
})

app.post("/api/addbook", (req, res) => {
    res.send("POST方式 添加书籍")
})
// 例1: 前端访问这个路由, 但是fs读不到文件, 报错
app.get("/api/index", (req, res) => {
    const data = fs.readFileSync(__dirname + "/index.html");
    res.send(data);
})
// 例2: 如果以上路由未匹配
app.all("*", (req, res, next) => {
    next({
        message: "路径未找到",
        status: 400
    })
})

// 如果报错, 直接进入到带有err参数的最后这个错误处理中间件
app.use((err, req, res, next) => {
    
    // 自定义错误才有status, 如果没有就是服务器内部代码报错500状态码使用
    res.status(err.status || 500).send(err.message); // 返回错误的提示消息
})
  • 错误处理中间件,必须传递 err、req、res、next四个参数,而且要放到所有接口的后面
    • 如果前面的中间件,没有给next传参,并且代码也没有错误,请求将不会进入到错误处理中间件
    • 如果前面的中间件,给next传递了实参(无论是什么实参),程序会绕过后面的所有中间件,直接进入到最后的错误处理中间件

14. 中间件分类

  1. 应用级别的中间件 - 绑定到app上的都是应用级别中间件 (全局中间件, 局部中间件, 自定义中间件)
app.use()
app.get()
app.post()
  • 错误处理中间件 - 一般在最后兜底使用 - 防止服务器崩溃停止(之前http报错就停止)
app.use((err, req, res, next) => {
    
})
  • 内置中间件(express自带的,比如 express.urlencoded())(express.static())
app.use(express.static())
app.use(express.json())
app.use(express.urlencoded())
  • 第三方中间件(比如multer、express-jwt、…)
app.post("请求路径", upload.single('cover_img'), (req, res) => {})
  1. 路由器级别的中间件
const app = express();
const router = express.Router(); // 实例化一个路由器对象 (与app对象相同, 但是是属于app下的一个子管家)
router.use((req, res, next) => {
    
})
router.get();
router.post();

实际开发中,自己写中间件的机会并不大,一般都有对应的第三方中间件直接下载使用

14.0 自定义中间件

之前用过express内置的urlencode中间件, 这次我们自己来模拟实现一下 (自己定义一个urlencode中间件, 解析 前端传递过来的key=value&key=value的字符串)

const express = require("express");
const querystring = require("querystring");
const app = express();
app.listen(3000, () => console.log("启动成功, http://localhost:3000"));

const urlencoded = (req, res, next) => {
    // 接收post方式的参数(如果有的话)
    var str = "";
    req.on("data", chunk => { // 1. on用于绑定事件, data是接收数据的事件, chunk是字节片段
        str += chunk;
    })
    req.on("end", () => { // 2. 接收请求数据完毕, 代表字节片段接受完毕
        // 3. 绑定到req对象身上的body中, 把字符串转成对象格式
        req.body = querystring.parse(str);
        next();
    })
}

app.use(urlencoded);

app.post("/api/addbook", (req, res) => {
    res.send({
        msg: "添加书籍",
        data: req.body
    });
})

14.1 封装中间件为模块使用

服务器里尽量只有服务器相关代码, 都一些一起很乱的, 所以把功能这种东西再单独提炼到一个独立的模块里, 引入使用以后也可以复用哦

const querystring = require("querystring");
const urlencoded = (req, res, next) => {
    var str = "";
    req.on("data", chunk => {
        str += chunk;
    })
    req.on("end", () => {
        req.body = querystring.parse(str);
        next();
    })
}
// 把中间件函数暴露出去
module.exports = urlencoded;

14.2 路由器 - 中间件使用

路由文件的定义 book.js

// 1. 新建路由器文件
const express = require("express");
const router = express.Router(); // 新建一个路由器对象(管理一类API接口) - 功能与app对象相同(监测请求触发响应)

router.post("/addbook", (req, res) => {
    res.send("新增图书")
})
router.get("/getbooks", (req, res) => {
    res.send("查询图书")
});
router.delete("/deletebook/:id", (req, res) => {
    res.send("删除图书")
})

// 这是一个独立的js模块, 需要导出-放到服务器文件里使用集成
module.exports = router;

路由文件的定义 cmt.js

// 新建路由器文件
const express = require("express");
const router = express.Router(); // 新建一个路由器对象(管理一类API接口) - 功能与app对象相同(监测请求触发响应)

router.post("/addcmt", (req, res) => {
    res.send("新增评论")
})
router.get("/getcmt", (req, res) => {
    res.send("查询-评论数据")
});
router.delete("/delcmt/:id", (req, res) => {
    res.send("删除某个评论")
})


module.exports = router;

服务器的.js文件使用

// 引入路由文件
const bookRouter = require("./14.2_router/book");
const cmtRouter = require("./14.2_router/cmt");

// 集成路由文件到app对象上
app.use("/api", bookRouter);
app.use("/cmt", cmtRouter);

15. 案例 - express重构图书管理API数据接口 - 跨域解决

前端程序员编写完代码, 在vscode打开网页

后端在一个ip:端口下部署

15.0 cors - 简单请求 - 跨域解决

服务器配置了响应头才可以被浏览器跨域资源共享访问

在这里插入图片描述

简单请求

请求方法:

  • GET
  • POST

请求头

  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

后端只要设置这个就可以开启了

app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    // res.header("Access-Control-Allow-Origin", "http://itcast.cn"); // 只允许这个域名下的ajax访问我们的服务器(*代表任何人)
    next();
})

测试获取数据的功能 是否实现

15.1 cors - 预检请求 - 跨域解决

如果某歌浏览器看不到options请求, 用火狐浏览器测试

在这里插入图片描述

非简单请求

请求方法

  • PUT
  • DELETE

请求头

  • 自定义名字的

会在正式发送PUT/DELETE请求前, 提前发送一次OPTIONS请求. 确认下后端是否允许非简单的请求

服务器端需要作出 设置

  • Access-Control-Allow-Methods 允许浏览器端的请求方式 例如"GET, POST, DELETE, PUT, OPTIONS"
  • Access-Control-Allow-Headers 允许浏览器端发送的请求头字段

小结

  • cors - 主要是在后端设置 - 前端什么都不需要干
  • 因为前端浏览器最新的默认支持cors方式的跨域资源共享
    • IE10+ / Chrome4+ / FireFox3.5+ 才支持XHR2.0, 才有cors功能

解决请求头 - 支持 测试 POST提交数据功能 - 查看预检

什么是预检:

浏览器先发送一次OPTIONS请求, 看看后端是否支持非简单请求, 如果支持, 前端再次发起一次正常的请求去后台请求数据

// 解决跨域问题
app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "*");
    res.header("Access-Control-Allow-Methods", "*");
    next();
})

第三方包解决

也可以下载别人封装好的包, 来解决cors跨域问题的支持

npm i cors

const cors = require("cors");
app.use(cors());

15.2 jsonp解决

需要前后端同时支持

如果服务器要同时开启jsonp 和cors, 一定要把jsonp写在cors之前,

app.get("/api/jsonp", (req, res) => {
    var fn = req.query.callback;
    var arr = [10, 20, "我是数据"];
    // res.send(`${fn}(${JSON.stringify(arr)})`);

    // 也可以写成如下形式
    res.jsonp(arr);
    // 前端如果传参callback=myFn
    // res.jsonp()相当于是 res.send(`myFn(JSON.stringify(arr))`)
})

第三方包解决

也可以下载别人封装好的包, 来解决cors跨域问题的支持
npm i cors

const cors = require("cors");
app.use(cors());

如有不足,请多指教,
未完待续,持续更新!
大家一起进步!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

東三城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值