Skip to content

Instantly share code, notes, and snippets.

@leijianning
Last active May 26, 2016 10:34
Show Gist options
  • Select an option

  • Save leijianning/1fe79272e1b6f18f1f2769fe018d9920 to your computer and use it in GitHub Desktop.

Select an option

Save leijianning/1fe79272e1b6f18f1f2769fe018d9920 to your computer and use it in GitHub Desktop.

Revisions

  1. leijianning revised this gist May 26, 2016. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。

    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位看官多多指导
    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位多多拍砖

    好了,废话不多说了,进入正题,首先先了解一下express源码的目录结构,如下图:
    ![express源码目录结构](https://camo.githubusercontent.com/db38f84e5d45aac7d160428862b87ea81f38cbc3/687474703a2f2f7777312e73696e61696d672e636e2f6c617267652f346365323563383967773165676b6b657664316e786a323037383061697439322e6a7067)
    @@ -28,7 +28,7 @@ methods.forEach(function(method){
    };
    });
    ```
    (⊙o⊙)哦,隐藏的好深,原来express的对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。
    (⊙o⊙)哦,隐藏的好深,原来express对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。

    接下来我们主要分析下函数内部的实现,首先判断如果method等于get,并且参数的长度是1,则直接返回this.set(path),大家查看express官网的API就可以发现,app.get()函数其实实现了两种功能,如果参数长度是1,则返回app.set()定义的变量,如果参数长度大于1,则进行路由处理。

    @@ -317,7 +317,7 @@ proto.handle = function handle(req, res, out) {
    }
    };
    ```
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true。
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true,也就是说,没有指定path的非路由中间件会匹配所有的http请求

    到这里,我们基本已经说明了router相关的所有内容,想必看到这里你一定会有点晕,我们接下来来重新梳理一下。看看express究竟是如何对http请求进行路由的。

  2. leijianning revised this gist May 26, 2016. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。

    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位多多拍砖
    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位看官多多指导

    好了,废话不多说了,进入正题,首先先了解一下express源码的目录结构,如下图:
    ![express源码目录结构](./1464230596479.png)
    ![express源码目录结构](https://camo.githubusercontent.com/db38f84e5d45aac7d160428862b87ea81f38cbc3/687474703a2f2f7777312e73696e61696d672e636e2f6c617267652f346365323563383967773165676b6b657664316e786a323037383061697439322e6a7067)
    application.js为express的主文件,express.js对application.js进行了包装,对外提供各种API,这里我们不多做说明,我们今天要说的就是router目录下的内容,express关于路由的具体实现都是由这个目录完成。我们先看一个简单的express路由的例子:
    ```
    var app = express();
    @@ -28,7 +28,7 @@ methods.forEach(function(method){
    };
    });
    ```
    (⊙o⊙)哦,隐藏的好深,原来express对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。
    (⊙o⊙)哦,隐藏的好深,原来express的对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。

    接下来我们主要分析下函数内部的实现,首先判断如果method等于get,并且参数的长度是1,则直接返回this.set(path),大家查看express官网的API就可以发现,app.get()函数其实实现了两种功能,如果参数长度是1,则返回app.set()定义的变量,如果参数长度大于1,则进行路由处理。

    @@ -317,7 +317,7 @@ proto.handle = function handle(req, res, out) {
    }
    };
    ```
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true,也就是说,没有指定path的非路由中间件会匹配所有的http请求
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true。

    到这里,我们基本已经说明了router相关的所有内容,想必看到这里你一定会有点晕,我们接下来来重新梳理一下。看看express究竟是如何对http请求进行路由的。

  3. leijianning revised this gist May 26, 2016. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。

    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位看官多多指导
    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位多多拍砖

    好了,废话不多说了,进入正题,首先先了解一下express源码的目录结构,如下图:
    ![express源码目录结构](./1464230596479.png)
    @@ -28,7 +28,7 @@ methods.forEach(function(method){
    };
    });
    ```
    (⊙o⊙)哦,隐藏的好深,原来express的对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。
    (⊙o⊙)哦,隐藏的好深,原来express对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。

    接下来我们主要分析下函数内部的实现,首先判断如果method等于get,并且参数的长度是1,则直接返回this.set(path),大家查看express官网的API就可以发现,app.get()函数其实实现了两种功能,如果参数长度是1,则返回app.set()定义的变量,如果参数长度大于1,则进行路由处理。

    @@ -317,7 +317,7 @@ proto.handle = function handle(req, res, out) {
    }
    };
    ```
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true。
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true,也就是说,没有指定path的非路由中间件会匹配所有的http请求

    到这里,我们基本已经说明了router相关的所有内容,想必看到这里你一定会有点晕,我们接下来来重新梳理一下。看看express究竟是如何对http请求进行路由的。

  4. leijianning revised this gist May 26, 2016. No changes.
  5. leijianning revised this gist May 26, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ express作为nodejs平台下非常流行的web框架,相信大家都对其已
    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位看官多多指导。

    好了,废话不多说了,进入正题,首先先了解一下express源码的目录结构,如下图:
    ![Alt text](./1464230596479.png)
    ![express源码目录结构](./1464230596479.png)
    application.js为express的主文件,express.js对application.js进行了包装,对外提供各种API,这里我们不多做说明,我们今天要说的就是router目录下的内容,express关于路由的具体实现都是由这个目录完成。我们先看一个简单的express路由的例子:
    ```
    var app = express();
  6. leijianning revised this gist May 26, 2016. 1 changed file with 324 additions and 0 deletions.
    324 changes: 324 additions & 0 deletions Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,324 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到[www.expressjs.com](www.expressjs.com)自行查看express的官方文档,今天主要是想说下express的路由机制。

    最近抽时间看了下express的源码,看完源码体会最深刻的还是express的路由机制,感觉搞懂了express的路由就算是基本搞懂了express,而express的路由机制都是router模块来实现,所以在这里对express的router模块实现进行一下简单的整理,所有理解都来自自己对源码的理解,如有不对的地方,还请各位看官多多指导。

    好了,废话不多说了,进入正题,首先先了解一下express源码的目录结构,如下图:
    ![Alt text](./1464230596479.png)
    application.js为express的主文件,express.js对application.js进行了包装,对外提供各种API,这里我们不多做说明,我们今天要说的就是router目录下的内容,express关于路由的具体实现都是由这个目录完成。我们先看一个简单的express路由的例子:
    ```
    var app = express();
    app.get('/hello', function(req,res){
    res.send('hello everyone!!!');
    });
    ```
    上边就是一个最简单的express路由的例子,将path为 '/hello' 的请求路由到当前的处理函数,并返回 'hello everyone!!!' ,那么我们来一起看看,app.get()何实现的,通过查看代码我们发现源码里并没有app.get()的实现,但仔细找找你会在application.js中发现如下的代码:
    ```
    methods.forEach(function(method){
    app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
    // app.get(setting)
    return this.set(path);
    }
    this.lazyrouter();
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
    };
    });
    ```
    (⊙o⊙)哦,隐藏的好深,原来express的对get,post等方法的添加都是动态的,methods来自[methods](https://www.npmjs.com/package/methods)这个模块,他提供了和nodejs http.METHODS 相似的东西,返回了http协议的所有method,这样一个循环搞定了所有method函数的定义,赞一个。

    接下来我们主要分析下函数内部的实现,首先判断如果method等于get,并且参数的长度是1,则直接返回this.set(path),大家查看express官网的API就可以发现,app.get()函数其实实现了两种功能,如果参数长度是1,则返回app.set()定义的变量,如果参数长度大于1,则进行路由处理。

    继续往下看,**this.lazyrouter()**,从名字来看,好像是懒加载router,那我们看看源码:
    ```
    app.lazyrouter = function lazyrouter() {
    if (!this._router) {
    this._router = new Router({
    caseSensitive: this.enabled('case sensitive routing'),
    strict: this.enabled('strict routing')
    });
    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
    }
    };
    ```
    果然是,如果_router不存在,就new一个Router出来,而这个Router就是我们刚才在目录结构中看到的router目录,也就是今天的主角Router模块。继续上边的代码,加载完_router之后,执行了**this._router.route(path)**这样一行代码,那这行代码有做了什么呢,我们再继续往下挖,我们在router目录下的index.js中找到了它的实现:
    ```
    proto.route = function route(path) {
    var route = new Route(path);
    var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
    }, route.dispatch.bind(route));
    layer.route = route;
    this.stack.push(layer);
    return route;
    };
    ```
    我们可以看到,这里new了一个Route对象,并且new了一个Layer对象,然后将Route对象赋值给layer.route,最后将这个Layer添加到stack数组中。在这里我们先不对Layer进行说明,后边会有专门的介绍,我们先来看看Route,那这个Route又是什么呢,它和Router模块有什么关系呢,我来说下我的理解:

    > Route模块对应的是route.js,主要是来处理路由信息的,每条路由都会生成一个Route实例。而Router模块对应的是index.js,Router是一个路由的集合,在Router模块下可以定义多个路由,也就是说,一个Router模块会包含多个Route模块。通过上边的代码我们已经知道,每个express创建的实例都会懒加载一个_router来进行路由处理,这个_router就是一个Router模块。
    理解了Route和Router的关系,感觉一下子清爽了有木有,O(∩_∩)O哈哈~~~

    好了,我们接着看代码,拿到route对象之后,通过apply的方式调用了route的对应method函数,假如我们现在使用的是get函数,那现在method就等于get。看到这里大家就会发现,express实例在处理路由的时候,会先创建一个Router对象,然后用Router对象和对应的path来生成一个Route对象,最后由Route对象来处理具体的路由实现。

    好了,那接下来我们继续深入研究,看看route.method究竟做了什么,我们找到route.js文件,发现如下的代码:
    ```
    methods.forEach(function(method){
    Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));
    for (var i = 0; i < handles.length; i++) {
    var handle = handles[i];
    if (typeof handle !== 'function') {
    var type = toString.call(handle);
    var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
    throw new Error(msg);
    }
    debug('%s %s', method, this.path);
    var layer = Layer('/', {}, handle);
    layer.method = method;
    this.methods[method] = true;
    this.stack.push(layer);
    }
    return this;
    };
    });
    ```
    啊啊啊,原来route和application运用了同样的技巧,通过循环methods来动态添加method函数,我们直接看函数内部实现,首先通过入参获取到handles,这里的handles就是我们定义的路由中间件函数,这里我们可以看到是一个数组,所以我们可以给一个路由添加多个中间件函数。接下来循环handles,在每个循环中利用handle来创建一个Layer对象,然后将Layer对象push到stack中去,这个stack其实是Route内部维护的一个数组,用来存放所有的Layer对象。现在你一定想这道这个Layer到底是什么东西,那我们继续往下看,看看layer.js的源代码:
    ```
    function Layer(path, options, fn) {
    if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
    }
    debug('new %s', path);
    var opts = options || {};
    this.handle = fn;
    this.name = fn.name || '<anonymous>';
    this.params = undefined;
    this.path = undefined;
    this.regexp = pathRegexp(path, this.keys = [], opts);
    if (path === '/' && opts.end === false) {
    this.regexp.fast_slash = true;
    }
    }
    ```
    上边是Layer的构造函数,我们可以看到这里定义handle,params,path和regexp等几个主要的属性:
    1. 其中最重要的就是handle,它就是我们刚刚在route中创建Layer对象传入的中间件函数。
    2. params其实就是req.params,至于如何实现的我们可以以后再做探讨,今天先不做说明。
    3. path就是我们定义路由时传入的path。
    4. regexp对于Layer来说是比较重要的一个属性,因为下边进行路由匹配的时候就是靠它来搞定的,而它的值是由pathRegexp得来的,其实这个pathRegexp对应的是一个第三方模块[path-to-regexp](https://www.npmjs.com/package/path-to-regexp),它的功能是将path转换成regexp,具体用法大家可以自行查看。

    看完属性,我们再来看看Layer有什么方法:
    ```
    Layer.prototype.match = function match(path) {
    if (path == null) {
    // no path, nothing matches
    this.params = undefined;
    this.path = undefined;
    return false;
    }
    if (this.regexp.fast_slash) {
    // fast path non-ending match for / (everything matches)
    this.params = {};
    this.path = '';
    return true;
    }
    var m = this.regexp.exec(path);
    if (!m) {
    this.params = undefined;
    this.path = undefined;
    return false;
    }
    // store values
    this.params = {};
    this.path = m[0];
    var keys = this.keys;
    var params = this.params;
    for (var i = 1; i < m.length; i++) {
    var key = keys[i - 1];
    var prop = key.name;
    var val = decode_param(m[i]);
    if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
    params[prop] = val;
    }
    }
    return true;
    };
    ```
    match函数主要用来匹配path的,当我们向express发送一个http请求时,当前请求对应的是哪个路由,就是通过这个match函数来判断的,如果path中带有参数,match还会把参数提取出来赋值给params,所以说match是整个路由中很重要的一点。
    ```
    Layer.prototype.handle_error = function handle_error(error, req, res, next) {
    var fn = this.handle;
    if (fn.length !== 4) {
    // not a standard error handler
    return next(error);
    }
    try {
    fn(error, req, res, next);
    } catch (err) {
    next(err);
    }
    };
    ```
    这个是错误处理函数,专门用来处理错误的。
    ```
    Layer.prototype.handle_request = function handle(req, res, next) {
    var fn = this.handle;
    if (fn.length > 3) {
    // not a standard request handler
    return next();
    }
    try {
    fn(req, res, next);
    } catch (err) {
    next(err);
    }
    };
    ```
    从上边的代码我们可以看到调用了fn,而这个fn就是layer的handle属性,就是我们定义路由时传入的路由中间件,到这里我们总算找到了我们的路由中间件被执行的地方,是不是很兴奋。好了,到这里我们已经看完了Layer的代码,但Layer到底是做什么的呢,它和Route之间又有什么千丝万缕的联系呢,说说我的理解:

    > 每一个Layer对应一个中间件函数,Layer存储了每个路由的path和handle等信息,并且实现了match和handle的功能。而从前边我们已经知道,每个Route都会维护一个Layer数组,所有可以发现Route和Layer是一对多的关系,每个Route代表一个路由,而每个Layer对应的是路由的每一个中间件函数。
    讲完了Route和Layer的关系,我们再来回头看看Router和Layer的关系,我们再来看看index.js中prop.route的代码:
    ```
    proto.route = function route(path) {
    var route = new Route(path);
    var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
    }, route.dispatch.bind(route));
    layer.route = route;
    this.stack.push(layer);
    return route;
    };
    ```
    从代码我们可以看出来Router每次添加一个route,都会把route包装到layer中,并且将layer添加到自己的stack中,那为什么要把route包装到layer中呢,前边我们已经仔细研究了Layer模块的代码,我们发现Layer具有match和handle的功能,这样我们就可以通过Layer的match来进行route的匹配了。这里有一个关键点我们需要特别讲解下,上边的代码中在创建Layer对象的时候传入的handle函数为**route.dispatch.bind(route)**,我们来看看route.js中的route.dispatch:
    ```
    Route.prototype.dispatch = function dispatch(req, res, done) {
    var idx = 0;
    var stack = this.stack;
    if (stack.length === 0) {
    return done();
    }
    var method = req.method.toLowerCase();
    if (method === 'head' && !this.methods['head']) {
    method = 'get';
    }
    req.route = this;
    next();
    function next(err) {
    if (err && err === 'route') {
    return done();
    }
    var layer = stack[idx++];
    if (!layer) {
    return done(err);
    }
    if (layer.method && layer.method !== method) {
    return next(err);
    }
    if (err) {
    layer.handle_error(err, req, res, next);
    } else {
    layer.handle_request(req, res, next);
    }
    }
    };
    ```
    我们发现dispatch中通过next()获取stack中的每一个layer来执行相应的路由中间件,这样就保证了我们定义在路由上的多个中间件函数被按照定义的顺序依次执行。到这里我们已经知道了单个路由是被如何执行的,那我们定义的多个路由之间又是如何被依次执行的呢,现在我们来看看index.js中的handle函数:
    ```
    proto.handle = function handle(req, res, out) {
    // middleware and routes
    var stack = self.stack;
    next();
    function next(err) {
    // find next matching layer
    var layer;
    var match;
    var route;
    while (match !== true && idx < stack.length) {
    layer = stack[idx++];
    match = matchLayer(layer, path);
    route = layer.route;
    if (match !== true) {
    continue;
    }
    if (!route) {
    // process non-route handlers normally
    continue;
    }
    }
    // no match
    if (match !== true) {
    return done(layerError);
    }
    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
    if (err) {
    return next(layerError || err);
    }
    if (route) {
    return layer.handle_request(req, res, next);
    }
    trim_prefix(layer, layerError, layerPath, path);
    });
    }
    };
    ```
    上边的代码我进行了处理,删除了一些逻辑,只留下关键部分。从上边的代码我们可以看出,这里也是利用next(),来处理stack中的每一个Layer,这里的stack是Router的stack,stack中存贮了多个route对应的layer,获取到每个layer对象之后,用请求的path与layer进行匹配,此处匹配用的是layer.match,如果能匹配到对应的layer,则获得layer.route,如果route不为空则执行对应的**layer.handle_request()**,如果route为空说明这个layer是通过use()添加的非路由中间件,需要特别说明的是,如果通过use()添加的非路由中间件没有指定path,则会在layer.match中默认返回true。

    到这里,我们基本已经说明了router相关的所有内容,想必看到这里你一定会有点晕,我们接下来来重新梳理一下。看看express究竟是如何对http请求进行路由的。

    当客户端发送一个http请求后,会先进入express实例对象对应的router.handle函数中,router.handle函数会通过next()遍历stack中的每一个layer进行match,如果match返回true,则获取layer.route,执行route.dispatch函数,route.dispatch同样是通过next()遍历stack中的每一个layer,然后执行layer.handle_request,也就是调用中间件函数。直到所有的中间件函数被执行完毕,整个路由处理结束。
  7. leijianning revised this gist May 26, 2016. 1 changed file with 0 additions and 143 deletions.
    143 changes: 0 additions & 143 deletions Express之Router.md
    Original file line number Diff line number Diff line change
    @@ -1,143 +0,0 @@
    # 欢迎使用马克飞象

    @(示例笔记本)[马克飞象|帮助|Markdown]
    **马克飞象**是一款专为印象笔记(Evernote)打造的Markdown编辑器,通过精心的设计与技术实现,配合印象笔记强大的存储和同步功能,带来前所未有的书写体验。特点概述:

    - **功能丰富** :支持高亮代码块、*LaTeX* 公式、流程图,本地图片以及附件上传,甚至截图粘贴,工作学习好帮手;
    - **得心应手** :简洁高效的编辑器,提供[桌面客户端][1]以及[离线Chrome App][2],支持移动端 Web;
    - **深度整合** :支持选择笔记本和添加标签,支持从印象笔记跳转编辑,轻松管理。

    --------

    [TOC]
    ## Markdown简介

    > Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [维基百科](https://zh.wikipedia.org/wiki/Markdown)
    正如您在阅读的这份文档,它使用简单的符号标识不同的标题,将某些文字标记为**粗体**或者*斜体*,创建一个[链接](http://www.example.com)或一个脚注[^demo]。下面列举了几个高级功能,更多语法请按`Ctrl + /`查看帮助。

    ### 代码块
    ``` python
    @requires_authorization
    def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
    print 'Greater'
    return (param2 - param1 + 1) or None
    class SomeClass:
    pass
    >>> message = '''interpreter
    ... prompt'''
    ```
    ### LaTeX 公式

    可以创建行内公式,例如 $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。或者块级公式:

    $$ x = \dfrac{-b \pm \sqrt{b^2 - ac}}{2a} $$

    ### 表格
    | Item | Value | Qty |
    | :-------- | --------:| :--: |
    | Computer | 1600 USD | 5 |
    | Phone | 12 USD | 12 |
    | Pipe | 1 USD | 234 |

    ### 流程图
    ```flow
    st=>start: Start
    e=>end
    op=>operation: My Operation
    cond=>condition: Yes or No?
    st->op->cond
    cond(yes)->e
    cond(no)->op
    ```

    以及时序图:

    ```sequence
    Alice->Bob: Hello Bob, how are you?
    Note right of Bob: Bob thinks
    Bob-->Alice: I am good thanks!
    ```

    > **提示:**想了解更多,请查看**流程图**[语法][3]以及**时序图**[语法][4]
    ### 复选框

    使用 `- [ ]``- [x]` 语法可以创建复选框,实现 todo-list 等功能。例如:
    - [x] 我也是醉了
    - [x] 已完成事项
    - [ ] 待办事项1
    - [ ] 待办事项2

    > **注意:**目前支持尚不完全,在印象笔记中勾选复选框是无效、不能同步的,所以必须在**马克飞象**中修改 Markdown 原文才可生效。下个版本将会全面支持。

    ## 印象笔记相关

    ### 笔记本和标签
    **马克飞象**增加了`@(笔记本)[标签A|标签B]`语法, 以选择笔记本和添加标签。 **绑定账号后**, 输入`(`自动会出现笔记本列表,请从中选择。

    ### 笔记标题
    **马克飞象**会自动使用文档内出现的第一个标题作为笔记标题。例如本文,就是第一行的 `欢迎使用马克飞象`

    ### 快捷编辑
    保存在印象笔记中的笔记,右上角会有一个红色的编辑按钮,点击后会回到**马克飞象**中打开并编辑该笔记。
    >**注意:**目前用户在印象笔记中单方面做的任何修改,马克飞象是无法自动感知和更新的。所以请务必回到马克飞象编辑。
    ### 数据同步
    **马克飞象**通过**将Markdown原文以隐藏内容保存在笔记中**的精妙设计,实现了对Markdown的存储和再次编辑。既解决了其他产品只是单向导出HTML的单薄,又规避了服务端存储Markdown带来的隐私安全问题。这样,服务端仅作为对印象笔记 API调用和数据转换之用。

    >**隐私声明:用户所有的笔记数据,均保存在印象笔记中。马克飞象不存储用户的任何笔记数据。**

    ### 离线存储
    **马克飞象**使用浏览器离线存储将内容实时保存在本地,不必担心网络断掉或浏览器崩溃。为了节省空间和避免冲突,已同步至印象笔记并且不再修改的笔记将删除部分本地缓存,不过依然可以随时通过`文档管理`打开。

    > **注意:**虽然浏览器存储大部分时候都比较可靠,但印象笔记作为专业云存储,更值得信赖。以防万一,**请务必经常及时同步到印象笔记**
    ## 编辑器相关
    ### 设置
    右侧系统菜单(快捷键`Ctrl + M`)的`设置`中,提供了界面字体、字号、自定义CSS、vim/emacs 键盘模式等高级选项。

    ### 快捷键

    帮助 `Ctrl + /`
    同步文档 `Ctrl + S`
    创建文档 `Ctrl + Alt + N`
    最大化编辑器 `Ctrl + Enter`
    预览文档 `Ctrl + Alt + Enter`
    文档管理 `Ctrl + O`
    系统菜单 `Ctrl + M`

    加粗 `Ctrl + B`
    插入图片 `Ctrl + G`
    插入链接 `Ctrl + L`
    提升标题 `Ctrl + H`

    ## 关于收费

    **马克飞象**为新用户提供 10 天的试用期,试用期过后需要[续费](maxiang.info/vip.html)才能继续使用。未购买或者未及时续费,将不能同步新的笔记。之前保存过的笔记依然可以编辑。


    ## 反馈与建议
    - 微博:[@马克飞象](http://weibo.com/u/2788354117)[@GGock](http://weibo.com/ggock "开发者个人账号")
    - 邮箱:<hustgock@gmail.com>

    ---------
    感谢阅读这份帮助文档。请点击右上角,绑定印象笔记账号,开启全新的记录与分享体验吧。




    [^demo]: 这是一个示例脚注。请查阅 [MultiMarkdown 文档](https://github.com/fletcher/MultiMarkdown/wiki/MultiMarkdown-Syntax-Guide#footnotes) 关于脚注的说明。 **限制:** 印象笔记的笔记内容使用 [ENML][5] 格式,基于 HTML,但是不支持某些标签和属性,例如id,这就导致`脚注``TOC`无法正常点击。


    [1]: http://maxiang.info/client_zh
    [2]: https://chrome.google.com/webstore/detail/kidnkfckhbdkfgbicccmdggmpgogehop
    [3]: http://adrai.github.io/flowchart.js/
    [4]: http://bramp.github.io/js-sequence-diagrams/
    [5]: https://dev.yinxiang.com/doc/articles/enml.php

  8. leijianning renamed this gist May 26, 2016. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  9. leijianning revised this gist May 26, 2016. 1 changed file with 143 additions and 1 deletion.
    144 changes: 143 additions & 1 deletion Express之Router
    Original file line number Diff line number Diff line change
    @@ -1 +1,143 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的官方文档,
    # 欢迎使用马克飞象

    @(示例笔记本)[马克飞象|帮助|Markdown]
    **马克飞象**是一款专为印象笔记(Evernote)打造的Markdown编辑器,通过精心的设计与技术实现,配合印象笔记强大的存储和同步功能,带来前所未有的书写体验。特点概述:

    - **功能丰富** :支持高亮代码块、*LaTeX* 公式、流程图,本地图片以及附件上传,甚至截图粘贴,工作学习好帮手;
    - **得心应手** :简洁高效的编辑器,提供[桌面客户端][1]以及[离线Chrome App][2],支持移动端 Web;
    - **深度整合** :支持选择笔记本和添加标签,支持从印象笔记跳转编辑,轻松管理。

    --------

    [TOC]
    ## Markdown简介

    > Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [维基百科](https://zh.wikipedia.org/wiki/Markdown)

    正如您在阅读的这份文档,它使用简单的符号标识不同的标题,将某些文字标记为**粗体**或者*斜体*,创建一个[链接](http://www.example.com)或一个脚注[^demo]。下面列举了几个高级功能,更多语法请按`Ctrl + /`查看帮助。

    ### 代码块
    ``` python
    @requires_authorization
    def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
    print 'Greater'
    return (param2 - param1 + 1) or None
    class SomeClass:
    pass
    >>> message = '''interpreter
    ... prompt'''
    ```
    ### LaTeX 公式

    可以创建行内公式,例如 $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。或者块级公式:

    $$ x = \dfrac{-b \pm \sqrt{b^2 - ac}}{2a} $$

    ### 表格
    | Item | Value | Qty |
    | :-------- | --------:| :--: |
    | Computer | 1600 USD | 5 |
    | Phone | 12 USD | 12 |
    | Pipe | 1 USD | 234 |

    ### 流程图
    ```flow
    st=>start: Start
    e=>end
    op=>operation: My Operation
    cond=>condition: Yes or No?

    st->op->cond
    cond(yes)->e
    cond(no)->op
    ```

    以及时序图:

    ```sequence
    Alice->Bob: Hello Bob, how are you?
    Note right of Bob: Bob thinks
    Bob-->Alice: I am good thanks!
    ```

    > **提示:**想了解更多,请查看**流程图**[语法][3]以及**时序图**[语法][4]。

    ### 复选框

    使用 `- [ ]` 和 `- [x]` 语法可以创建复选框,实现 todo-list 等功能。例如:
    - [x] 我也是醉了
    - [x] 已完成事项
    - [ ] 待办事项1
    - [ ] 待办事项2

    > **注意:**目前支持尚不完全,在印象笔记中勾选复选框是无效、不能同步的,所以必须在**马克飞象**中修改 Markdown 原文才可生效。下个版本将会全面支持。


    ## 印象笔记相关

    ### 笔记本和标签
    **马克飞象**增加了`@(笔记本)[标签A|标签B]`语法, 以选择笔记本和添加标签。 **绑定账号后**, 输入`(`自动会出现笔记本列表,请从中选择。

    ### 笔记标题
    **马克飞象**会自动使用文档内出现的第一个标题作为笔记标题。例如本文,就是第一行的 `欢迎使用马克飞象`。

    ### 快捷编辑
    保存在印象笔记中的笔记,右上角会有一个红色的编辑按钮,点击后会回到**马克飞象**中打开并编辑该笔记。
    >**注意:**目前用户在印象笔记中单方面做的任何修改,马克飞象是无法自动感知和更新的。所以请务必回到马克飞象编辑。

    ### 数据同步
    **马克飞象**通过**将Markdown原文以隐藏内容保存在笔记中**的精妙设计,实现了对Markdown的存储和再次编辑。既解决了其他产品只是单向导出HTML的单薄,又规避了服务端存储Markdown带来的隐私安全问题。这样,服务端仅作为对印象笔记 API调用和数据转换之用。

    >**隐私声明:用户所有的笔记数据,均保存在印象笔记中。马克飞象不存储用户的任何笔记数据。**


    ### 离线存储
    **马克飞象**使用浏览器离线存储将内容实时保存在本地,不必担心网络断掉或浏览器崩溃。为了节省空间和避免冲突,已同步至印象笔记并且不再修改的笔记将删除部分本地缓存,不过依然可以随时通过`文档管理`打开。

    > **注意:**虽然浏览器存储大部分时候都比较可靠,但印象笔记作为专业云存储,更值得信赖。以防万一,**请务必经常及时同步到印象笔记**。

    ## 编辑器相关
    ### 设置
    右侧系统菜单(快捷键`Ctrl + M`)的`设置`中,提供了界面字体、字号、自定义CSS、vim/emacs 键盘模式等高级选项。

    ### 快捷键

    帮助 `Ctrl + /`
    同步文档 `Ctrl + S`
    创建文档 `Ctrl + Alt + N`
    最大化编辑器 `Ctrl + Enter`
    预览文档 `Ctrl + Alt + Enter`
    文档管理 `Ctrl + O`
    系统菜单 `Ctrl + M`

    加粗 `Ctrl + B`
    插入图片 `Ctrl + G`
    插入链接 `Ctrl + L`
    提升标题 `Ctrl + H`

    ## 关于收费

    **马克飞象**为新用户提供 10 天的试用期,试用期过后需要[续费](maxiang.info/vip.html)才能继续使用。未购买或者未及时续费,将不能同步新的笔记。之前保存过的笔记依然可以编辑。


    ## 反馈与建议
    - 微博:[@马克飞象](http://weibo.com/u/2788354117),[@GGock](http://weibo.com/ggock "开发者个人账号")
    - 邮箱:<hustgock@gmail.com>

    ---------
    感谢阅读这份帮助文档。请点击右上角,绑定印象笔记账号,开启全新的记录与分享体验吧。




    [^demo]: 这是一个示例脚注。请查阅 [MultiMarkdown 文档](https://github.com/fletcher/MultiMarkdown/wiki/MultiMarkdown-Syntax-Guide#footnotes) 关于脚注的说明。 **限制:** 印象笔记的笔记内容使用 [ENML][5] 格式,基于 HTML,但是不支持某些标签和属性,例如id,这就导致`脚注`和`TOC`无法正常点击。


    [1]: http://maxiang.info/client_zh
    [2]: https://chrome.google.com/webstore/detail/kidnkfckhbdkfgbicccmdggmpgogehop
    [3]: http://adrai.github.io/flowchart.js/
    [4]: http://bramp.github.io/js-sequence-diagrams/
    [5]: https://dev.yinxiang.com/doc/articles/enml.php

  10. leijianning revised this gist May 26, 2016. No changes.
  11. leijianning renamed this gist May 26, 2016. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions express router → Express之Router
    Original file line number Diff line number Diff line change
    @@ -1,2 +1 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到www.expressjs.com
    自行查看express的官方文档,
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的官方文档,
  12. leijianning revised this gist May 26, 2016. 2 changed files with 2 additions and 1 deletion.
    2 changes: 2 additions & 0 deletions express router
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于nodejs的使用这里不再多说,如有需要可以移步到www.expressjs.com
    自行查看express的官方文档,
    1 change: 0 additions & 1 deletion test
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    test
  13. leijianning created this gist May 24, 2016.
    1 change: 1 addition & 0 deletions test
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    test