Express是Node.js服务端开发中一个比较完善的框架了,其中很多的思想理念还是很值得我们深入学习的,这一专题开始,我们开始尝试实现手写Express框架,对Express进行深入解析
- 这里将以需求带动开发的方式逐步对Express进行实现,所以这里先简单写一个Express的例子:
const express = require("express")
const app = express();
app.get("/", function(req, res, next){
console.log("1")
next()
}, function(req, res, next){
console.log("11")
res.end("..../.....")
})
app.get("/test", function(req, res, next){
res.end("..../test.....")
})
app.listen(3000, function(){
console.log("Server Start on 3000")
})
- 这里我们只写了一个很简单的例子,实现了两个get请求的接口,当然我们实现时会支持多种的请求方式,以上需求,就是我们将要实现路由机制的部分。
需求分析
Express路由实现中出现的几个类:
- Router类: 所有的路由都会在Router类中使用桟(实际就是一个数组)进行存储
- Route类: 一个路由就是一个Route类,里面会执行路由匹配的处理函数(handler)
- Laryer类: 路由的层,每个路由中可以传递多个处理函数和一个路由可以执行多种方法,如 “/user” 下可以有 “POST”, “PUT”, “DELETE”, "GET"等,这些都可以被放到一个处理层里.
以上三个类之间的关系如下如:
- 首先先创建一个Application的类用于获取express的实例:
express.js文件
const http = require("http")
const url = require("url")
const Application = require("./application")
function createApplication(){
return new Application()
}
module.exports = createApplication
application.js文件
const Router = require("./router")
const http = require("http")
const methods = require("./router/methods")
const slice = Array.prototype.slice;
function Application(){
// 在一个express中就有一个Rputer的实例,可以使用app.get()之类的方式进行路由的创建
this._router = new Router();
}
// 用于懒加载路由实例,因为在使用者没有使用路由时就没必要去创建
Application.prototype.lazyrouter = function(){
if(!this._router){
this._router = new Router()
}
}
// 在application实例上绑定相应的路由处理的方法,这里的methods中是一个数组,存放了相应的方法名称(POST, GET, DELETE, PATCH, PUT等)
methods.forEach(function(method){
Application.prototype[method] = function(){
this.lazyrouter();
// 这样可以支持多个处理函数,就是将一个路由处理方法中传递的所有的内容全部传给了相应的Router中的处理方法
this._router[method].apply(this._router, slice.call(arguments));
// 返回this的目的是可以使用串联的形式将多个方法串起来: app.get().post()这样的形式
return this;
}
})
Router类文件
const Route = require("./route")
const Layer = require("./layer")
const url = require("url")
const methods = require("./methods")
const slice = Array.prototype.slice;
function Router(){
// 这是Router中路由处理的桟,里面放的是一个个的Layer(路由层)
this.stack = [];
return router;
}
// 创建一个Route的实例,向当前的路由系统中添加一个层
Router.prototype.route = function(path){
let route = new Route(path);
// Route中提供dispatch方法用于执行路由的相应的处理函数(handler),而该函数的执行放到了每一层中(Layer)
let layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
// 将该层添加入栈
this.stack.push(layer);
return route;
}
methods.forEach(function(method){
Router.prototype[method] = function(path){
// 这里是在往Router里面添加一层,调用route方法
let route = this.route(path);
// 将相应路由的处理方法都传递给Route中定义的相应的方法,去掉第一个参数的原因是第一个参数应该是path值,不是处理函数
route[method].apply(route, slice.call(arguments, 1))
}
return this;
})
// 调用handle方法,匹配stack中的每一层的路由执行相应的方法函数
Router.prototype.handle = function(req, res, out){
let idx = 0, self = this;
let {pathname} = url.parse(req.url, true)
// 执行下一个路由层
function next(){
// 如果stack中的路由都匹配完毕,则直接从Router路由层中出去
if(idx >= self.stack.length){
return out()
}
let layer = self.stack[idx++];
// layer中定义match方法用于路由的匹配
// handle_method是在Route中提供的方法,用于判断是否有该请求类型的处理函数(这样做的目的是为了加多匹配,一个相同的路由下可以有多个类型的处理方法)
if(layer.match(pathname) && layer.route && layer.route.handle_method(req.method)){
// layer中提供handle_request()用于执行Route中的handler处理函数
// 这里实际就是在创建Layer中传递进去的Route中的dispatch方法
layer.handle_request(req, res, next);
}else{
next();
}
}
next();
}
module.exports = Router;
Layer类
// 路由的每一层的类
function Layer(path, handler){
this.path = path;
this.handler = handler;
}
// 匹配路由
Layer.prototype.match = function(path){
return this.path == path;
}
// 执行该层的路由处理函数
Layer.prototype.handle_request = function(req, res, next){
this.handler(req, res, next)
}
module.exports = Layer;
Route类
const Layer = require("./layer")
const methods = require("./methods")
const slice = Array.prototype.slice;
// 路由类
function Route(path){
this.path = path;
this.stack = []; //每一个Route里面有一个stack处理函数层
// 表示路由中有此方法的处理函数,这样的目的是为了加多匹配
this.methods = {};
}
// Route中用于判断是否有相应类型的处理函数:如Get类型,POST类型等
Route.prototype.handle_method = function(method){
method = method.toLowerCase();
return this.methods[method];
}
methods.forEach(function(method){
Route.prototype[method] = function(){
let handlers = slice.call(arguments);
this.methods[method] = true;
for(let i = 0; i < handlers.length; i++){
// 在Route中的路由处理函数也是一个Layer
let layer = new Layer("/", handlers[i]);
layer.method = method;
this.stack.push(layer)
}
return this;
}
})
// 用于执行该层中的路由处理函数
Route.prototype.dispatch = function(req, res, out){
let idx = 0, self = this;
// 执行该层路由中的匹配函数
function next(){
if(idx >= self.stack.length){
// 说明当前的Route已经走完了,就需要到Router中的下一层
// 这里调用的out方法其实就是外层Router中传入进来的Router层的next方法,这算是express中设计比较巧妙的环节了
return out()
}
// 每个方法的执行函数也是一个layer
let layer = self.stack[idx++];
// 判断有相应类型的处理函数匹配才调用执行,而这里传递到layer中给handle_request执行的方法就是用户创建路由时传递进来的方法
if(layer.method === req.method.toLowerCase()){
layer.handle_request(req, res, next)
}else{
next()
}
}
next();
}
module.exports = Route
methods
const methods = ["get", "post", "patch", "delete", "put"]
module.exports = methods;
这里对路由只是做了第一层的处理,里面有些内容在之后会进行删改,这里今天的目的就是进行路由的第一次抽离,当然,其中get,post之类的路由方法中的请求数据处理等会在之后解决。