~ Express 路由和中间件
Express 基于 Node.js,是一个简洁、灵活的 web 开发框架。
它是对 Node.js 中 http 模块的一层抽象,这使得开发者可以无需注意细节,而直接上手进行页面和业务逻辑的开发:
- 路由:定义路由表执行不同的 http 请求动作
- 中间件:设置中间件来响应 http 请求
- 模板引擎:通过向模板传递参数动态渲染 HTML 页面
其核心概念是「 路由和中间件 」,从本质上来说,一个 Express 应用就是在调用各种中间件。
路由
路由,通过特定的 http 请求方法和请求路径( URL ),确定应用程序如何响应客户端对特定端点的请求。
每个路由可以有一个或多个处理函数,这些函数在路由匹配的时候被执行。
「 基本语法 」app.METHOD(path,handler)
[1] app:一个 express 实例。 // app 方法
// 安装并导入 express
let express = require('express');
const app = express(); // 实例化
[2] METHOD:请求方法 - express 支持对应 http 的请求方法,如 get、post ... 以及一个特殊的路由方法 app.all()
app.get('/',(req,res)=>{ res.end('get request'); }) app.post('/',(req,res)=>{ res.end('post request') })
- app.all() 该方法用于为所有请求方法的路径提供加载中间件功能
app.all('/secret',(req,res,next)=>{ console.log('app-all'); next(); // pass control to the next handler });
[3] path:路由路径 // 请求方法和路由路径,共同确定了对特定端点的请求
- 基于字符串的路由路径
// 此路由路径将匹配请求到根路由,/ app.get('/', function (req, res) { res.send('root') }) // 此路由路径将匹配请求/about app.get('/about', function (req, res) { res.send('about') }) // 此路由路径将匹配请求/random.text app.get('/random.text', function (req, res) { res.send('random.text') })
- 基于字符串模式的路由路径
// 如果要在路径字符串中使用字符($),需将其包含在([])之内,如 /data/([\$])book 表示:/data/$book // 此路由路径将匹配acd并abcd app.get('/ab?cd', function (req, res) { res.send('ab?cd') }) // 此路由路径将会匹配abcd,abbcd,abbbcd等等 app.get('/ab+cd', function (req, res) { res.send('ab+cd') }) // 此路由路径匹配abcd,abxcd,abRANDOMcd,ab123cd等 app.get('/ab*cd', function (req, res) { res.send('ab*cd') }) // 此路由路径将匹配/abe并/abcde app.get('/ab(cd)?e', function (req, res) { res.send('ab(cd)?e') })
- 基于正则表达式的路由路径
// 此路由路径将与路由名称中的 “a” 匹配 app.get(/a/, function (req, res) { res.send('/a/') }) // 此路由路径将匹配butterfly和dragonfly,但不butterflyman,dragonflyman等 app.get(/.*fly$/, function (req, res) { res.send('/.*fly$/') })
- 路由参数:带参数的路由路径,用于捕获 URL 中指定位置对应的值
命名:路由参数的名称必须由“单词字符”( A-Za-z0-9_ )组成
// 由于连字符(-)和点(.)是字面解释的,因此,它们可以与路由参数一起使用以达到有用的目的 Route path: /flights/:from-:to Request URL: http://localhost:3000/flights/LAX-SFO req.params: { "from": "LAX", "to": "SFO" } Route path: /plantae/:genus.:species Request URL: http://localhost:3000/plantae/Prunus.persica req.params: { "genus": "Prunus", "species": "persica" } // 要更好地控制可以通过路由参数匹配的确切字符串,可以在括号(())中追加正则表达式 Route path: /user/:userId(\d+) Request URL: http://localhost:3000/user/42 req.params: {"userId": "42"}使用:捕获到的值,将填充到 req.params 对象中,而路径中指定的路由参数的名称,则作为其各自的键。
Request URL: http://localhost:3000/users/34/books/8989 // 代码示例:要使用路由参数定义路由,只需在路由路径中指定路由参数 app.get('/users/:userId/books/:bookId', function (req, res) { res.send(req.params) // { "userId": "34", "bookId": "8989" } })
[4] handler:路由处理程序 // 请求对象和响应方法
路由处理程序,也可以称之为路由句柄,本质上是一个或多个回调函数,这些函数的行为与中间件相似,可以处理请求。
唯一的例外是,这些回调可能会调用 next('route') 绕过剩余的路由回调。
路由处理程序,可以使用单个 handler、多个 handler、handler 数组或混合的形式来处理请求
- 一个 handler,可以处理一个路由
app.get('/', (req, res) => { res.writeHeader(200, { 'content-Type': 'text/plain;charset=utf8' }); res.end('一个handler可以处理一个路由'); });
- 多个 handler,可以处理一个路由
app.get('/about',(req,res,next)=>{ res.writeHeader(200, { 'content-Type': 'text/plain;charset=utf8' }); res.write('多个handler可以处理一个路由。'); next(); },(req,res)=>{ res.end('但,需要指定next对象'); });
- 可以使用 handler 数组,来处理一个请求
var cb0 = function (req, res, next) { console.log('CB0') next() } var cb1 = function (req, res, next) { console.log('CB1') next() } var cb2 = function (req, res) { res.send('Hello from C!') } app.get('/example/c', [cb0, cb1, cb2])
- 可以使用 handler、handler 数组混合的形式,来处理函数
var cb0 = function (req, res, next) { console.log('CB0') next() } var cb1 = function (req, res, next) { console.log('CB1') next() } app.get('/example/d', [cb0, cb1], function (req, res, next) { console.log('the response will be sent by the next function ...') next() }, function (req, res) { res.send('Hello from D!') })也可以是,可链接的路由处理程序
其优势在于:路径在单个位置指定的,所以对创建模块化路由很有帮助,因为这可以减少冗余和拼写错误
app.route('/book') .get(function (req, res) { res.send('Get a random book') }) .post(function (req, res) { res.send('Add a book') }) .put(function (req, res) { res.send('Update the book') })
中间件
express 实例( app )在运行过程中,会调用一系列的中间件,其最大的特点是:一个中间件处理完,会传递给下一个中间件。
简单来说,中间件( middleware ),就是在收到请求后、发送响应之前这个阶段,所执行的一些函数。
[1] 应用程序级中间件
其绑定的对象是 express 实例:app 对象,然后,使用 app.use() 或 app.METHOD() 方法进行加载
- app.use 的用法和路由基本相同,都可以使用单个 handler、多个 handler、handler 数组等形式
// 引入 express,并实例化 const express = require('express'); const app = express(); app.use([path],[callback1[,callback2]]) // path 默认为 /,会匹配以 path 开头的路径,不接受正则
「 拓展 」app.all 和 app.use 的区别在于:
- app.all() 指的是路由中的所有请求方式,如:app.all('/a'),可以同时覆盖:app.get('/a')、app.post('/a') ...
- app.use() 接受一个或多个回调函数作为中间件。中间件,通常不处理请求和响应(技术上可以),往往处理输入数据,然后传递请求给下一个中间件,如处理 header、cookies、sessions等;执行顺序按照书写顺序,必须写在 app[method] 之前,否则不执行。
[2] 路由器级中间件
路由器级中间件,其工作方式与应用程序级中间件类似,但又有不同;
区别在于:它绑定的对象是 express.Router() 的实例:router 对象,同时,可以使用 router.use 或 router.METHOD 来加载
「 应用场景 - 路由的模块化 」定义一些路由,把这些路由模块安装在应用程序级路由上,并加载中间件功能
- 编写路由器级中间件文件
// /routes/bird.js const express = require('express'); const router = express.Router(); // 安装并引入路由器级中间件对象 router.use((req,res,next)=>{ console.log('Time',Date.now()); next() }); router.get('/',(req,res)=>{ res.send('birds homepage'); }); router.get('/about',(req,res)=>{ res.send('birds aboutpage'); }); module.exports = router;
- 在应用程序中,加载路由器级中间件模块
// app.js const express = require('express'); const app = express(); // 引入路由器级中间件文件 bird.js const birdRouter = require('./routes/bird.js'); app.use('/bird',birdRouter); app.listen(3000,()=>{ console.log('app is running'); });
[3] 错误处理中间件
错误处理中间件,始终需要 4 个参数来对其进行标识:即使不需要使用
next对象,也必须指定它以维护签名;否则,该
next对象会被解释为常规中间件,并且将无法处理错误。// 实际开发中,错误处理通常放在入口文件的最后 app.use(function(err, req, res, next) { res.locals.message = err.message; // ? 开发环境 : 生产环境错误提示 res.locals.error = req.app.get('env') === 'development' ? err : {}; res.status(err.status || 500); res.render('error'); });
[4] express 内置中间件
express.staitc 是 Express 唯一内置的中间件,负责 Express 应用中的静态资源托管
express.static(root,[option]); // root,指的是提供静态资源的根目录;
// 通常,不需要特殊设置,只需要将静态资源所在目录(假设为:public)传递给 // express.static 中间件,就可以访问 public 目录下的资源了 app.use(express.staitc('public')); // localhost:3000/images/logo.png // 如果静态资源放在多个目录下面,可以多次调用 express.static 中间件 app.use(express.static('public')); app.use(express.static('files')); // 中间件会根据目录添加的顺序查找所需的文件 // 如果希望所有通过该中间件访问的文件存放在一个'虚拟'目录(实际不存在)中, // 可以通过为静态资源目录指定一个加载路径的方式来实现 app.use('/static',express.static('public')); // 这样就可以通过带有‘/static’前缀的地址来访问public目录下的资源了 // localhost:3000/static/images/logo.png
[5] 第三方中间件
第三方插件,即为所需要的功能安装 Node.js 模块,然后将其加载到 app 的应用程序级别或路由器级别。