深入理解express框架的匹配路由机制

更新日期: 2019-02-28阅读: 3k标签: express

现在node的web框架有很多,除express 还有koa egg等等。 但它们本质上还是基于原生node框架的http。其实框架都大差不差,主要是观摩和学习。本篇文章主要记录下自己在node爬坑之路上的经历和收获~

本文主要实现express的功能之一, 匹配路由

  • 匹配简单静态路由
  • 匹配动态路由

首先我们看一下express:

const express = require('express');

let app = new express();

app.get('/',(req,res)=>{
    res.end('home Page.');
});

app.get('/center',(req,res)=>{
    res.end('center Page.');
});

/** 匹配到动态路由 获取路由参数并返回 */
 app.get('/product/:id/:name',(req,res)=>{
     res.end(JSON.stringify(req.params));
 });

/** 当以上路径都没有匹配成功时 返回404 */
app.all('*',(req,res)=>{
    res.end('404');
});

let port = 3000;

app.listen(port,()=>{
    console.log(`Server is start on port ${port}`);
});


ok.代码很简单。引入express,new了个express实例,写了几个路由,最后启了本地服务。

代码第二行 我们把引入的express 给new出来,说明express内部返回的是一个function。

好.麻雀虽小 五脏俱全,我们今天就来实现express的这些功能。

let http = require('http'); /** express基于http */
let url = require('url'); /** 用来解析请求的路径 */
/** express引入了methods 它的作用是返回各种的请求方法 */
let methods = require('methods');

function application(){
    /** 1 express返回了一个函数 
     * 这个函数就是http.createServer的监听函数
    */
    let app = (req,res) => {
        /** 1.1 url模块解析 拿到请求路径 比如 /user */
        let { pathname } = url.parse(req.url);
        /** 1.2 拿到请求方法 方法是大写 记得转换为小写 */
        let requestMethod = req.method.toLowerCase();
        /** 1.3 通过拿到的路径和方法 在之前定义好的路由数组routes中 循环去匹配 */
        for(let i = 0; i < app.routes.length; i++){
            /** 1.4 解构 拿到每一个路由的 路径 方法 回调 */
            let { path, method, cb } = app.routes[i];
            if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){
                /** 1.5 如果匹配到 返回回调并执行 */
                return cb(req,res);
            }
        }
        /** 1.6 没有匹配到任何路由 */
        res.end(`Cannot found ${pathname}/${requestMethod}`);
    }

    /** 2 定义一个存放所有路由的数组 */
    app.routes = [];
    /** 2.1 往methods数组中添加一个方法 all  并循环数组 */
    [...methods,'all'].forEach((method)=>{
        app[method] = function(path,cb){
            /** 2.2 先将每个请求的路由地址 方法和回调保存起来 
             * path:路径  method:方法   cb:回调
            */
            let layer = { path, method, cb };
            app.routes.push(layer);
        }
    });

    /** 3 监听端口 */
    app.listen = function(...arguments){
        /** 3.1 利用http的createServer方法 将app传进去 */
        let server = http.createServer(app);
        server.listen(...arguments);
    }
    return app;
}

/** 4 将方法导出出去 */
module.exports = application;


代码上面都仔细的标注了观看序号,1.2.3... 按照顺序观看即可。

我们手写的整个express就是一个函数 函数里面return了一个函数。通过node原生框架http的方法 包装了该函数,最后再将整个函数module.exports导出出去。
最后我们启动项目,通过浏览器或者postman调用接口,发现确实能实现部分的express功能,但是有一点,此时我们能实现的仅仅是静态的路由,如果有路由参数的情况下,比如/product/:id/:name。结果就不符合预期。 改造:

代码上面都仔细的标注了观看序号,1.2.3... 按照顺序观看即可。

let http = require('http');
let url = require('url');
let methods = require('methods');

function application(){
    let app = (req,res) => {
        let { pathname } = url.parse(req.url);
        let requestMethod = req.method.toLowerCase();
        for(let i = 0; i < app.routes.length; i++){
            let { path, method, cb } = app.routes[i];
            /** 7 如果请求路径path中 就说明该路由是动态的 */
            if(path.params){
                /** 8 匹配该动态路由后面的动态参数 匹配成功返回true */
                if(path.test(pathname)){
                    /** 9 解构赋值 拿到动态路由的参数 */
                    let [, ...otherParams] = pathname.match(path);
                    /** 10 通过reduce()方法 将路由参数转换为对象形式
                     * 并放到req.params中
                     */
                    req.params = path.params.reduce(
                        (memo,key,index)=>(
                            memo[key]=otherParams[index],memo
                        ),{}
                    );
                    /** 11 返回匹配到的动态路由 */
                    return cb(req,res);
                }
            }

            if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){
                return cb(req,res);
            }
        }
        res.end(`Cannot found ${pathname}/${requestMethod}`);
    }

    app.routes = [];
    [...methods,'all'].forEach((method)=>{
        app[method] = function(path,cb){
            let layer = { path, method, cb };

            /** 1 定义一个空数组 来存放动态路由的参数 */
            let params = [];
            /** 2 如果路径中包含: 说明该路由是动态路由 */
            if(path.includes(':')){
                /** 3 更改该动态路由的路径path为一个正则表达式
                 * 目的是为了等真正请求到来时 匹配到该动态路由 并拿到路由参数
                 */
                layer.path = new RegExp(path.replace(/:([^\/]*)/g,function(){
                    /** 4 将动态路由参数的key 放入params数组中 */
                    params.push(arguments[1]);
                    /** 5 返回了一个正则来匹配真正的动态路由参数 注意此处没有: */
                    return '([^\/]*)';
                }));
                /** 6 把解析到的动态路由放到该路由路径path的params上 */
                layer.path.params = params;
            }

            app.routes.push(layer);
        }
    });

    app.listen = function(...arguments){
        let server = http.createServer(app);
        server.listen(...arguments);
    }

    return app;
}

module.exports = application;



先通过正则匹配到该动态路由,并把该动态路由的path替换为一个正则,放到数组中,等待真正的动态路由到来时,从路由数组中拿到该动态路由的路径,也就是刚才替换的正则,来匹配该动态路由后的参数即可。

通过以上就能实现获取动态路由的参数 上图:


代码在git mock-express


链接: https://fly63.com/article/detial/2149

Node.js Express 的使用

Node.js 的 Express 估计是那种你第一次接触,就会喜欢上用它的框架。因为它真的非常简单,直接。在当前版本上,一共才这么几个文件:

node express使用HTML模板

一般我们在做node web项目的时候,想使用我们平时常用的html模板,express默认使用jade模板,本身是没有HTML的,那么如何实现呢?上面实际上是调用了ejs的.renderFile()方法

单页面应用的History路由模式express后端中间件配合

这篇文章主要分享一下通过HTML5的history API的时候,使用NodeJS后端应该如何配置,来避免产生404的问题,这里是使用的express的框架,主要是通过connect-history-api-fallback这个中间件来实现的。

express中间件原理connect

不知道用了express.js的你有没有这样的疑问:app.use为什么可以添加一个又一个中间件?connect是如何区分普通中间件和错误中间件的?中间件处理函数中的next指代的又是什么?

express 的 middleware 设计

还没用express写过server,先把部分源码撸了一遍,各位大神求轻拍。express入口文件在lib文件夹下的express.js,其向外界暴露了一些方法。

node express上传大文件报错

在项目开发当中有时候需要上传大文件,这时候你的代码写的完全正确,等了好半天文件上传完成了,但服务器突然在控制台报错一个错误,request entity too large,那对于这个棘手的问题改如何解决呢?

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!