Express 的简单使用
index.js
// 不是 es6 的写法, 构造函数, 异步处理, 迭代 都是通过回调的方式
const express = require("express");
const app = express();
app.get("/", function (req, res) {
res.end("/");
});
app.get("/hello", function (req, res) {
res.end("/hello");
});
app.listen(3000);
命令行运行,在浏览器中打开,一个简单的 express 项目就启动了,浏览器中访问localhost:3000/ 返回 ‘/’,访问 ‘/hello’ 返回 ‘/hello’
node index.js
从这个demo 中可以得出以下几点:
- express 导出的是一个函数
- 函数执行后返回一个对象
- 这个对象上有 get 和 listen 两个方法
- get 方法是一个订阅事件
- listen 方法用来启动一个 httpserver 并对不同的请求做出响应(发布事件)。
版本一
大概结构
function express() {
const stack = []
return {
get(){},
listen() {}
}
}
module.exports = express
先实现 get 的订阅逻辑
const stack = []
function get(path, handler) {
stack.push({
path,
handler
})
}
listen 的发布逻辑
const url = require('url')
function listen() {
const server = http.createServer((req, res) => {
const { pathname } = url.parse(req.url); // 获取请求路径
console.log(pathname);
const methodName = req.method.toLocaleLowerCase(); // 请求方法
function out() {
res.end(`Cannot ${req.method} ${req.url}`);
}
let idx = 0;
const dispatch = () => {
if (idx === this.stack.length) {
return out(); // 如果路由处理不了,做一个兜底处理
}
let layer = this.stack[idx++];
if (layer.path === pathname) {
layer.handler(req, res, dispatch);
} else {
dispatch();
}
};
dispatch();
});
server.listen(...arguments);
}
目前最最简单的 Express 已经可以使用了。
版本二
进一步优化和完善项目
本着单一职责的原则,我们把express 拆分为应用和路由管理
lib/express.js 入口只创建应用
const Application = require("./application");
function createApplication() {
return new Application();
}
module.exports = createApplication;
lib/application.js
应用有 get、listen 方法,路由的配置归属应用来管理。
const http = require("http");
const Router = require("./router");
function Application() {
// 路由的配置应该归属应用来管理
this._router = new Router();
}
Application.prototype.get = function (path, handler) {
this._router.get(path, handler);
// 订阅
};
Application.prototype.listen = function () {
const server = http.createServer((req, res) => {
// 应用提供一个找不到的方法, 路由匹配不到的时候交给应用
function done() {
res.end(`Cannot ${req.method} ${req.url}`);
}
this._router.handler(req, res, done);
});
server.listen(...arguments);
};
module.exports = Application;
lib/router/index.js
具体的匹配路由和路由执行的逻辑交由路由自己处理
const url = require("url");
function Router() {
this.stack = [];
}
Router.prototype.get = function (path, handler) {
this.stack.push({
path, handler
});
};
Router.prototype.handler = function (req, res, out) {
const { pathname } = url.parse(req.url); // 获取请求路径
console.log(pathname);
const methodName = req.method.toLocaleLowerCase(); // 请求方法
let idx = 0;
const dispatch = () => {
if (idx === this.stack.length) {
return out(); // 如果路由处理不了, 交给 application 处理
}
let layer = this.stack[idx++];
// 路由必须满足路径匹配了才能执行
if (layer.path === pathname) {
layer.handler(req, res, dispatch);
} else {
dispatch();
}
};
dispatch();
};
module.exports = Router;
此时 express 中各个函数的职能更加明确了。
express 还有一种用法就是不但能处理异步逻辑,还可以有多个回调函数
app.get(
"/",
function (req, res, next) {
console.log(1)
next();
},
function (req, res, next) {
console.log(2)
next();
},
function (req, res, next) {
console.log(3)
next();
}
);
app.get(
"/",
function (req, res, next) {
console.log(4)
res.end("a");
}
);
最终访问 ‘/’ 会依次打印 1234, app.get 的所有 callback 函数都执行了。 每一次 get 请求都会往 router 的 stack 中放入一个对象{method,path, handler}, 我们可以把 router 中 stack 的对象单独抽出一个类 Layer。
layer 的功能很简单。主要存两个东西 path, handler 即 路径和处理方法。这里没有存储 method。
而每个请求又可能对应多个 method, 这里再引入一个类 Route。 Route 中也有一个 stack, stack 中放入 method 和真正的处理函数
大概的流程图如下:
lib/router/layer.js
function Layer(path, handler) {
this.path = path;
this.handler = handler
}
Layer.prototype.match = function(pathname) {
return this.path === pathname
}
这里改写一下以下几个文件
lib/application.js
Application.prototype.get = function (path, ...handlers) { // 用 handlers 数组接收可能的回调函数
this._router.get(path, handlers);
};
lib/router/index.js
const url = require("url");
const Layer = require("./layer");
const Route = require("./route");
function Router() {
this.stack = [];
}
Router.prototype.route = function (path) {
const route = new Route()
// 每次 app.get 都会在 Router 的 stack 中放入一层
const layer = new Layer(path, route.dispatch.bind(route))
// 而每一层又会有对应的 method 和 多个处理函数
layer.route = route
this.stack.push(layer);
return route
};
Router.prototype.get = function (path, handler) {
// 每次 app.get 都会在 Router 的 stack 中放入一层
const route = this.route(path);
// 而每一层又会有对应的 method 和 多个处理函数
route.get(handlers);
};
Router.prototype.handler = function (req, res, out) {
const { pathname } = url.parse(req.url); // 获取请求路径
console.log(pathname);
const methodName = req.method.toLocaleLowerCase(); // 请求方法
let idx = 0;
const dispatch = () => {
if (idx === this.stack.length) {
return out(); // 如果路由处理不了, 交给 application 处理
}
let layer = this.stack[idx++];
// 必须满足路径匹配了才能执行
if (layer.match(pathname)) { // 匹配逻辑也交由具体的 layer 处理
layer.handler(req, res, dispatch);
} else {
dispatch();
}
};
dispatch();
};
module.exports = Router;
./lib/router/route.js
function Route() {
this.stack = [];
}
Route.prototype.get = function(handlers) {
// 遍历处理函数,放入 stack 中
handlers.forEach(handler => {
const layer = new Layer('/', handler)
layer.method = 'get'
this.stack.push(layer)
})
}
Route.prototype.dispatch = function(req, res, next) {
const methodName = req.method.toLowerCase();
let idx = 0
function dispatch() {
if(idx === this.stack.length) {return next()}
const layer = this.stack[idx++]
if(layer.method === methodName) {
layer.handler(req, res, dispatch)
}else {
dispatch()
}
}
dispatch()
}
这样就满足了基本的 express 的写法。
版本三
还有一些功能需要完善,比如 express 的 use 方法
app.use(function(req, res, next) {
console.log(1)
req.a = 1
next()
})
app.use('/',function(req, res, next) {
console.log(2)
req.a++
next()
})
app.use('/a',function(req, res, next) {
console.log(3)
req.a++
next()
})
app.get('/', function(req, res, next) {
console.log(4)
res.end(req.a + '')
})
app.get('/a', function(req, res, next) {
console.log(5)
res.end(req.a + '')
})
当请求 / 时,最终页面上显示的是 2。 执行的是 1,2,4
当请求 /a 时,最终页面上显示的也是 3。执行的是 1,2,3,5
use 方法的回调函数可以是一个参数,也可以是两个参数(path, handler),而且的参数的匹配不是绝对的匹配,而是以 path 开头的都可以的匹配上
lib/application.js
Application.prototype.use = function(path, handler) {
this._router.use(path, hander)
}
lib/router/index.js
Router.prototype.use = function() {
if(typeof path === 'function') {
handler = path
path = '/'
}
const layer = new Layer(path, handler)
layer.route = undefined; // 用 route 属性来区分是路由还是中间件
this.stack.push(layer)
}
lib/router/layer.js
改写一下 match 方法已适用 use 中间件的 path 匹配规则
Layer.prototype.match = function(pathname) {
if(this.path === pathname) {
return true
}
if(!this.route) {
if(this.path === '/') {
return true
}
return path.startsWith(this.path + '/') // /a/b. /a/
}
}