假设Express生成的应用实例名称为 app。我使用的是 TypeScript,关于如何搭建 TypeScript语言的 Node.js 开发环境,请参考这个链接:搭建Node.js+TypeScript开发环境要点及注意事项;
也可以执行以下命令直接拉取仓库,将会得到一个框架。
git clone git@gitee.com:war_horse/framework_for_node.js_using_ts.git
实质意义
- 他们本质都是处理各种路由和请求方式的函数,两者的界限并不是很清晰,添加方式和运行机制大致相同;
- 他们都有 req、res 和 next 参数;
- 他们都能按指定条件匹配不同的路由和请求方式。
添加方式
他们都可以使用app.use 、 app.METHOD(METHOD代表 post、get、patch、put、delete 等请求方法中的一种,下同)和 app.all(all 代表所有请求方法,下同) 的方式添加到应用实例;一般情况下,用 app.use 方式添加的函数称为“中间件处理函数”,而用其他方式添加的函数称为“路由处理函数”。
-
示例 1:中间件函数的各种添加方式
import express from 'express'; const app = express(); /** * 尝试用各种方式添加中间件处理函数 */ app.use((req, res, next) => { console.log('我是采用 app.use 方法添加的中间件'); next(); }) app.get('/',(req,res,next) => { console.log('我是采用 app.get 方法添加的中间件') next() }) app.all('/',(req,res,next) => { console.log('我是采用 app.all 方法添加的中间件') res.end() }) app.listen(3000, () => { console.log('服务已成功启动,监听端口:3000') })
当用浏览器打开 http://localhost:3000 的时候,在服务器端将输出以下结果:
-
实例 2:路由处理函数各种添加方式
import express from 'express'; const app = express(); /** * 用各种方式添加路由处理函数 */ app.use('/items',(req, res, next) => { console.log(`我是采用 app.use 方法添加的路由处理函数。当前请求方式:${req.method}`); next(); }) app.get('/items',(req,res,next) => { console.log('我是采用 app.get 方法添加的路由处理函数') next() }) app.post('/items',(req,res,next) => { console.log('我是采用 app.post 方法添加的路由处理函数') next() }) app.all('/items',(req,res,next) => { console.log(`我是采用 app.all 方法添加的路由处理函数。当前请求方式:${req.method}`) res.end() }) app.listen(3000, () => { console.log('服务已成功启动,监听端口:3000') })
当用 get 方式 向 http://localhost:3000/items发送请求的时候,服务器端输出以下结果,可以看到,除了 app.get 方式以外, app.use 和 app.all 方式添加的路由处理函数都匹配到了:
我们再次用 post 方式向http://localhost:3000/items 发送请求,输出以下结果:
存储和调用方式
通过观察以上的示例可以得出结论:他们在应用实例内部数组的形式存储,按添加和匹配的顺序被调用;前序匹配的函数调用了 next 方法,后一个匹配的函数才会被调用。
各种添加方式的注意事项
app.use方法
app.use 方法表示匹配所有的请求方式,他有两种参数形式,既可以指定路由,也可以不指定路由,但处理函数不能省略 :
-
如果只传递了一个参数,那么这个参数必须是一个函数,这个函数匹配所有请求方式和路径:
import express from 'express'; const app = express(); /** * app.use 方法研究 */ app.use((req, res) => { console.log(`采用 app.use 直接添加一个处理函数的方式,能匹配所有路径和请求方式,当前请求方式:${req.method},当前请求路径:${req.path}`) res.end() }) app.listen(3000, () => { console.log('服务已成功启动,监听端口:3000') })
以 post 方式向 http://localhost:3000/abded 发送请求,结果如下:
以 delete 方式向http://localhost:3000/123456发送请求,结果如下:
以上示例能证明,app.use 直接添加处理函数(但不指定路径)的方式能匹配所有路径和请求方式。 -
如果传递了两个参数,则第一个参数必须是一个路径字符串,第二个参数是一个函数,这个函数匹配这个路径下的所有请求方式,以 get 请求为例:
import express from 'express'; const app = express(); /** * app.use 方法研究 */ app.use('/test',(req, res) => { console.log(`采用 app.use 向一个路由添加处理函数,能匹配所有请求方式,当前请求方式:${req.method}`) res.end() }) app.listen(3000, () => { console.log('服务已成功启动,监听端口:3000') })
当向 http://localhost:3000/test 发送各种类型请求之后,输出以下结果:
app.METHOD 和 app.all方法
方式添加的路由处理函数或者中间件函数,至少接受两个参数,第一个参数必须是路径字符串,其他特性与 app.use 方法一样。
确保他们正常运行的注意事项
- 中间件处理函数和路由处理函数是按照匹配规则和添加的顺序先后调用的;可以这样理解,一次请求过程中,首先找出能匹配这个请求方式和路由的中间件处理函数和路由处理函数,然后按照他们添加的顺序依次执行;
- 匹配成功的函数必须内部调用 next 方法,才能确保后续匹配成功的函数能被调用;
- 调用res.download、res.send、res.end、res.json、res.jsonp、res.redirectr、res.render、res.sendFile、res.sendStatus 方法中的一种能完成一次请求处理;
- 一次响应的处理过程中,以上方法的调用有且仅有一次,否则会出错。不调用的话,程序将会处于挂起状态,调用1 次以上服务端会报错。
- 如果在一个匹配成功的函数内调用了以上完成请求处理方法中的任意一个,后续匹配成功的函数将不能被调用;
- 如果一个匹配成功的函数内部既没有调用 next,也没有调用完成请求处理的方法,那么应用实例会被挂起,在浏览器端也会一直等待响应。