物联网服务NodeJs-5天学习第二天篇③ ——Express Web框架 和 中间件

Posted 单片机菜鸟哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物联网服务NodeJs-5天学习第二天篇③ ——Express Web框架 和 中间件相关的知识,希望对你有一定的参考价值。

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

面向读者群体

  • ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
  • ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️
  • ❤️ 本篇创建记录 2023-03-12 ❤️
  • ❤️ 本篇更新记录 2023-03-12 ❤️

技术要求

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

专栏介绍

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

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

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')
  • 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

  • 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

2.3 ③ 创建 web 服务器

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

这里定义了服务器的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: 服务器异常,请耐心等待!')
)
/*********************** 错误处理中间件 *************************/

这里的中间件分了几类:

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

一般这种中间件主要是在处理业务逻辑之前对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)
  • 第二个执行的中间件是第三方编写的预处理中间件,主要是解析JSON格式的请求体数据 (post请求:application/json),内部会调用next方法将操作权流转给下一个中间件

// 解析JSON格式的请求体数据 (post请求:application/json)
app.use(bodyParser.json());
  • 第三个执行的中间件是第三方编写的预处理中间件,主要是解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded),内部会调用next方法将操作权流转给下一个中间件
// 解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded)
app.use(bodyParser.urlencoded( extended: true ));

2.4.2 路由中间件

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

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

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 访问前缀
  • 第五个执行的中间件是静态文件路由中间件,主要是提供外界可以访问本地文件服务器(需要指定一个文件夹目录,可以用于存放web项目),一般都是用于html、css、js等等文件组成的web页面。内部会调用next方法将操作权流转给下一个中间件
// 注入静态路由中间件,快速托管静态资源的中间件,比如 HTML文件、图片、CSS等
app.use(express.static('web'))
  • 假设上面两个路由中间件都没有命中,会继续执行第六个API路由中间件。这里我们作为兜底处理,所以无法匹配的请求方法和请求URL都执行这个,一般多是用于提供一个友好页面给到用户。
// all可以匹配任何提交方式 兜底方案
app.all('*',(req,res)=>
  // 做一个其它比较友好界面 响应给浏览器
   console.log('页面还没完成,请等待...')
   res.send('页面还没完成,请等待...')
 )

注意:

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

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

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)  => /*处理函数*/) // 利用箭头函数

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)  => /*处理函数*/)    // 利用箭头函数
2.4.2.3 监听 所有 请求 app.all()

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

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

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

注意:
不管是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('请求成功')
)
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)
)

2.4.2.6 获取 URL 中的动态参数 req.params

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

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

具体案例:

// 动态参数可以是多个 例如:/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
)
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)
)
  • req.body
app.use物联网服务NodeJs-5天学习第二天篇④ ——项目模块化

物联网服务NodeJs-5天学习第二天篇② —— 网络编程(TCPHTTPWeb应用服务)

物联网服务NodeJs-5天学习第三天实战篇③ ——基于MQTT的环境温度检测

物联网服务NodeJs-5天学习第四天存储篇③ ——基于物联网的WiFi自动打卡考勤系统,升级存储为mysql,提醒功能改为QQ

物联网服务NodeJs-5天学习第一天篇③ —— VsCode上运行第一个NodeJs 程序,配置自动重启插件 nodemon

物联网服务NodeJs-5天学习第三天实战篇② ——基于物联网的WiFi自动打卡考勤系统