本文只是实现了express的基本用法的原理,具体代码结构和真正的express.js代码结构不一样,可以说是简化版的express,重要的是理解express的一些基本用法的实现原理,比如路由,中间件,路径参数,错误处理等,这样我们在使用的时候就能了然于心!
测试用例
const express = require("./express.js");
// 初始化express
const app = express();
// 中间件匹配所有路径
app.use(function (req,res,next) {
console.log('全部匹配');
next();
});
app.use('/world', function (req,res,next) {
console.log('只匹配/world');
//next("特色错误");
next();
});
app.get("/hello",function(req,res){
res.end("hello");
});
app.post("/world",function(req,res) {
res.end("world");
});
// 路径参数
app.post("/man/:age/:name",function(req,res) {
console.log(req.params);
res.end("man");
});
// 匹配剩余所有其他路径请求
app.all("*",function(req,res) {
res.end("all")
});
// 错误中间件
app.use(function (err, req, res, next) {
console.log(err);
res.end(err);
});
// 监听端口
app.listen(8080);
express.js源码
let http = require("http");
let url = require("url");
// 返回expres的实例方法,需要用户调用
const createApplication = function () {
// 真正的请求方法
let app = function (req, res) {
var urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname;
const method = req.method.toLowerCase();
// 增加一些通用参数
req.path = pathname;
req.query = urlObj.query;
req.hostname = req.headers['host'].split(':')[0];
// 当只有简单的路由匹配的时候可以用循环来处理,因为不会被中断
// for (let index = 0; index < app.routes.length; index++) {
// const route = app.routes[index];
// if ((method === route.method || route.method === "all" )
// && (pathname === route.path || route.path === "*")) {
// return route.handler(req, res);
// }
// }
// 但是要实现遍历所以要声明一个索引递增来控制
let index = 0;
// 当要加入中间件的时候,需要用next函数来递归调用,把控制权传递给外部来实现中间件原理
function next(err) {
// 所有路由不能匹配时报错
if (index >= app.routes.length) {
return res.end(`Cannot find ${method} ${pathname}`);
}
let route = app.routes[index++];
// 外部传入参数时,则定义为err, 直接调用拥有四个参数的处理函数
if (err) {
// route.handler.length : 表示函数的参数是四个
if (route.method == "middle" && route.handler.length == 4) {
// console.log("错误处理啦", req, res);
route.handler(err, req, res, next);
} else { // 没有找到继续向下找错误处理函数
next(err);
}
} else {
if (route.method == "middle") { // 处理中间件部分
// route.path == "/": 表示全部匹配
// pathname.startsWith(route.path + "/") : 表示路由匹配以这个开头的所有 ,如 /user/to/...
// pathname == route.path : 表示相等 如 /user
if (route.path == "/" || pathname.startsWith(route.path + "/") || pathname == route.path ) {
route.handler(req,res,next); // 注意这里不能return 因为中间件只是处理,不返回
} else {
next();
}
} else {
// 处理路径参数部分
if (route.params) {
/**
* "a/27/jason".match(new RegExp("a/([^\/]+)/([^\/]+)"))
* 匹配如下
* 0: "a/v/d"
* 1: "27"
* 2: "jason"
*/
let macther = pathname.match(new RegExp(route.path));
// 用于存放参数
let params = {};
for (let j = 0; j < route.params.length; j++) {
params[route.params[j]] = macther[j+1];
}
// 放入req中
// req.params = {age:27, name:jason};
req.params = params;
// 执行回调
route.handler(req, res);
}
// 处理路由部分
// 只有当路由路径完全匹配或者为all全部匹配时才执行处理函数
if ((method === route.method || route.method === "all" ) && (pathname === route.path || route.path === "*")) {
route.handler(req, res); // 这里要return,因为请求处理完了
} else {
next();
}
}
}
}
// 默认调用
next();
//res.end(`cannot find ${req.method} ${pathname}`);
}
// 中间件函数,类型和路由一样,只是方法名都叫middle, 这种思想值得学习
app.use = function(path, handler) {
// 匹配只传回调函数的情况
if (typeof path == "function" && typeof handler != 'function') {
handler = path;
path = "/";
}
app.routes.push({
method:"middle",
path,
handler
});
}
// 路由表
app.routes = [];
// 循环遍历请求方法名, 生成对应的请求函数
http.METHODS.forEach(function (method) {
method = method.toLowerCase();
app[method] = function (path, handler) {
let layer = {
method,
path,
handler
};
// 如果路径中有冒号,则是存在参数的路径
if (path.includes(":")) {
let paramsArray = [];
// 替换处理参数,并存入带正则的路径到路由表
/**
* path = "/a/:age/:name"
* 匹配后
* layer.path = /a/([^\/]+)/([^\/]+);
* layer.params = ["age","name"];
*/
layer.path = path.replace(/:([^\/]+)/g,function() {
// 把匹配的参数名存入数组
paramsArray.push(arguments[1]);
// 换成一个正则,留作以后路由匹配时用,这里十分巧妙,一举两得
return "([^\/]+)";
})
// 存入路由中,留做以后路由匹配时用
//console.log(layer);
layer.params = paramsArray;
}
app.routes.push(layer);
}
});
// 监听所有请求路径
app.all = function (path, handler) {
app.routes.push({
method: "all",
path,
handler
});
}
// 监听函数,此时才启动服务,传递参数到真正的函数
app.listen = function () {
// 这里把app函数传入才是真正的监听函数
let server = http.createServer(app);
server.listen.apply(server,arguments);
// 下面会把arguments当作一个对象传入, 不符合实际
//http.createServer(app).listen(arguments);
}
return app;
}
module.exports = createApplication;
网友评论