1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【Nodejs】深入理解Express框架之如何使用各类中间件_04

【Nodejs】深入理解Express框架之如何使用各类中间件_04

时间:2019-12-02 15:29:23

相关推荐

【Nodejs】深入理解Express框架之如何使用各类中间件_04

目录

一. 中间件

❣️ 中间件概念

❣️中间件结构

❣️中间件分类

1. 应用级中间件

2.路由级中间

3. 内置中间件

4. 第三方中间件

5. 错误处理中间件

二. mysql模块

1. 创建普通连接

2. 创建连接池

🚀 写在最后🚀

【前文回顾】👉初识Node.js Web应用开发框:Express_03

一. 中间件

中间件为路由服务的,用于拦截对路由的请求,也可以作出响应。

说到中间件,官网对它的阐述是这样的:

“Express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。”

由此可见,中间件在Express web应用开发中的重要性。

❣️ 中间件概念

在nodejs中,中间件主要是指封装所有Http请求细节处理的方法,是从Http请求发起到响应结束过程中的处理方法。一次Http请求通常包含很多工作,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,但对于Web应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。

中间件的行为比较类似Java中过滤器的工作原理,就是在进入具体的业务处理之前,先让过滤器处理。它的工作模型下图所示。

❣️中间件结构

app.use([path],function)

path:是路由的url,默认参数‘/',意义是路由到这个路径时使用这个中间件

function:中间件函数

这个中间件函数可以理解为就是function(request,response,next)

❣️中间件分类

中间件分为5大类,应用级中间件、路由级中间件、内置中间件、第三方中间件、错误处理中间件

1. 应用级中间件

也称为自定义中间件 ------本质就是个函数

app.use(url,(req,res ,next)=>{});

url 表示要拦截的URl,对应路由中的URl,一旦拦截会自动执行回调函数

next 是一个函数,表示往后执行下一个中间件或者路由

🌴 注:自定义中间件的参数说明

这个function总共有三个参数(req,res,next);

当每个请求到达服务器时,nodejs会为请求创建一个请求对象(request),该请求对象包含客户端提交上来的数据。同时也会创建一个响应对象(response),响应对象主要负责将服务器的数据响应到客户端。而最后一个参数next是一个方法,因为一个应用中可以使用多个中间件,而要想运行下一个中间件,那么上一个中间件必须运行next()。

练习:创建添加到购物车的路由(get /shopping),传递商品的价格price,最后响应'商品的最终价格为:xxxx'。添加中间件。实现对价格打九折

*02_middleware.js文件

const express = require('express');//创建web服务器const app = express();//设置端口app.listen(8080);//只能按照URL拦截//拦截对 /list的请求//参数1:要拦截的URL//参数2:回调函数,一旦拦截到,自动调用这个函数app.use('/list', (req, res, next) => {//req res和路由中的是一样的//next 是一个函数,表示执行下一个中间件或者路由//获取以查询字符串传递的数据 console.log(req.query);//判断传递的用户名是否为管理员root//如果不是if (req.query.uname !== 'root') {//响应res.send('请提供管理员账户');} else {//往后执行(下一个中间件或者路由)next();}});//用户列表路由//get /listapp.get('/list', (req, res) => {res.send('这是所有用户的数据');});// http://127.0.0.1:8080/list?uname=abcd//添加中间件,拦截添加购物车的请求app.use('/shopping', (req, res, next) => {//在中间件中获取数据,然后打折console.log(req.query); // {}//对价格打九折req.query.price *= 0.9; //req.query.price={}*0.9=NaN//往后执行next();});//添加购物车路由 get /shoppingapp.get('/shopping', (req, res) => {//获取查询字符串传递的数据//个人尝试理解:req.query格式化对象,req.query.price=NaN,即给空对象强制添加price属性,属性值为NAN,所以{ price: NaN } console.log(req.query); // { price: NaN } res.send('商品最终价格为:' + req.query.price);});

我们简单分析一下:

启动服务:node 02_middleware.js

在8080后面输入/lis请求,被中间件拦截,向页面作出响应res.send('请提供管理员账户');

在/list后面输入?uname=root,即利用查询字符串传参,然后经过中间件验证,通过验证后,往后执行用户列表路由,服务器作出响应res.send('这是所有用户的数据');

所以,如同以上,本例中我们来实现购物车路由,利用中间件打折功能

传参前:在8080后面输入/shopping请求,经过中间件打折功能,往后执行购物车路由,服务器作出响应,res.send('商品最终价格为:'+req.query.price);由于还没有把参数price的值传过去,所以最终价格为NaN。

传参后:在/shopping后面输入?price=8000,即利用查询字符串传参,然后经过中间件打折功能,往后执行购物车路由,此时已携带price参数值,然后服务器作出响应res.send('商品最终价格为:'+req.query.price);

2.路由级中间

路由器的使用,就是路由级中间件

app.use('/product',productRouter)

3. 内置中间件

托管静态资源(html,css,js,图像...)

当浏览器端请求(静态资源)文件时,不需要通过路由去寻找文件,而是让浏览器自动到指定的目录下去寻找。(没有托管的话,浏览器每请求一个文件,就得写一个请求路由去响应浏览器的请求。使用res.sendFile(__dirname+'/文件名.html'))

app.use(express.static('目录路径'))

🌴注:我该如何请求托管的静态资源?

托管静态资源后,向服务器请求一个文件时,启动服务器后,在浏览器地址栏,端口后输入/带后缀的文件名,即可完成请求

🏝️ 扩展:内置中间件

从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static, Express 以前内置的中间件现在已经全部单独作为模块安装使用了。请参考中间件列表。

express.static(root, [options])

express.static是Express中唯一的内建中间件。用来处理静态资源文件。它以server-static模块为基础开发,负责托管 Express 应用内的静态资源。 参数root为静态资源的所在的根目录。 参数options是可选的,支持以下的属性:

如果你想获得更多关于使用中间件的细节,你可以查阅在 Express 中提供静态文件Serving static files in Express和使用中间件 - 内置中间件

❣️ Express 4.x API 中文文档:Express 4.x API 中文文档 | 菜鸟教程

❣️ Express 4.xAPI reference:Express 4.x - API Reference

下面的例子使用了 express.static 中间件,其中的 options 对象经过了精心的设计。

var options = {dotfiles: 'ignore',etag: false,extensions: ['htm', 'html'],index: false,maxAge: '1d',redirect: false,setHeaders: function (res, path, stat) {res.set('x-timestamp', Date.now())}}app.use(express.static('public', options))

练习:再次托管静态资源到file目录,然后添加文件测试。加入public和file目录下出现相同的文件名称,查看显示哪一个。

//引入express包const express=require('express');//创建web服务器const app=express();//设置端口app.listen(8080);//托管静态资源到public目录//浏览器端请求文件,自动到public目录寻找app.use( express.static('./public') );app.use( express.static('./files') );

🌴注:每个应用可有多个静态目录。

那么,同一个文件,多个托管目录,听谁的?

如果在第一个托管的目录中找到文件,就不在寻找;反之,去第二个托管目录寻找,依次直到找到为止

练习:编写文件04_three.js,使用express创建web服务器,托管静态资源到public目录下,包含登录文件login.html,点击提交向服务器发请求(post /mylogin),创建对应的路由,获取请求的数据

//引入express包const express = require('express');//引入查询字符串模块//const querystring=require('querystring');//1.引入body-parser模块const bodyParser = require('body-parser');const app = express();app.listen(8080);//托管静态资源到publicapp.use(express.static('./public')); //托管静态资源代替了通过路由找寻文件:app.get('/login',(req,res)=>{res.sendFile(__dirname+'/login.html');});详见day04中的03_express.js//2.将post请求的数据解析为对象app.use(bodyParser.urlencoded({//是否使用扩展的查询字符串模块qs;false不使用,此时会使用官方提供的querystring,true使用extended: false}));//根据表单的请求创建对应的路由//post /myloginapp.post('/mylogin', (req, res) => {//获取post请求的数据//3.获取数据,前提已经使用了body-parser中间件console.log(req.body);//以往我们是通过事件,来获取post请求的数据/*req.on('data',(chunk)=>{//格式为buffer,需要转字符串,转后格式为查询字符串let str=String(chunk);//将查询字符串解析为对象---需要引入querystring模块let obj=querystring.parse(str);console.log(obj);})*/res.send('登录成功');});

4. 第三方中间件

属于第三方模块,需要提前下载安装

使用body-parser将post请求数据解析为对象

//1.引入body-parser模块const bodyParser=require('body-parser');//2.使用body-parser中间件,会将所有post请求数据解析为对象app.use( bodyParser.urlencoded({//是否使用扩展的模块qs;true表示使用,false表示不使用,此时会自动使用querystring模块extended:false}) );//3. 在路由中获取对象格式数据req.body

🌴 注:body-parser中间件要写在路由或是路由器之前!

🏝️ 扩展扩展:关于body-parser中间件

body-parser代替了客户端post请求发送(传递)的http请求体内容,通过事件以流的形式获取buffer数据、转字符串再转为方便的对象格式内容的繁琐过程,从而让后台可以使用req.body直接获取(接收)为对象格式的数据

//😅通过事件:以往我们是通过事件,来获取post请求的数据req.on('data',(chunk)=>{//格式为buffer,需要转字符串,转后格式为查询字符串let str=String(chunk);//将查询字符串解析为对象let obj=querystring.parse(str);console.log(obj);})

不用body-parser的话,post请求必须通过req.on('data',(chunk)=>{…}以数据流事件来获取数据(buffer格式),进而利用全局函数String()或是tostring()方法转字符串、引入查询字符串模块解析为对象

😇闲话:express中间件—body-parser获取post、get数据

在express中,已经封装好获取get参数的方法,即req.query,但是post请求的参数却没有被封装,需要我们借助中间件(body-parser)来获取!

也就是说在express中对get请求内置了req.query来获取请求数据,对post请求,需要配合使用body-parser中间件来获取,否则无法使用req.body获取post传递的数据(不引入body-parser,默认获取到的是undefined)。这也就是为什么我们在nodejs里面使用express框架中的req.body 获取post传值为undifined的原因

另外,body-parser是非常常用的一个express中间件,作用是对post请求的请求体进行解析。即bodyparser的作用:它用于解析客户端请求的body中的内容,内部使用JSON编码处理,url编码处理以及对于文件的上传处理。使用非常简单,以下两行代码已经覆盖了大部分的使用场景。

①app.use(bodyParser.json());

②app.use(bodyParser.urlencoded({ extended: false }));

5. 错误处理中间件

错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要 next 对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。

错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其签名如下: (err, req, res, next)。

app.use((err,req,res,next)=>{console.error(err.stack)res.status(500).send('Something broke!')})

二. mysql模块

nodejs操作mysql数据库的工具模块

🌴闲话:mysql模块跟express模块没有任何关系,他们都是第三方模块,需要下载安装,安装后,在node_modules文件夹里查看已下载的包

连接数据库

mysql -urootmysql -h127.0.0.1-p3306 -uroot -p #数据库服务器IPmysql端口 用户名 密码

🌴闲话:本机既做客户端又做服务器。本机做数据库服务器 ip为127.0.0.1、3306为MySQL的默认端口;本机又是客户端,用户名是root、密码为空,就像ATM机器取钱(要访问银行数据库服务器)一样要输入用户名密码

增删改查

select * from emp where eid=1;insert into emp values(...);update emp set sex=1,salary=8000 where eid=3;delete from emp where eid=2;

下载安装

npm install mysql

mysql使用

mysql包的使用:/package/mysql

1. 创建普通连接

创建普通连接或是创建连接对象:👇

//创建连接对象。mysql模块下的Connection()方法const c=mysql.createConnection({ }) //需要提供mysql连接的相关内容//测试连接。connect()为连接对象下的connect方法c.connect() //测试连接,可以省略。//执行SQL命令c.query(sql命令,回调函数) // 要执行的SQL命令,通过回调函数获取结果

🌴注:连接对象下的query方法

连接对象.query(sql命令,回调函数)

c.query(sql命令,回调函数)

其中回调函数的参数:

err 可能产生的错误 ---比如sql命令写错了等等

result 具体SQL命令的执行结果

//引入mysql模块const mysql = require('mysql');//创建连接对象const c = mysql.createConnection({host: '127.0.0.1',port: '3306',user: 'root',password: '',database: 'tedu' //连接成功后要进入的数据库});//测试连接:没有报错就是连接成功//c.connect();//接下来,我们使用nodejs来操作数据库,即使用mysql模块下的query(sql命令,回调函数)方法操作数据库//执行SQL命令//连接对象下的query方法//参数1:SQL命令//参数2:回调函数,用于获取结果c.query('SELECT * FROM emp', (err, result) => {//err 可能产生的错误if (err) throw err;//result SQL命令执行的结果console.log(result); //查看SQL命令执行的结果});

2. 创建连接池

连接池中保存若干个mysql的连接,如果要操作数据库,只需要从中取出一个连接。当使用完会自动归还到连接池

创建连接池对象:👇

//创建连接池对象。mysql模块下的createPool()方法const pool=mysql.createPool({ });//执行SQL命令pool.query(sql命令,回调函数);//要执行的SQL命令,通过回调函数获取结果

🌴注:连接池对象下的query方法

连接池对象.query(sql命令,回调函数)

pool.query(sql命令,回调函数)

其中回调函数的参数:

err 可能产生的错误

result 具体SQL命令的执行结果 // sql命令执行后返回的对象存放在这里

❣️ 连接池对象没有上面普通连接对象的测试方法connect(),只能通过直接执行SQL命令pool. query(sql命令,回调函数)

SQL注入:在用户提供的条件中,添加了具有攻击性条件。

delete from emp WHERE eid=5 or 1=1;

1=1 所有的数据都满足这个条件

占位符(?):会对用户提供的条件进行过滤,把具有攻击性的条件给删除

pool.query( SQL命令,要过滤的数据,回调函数 )

要过滤的数据格式为数组,过滤完会自动替换到SQL命令中的占位符

pool.query('delete from emp WHERE eid=?',['5 or 1=1'],(err,result)=>{

if(err) throw err;

console.log(result);

});

🌴注:普通连接与连接池,没有区别,平时我们使用连接池更多

练习:往员工表emp下插入1条数据,对所有的值进行过滤。

//引入mysql模块const mysql = require('mysql');//创建连接池对象const pool = mysql.createPool({host: '127.0.0.1',port: '3306',user: 'root',password: '',database: 'tedu',connectionLimit: '20'});//执行SQL命令/*//在员工表中查询员工编号为1的员工的所有信息pool.query('SELECT * FROM emp WHERE eid=1',(err,result)=>{if(err) throw err;console.log(result);})//删除编号为5的员工信息 ------附带过滤数据的功能,防止sql注入pool.query('DELETE FROM emp WHERE eid=?',['5 or 1=1'],(err,result)=>{if(err) throw err;console.log(result);});//在员工表中插入一条数据:使用连接池对象query方法,用对象的形式向数据库插入数据pool.query('INSERT INTO emp VALUES(?,?,?,?,?,?)',[null,'然哥',0,'1977-5-29',50000,10],(err,result)=>{if(err) throw err;console.log(result);});*/let ran = {//向emp表插入数据时,由于缺失的列会默认使用默认值//所以,eid列如果设置为空的话,可以省略,会使用默认值//eid:null,ename: '然然',salary: 40000,deptId: 20,sex: 0}pool.query('INSERT INTO emp SET ?', [ran], (err, result) => { // 插入对象的方式只能在mysql模块里使用,在操作mysql数据库时不能这样使用,只能使用valuesif (err) throw err;console.log(result);});

❣️:一般的,服务器获取从客户端请求过来的数据,无论是使用get或post方法请求的,最后服务器接受(获取)的数据(req.query或req.body)都会解析为对象格式,所以,在mysql模块里提供了在要过滤的数组里放置对象数据,这样的便利操作,仅限在mysql模块里使用,在正常操作数据库时不能这样使用,只能使用values

练习:创建web服务器,托管静态资源到public目录,包含部门添加网页add.html,输入部门名称,点击提交,向服务器发请求(get /add),创建对应的路由,在路由中获取传递的数据,将数据插入到数据库tedu下的dept表中,响应“部门添加成功”

*add.html文件

<h2>部门添加</h2><form method="get" action="/add">部门名称<input type="text" name="dname"><input type="submit"></form>

*app.js文件

//引入express包const express = require('express');//引入mysql包const mysql = require('mysql');//创建web服务器const app = express();//设置端口app.listen(8080);//托管静态资源到public目录app.use(express.static('./public'));//连接mysql数据库,创建连接池const pool = mysql.createPool({host: '127.0.0.1',port: '3306',user: 'root',password: '',database: 'tedu',connectionLimit: '20'});//根据表单请求创建对应的路由//get /add// 因为托管了静态资源,所以地址栏直接输入/add.html即可。http://127.0.0.1:8080/add.htmlapp.get('/add', (req, res) => {//获取查询字符串传递的数据console.log(req.query);//将数据插入到数据表deptpool.query('INSERT INTO dept SET ?', [req.query], (err, result) => {if (err) throw err;console.log(result);//只有数据插入后再响应res.send('部门添加成功');});});

🚀 写在最后🚀

Tips:对中间件的一个小总结👇

1、中间件就是一种功能的封装方式,就是封装在程序中处理http请求的功能,

2、中间件是在管道中执行

3、中间件有一个next()函数,如果不调用next函数,请求就在这个中间件中终止了,

4、中间件和路由处理器的参数中都有回调函数,这个函数有2,3,4个参数 如果有两个参数就是req和res;如果有三个参数就是req,res和next 如果有四个参数就是err,req,res,next

5、如果不调用next ,管道就会终止,不会再有处理器做后续响应,应该向客户端发送一个响应

6、如果调用了next,不应该发送响应到客户端,如果发送了,则后面发送的响应都会被忽略

7、中间件的第一个参数可以是路径,如果忽略则全部都匹配

【后文传送门】👉 回顾node中如何使用Express模块写接口_05​​​​​​​

如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️青春木鱼❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。