ter = (req, res) => {
};
router.use = (fn) => {
routes.push({
method: null,
path: null,
handler: fn
});
};
METHODS.forEach(item => {
const method = item.toLowerCase();
router[method] = (path, fn) => {
routes.push({
method,
path,
handler: fn
});
};
});
};
以上主要是给 router 添加了 use
、get
、post
等方法,每当调用这些方法时,给 routes 添加一条 route 规则。
Note:
java script 中函数是一种特殊的对象,能被调用的同时,还可以拥有属性、方法。
接下来的重点在 router
函数,它需要做的是:
- 从
req
对象中取得 method、pathname
- 依据 method、pathname 将请求与routes数组内各个 route 按它们被添加的顺序依次匹配
- 如果与某个route匹配成功,执行 route.handler,执行完后与下一个 route 匹配或结束流程 (后面详述)
- 如果匹配不成功,继续与下一个 route 匹配,重复3、4步骤
const router = (req, res) => {
const pathname = decodeURI(url.parse(req.url).pathname);
const method = req.method.toLowerCase();
let i = 0;
const next = () => {
route = routes[i++];
if (!route) return;
const routeForAllRequest = !route.method && !route.path;
if (routeForAllRequest || (route.method === method && pathname === route.path)) {
route.handler(req, res, next);
} else {
next();
}
}
next();
};
对于非路由中间件,直接调用其 handler。对于路由中间件,只有请求方法和路径都匹配成功时,才调用其 handler。当没有匹配上的 route 时,直接与下一个route继续匹配。
需要注意的是,在某条 route 匹配成功的情况下,执行完其 handler 之后,还会不会再接着与下个 route 匹配,就要看开发者在其 handler 内有没有主动调用 next() 交出控制权了。
在__server.js__中添加一些route:
router.use((req, res, next) => {
console.info('New request arrived');
next()
});
router.get('/actors', (req, res) => {
res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});
router.get('/actresses', (req, res) => {
res.end('Jennifer Aniston, Scarlett Johansson, Kate Winslet');
});
router.use((req, res, next) => {
res.statusCode = 404;
res.end();
});
每个请求抵达时,首先打印出一条 log,接着匹配其他route。当匹配上 actors 或 actresses 的 get 请求时,直接发回演员名字,并不需要继续匹配其他 route。如果都没匹配上,返回 404。
在浏览器中依次访问 http://localhost:9527/erwe
、http://localhost:9527/actors
、http://localhost:9527/actresses
测试一下:
network
中观察到的结果符合预期,同时后台命令行中也打印出了三条 New request arrived
语句。
接下来继续改进 router 模块。
首先添加一个 router.all 方法,调用它即意味着为所有请求方法都添加了一条 route:
router.all = (path, fn) => {
METHODS.forEach(item => {
const method = item.toLowerCase();
router[method](path, fn);
})
};
接着,添加错误处理。
/lib/router.js
const defaultErrorHander = (err, req, res) => {
res.statusCode = 500;
res.end();
};
module.exports = (errorHander) => {
const routes = [];
const router = (req, res) => {
...
errorHander = errorHander || defaultErrorHander;
const next = (err) => {
if (err) return errorHander(err, req, res);
...
}
next();
};
server.js
...
const router = require('./lib/router')((err, req, res) => {
console.error(err);
res.statusCode = 500;
res.end(err.stack);
});
...
默认情况下,遇到错误时会返回 500,但开发者使用 router 模块时可以传入自己的错误处理函数将其替代。
修改一下代码,测试是否能正确执行错误处理:
router.use((req, res, next) => {
console.info('New request arrived');
next(new Error('an error'));
});
这样任何请求都应该返回 500:
继续,修改 route.path 与 pathname 的匹配规则。现在我们认为只有当两字符串相等时才让匹配通过,这没有考虑到 url 中包含路径参数的情况,比如:
localhost:9527/actors/Leonardo
与
router.get('/actors/:name', someRouteHandler);
这条route应该匹配成功才是。
新增一个函数用来将字符串类型的 route.path 转换成正则对象,并存入 route.pattern:
const getRoutePattern = pathname => {
pathname = '^' + pathname.replace(/(\:\w+)/g, '\(\[a-zA-Z0-9-\]\+\\s\)') + '$';
return new RegExp(pathname);
};
这样就可以匹配上带有路径参数的url了,并将这些路径参数存入 req.params 对象:
const matchedResults = pathname.match