实现原理
...大约 7 分钟
源码结构

首先我们去
package.json
文件中查找main
字段来确定入口文件在哪里但是我们并没有发现
main
字段,说明入口文件是根目录下的index.js
文件module.exports = require('./lib/express');
我们又发现
index.js
文件引用了./lib/express
导出的模块,其它的代码文件都是来辅助实现express.js
// express.js 部分代码 exports = module.exports = createApplication; function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); // expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }) // expose the prototype that will get set on responses app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } }) app.init(); return app; }
至此我们大概了解了express 的源码结构
快速体验
源码
// express.js
const http = require("http");
const url = require("url");
const routes = [
// { path: "", method: "", handler: () => {} },
// { path: "", method: "", handler: () => {} },
// { path: "", method: "", handler: () => {} }
];
function createApplication() {
return {
// 把路由收集起来
get(path, handler) {
routes.push({
path,
method: "get",
handler
});
},
listen(...args) {
const server = http.createServer((req, res) => {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = routes.find((route) => route.path === pathname && route.method === method);
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
});
server.listen(...args);
}
};
}
module.exports = createApplication;
使用
// app.js
const express = require("./express");
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.end("Hello World!");
});
app.get("/zx", (req, res) => {
res.end("zx2!");
});
app.listen(port, () => {
console.log("GodX------>log服务端应用已经启动在 3000 端口");
});
抽取 App 模块
接下来我们继续在上面的源码基础上继续开发。由于具体的实现都写死在 createApplication
函数中,不方便扩展与维护。更好的做法是将返回的对象包装到一个构造函数来实现然后提取出来,这样管理维护起来会更加方便。
// application.js
const http = require("http");
const url = require("url");
function App() {
this.routes = [];
}
App.prototype.get = function (path, handler) {
this.routes.push({
path,
method: "get",
handler
});
};
App.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.routes.find((route) => route.path === pathname && route.method === method);
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
});
server.listen(...args);
};
module.exports = App;
// express.js
const App = require("./application");
function createApplication() {
const app = new App();
return app;
}
module.exports = createApplication;
提取路由模块
为了更方便的开发和管理,接下来我们继续将路由相关逻辑单独封装到一个模块当中。新建 lib/router/index.js
文件
const url = require("url");
function Router() {
this.stack = [];
}
Router.prototype.get = function (path, handler) {
this.stack.push({
path,
method: "get",
handler
});
};
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.stack.find((route) => route.path === pathname && route.method === method);
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
};
module.exports = Router;
修改 express.js
文件
const App = require("./application");
function createApplication() {
const app = new App();
return app;
}
module.exports = createApplication;
处理不同的请求方法
测试用例
app.get("/zx", (req, res) => {
res.end("zx2!");
});
app.post("/zx", (req, res) => {
res.end("post zx!");
});
app.patch("/zx", (req, res) => {
res.end("patch zx!");
});
app.delete("/zx", (req, res) => {
res.end("delete zx!");
});
目前我们只支持get
请求,现在我们扩展一下其它的请求方法。
// route/index.js
const url = require("url");
const methods = require("methods");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handler) {
this.stack.push({
path,
method,
handler
});
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.stack.find((route) => route.path === pathname && route.method === method);
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
};
module.exports = Router;
// application.js
const http = require("http");
const Router = require("./router");
const methods = require("methods");
function App() {
this._router = new Router();
}
methods.forEach(method => {
App.prototype[method] = function (path, handler) {
this._router[method](path, handler);
};
})
App.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
this._router.handler(req, res);
});
server.listen(...args);
};
module.exports = App;
提示
这里我们使用了一个第三方模块:methods。改模块返回了大部分常用的请求方法。内部实现也很简单
var http = require('http');
module.exports = getCurrentNodeMethods() || getBasicNodeMethods();
function getCurrentNodeMethods() {
return http.METHODS && http.METHODS.map(function lowerCaseMethod(method) {
return method.toLowerCase();
});
}
function getBasicNodeMethods() {
return [
'get',
'post',
'put',
'head',
'delete',
'options',
'trace',
'copy',
'lock',
'mkcol',
'move',
'purge',
'propfind',
'proppatch',
'unlock',
'report',
'mkactivity',
'checkout',
'merge',
'm-search',
'notify',
'subscribe',
'unsubscribe',
'patch',
'search',
'connect'
];
}
更强大的路由路径匹配模式-基本实现
测试用例
app.get("/ab?c", (req, res) => {
res.end("/ab?c");
});
express 内部主要使用了第三方模块path-to-regexp
来实现路由匹配模式。
const url = require("url");
const methods = require("methods");
const pathRegexp = require("path-to-regexp");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handler) {
this.stack.push({
path,
method,
handler
});
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.stack.find((route) => {
const keys = [];
const regexp = pathRegexp(route.path, keys, {});
const match = regexp.exec(pathname);
return match && route.method === method;
});
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
};
module.exports = Router;
处理动态路由路径参数
测试用例
app.get("/users/:userId/books/:bookId", (req, res) => {
console.log(req.params) // { userId: '12',bookId: '34' }
res.end("/users/:userId/books/:bookId");
});
const url = require("url");
const methods = require("methods");
const pathRegexp = require("path-to-regexp");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handler) {
this.stack.push({
path,
method,
handler
});
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.stack.find((route) => {
const keys = [];
const regexp = pathRegexp(route.path, keys, {});
const match = regexp.exec(pathname);
console.log("GodX------>logkeys:", keys);
console.log("GodX------>logmatch:", match);
if(match) {
req.params = req.params || {};
keys.forEach((key,index) => {
req.params[key.name] = match[index + 1]
})
}
return match && route.method === method;
});
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
};
module.exports = Router;
控制台打印

提取 Layer 处理模块
为了方便开发与维护,我们进一步将路由处理的部分逻辑提取出来
// router/layer.js
const pathRegexp = require("path-to-regexp");
function Layer(path, handler) {
this.path = path;
this.handler = handler;
this.keys = [];
this.regexp = pathRegexp(path, this.keys, {});
this.params = {};
}
Layer.prototype.match = function (pathname) {
const match = this.regexp.exec(pathname);
if (match) {
this.keys.forEach((key, index) => {
this.params[key.name] = match[index + 1];
});
return true;
}
return false;
};
module.exports = Layer;
// router/index.js
const url = require("url");
const methods = require("methods");
const Layer = require("./layer");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handler) {
const layer = new Layer(path, handler);
layer.method = method;
this.stack.push(layer);
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
const route = this.stack.find((layer) => {
const match = layer.match(pathname);
if (match) {
req.params = req.params || {};
Object.assign(req.params, layer.params);
}
return match && layer.method === method;
});
if (route) {
return route.handler(req, res);
}
res.end("404 Not Found");
};
module.exports = Router;
实现单个处理函数的中间件功能
测试用例
// 1. 第一种方式:多个处理函数
app.get('/', (req, res, next) => {
console.log('/ 1')
next()
}, (req, res, next) => {
console.log('/ 2')
next()
}, (req, res, next) => {
console.log('/ 3')
next()
})
app.get('/', (req, res, next) => {
res.end('get /')
})
// 2. 第二种方式:单个处理函数
app.get('/foo', (req, res, next) => {
console.log('foo 1')
setTimeout(() => {
next()
}, 1000)
})
app.get('/foo', (req, res, next) => {
console.log('foo 2')
next()
})
app.get('/foo', (req, res, next) => {
res.end('get /foo')
})
单个处理函数实现
// router/index.js
const url = require("url");
const methods = require("methods");
const Layer = require("./layer");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handler) {
const layer = new Layer(path, handler);
layer.method = method;
this.stack.push(layer);
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
let index = 0;
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`);
}
const layer = this.stack[index++];
const match = layer.match(pathname);
if (match) {
req.params = req.params || {};
Object.assign(req.params, layer.params);
}
if (match && layer.method === method) {
return layer.handler(req, res, next);
}
next();
};
next();
};
module.exports = Router;
多个处理函数
修改router/index.js
const url = require("url");
const methods = require("methods");
const Layer = require("./layer");
const Route = require("./route");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handlers) {
const route = new Route();
const layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
route[method](path, handlers);
// const layer = new Layer(path, handler);
// layer.method = method;
// this.stack.push(layer);
};
});
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
let index = 0;
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`);
}
const layer = this.stack[index++];
const match = layer.match(pathname);
if (match) {
req.params = req.params || {};
Object.assign(req.params, layer.params);
}
// 顶层只判定请求路径,内层判定请求方法.目的是为了支持以下写法:
// app.route('/foo)
// .get((req,res) => {})
// .post((req.res) => {})
// .delete((req.res) => {})
if (match) {
// 顶层这里调用的 handler 其实就是 dispatch 函数
return layer.handler(req, res, next);
}
next();
};
next();
};
module.exports = Router;
实现多个处理函数的路由中间件
// router/route.js
const methods = require("methods");
const Layer = require("./layer");
function Route() {
this.stack = [];
}
// 遍历执行当前路由对象中多有的处理函数
Route.prototype.dispatch = function (req, res, out) {
// 遍历内层的 stack
let index = 0;
const method = req.method.toLowerCase();
const next = () => {
if (index >= this.stack.length) return out();
const layer = this.stack[index++];
if (layer.method === method) {
return layer.handler(req, res, next);
}
next();
};
next();
};
methods.forEach((method) => {
Route.prototype[method] = function (path, handlers) {
handlers.forEach((handler) => {
const layer = new Layer(path, handler);
layer.method = method;
this.stack.push(layer);
});
};
});
module.exports = Route;
回顾 use 方法使用规则
不验证请求方法和请求路径
app.use((req, res, next) => {
res.end("Hello");
});
app.use('/',(req,res) => {
res.end('use //')
})
匹配路径
// /foo/a 、 /foo/b 、 /foo/c 、 /foo/d 可以
// /fooa 、/foob 不可以
app.use("/foo", (req, res) => {
res.end("use foo");
});
多个处理函数
app.use(
"/foo",
(req, res, next) => {
console.log("/ 1");
next();
},
(req, res, next) => {
console.log("/ 2");
next();
},
(req, res, next) => {
console.log("/ 3");
res.end('use foo3')
// next();
}
);
实现 use 方法
首先在 /express/application.js
添加以下代码
App.prototype.use = function (path,...handlers) {
this._router.use(path, handlers);
}
修改 router.js
中的相关代码
// router/index.js
const url = require("url");
const methods = require("methods");
const Layer = require("./layer");
const Route = require("./route");
function Router() {
this.stack = [];
}
methods.forEach((method) => {
Router.prototype[method] = function (path, handlers) {
const route = new Route();
const layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
route[method](path, handlers);
};
});
Router.prototype.use = function (path, handlers) {
if (typeof path === 'function') {
handlers.unshift(path) // 处理函数
path = '/' // 任何路径都以它开头的
}
handlers.forEach(handler => {
const layer = new Layer(path, handler)
layer.isUseMiddleware = true
this.stack.push(layer)
})
}
Router.prototype.handler = function (req, res) {
const { pathname } = url.parse(req.url);
const method = req.method.toLowerCase();
let index = 0;
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`);
}
const layer = this.stack[index++];
const match = layer.match(pathname);
if (match) {
req.params = req.params || {};
Object.assign(req.params, layer.params);
}
// 顶层只判定请求路径,内层判定请求方法.目的是为了支持以下写法:
// app.route('/foo)
// .get((req,res) => {})
// .post((req.res) => {})
// .delete((req.res) => {})
if (match) {
// 顶层这里调用的 handler 其实就是 dispatch 函数
return layer.handler(req, res, next);
}
next();
};
next();
};
module.exports = Router;
// /router/layer.js
const pathRegexp = require("path-to-regexp");
function Layer(path, handler) {
this.path = path;
this.handler = handler;
this.keys = [];
this.regexp = pathRegexp(path, this.keys, {});
this.params = {};
}
Layer.prototype.match = function (pathname) {
const match = this.regexp.exec(pathname);
if (match) {
this.keys.forEach((key, index) => {
this.params[key.name] = match[index + 1];
});
return true;
}
// 匹配 use 中间件的路径处理
if(this.isUseMiddleware) {
if(this.path === '/' || pathname.startsWith(`${this.path}/`)) return true;
}
return false;
};
module.exports = Layer;
完整代码
Powered by Waline v3.3.0