我们仅仅实现express框架的路由和中间件功能。
我们先来看一下效果咋样。
const express = require("../express");
const app = express();
app.get("/getName", function (req, res, next) {
res.end("hello world");
next();
})
app.use("/", function (req, res, next) {
console.log("这是第一个中间件")
next("worry");
})
app.use("/getName", function (err,req, res, next) {
console.log(err + "这是错误处理中间件")
})
app.listen(8000, () => {
console.log("8000服务器已经启动");
});
访问http://localhost:8000/getName(hello world已经打印)
终端显示。
我们在看看带有参数的路径
app.get("/getName/:name/:age", function (req, res, next) {
res.end("over")
console.log(req.params);
});
app.listen(8000, () => {
console.log("8000服务器已经启动");
});
大致上先实现了这些功能,我们来看一下怎么做的。先看一下整体的结构
app.listen 这个函数用于开启一个服务器,代码很简单。(app是回调函数)
app.listen = function (...args) { //就是开启一个服务器
let server = http.createServer(app);
server.listen.apply(server, args);
};
接下来就是在app上面挂载一些类似get,post的路由(也是中间件)。为了方便,我们遍历http.MEHODS上面的方法,然后给每个方法绑定一个回调。这里还需要对路径进行一些处理,比如上面出现的/getName/:name/:age等这样的,需要利用正则处理一些,为了方便后面的匹配工作,而且需要把参数都保留下来。
http.METHODS.forEach((method) => { //遍历http上面的一些方法
method = method.toLocaleLowerCase(); //转小写
app[method] = function (path, handle) {
if (path.includes(":")) { //判断是否有: 表面使用参数/getName/:name/:age
let paramterName = [];
path = path.replace(/:([^\/]+)/g, function () {
paramterName.push(arguments[1]); //保存参数
return "([^\/]+)" //将path有参数的位置换成正则表达式
})
//getName/:name/:age
//getName/([^\/]+)/([^\/]+)
routers.push({//方法 路径 回调 参数
method,path,handle, paramterName
});
} else {//如果没有就直接添加 方法 路径 回调
routers.push({
method,path,handle
});
}
}
});
对于路由匹配。这里还有一个特殊的all,也就是只匹配路径,不看方法(因此把method改为all)
app.all = function (path, handle) {
routers.push({
method: "all",path, handle
})
}
下面是中间件的使用use,在express中use可以一个参数,也可以俩个(回调函数必须有,路径可以不写默认为"/")
app.use = function (path, handle) {
if (typeof handle !== "function") { //表明没有写路径 path
handle = path;
path = "/";
}
routers.push({
method: "middle", path, handle
})
}
下面就是重点,app函数里面的next函数。next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么请求将被挂起,后边定义的中间件将得不到被执行的机会。路由和中间件也不一样,因为中间件是前缀匹配/getName/1 就能与/getName匹配成功。当next()里面加了参数,相当于出现“错误”,我们需要去寻找错误处理中间件。如果当前中间件不满足要求,那么就去下一个中间件。
function app (req, res) {
const { pathname } = url.parse(req.url, true);
let next = (function () {
let index = 0;
function next (err) {
if (index >= routers.length) { return ; } //应该退出
let route = routers[index++]; //获取当前中间件
if (err) { // 遇到错误
if (route.method === "middle") {//如果是中间件
if (route.handle.length === 4) { //而且是错误处理中间件
if (route.path === "/" || pathname.startsWith(route.path+"/")
|| route.path === pathname) {
return route.handle(err, req, res, next);
} else next(err);
} else next(err);
} else {
next(err);
}
} else {
if (route.method === "middle") { //中间件
if (route.path === "/" || pathname.startsWith(route.path+"/")
|| route.path === pathname) { //中间件匹配前缀 匹配成功
if (route.handle.length !== 4) return route.handle(req, res, next);
else next();
} else {
next(); //继续调用
}
} else { //路由组件
if (route.paramterName) { //判断路径上面是否有参数
let match = pathname.match(route.path);
if (match) { //如果匹配成功
req.params = {};//将参数挂载到req对象上面
for (let i=0; i<route.paramterName.length; i++) {
req.params[route.paramterName[i]] = match[i+1];
}
return route.handle(req, res, next);
} else {
next();
}
} else {//路径里面没有参数
if ((route.method === req.method.toLocaleLowerCase() || route.method === "all")
&& (route.path === pathname || route.path === "*")) {
return route.handle(req, res, next);
} else {
next();
}
}
}
}
}
return next;
})();
next();
};
内置中间件就是在req上面挂载三个属性而已。
源代码:
const http = require("http");
const url = require("url");
function express () {
let routers = []; //路由数组保存方法 路径 回调函数
function app (req, res) {
const { pathname } = url.parse(req.url, true);
let next = (function () {
let index = 0;
function next (err) {
if (index >= routers.length) { return ; } //应该退出
let route = routers[index++]; //获取当前中间件
if (err) { // 遇到错误
if (route.method === "middle") {//如果是中间件
if (route.handle.length === 4) { //而且是错误处理中间件
if (route.path === "/" || pathname.startsWith(route.path+"/")
|| route.path === pathname) {
return route.handle(err, req, res, next);
} else next(err);
} else next(err);
} else {
next(err);
}
} else {
if (route.method === "middle") { //中间件
if (route.path === "/" || pathname.startsWith(route.path+"/")
|| route.path === pathname) { //中间件匹配前缀 匹配成功
if (route.handle.length !== 4) return route.handle(req, res, next);
else next();
} else {
next(); //继续调用
}
} else { //路由组件
if (route.paramterName) { //判断路径上面是否有参数
let match = pathname.match(route.path);
if (match) { //如果匹配成功
req.params = {};//将参数挂载到req对象上面
for (let i=0; i<route.paramterName.length; i++) {
req.params[route.paramterName[i]] = match[i+1];
}
return route.handle(req, res, next);
} else {
next();
}
} else {//路径里面没有参数
if ((route.method === req.method.toLocaleLowerCase() || route.method === "all")
&& (route.path === pathname || route.path === "*")) {
return route.handle(req, res, next);
} else {
next();
}
}
}
}
}
return next;
})();
next();
};
app.listen = function (...args) { //就是开启一个服务器
let server = http.createServer(app);
server.listen.apply(server, args);
};
//all只匹配路径 不匹配方法
app.all = function (path, handle) {
routers.push({
method: "all",path, handle
})
}
http.METHODS.forEach((method) => { //遍历http上面的一些方法
method = method.toLocaleLowerCase(); //转小写
app[method] = function (path, handle) {
if (path.includes(":")) { //判断是否有: 表面使用参数/getName/:name/:age
let paramterName = [];
path = path.replace(/:([^\/]+)/g, function () {
paramterName.push(arguments[1]); //保存参数
return "([^\/]+)" //将path有参数的位置换成正则表达式
})
//getName/:name/:age
//getName/([^\/]+)/([^\/]+)
routers.push({//方法 路径 回调 参数
method,path,handle, paramterName
});
} else {//如果没有就直接添加 方法 路径 回调
routers.push({
method,path,handle
});
}
}
});
//处理中间件
app.use = function (path, handle) {
if (typeof handle !== "function") { //表明没有写路径 path
handle = path;
path = "/";
}
routers.push({
method: "middle", path, handle
})
}
//内置中间件
app.use(function (req, res, next) { //处理 req.query req.hostname req.path
const urlObj = url.parse(req.url, true);
req.query = urlObj.query;
req.path = req.pathname;
req.hostname = req.headers["host"].split(":")[0];
next();
})
return app;
}
module.exports = express;