前置内容
Node 可以简单快速地开启 Http 服务:
const http = require("http");
// 创建http服务
const server = http.createServer((req, res) => {
res.end("hello Node");
});
// 监听端口
server.listen(3000, () => {
console.log("服务已启动");
});
这样一个 Node 服务就启动了,此时浏览器访问http://127.0.0.1:3000
会返回hello Node
,但是目前还没有路由,所以你会发现无论输入http://127.0.0.1:3000/hi
或是其他路径都会返回相同的结果。因此 Node 服务端框架 express 提供了路由、中间件、请求处理等功能帮助我们高效的提供服务。
启动 express 服务
我们借助一个例子,来理解 express 如何启动服务
const express = require("express");
const app = express();
// 开启express服务
app.listen("3000", () => {
console.log("服务已运行在3000端口上");
});
-
express 是一个函数,执行 express 会返回一个 app 函数,app 就被作为
http.createServer
的处理函数 -
调用
app.listen
可以开启服务,本质还是通过http.createServer(app)
开启 Node 服务 -
观察 app 这个处理函数,它在原来参数基础上扩展了next函数,next可以控制下一个handle或者中间件的调用,并且将具体逻辑处理交给
app.handle
,实际上这是 express 中非常常见的代理模式,app 提供了 express 服务中所有请求唯一的入口
注册路由
const express = require("express");
const app = express();
app.get("/", (req, res, next) => {
res.end("hello");
});
// 开启express服务
app.listen("3000", () => {
console.log("服务已运行在3000端口上");
});
express 只需要调用app.get
就可以完成 get 请求方法的路由注册,express 中路由注册的内部执行流程涉及到最重要的四个模块:app、router、route、layer,其层次为:
- app层是express中的顶层设计,持有一个router路由对象
- router层存放所有的路由信息,内部会持有layer存放在自己的栈中
- layer在express中主要有三种(express中被统一抽象成了layer类):
- route layer:持有route,保存在
router
中, - middleware layer:不会持有route,只会持有相应的
handle
函数,保存在router
中, - handle layer:持有路由最终的
handle
,保存在route
中
- route layer:持有route,保存在
以上我们对express中各模块的职责了解一番后,app.get
路由注册的流程会是这样:
- app.get会调用router的route方法,这个方法会生成一个
route
和一个route layer
,让route layer
持有route
,route
提供route layer
一个dispatch
方法作为handle
,最后压入router
的栈中 - 将所有的
handle
函数传递给新生成的route
,route.get会生成handle layer保存相应的handle,并存放在自己的栈中
再通过源码来看看express具体实现:
// application.js
// methods是所有http方法集合,这里定义了app.get、app.post等方法
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
// 懒初始化router对象
this.lazyrouter();
// 执行第一步,执行router的route方法
var route = this._router.route(path);
// 执行第二步,调用新创建的route.get方法,将handle传递出去
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
// router.js
proto.route = function route(path) {
// 生成一个新route
var route = new Route(path);
// 生成一个route layer
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
// 让layer持有route
layer.route = route;
// 将layer推入到栈中
this.stack.push(layer);
// 返回route对象
return route;
};
// route.js
methods.forEach(function(method){
Route.prototype[method] = function(){
// 打平数组数据结构
var handles = flatten(slice.call(arguments));
// 遍历所有处理函数
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (typeof handle !== 'function') {
var type = toString.call(handle);
var msg = 'Route.' + method + '() requires a callback function but got a ' + type
throw new Error(msg);
}
// 创建一个handle layer, 每个handle layer都会保存一个handle函数
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
// handle layer被推入到route的栈中
this.stack.push(layer);
}
return this;
};
});
命中路由
由于我们通过app.get
注册了一个路由,此时浏览器访问http://127.0.0.1:3000/
会有相应的响应,但访问其他地址不会有响应,这表明路由已经在工作了。
现在命中路由的流程是这样:
- 根据前文我们知道express开启的服务所有请求都会走到app的逻辑中,app则执行
app.handle
方法,app.handle
又把处理具体逻辑交给router.handle
来实现,相当于把请求转给router层来处理 - 请求到了router层,
router.handle
会遍历自己stack
中所有的layer(route layer和middleware layer, 例子中为route layer),并根据当前请求的url地址依次匹配注册时保存的路由path,当条件命中时会执行该layer的handle_request
方法,这个方法就是注册时绑定在route layer
上对应的route的dispatch
方法 route.dispatch
的逻辑和router.handle
相似,遍历执行自己stack
中的handle layer,执行其handle_request
方法,即执行注册路由时绑定的handle
函数。这里express将调用下一个layer.handle_request交给了开发者,如果有多个handle layer
,开发者可以显示使用express提供的函数(一般是next)来实现各个handle
调用的逻辑
中间件
中间件注册
中间件是Web服务端框架的重要模块,服务端常常把系统中相同的逻辑交给中间件完成,从而实现认证鉴权、日志、错误处理、数据转换等功能,express中中间件就是一个函数,我们可以使用app.use
来添加一个中间件。
const express = require("express");
const app = express();
app.use(function(req, res, next) {
console.log('当前请求地址:', req.url);
// 注意,要显式调用next,否则请求/不会被匹配
next()
})
app.get("/", (req, res, next) => {
res.end("hello");
});
// 开启express服务
app.listen("3000", () => {
console.log("服务已运行在3000端口上");
});
我们创建了一个打印请求路径的中间件,express注册中间件的内部流程是:
app.use
将所有handles
处理函数传递给router.use
方法router.use
会遍历所有handles
,每个handle都会创建一个middleware layer,将其保存在middleware layer上,与route layer不同的是,该layer不会持有route
,最后将其压入router
的栈中
中间件命中
实际上中间件还支持添加路径即app.use(path, ...fns)
,不加路径则是一个全局的中间件,能响应所有的请求,因为中间件的注册和路由注册相似,其命中过程也和路由相同,都是根据path寻找对应的layer
,执行其绑定的handle函数。
app.handle
将处理逻辑交给router.handle
router.handle
通过path
找到相应的middleware layer,由于例子中我们注册的是全局中间件,则会命中该middleware layer,将其取出执行绑定在该layer
上的handle_request
方法
路径匹配
express支持正则作为路径注册路由,可以使用以下几种方式:
app.get('some?path', handle)
// 字符串正则app.get('somepath/:id', handle)
// 路径带参数app.get(/somepath/, handle)
// 支持路径为正则表达式
这些特性主要得益于path-to-regexp的支持,express中路由的命中是根据注册时保存的正则进行的。
总结
express把app设计为顶层结构,将app作为接口层暴露出去供开发者使用,内部将app的use
、get
、handle
方法具体处理逻辑交给router
,相当于app是一层向外暴露的代理。同时express使用while可中断的路由匹配方式让开发者可以使用next
控制handle
函数的调用,开发者在定义路由或中间件时需注意彼此的顺序。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!