• 【NodeJs-5天学习】第二天篇③ ——Express Web框架 和 中间件


    NodeJs-5天学习】第二天篇③ ——Express Web框架 和 中间件

    面向读者群体

    • ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
    • ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️

    技术要求

    • HTMLCSSJavaScript基础更好,当然也没事,就直接运行实例代码学习

    专栏介绍

    • 通过简短5天时间的渐进式学习NodeJs,可以了解到基本的服务开发概念,同时可以学习到npm、内置核心API(FS文件系统操作、HTTP服务器、Express框架等等),最终能够完成基本的web开发,而且能够部署到公网访问。

    学习交流群

    • NodeJs物联网五天入门学习之旅(搜索:729040020

    🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝

    1. 前言

    在前面一篇

    【NodeJs-5天学习】第二天篇② —— 网络编程(TCP、HTTP、Web应用服务)

    我们讲解了HTTP服务器相关内容,但是你会发现我们需要关注非常多的细节(比如需要人工编码干预body的解析,需要分发请求方法等等),用起来有点复杂,开发效率低。那么有没有一些更加简单快捷的方式来创建web服务器?

    当然有,这就是本篇要重点讲解的 Express框架。那我们先看看它和HTTP模块的关系。

    • 问题1:不使用Express 能否创建 Web 服务器
      能,使用 Node.js提供的原生http 模块即可。
    • 问题2:有了http 内置模块,为什么还有用 Express
      http内置模块用起来很复杂,开发效率低;Express是基于内置的http模块进一步封装出来的,能够极大的提高开发效率。
    • 问题3:http 内置模块与Express是什么关系
      后者是基于前者进一步封装出来的。

    1.1 Express简介

    官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的Web 开发框架

    通俗的理解:Express 的作用和 Node.js 内置的http模块类似,是专门用来创建Web 服务器的。

    Express 是npm上的一个第三方包,http模块是Node内置的模块,只不过Express基于http模块之上提供了更加简单快速创建web服务器的方法。

    Express 中文官方网站:

    https://www.expressjs.com.cn/
    习惯性,我们都要点开一下官方说明看看:

    • 官方定义在这里插入图片描述
    • 学习内容1:快速入门,如何快速搭建运行服务器,并且搭载静态文件资源(html、css等等)
      在这里插入图片描述
    • 学习内容2:指南,主要是学习中间件(MiddleWare),包括了路由、错误、全局、局部等等
      在这里插入图片描述
    • 学习内容3:API手册,对各个api进行详细介绍,目前主要是4.x版本
      在这里插入图片描述
    • 学习内容4:最佳实践
      在这里插入图片描述

    1.2 Express能做什么

    对于大前端程序员来说,最常见的两种服务器,分别是:

    • Web 网站服务器:专门对外提供Web 网页资源的服务器。

    典型代表:我们经常使用浏览器看到的页面信息,基本上都是Web网页资源。

    • API 接口服务器:专门对外提供API 接口的服务器。

    典型代表:我们平常使用App看到的信息,基本上都是通过API接口返回给到app,app拿到数据之后进行渲染显示。

    2. Express 快速入门

    • ① 安装 expressbody-parsermoment 模块

    • ② 导入 expressbody-parser 模块

    • ③ 创建 web 服务器

    • ④ 注册中间件,处理业务逻辑

    • ⑤ 调用 app.listen(端口号, 启动成功后的回调函数) ,启动服务器

    2.1 ① 安装 expressbody-parsermoment 模块

    分别执行:

    • npm install express --save
      express主要是构建web服务器。
      在这里插入图片描述
    • npm install body-parser --save
      body-parser主要是用来解析post请求体,包括jsonurlencoded表单
      在这里插入图片描述
    • npm install moment --save
      在这里插入图片描述
      moment主要是用来处理时间

    2.2 ② 导入 expressbody-parser 模块

    创建一个 web 服务器,对外提供 web 服务,需要导入 express 模块:

    // 1. 导入 express
    const express = require('express')
    const {getIPAdress} = require('./utils/utils.js')
    const time = require('./utils/time.js')
    const bodyParser = require('body-parser')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • express 用来构建web服务器
    • bodyParser 用来解析post请求体,包括jsonurlencoded表单
    • time 主要是使用特定格式显示时间
    const moment = require('moment');
    
    // 获取当前时间 2022-07-31
    function getCurrentDate() {
        return moment().format("YYYY-MM-DD");
    }
    
    // 获取当前时间 2022-07-31 11:30:30
    function getCurrentDateTime() {
        return moment().format("YYYY-MM-DD HH:MM:SS");
    }
    
    // 获取当前时间 
    function getCurrentDateFormat(format) {
        return moment().format(format);
    }
    
    // 向外导出方法
    module.exports = {
        getCurrentDate,
        getCurrentDateFormat,
        getCurrentDateTime
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • getIPAdress用来获取本机IP地址,后面浏览器用来访问服务
    const os = require('os');
    
    // 获取本机ip
    function getIPAdress() {
        var interfaces = os.networkInterfaces();
        for (var devName in interfaces) {
            var iface = interfaces[devName];
            for (var i = 0; i < iface.length; i++) {
                var alias = iface[i];
                if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                    return alias.address;
                }
            }
        }
    }
    
    // 向外导出方法
    module.exports = {
        getIPAdress
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.3 ③ 创建 web 服务器

    // 2. 创建 web 服务器
    const app = express()
    const port = 8266 // 端口号                 
    const myHost = getIPAdress(); // 获取本机IP地址
    
    • 1
    • 2
    • 3
    • 4

    这里定义了服务器的IP和端口号,后面监听客户端请求会用到。

    2.4 ④ 注册中间件,处理业务逻辑

    // 3.注册中间件,处理业务逻辑
    // 注意:中间件注入顺序,必须严格区分  
    // - 1、预处理中间件(排在最前面)
    // - 2、路由中间件(中间位置,路由分为API路由和静态文件路由)
    // - 3、错误处理中间件(兜底,专门用于捕获整个项目发生的异常错误,防止项目奔溃,必须注册在所有路由之后)
    
    /*********************** 预处理中间件 *************************/
    // 注入一些自定义中间件
    // 定义一个最简单的中间件函数
    // 常量 mw1 所指向的,就是一个中间件,这里打印请求进来的时间
    const mw1 = function(req , res , next) {
      console.log('这是第一个中间件函数')
    
      var date = time.getCurrentDateTime()
      console.log('请求时间:%s', date)
      // 注意:在当前中间件的业务处理完毕后, 必须调用next()函数
      // 表示把流转关系转交给下一个中间件或路由
      next()
    }
    
    app.use(mw1)
    
    // 解析JSON格式的请求体数据 (post请求:application/json)
    app.use(bodyParser.json());
    // 解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded)
    app.use(bodyParser.urlencoded({ extended: true }));
    /*********************** 预处理中间件 *************************/
    
    /*********************** 路由中间件 *************************/
    // 创建路由对象
    const router = express.Router();
    router.get('/api/test1', (req, res) => {
      console.log("请求:GET /api/test1")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test1 get OK")
    })
    router.post('/api/test1', (req, res) => {
      console.log("请求:POST /api/test1")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test1 Post OK")
    })
    router.get('/api/test2', (req, res) => {
      console.log("请求:GET /api/test2")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test2 get OK")
    })
    router.post('/api/test2', (req, res) => {
      console.log("请求:POST /api/test2")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test2 Post OK")
    })
    // 注入API路由中间件
    app.use(router);
    // app.use('/api', router) // 添加/api 访问前缀
    
    // 注入静态路由中间件,快速托管静态资源的中间件,比如 HTML文件、图片、CSS等
    app.use(express.static('web'))
    
    // all可以匹配任何提交方式 兜底方案
    app.all('*',(req,res)=>{
      // 做一个其它比较友好界面 响应给浏览器
       console.log('页面还没完成,请等待...')
       res.send('页面还没完成,请等待...')
     })
    /*********************** 路由中间件 *************************/
    
    /*********************** 错误处理中间件 *************************/
    app.use((err, req, res, next) => {
      console.error('出现异常:' + err.message)
      res.send('Error: 服务器异常,请耐心等待!')
    })
    /*********************** 错误处理中间件 *************************/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    这里的中间件分了几类:

    • 预处理中间件(排在最前面)

    一般这种中间件主要是在处理业务逻辑之前对req或者res做处理,比如打印请求到来的时间、解析post请求带过来的数据。

    • 路由中间件(中间位置,路由分为API路由和静态文件路由)

    一般我们会在这里处理对应请求URL,比如路由映射以及html链接访问等等

    • 错误处理中间件(兜底,专门用于捕获整个项目发生的异常错误,防止项目奔溃,必须注册在所有路由之后)

    一般这里就是错误兜底,当其他中间件出现错误问题时,会在这里捕获到。

    注意:

    • 中间件一定是从上到下依序执行,它们之间通过next方法进行流转,部分3会详细讲解中间件。

    接下来介绍一下用到的中间件。

    2.4.1 预处理中间件
    • 第一个执行的中间件是自定义的预处理中间件,主要是打印请求进来的时间,然后调用next方法将操作权流转给下一个中间件
    // 注入一些自定义中间件
    // 定义一个最简单的中间件函数
    // 常量 mw1 所指向的,就是一个中间件,这里打印请求进来的时间
    const mw1 = function(req , res , next) {
      console.log('这是第一个中间件函数')
    
      var date = time.getCurrentDateTime()
      console.log('请求时间:%s', date)
      // 注意:在当前中间件的业务处理完毕后, 必须调用next()函数
      // 表示把流转关系转交给下一个中间件或路由
      next()
    }
    
    app.use(mw1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 第二个执行的中间件是第三方编写的预处理中间件,主要是解析JSON格式的请求体数据 (post请求:application/json),内部会调用next方法将操作权流转给下一个中间件
    
    // 解析JSON格式的请求体数据 (post请求:application/json)
    app.use(bodyParser.json());
    
    • 1
    • 2
    • 3
    • 第三个执行的中间件是第三方编写的预处理中间件,主要是解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded),内部会调用next方法将操作权流转给下一个中间件
    // 解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded)
    app.use(bodyParser.urlencoded({ extended: true }));
    
    • 1
    • 2
    2.4.2 路由中间件

    在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系
    Express中的路由分3 部分组成,分别是请求的类型、请求的URL 地址、处理函数,格式如下:

    app.METHOD(PATH , HANDLER)
    // METHOD 请求的类型 可以是get / post
    // PATH  请求的URL地址
    // HANDOD 处理函数
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    https://www.expressjs.com.cn/guide/routing.html

    • 第四个执行的中间件是API路由中间件,主要是解析各个请求方法以及对应URL,然后响应具体对应的操作处理,内部会调用next方法将操作权流转给下一个中间件
    // 创建路由对象
    const router = express.Router();
    router.get('/api/test1', (req, res) => {
      console.log("请求:GET /api/test1")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test1 get OK")
    })
    router.post('/api/test1', (req, res) => {
      console.log("请求:POST /api/test1")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test1 Post OK")
    })
    router.get('/api/test2', (req, res) => {
      console.log("请求:GET /api/test2")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test2 get OK")
    })
    router.post('/api/test2', (req, res) => {
      console.log("请求:POST /api/test2")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test2 Post OK")
    })
    // 注入API路由中间件
    app.use(router);
    // app.use('/api', router) // 添加/api 访问前缀
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 第五个执行的中间件是静态文件路由中间件,主要是提供外界可以访问本地文件服务器(需要指定一个文件夹目录,可以用于存放web项目),一般都是用于html、css、js等等文件组成的web页面。内部会调用next方法将操作权流转给下一个中间件
    // 注入静态路由中间件,快速托管静态资源的中间件,比如 HTML文件、图片、CSS等
    app.use(express.static('web'))
    
    • 1
    • 2
    • 假设上面两个路由中间件都没有命中,会继续执行第六个API路由中间件。这里我们作为兜底处理,所以无法匹配的请求方法和请求URL都执行这个,一般多是用于提供一个友好页面给到用户。
    // all可以匹配任何提交方式 兜底方案
    app.all('*',(req,res)=>{
      // 做一个其它比较友好界面 响应给浏览器
       console.log('页面还没完成,请等待...')
       res.send('页面还没完成,请等待...')
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:

    • 路由中间件只会命中其中一个,不会依序执行。

    在路由中间件中,我们需要注意一些知识点。

    2.4.2.1 监听GET 请求 app.get()

    通过 app.get() 方法,可以监听客户端的 GET 请求,具体的语法格式如下:

    // 参数1: 客户端请求的 URL 地址
    // 参数2: 请求对应的处理函数
    //       req:请求对象(包含了与请求相关的属性与方法)
    //       res:响应对象(包含了与响应相关的属性与方法)
    app.get('请求路径URL' , function(req , res) {/*处理函数*/})
    app.get('请求路径URL' , (req , res)  => {/*处理函数*/}) // 利用箭头函数
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.4.2.2 监听 POST 请求 app.post()

    通过 app.post() 方法,可以监听客户端的POST请求,具体的语法格式如下:

    // 参数1: 客户端请求的 URL 地址
    // 参数2: 请求对应的处理函数
    //       req:请求对象(包含了与请求相关的属性与方法)
    //       res:响应对象(包含了与响应相关的属性与方法)
    app.post('请求路径URL' , function(req , res) {/*处理函数*/})
    app.post('请求路径URL' , (req , res)  => {/*处理函数*/})    // 利用箭头函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.4.2.3 监听 所有 请求 app.all()

    通过 app.all() 方法,可以监听客户端的任意请求,具体的语法格式如下:

    // 参数1: 客户端请求的 URL 地址
    // 参数2: 请求对应的处理函数
    //       req:请求对象(包含了与请求相关的属性与方法)
    //       res:响应对象(包含了与响应相关的属性与方法)
    app.post('*' , function(req , res) {/*处理函数*/})
    app.post('*' , (req , res)  => {/*处理函数*/})    // 利用箭头函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里使用到了正则匹配表达式。

    注意:
    不管是GET、POST、PUT、DELETE还是all,它们的路径均可以使用正则匹配表达式。比如:

    • '/ab?cd'会匹配到 acdabcd
    • '/ab+cd'会匹配到abcd, abbcd, abbbcd
    • '/ab*cd'会匹配到abcd, abxcd, abRANDOMcd, ab123cd
    • '/ab(cd)?e'会匹配到/abe and /abcde.
    2.4.2.4 把内容响应给客户端 res.send()

    通过 res.send() 方法,可以把处理好的内容,发送给客户端:

    app.get('/user' , (req , res) => {
        // 调用express 提供的 res.send() 方法 , 向客户端响应(发送)一个 JSON 对象
        res.send({name: 'zs' , age : 20 , gender: '男'})
    })
    
    
    app.post('/user' , (req , res) => {
        // 调用express 提供的 res.send() 方法 , 向客户端响应(发送)一个 文本字符串
        res.send('请求成功')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2.4.2.5 获取 URL 中携带的查询参数 req.query

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

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

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

    // URL 地址中,可以通过 :参数名 的形式 , 匹配动态参数值
    // 注意 : 这里的 :id 是一个动态的参数  
    app.get('/user/:id' , (req , res) => {
        // req.params 默认是一个空对象
        // 里面存放着通过 :  动态匹配到的参数值
        console.log(req.params)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    具体案例:

    // 动态参数可以是多个 例如:/user/:id/:name 
    //: 后面的值可以随便写(只要合理) 例如: /user/:ids 
    // 动态参数的个数要保持一致 ,不然会报错
    http://127.0.0.1/user/1/zs/20   // 这时会报错
    app.get('/user/:id/name' , (req , res) => {
        console.log(req.params)
    })
    // 动态参数的顺序可以调换,对应的参数也会改变
    // 第一次   http://127.0.0.1/user/1/zs
    app.get('/user/:id/name' , (req , res) => {
        console.log(req.params) // id:1 , name:zs
    })
    
    // 第二次   http://127.0.0.1/user/zs/1
    app.get('/user/:id/name' , (req , res) => {
        console.log(req.params) // id:zs , name:1
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2.4.2.7 req.body、req.query、req.params区别
    • req.query:
    // 注册中间件
    app.use(express.urlencoede({extended:false}))
    
    // 以?传递的参数都是get形式 接收的时候使用req.query
    app.post('/login' ,(req , res) => {
        console.log(req.query)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • req.body
    app.use(expres.json())
    
    // req.body 取到的是post请求
    app.post('/login' ,(req , res) => {
        console.log(req.body)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • req.params
    // URL 地址中,可以通过 :参数名 的形式 , 匹配动态参数值
    // 注意 : 这里的 :id 是一个动态的参数  
    app.get('/user/:id' , (req , res) => {
        // req.params 默认是一个空对象
        // 里面存放着通过 :  动态匹配到的参数值
        console.log(req.params)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.4.2.8 express.static()静态路由

    express 提供了一个非常好用的函数,叫做 express.static()。通过它,我们可以非常方便地创建一个静态资源服务器, 例如,通过如下代码就可以将public目录下的图片、CSS文件、JavaScript文件对外开放访问了:

    app.use(express.static('静态资源目录名'))
    // 例如:
    app.use(express.static('public'))
    
    
    • 1
    • 2
    • 3
    • 4

    现在,你就可以访问public目录中的所有文件了:

    • http://localhost:8266/images/bg.jpg
    • http://localhost:8266/css/style.css
    • http://localhost:8266/js/login.js

    注意:

    • Express在指定的静态目录中查找文件,并对外提供资源的访问路径。 因此,存放静态文件的目录名不会出现在URL 中。托管多个静态资源目录,请多次调用express.static() 函数:
    app.use(express.static('public'))
    app.use(express.static('files'))
    
    • 1
    • 2

    访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件。

    如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

    app.use("路径前缀",express.static('目录名'))
    // 前缀可以随便写
    // 例如
    app.use("/abc",express.static('public'))
    // 建议前缀 和 目录名一致
    // 例如:
    app.use("/public",express.static('public'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在,你就可以通过带有/public前缀地址访问public目录中的所有文件了:

    • http://localhost:8266/public/images/bg.jpg
    • http://localhost:8266/public/css/style.css
    • http://localhost:8266/public/js/login.js
    2.4.2.9 为API路由模块添加前缀

    类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单:

    //服务器 文件名: 02.模块化路由.js
    
    const express = require('express')
    // 创建 Web 服务器 , 命名为 app
    const app = express()
    
    // 1. 导入路由模块
    const router = require('./03.router')
    
    // 2. 使用 app.use() 注册路由模块 , 并添加统一的访问前缀 /api
    //参考 app.use('/files', express.static('./files'))
    
    app.use('/api', router)
    
    // 注意: app.use() 函数的作用,就是来注册全局中间件
    
    app.listen(80 , (req , res) => {
        console.log('http:127.0.0.1')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    2.4.2.10 路由的匹配过程

    每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
    在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL 同时匹配成功,则Express 会将这次请求,转 交给对应的function函数进行处理。
    在这里插入图片描述
    路由匹配的注意点:

    • ① 按照定义的先后顺序进行匹配
    • ② 请求类型和请求的URL同时匹配成功, 才会调用对应的处理函数
    2.4.3 错误处理中间件
    • 当前面的中间件出现异常问题时,为了让系统还能继续正常运行,一般需要对错误进行处理,这也是为什么错误处理中间件放在最后的原因。第七个执行的中间件就是我们的错误处理中间件。
    /*********************** 错误处理中间件 *************************/
    app.use((err, req, res, next) => {
      console.error('出现异常:' + err.message)
      res.send('Error: 服务器异常,请耐心等待!')
    })
    /*********************** 错误处理中间件 *************************/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.4.4 中间件的使用注意事项
    • ① 一定要在路由中间件之前注册预处理中间件
    • ② 客户端发送过来的请求,可以连续调用多个中间件进行处理
    • ③ 执行完中间件的业务代码之后,不要忘记调用next() 函数进行转移
    • ④ 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
    • ⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res对象

    2.5 ⑤ 调用 app.listen(端口号, 启动成功后的回调函数) ,启动服务器

    // 4.调用 app.listen(端口号, 启动成功后的回调函数) ,启动服务器
    app.listen(port, () => {
      console.log("express 服务器启动成功 http://"+ myHost +":" + port);
    })
    
    • 1
    • 2
    • 3
    • 4

    这里就会开始启动服务器,并且使用上面定义的端口号进行监听(至于端口号有啥用请看上一篇)。

    2.6 测试效果

    2.6.1 完整代码
    // 1. 导入 express
    const express = require('express')
    const {getIPAdress} = require('./utils/utils.js')
    const time = require('./utils/time.js')
    const bodyParser = require('body-parser')
    
    // 2. 创建 web 服务器
    const app = express()
    const port = 8266 // 端口号                 
    const myHost = getIPAdress(); // 获取本机IP地址
    
    // 3.注册中间件,处理业务逻辑
    // 注意:中间件注入顺序,必须严格区分  
    // - 1、预处理中间件(排在最前面)
    // - 2、路由中间件(中间位置,路由分为API路由和静态文件路由)
    // - 3、错误处理中间件(兜底,专门用于捕获整个项目发生的异常错误,防止项目奔溃,必须注册在所有路由之后)
    
    /*********************** 预处理中间件 *************************/
    // 注入一些自定义中间件
    // 定义一个最简单的中间件函数
    // 常量 mw1 所指向的,就是一个中间件,这里打印请求进来的时间
    const mw1 = function(req , res , next) {
      console.log('这是第一个中间件函数')
    
      var date = time.getCurrentDateTime()
      console.log('请求时间:%s', date)
      // 注意:在当前中间件的业务处理完毕后, 必须调用next()函数
      // 表示把流转关系转交给下一个中间件或路由
      next()
    }
    
    app.use(mw1)
    
    // 解析JSON格式的请求体数据 (post请求:application/json)
    app.use(bodyParser.json());
    // 解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded)
    app.use(bodyParser.urlencoded({ extended: true }));
    /*********************** 预处理中间件 *************************/
    
    /*********************** 路由中间件 *************************/
    // 创建路由对象
    const router = express.Router();
    router.get('/api/test1', (req, res) => {
      console.log("请求:GET /api/test1")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test1 get OK")
    })
    router.post('/api/test1', (req, res) => {
      console.log("请求:POST /api/test1")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test1 Post OK")
    })
    router.get('/api/test2', (req, res) => {
      console.log("请求:GET /api/test2")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test2 get OK")
    })
    router.post('/api/test2', (req, res) => {
      console.log("请求:POST /api/test2")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test2 Post OK")
    })
    // 注入API路由中间件
    app.use(router);
    // app.use('/api', router) // 添加/api 访问前缀
    
    // 注入静态路由中间件,快速托管静态资源的中间件,比如 HTML文件、图片、CSS等
    app.use(express.static('web'))
    
    // all可以匹配任何提交方式 兜底方案
    app.all('*',(req,res)=>{
      // 做一个其它比较友好界面 响应给浏览器
       console.log('页面还没完成,请等待...')
       res.send('页面还没完成,请等待...')
     })
    /*********************** 路由中间件 *************************/
    
    /*********************** 错误处理中间件 *************************/
    app.use((err, req, res, next) => {
      console.error('出现异常:' + err.message)
      res.send('Error: 服务器异常,请耐心等待!')
    })
    /*********************** 错误处理中间件 *************************/
    
    // 4.调用 app.listen(端口号, 启动成功后的回调函数) ,启动服务器
    app.listen(port, () => {
      console.log("express 服务器启动成功 http://"+ myHost +":" + port);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    2.6.2 测试效果

    先把服务器启动起来。
    在这里插入图片描述
    接下来我们使用Apifox进行模拟测试。

    2.6.2.1 测试 POST http://ip地址:8266/api/test2,请求体是JSON

    Apifox执行:
    在这里插入图片描述
    vscode打印结果:
    在这里插入图片描述

    可以看到打印了请求时间以及请求体JSON内容。

    2.6.2.2 测试 POST http://ip地址:8266/api/test1,请求体是urlencoded

    Apifox执行:
    在这里插入图片描述
    vscode打印结果:
    在这里插入图片描述
    可以看到打印了请求时间以及请求体urlencoded内容。

    2.6.2.3 测试 GET http://ip地址:8266/api/test1

    浏览器输入:

    http://ip地址:8266/api/test1
    在这里插入图片描述

    vscode打印结果:
    在这里插入图片描述

    2.6.2.4 测试 GET http://ip地址:8266/api/test3

    这里测试一个不存在的URL,浏览器输入:

    http://ip地址:8266/api/test3
    在这里插入图片描述

    vscode打印结果:
    在这里插入图片描述

    2.6.2.5 测试web页面,GET http://ip地址:8266/love.html

    浏览器输入:

    http://ip地址:8266/love.html
    这个web项目是博主从网上找到的一个表白效果。读者也可以放入其他的
    在这里插入图片描述
    在这里插入图片描述
    vscode打印结果:
    在这里插入图片描述

    2.6.2.6 测试 中间件异常

    在预处理中间件上加入一个主动抛出异常(除以0)的中间件。

    const mw2 = function(req , res , next) {
      console.log('这是第二个中间件函数,主动抛出异常')
    
      throw new Error('主动抛出异常!!!!!!!!!!!!!!!!!!!!') 
    
      // 注意:在当前中间件的业务处理完毕后, 必须调用next()函数
      // 表示把流转关系转交给下一个中间件或路由
      next()
    }
    
    app.use(mw2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    同样执行 2.6.2.4的操作看看。
    在这里插入图片描述
    在这里插入图片描述

    3. Express常见中间件

    Express官方把常见的中间件,根据功能分成了5 大类,分别是:

    • 应用级别的中间件
    • 路由级别的中间件
    • 错误级别的中间件
    • Express 内置的中间件
    • 第三方的中间件

    根据作用范围又区分为两类:

    • ① 全局中间件
    • ② 局部中间件

    3.1 功能分类

    3.1.1 应用级别的中间件

    通过 app.use() 或 app.METHOD() (METHOD包括 getpostputdeleteall),绑定到 app 实例上的中间件,叫做应用级别的中间件。

    示例代码:

    const express = require('express')
    const app = express()
    
    // 应用级别的中间件(全局中间件)
    app.use((req , res , next) => {
        next()
    })
    
    // 应用级别的中间件(局部中间件)
    app.get('/' , mw1 , (req , res) => {
        res.send('Home page.')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    3.1.2 路由级别的中间件

    绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到router实例上。

    示例代码:

    const express = require('express')
    const app = express()
    
    const router = express.Router()
    
    // 路由级别的中间件
    router.use(function(req , res, next) => {
      console.log('Time:' , Date.now())
      next()
    })
    
    app.use('/' , router)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    3.1.3 错误级别的中间件

    错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。

    格式:错误级别中间件的function处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。

    // 导入 express 模块
    const express = require('express')
    
    // 创建 express 的服务器实例
    const app = express()
    
    // 1.定义路由
    app.get('/' ,function(req , res) {
        // 1.1 抛出一个自定义的错误(人为的制造错误)
        throw new Error('服务器内部发生了错误!')
        res.send('Home Page.')
    } )
    
    // 2.定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
    app.use(function(err, req , res , next) {
        // 2.1 在服务器打印错误消息
        console.log('发生了错误:' + err.message)
        // 2.2 向客户端响应错误相关的内容
        res.send('Error!' + err.message)
    })
    
    // 调用 app.listen 方法 , 指定端口号并启动web服务器
    app.listen(80, (req, res) => {
        console.log('Express server running at http://127.0.0.1');
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意:错误级别的中间件,必须注册在所有路由之后

    3.1.4 Express内置的中间件

    自 Express 4.16.0 版本开始,Express内置了 3 个常用的中间件,极大的提高了Express 项目的开发效率和体验:

    • express.static快速托管静态资源的内置中间件,例如:HTML 文件、图片、CSS 样式等(无兼容性)
    • express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+ 版本中可用)
    • express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+ 版本中可用)

    后面两个跟body-parser是一个作用。

    3.1.5 第三方级别的中间件

    非 Express 官方内置的中间件,而是由第三方开发出来的Express 中间件,叫做第三方中间件。在项目中,大家可以 按需下载并配置第三方中间件,从而提高项目的开发效率。

    例如:除了使用express.urlencoded这个内置中间件来解析请求体数据,还可以使用body-parser 这个第三方中间 件,来解析请求体数据。使用步骤如下:

    • ① 运行npm install body-parser安装中间件
    • ② 使用require导入中间件
    • ③ 调用 app.use()注册并使用中间件
    // 导入 express 模块
    const express = require('express')
    
    // 创建 express 的服务器实例
    const app = express()
    
    // 1.导入解析表单数据的中间件 body-parser
    const parser = require('body-parser')
    
    // 2.使用 app.use() 注册中间件
    app.use(parser.urlencoded({ extended: false }))
    // app.use(express.urlencoded({ extended : false }))
    
    app.post('/' , (req , res) => {
    // 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
        console.log(req.body);
        res.send('ok')
    })
    
    // 调用 app.listen 方法 , 指定端口号并启动web服务器
    app.listen(80, (req, res) => {
        console.log('Express server running at http://127.0.0.1');
    }) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    注意:Express内置的express.urlencoded中间件,就是基于`body-parser 这个第三方中间件进一步封装出来。

    3.2 作用范围分类

    3.2.1 `全局中间件

    客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。

    通过调用 app.use(中间件函数),即可注册一个全局生效的中间件,示例代码如下:

    // 定义一个最简单的中间件函数
    const mw = function (req, res, next) {
      console.log('这是最简单的全局中间件函数')
      // 把流转关系,转交给下一个中间件或路由
      next()
    }
    
    // 将 mw 注册为全局生效的中间件
    app.use(mw)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以使用 app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行 调用,示例代码如下::

    const express = require('express')
    const app = express()
    
    // 这是定义全局中间件的简化形式
    app.use((req, res, next) => {
      console.log('这是最简单的中间件函数')
      next()
    })
    
    app.get('/', (req, res) => {
      console.log('调用了 / 这个路由')
      res.send('Home page.')
    })
    app.get('/user', (req, res) => {
      console.log('调用了 /user 这个路由')
      res.send('User page.')
    })
    
    app.listen(80, () => {
      console.log('http://127.0.0.1')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    3.2.2 `局部中间件

    不使用 app.use()定义的中间件,叫做局部生效的中间件,示例代码如下:

    // 导入 express 模块
    const express = require('express')
    // 创建 express 的服务器实例
    const app = express()
    
    // 1. 定义中间件函数 mw1
    const mw1 = (req, res, next) => {
      console.log('调用了局部生效的中间件')
      next()
    }
    
    // 2. 创建路由 
    // mw1 这个中间件只在"当前路由中生效" ,这种用法属于"局部生效的中间件"
    app.get('/', mw1, (req, res) => {
      res.send('Home page.')
    })
    // mw1 这个中间件不会影响下面的这个路由
    app.get('/user', (req, res) => {
      res.send('User page.')
    })
    
    // 调用 app.listen 方法,指定端口号并启动web服务器
    app.listen(80, function () {
      console.log('Express server running at http://127.0.0.1')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以在路由中,通过如下两种等价的方式,使用多个局部中间件:

    // 以下两种写法是'完全等价'的 , 可根据自己的喜好 , 选择任意一种方式进行使用
    app.get('/' , mw1 , mw2 , (req , res) => { res.send('Home page')})
    app.get('/' ,[ mw1 , mw2 ], (req , res) => { res.send('Home page')})
    
    
    • 1
    • 2
    • 3
    • 4

    同时使用多个局部中间件:

    // 导入 express 模块
    const express = require('express')
    // 创建 express 的服务器实例
    const app = express()
    
    // 1. 定义中间件函数
    const mw1 = (req, res, next) => {
      console.log('调用了第一个局部生效的中间件')
      next()
    }
    
    const mw2 = (req, res, next) => {
      console.log('调用了第二个局部生效的中间件')
      next()
    }
    
    // 2. 创建路由
    app.get('/', [mw1, mw2], (req, res) => {
      res.send('Home page.')
    })
    app.get('/user', (req, res) => {
      res.send('User page.')
    })
    
    // 调用 app.listen 方法,指定端口号并启动web服务器
    app.listen(80, function () {
      console.log('Express server running at http://127.0.0.1')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    注意:

    • 局部或者全局主要是区分app.use方法。

    4. 路由中间件实现思路(选读)

    假设我们也来实现一个简单的Express 路由中间件我们会怎么实现?

    路由中间件最核心的思路:

    • method 请求方法(all、get、post、put、delete)
    • URL 请求地址
    • handle 处理函数

    那么按照思路:

    定义一个app对象,并且它有一个叫做use的方法,use方法里面肯定需要存在一个路由映射关系

    4.1 请求方法

    需要区分请求方法设计

    var routes = {'all':[]}
    var methods = ['get', 'post', 'put', 'delete']
    var app = {}
    app.use = function(path, action){
      routes.all.push([path,action])
    }
    
    // 每个method方法都对应自己的一个路由映射,这样我们就可以使用类似于express的
    // app.get/app.post/app.put等等方法
    for (let index = 0; index < methods.length; index++) {
      const method = methods[index];
      routes[method] = []
      app[method] = function(path, action){
        routes[method].push([path],action)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里就提供了一个类似于express的路由方法:

    • app.use
    • app.get
    • app.post
    • app.put
    • app.delete

    每个方法都最终共享一个路由对象 routes ,对象分别有 all、get、post、put、delete五个属性,每个属性对应一个数组A,数组A元素又是一个数组B,而数组B的元素1就是URL,元素2就是处理函数。(往下看能看到效果)

    4.2 路由匹配

    针对我们注册进来的请求方法、URL来做匹配

    var match = function(pathname, routes, req, res) {
      for (let index = 0; index < routes.length; index++) {
        const route = routes[index];
        var key = route[0]
        var action = route[1]
        if (pathname === key) {
          action(req, res)
        }
      }
    
      return false
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.3 路由分发

    针对请求过来的方法和URL进行内容分发。

    function route(req, res){
      var pathname = url.parse(req.url).pathname
      // 将请求方法变成小写
      var method = req.method.toLowerCase()
      // 根据请求方法分发
      if (routes.hasOwnProperty(method)){
        // 匹配请求URL
        if (match(pathname, routes[method], req, res)) {
          return
        }
      }
    
      // 上面没有匹配,尝试让all()来处理
      if (match(pathname, routes.all, req, res)) {
        return;
      }
    
      // 处理 404 请求
      handle404(req, res)
    }
    
    // 处理 404 请求
    function handle404(req, res) {
      res.writeHead(404)
      res.end()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4.4 注册路由

    app.get('/get', (req, res)=>{
       console.log('匹配/get')
       res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
       res.end('匹配/get')
    })
    
    app.put('/put', (req, res)=>{
      console.log('匹配/put')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/put')
    })
    
    app.delete('/delete', (req, res)=>{
      console.log('匹配/delete')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/delete')
    })
    
    app.post('/post', (req, res)=>{
      console.log('匹配/post')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/post')
    })
    
    app.use('/all', (req, res)=>{
      console.log('匹配/all')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/all')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.5 完整业务代码使用

    我们在原来的HTTP Server试试效果:

    // 1.导入 http 模块
    const http = require ('http')
    const url = require('url')
    const querystring = require('querystring')
    
    // 2. 创建 web 服务器实例
    const sever = http.createServer()
    
    /****************** 自定义路由处理 **************************/
    var routes = {'all':[]}
    var methods = ['get', 'post', 'put', 'delete']
    var app = {}
    app.use = function(path, action){
      routes.all.push([path,action])
    }
    
    // 每个method方法都对应自己的一个路由映射,这样我们就可以使用类似于express的
    // app.get/app.post/app.put等等方法
    for (let index = 0; index < methods.length; index++) {
      const method = methods[index];
      routes[method] = []
      app[method] = function(path, action){
        routes[method].push([path,action])
      }
    }
    
    var match = function(pathname, routes, req, res) {
      for (let index = 0; index < routes.length; index++) {
        const route = routes[index];
        var key = route[0]
        var action = route[1]
        if (pathname === key) {
          action(req, res)
        }
      }
    
      return false
    }
    
    function route(req, res){
      var pathname = url.parse(req.url).pathname
      // 将请求方法变成小写
      var method = req.method.toLowerCase()
      // 根据请求方法分发
      if (routes.hasOwnProperty(method)){
        // 匹配请求URL
        if (match(pathname, routes[method], req, res)) {
          return
        }
      }
    
      // 上面没有匹配,尝试让all()来处理
      if (match(pathname, routes.all, req, res)) {
        return;
      }
    
      // 处理 404 请求
      handle404(req, res)
    }
    
    // 处理 404 请求
    function handle404(req, res) {
      res.writeHead(404)
      res.end()
    }
    
    app.get('/get', (req, res)=>{
       console.log('匹配/get')
       res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
       res.end('匹配/get')
    })
    
    app.put('/put', (req, res)=>{
      console.log('匹配/put')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/put')
    })
    
    app.delete('/delete', (req, res)=>{
      console.log('匹配/delete')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/delete')
    })
    
    app.post('/post', (req, res)=>{
      console.log('匹配/post')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/post')
    })
    
    app.use('/all', (req, res)=>{
      console.log('匹配/all')
      res.writeHead(200,{
        "content-type":"text/html;charset=UTF-8"
      });
      res.end('匹配/all')
    })
    
    // 打印 routes 路由对象
    console.log(routes)
    
    /******************* 自定义路由处理 *************************/
    
    
    // 3.为服务器实例绑定request 事件, 监听客户端的请求
    // request req
    // response res
    sever.on('request' , function(req , res){
      console.log('服务端:我收到了客户端请求');
      // 3.1 打印请求状态行
      console.log(`请求方式: ${req.method}`);
      console.log(`HTTP协议版本: ${req.httpVersion}`)
      console.log(`请求地址: ${req.url}`)
      console.log(`请求头: `)
      console.log(req.headers)
    
      // 路由解析
      route(req, res)
    })
    
    sever.on('connection', function(){
      console.log('服务端:我和客户端建立了底层TCP连接')
    })
    
    sever.on('close', function(){
      console.log("服务端:我已经关闭服务")
    })
    
    sever.on('checkContinue', function(){
      console.log("服务端:客户端数据较大")
    })
    
    sever.on('connect', function(){
      console.log("服务端:客户端发起了连接请求")
    })
    
    sever.on('upgrade', function(){
      console.log("服务端:客户端协议升级")
    })
    
    // 4. 启动服务器
    sever.listen(80 , function(){
      console.log('server running at http://127.0.0.1:80');
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152

    执行nodejs文件:
    在这里插入图片描述
    在浏览器上输入 http://127.0.0.1:80/all 看看效果:
    在这里插入图片描述
    在这里插入图片描述
    说明我们整个流程是可以跑通的。

    思考:

    • 如何匹配正则表达式?

    5. 中间件剖析(选读)

    在学习 【NodeJs-5天学习】第二天篇② —— 网络编程(TCP、HTTP、Web应用服务) 过程中,我们片段式地接触完Web应用的基础功能和路由功能后,我们发现实际项目中有太多琐碎的细节工作需要处理。而对于web应用来说,我们作为开发者来说当然不希望接触到这么多细节性的处理,为此我们引入中间件(middleware)来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务开发上,以达到提升开发效率的目的。

    在最早的中间件定义中,它是一种在操作系统上为应用软件提供服务的计算机软件。它处于操作系统与应用软件之间,让应用软件更好、更方便地使用底层服务。借助于这种思想,对于HTTP请求的很多细节,我们也可以封装为中间件,开发者可以脱离这部分细节,专注于业务上。

    在这里插入图片描述
    从HTTP请求到具体业务逻辑,经历了一系列中间件处理(访问日志中间件、查询字符串中间件、cookie中间件、其他中间件),每个中间件处理掉相对简单的逻辑,然后给到应用层业务处理。 而中间件的上下文也就是请求对象和响应对象:reqres,而要完成它们之间的顺序调用就需要依赖于next传递(next 函数是实现多个中间件连续调用的关键)。我们用一个链式图来看看:

    在这里插入图片描述

    • 红色箭头表示我们一个请求到来之后会经历的处理顺序,最后到达具体业务处理,业务处理完之后把结果响应给到客户端,它们主要依靠next来流转。
    • 虚线箭头表示每一个请求在任意一个中间件上都共享同一份req & res对象,这样意味着我们对req & res对象进行的数据操作会在后续的中间件能够引用到。

    比如我在中间件1加入了req.a = 1, 在中间件2加入了 req.b = 2。那么我们就可以在 中间件3或者4访问到 req.a以及req.b。

    所以一个中间件的基本格式如下:

    // 基本中间件定义
    var middleware = function(req, res, next) {
      // 逻辑处理
      handle(req,res)
      // 传递给下一个中间件
      next()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    思考:

    • 如何在路由中间件基础上实现链式?

    6.总结

    篇③主要是讲解了Express这个强大的web框架用来构造api请求或者web页面,同时剖析了一下中间件的概念以及实现了一下简单的路由中间件,更多是抛砖引玉,需要物联网初学者慢慢消化。

  • 相关阅读:
    在基于ABP框架的前端项目Vue&Element项目中采用电子签章处理文件和打印处理
    go的解析命令行库flag
    iMazing 3 for Windows iOS设备管理软件2024最新功能解析
    Spring IOC和AOP
    web3j solidity 转java
    一文带你了解2023年最新央企名单、业务和管理机构(附资料)
    ModuleNotFoundError_ No module named ‘Crypto‘
    【RocketMQ 九】mqadmin命令
    JavaScript之void 0 === undefined
    机器学习从入门到放弃:卷积神经网络CNN(二)
  • 原文地址:https://blog.csdn.net/weixin_44614230/article/details/126561648