【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  /list
app.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  /shopping
app.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是可选的,支持以下的属性:

属性描述类型默认值
dotfiles是否响应点文件(以点“.”开头的文件或目录)。供选择的值有"allow","deny"和"ignore"。请参阅下面的点文件String"ignore"
etag是否启用etag生成Booleantrue
extensions设置文件扩展名回退(设置文件扩展名备份选项):如果未找到文件,请搜索具有指定扩展名的文件并提供找到的第一个扩展名,例如:['html', 'htm'] Array[]
index发送目录索引文件。设置false将不发送。Mixed"index.html"
lastModified设置文件在系统中的最后修改时间到Last-Modified头部。可能的取值有falsetrueBooleantrue
maxAge在Cache-Control头部中设置max-age属性,精度为毫秒(ms)或则一段ms format的字符串Number0
redirect当请求的pathname是一个目录的时候,重定向到尾随"/"Booleantrue
setHeaders当响应静态文件请求时设置headers的方法Funtion

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


❣️ Express 4.x API 中文文档:Express 4.x API 中文文档 | 菜鸟教程
❣️ Express 4.x API 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);

//托管静态资源到public
app.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  /mylogin
app.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 -uroot
mysql -h127.0.0.1 -p3306 -uroot -p  #数据库服务器IP  mysql端口  用户名  密码

🌴 闲话:本机既做客户端又做服务器。本机做数据库服务器 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包的使用:https://www.npmjs.com/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数据库时不能这样使用,只能使用values
  if (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.html
app.get('/add', (req, res) => {
  //获取查询字符串传递的数据
  console.log(req.query);
  //将数据插入到数据表dept
  pool.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​​​​​​​

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

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: Express是一个基于Node.js平台的Web应用程序框架,它提供了一系列强大的功能和工具,使得开发Web应用程序变得更加简单和高效。Express框架具有灵活性和可扩展性,可以轻松地与其他Node.js模块和库集成,同时也支持多种模板引擎和中间件,使得开发者可以根据自己的需求进行定制和扩展。Express框架还提供了丰富的路由和HTTP请求处理功能,使得开发者可以轻松 ### 回答2: Express是一个流行的Node.js Web应用程序开发框架。它提供了一组简单、灵活和易于使用的功能,可以帮助开发人员快速构建高性能的Web应用程序和API。 Express提供了许多有用的功能,包括路由、中间件、模板引擎等。通过使用Express,可以轻松地定义应用程序的不同路由,并将请求映射到相应的处理函数中。这使得应用程序的结构更清晰,并且可以更好地组织和管理代码。 Express还提供了中间件的概念,允许开发人员在请求和响应之间执行一些通用的功能。中间件可以用于处理身份验证、日志记录、错误处理等。使用中间件,可以将这些功能分离到单独的模块中,提高了代码的可重用性和可维护性。 另外,Express还支持多种模板引擎,如EJS、Handlebars等。这些模板引擎允许开发人员动态生成HTML页面,并将数据和逻辑与界面分离。使用模板引擎,可以更方便地构建可重用和可扩展的视图。 总的来说,Express是一个强大而灵活的Web应用程序开发框架。它提供了许多功能,简化了Node.js应用程序的开发过程。使用Express,开发人员可以更快速、更高效地构建出高性能的Web应用程序和API。 ### 回答3: Express是一个流行的Node.js Web应用程序框架,它建立在Node.js的核心HTTP模块之上,旨在简化和加速Web应用程序的开发过程。 Express具有简单易用的API,使得创建Web服务器和处理HTTP请求变得容易。它提供了一组强大的功能和中间件,用于处理路由、模板引擎、HTTP请求和响应等。开发者可以使用Express轻松地创建和管理路由,处理GET、POST、PUT等HTTP请求,并返回各种类型的响应。 Express还支持各种模板引擎,如EJS和Handlebars,使得动态生成HTML页面变得简单。通过使用模板引擎,可以将动态数据嵌入到静态HTML页面中,并将结果返回给客户端。 此外,Express还提供了强大的中间件功能,用于处理HTTP请求和响应。中间件是在请求和响应之间执行的函数,用于执行各种任务,如处理身份验证、错误处理、日志记录等。开发者可以将多个中间件链接在一起,以便在请求到达路由处理之前,对请求进行预处理。 Express的优势之一是其灵活性和可扩展性。它允许开发者使用其他Node.js模块和库,以满足特定的需求。开发者可以自由地选择适合他们项目的组件和解决方案,并根据需要对其进行自定义。 总之,Express是一个强大而灵活的Node.js框架,用于快速构建Web应用程序。它提供了丰富的功能和易于使用的API,使得构建高性能的Web应用程序变得简单。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

儒雅的烤地瓜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值