在前面章节中介绍的静态资源服务器在渲染目录页的时候使用了部分动态特性——模板渲染,而路由功能是动态服务器的基础
路由简单来讲就是根据客户端的请求路径和方法(get、post 等)调用不同的功能
最简单的路由
使用中间件可以轻松实现一个极简的路由
为了方便 response 内容输出,先写一个最简单的处理 response 的中间件 src/middleware/body.js
module.exports = async function (ctx, next) {await next();const { body } = ctx;if (body !== null && body !== undefined) {if (typeof ctx.body === 'string') {ctx.res.end(ctx.body);} else {ctx.body.pipe(ctx.res);}}};
然后是最简单的路由实现,对响应的请求处理有限
- 只能响应 GET 方法
- 路径以
file/或者/data/开头
src/middleware/router.js
async function router(ctx, next) {await next();const { path, method } = ctx;if (/^\/file\/.+/.test(path) && method === 'GET') {ctx.body = `get file: ${path}`;} else if (/^\/data\/.+/.test(path) && method === 'GET') {ctx.body = `get data: ${path}`;} else {ctx.body = 'error!';}}module.exports = router;
在 start() 方法中注册两个中间件 src/index.js
this.server = http.createServer((req, res) => {const { url, method } = req;// 准备中间件的执行环境const ctx = {req,res,config: this.config,path: url,method,};// 按顺序调用中间件compose([body, router])(ctx);}).listen(port, () => {console.log(`Dynamic server started at port ${port}`);});
完整代码:https://github.com/Samaritan89/dynamic-server/tree/v1
koa-router
这样的实现问题很明显,当路由多起来后代码中充斥大量的 if-else 不方便管理,koa-router 通过简洁的 api 让路由管理非常简单
const Koa = require('koa');const KoaRouter = require('koa-router');const app = new Koa();// 创建 router 实例对象const router = new KoaRouter();//注册路由router.get('/', async (ctx, next) => {console.log('index');ctx.body = 'index';});app.use(router.routes()); // 添加路由中间件app.listen(3000);
通过 router.get(path, middleware) 就可以注册一个路径对应的处理函数,而且支持链式调用,最后通过 router.routes() 获取所有路径处理函数,转成一个中间件注册到 koa
实现
koa-router 功能很多,支持携带 param 参数的路由,了解其原理可以实现一个最基础的 router
src/util/router.js
class Router {constructor() {const methods = ['get', 'post'];this.stack = {};methods.forEach(method => {// router.get(path, middleware), router.post(path, middleware)// router.register(path, method, middleware) 快捷方式Router.prototype[method] = function (path, middleware) {this.register(path, method, middleware);// 支持链式调用return this;};// 每种方法单独维护一个数组,提升路由匹配性能this.stack[method] = [];});}register(path, method, middleware) {// 注册一个处理函数导 this.stackthis.stack[method].push({path,middleware,});}routes() {// 导出 koa 中间件}}module.exports = Router;
register(path, method, middleware) 用来代替之前 if(path === xxx && method === xxxx),为了方便使用在构造函数处理成了
- router.get(path, middleware)
- router.post(path, middleware)
routes() 方法用于最终请求来了匹配路径和method,选择合适的中间件执行
routes() {const stack = this.stack;return async function (ctx, next) {const middlewareList = stack[ctx.method.toLowerCase()];let targetMiddleware;for (let i = 0; i < middlewareList.length; i++) {const { path, middleware } = middlewareList[i];// 忽略了 url 参数等影响const isPathMatch = path instanceof RegExp ? path.test(ctx.path) : path === ctx.path;if (isPathMatch) {targetMiddleware = middleware;// 使用匹配到的第一个路由,不再继续尝试break;}}if (targetMiddleware) {await targetMiddleware(ctx, next);} else {await next();}}}
这样就可以和 koa-router 一样写路径处理函数了
src/route.js
const Router = require('./util/router');const router = new Router();router.get(/^\/file\/.+/, async (ctx, next) => {await next();const { path } = ctx;ctx.body = `get file: ${path}`;}).get(/^\/data\/.+/, async (ctx, next) => {await next();const { path } = ctx;ctx.body = `get data: ${path}`;}).get(/.*/, async (ctx, next) => {await next();const { path } = ctx;ctx.body = `error: ${path}`;});module.exports = router.routes();
