Expressソース学習-ルーティング編


Expressの概要
Expressは簡潔で柔軟なnode.js Webアプリケーションフレームワークであり、さまざまなWebアプリケーションを作成し、豊富なHTTPツールを提供します.Expressを使用すると、完全な機能を備えたWebサイトを迅速に構築できます.
Express公式サイトもとてもフレンドリーなドキュメントです.
Expressフレームワークのコア特性:
  • ルーティング:異なるHTTP要求動作を実行するためのルーティングテーブルが定義される.
  • ミドルウェア:HTTP要求に応答するためにミドルウェアを設定できます.
  • テンプレートエンジン:テンプレートにパラメータを渡すことでHTMLページを動的にレンダリングできます.

  • ここではまずExpressルーティングから解析を学習し,その他のコア特性の学習と探索を継続する.
    Expressのインストール(V 4.16.2)
    npm i express -S

    Expressの簡単な使用
    1.getリクエストを受信するサーバの作成
    // 1.get.js
    const express = require('express');
    const app = express();
    
    const port = 8080;
    
    // path         ,callback             。
    app.get('/', function(req,res){ //     get    
        res.end('hello express!'); //     
    });
    
    app.listen(port,function(){ //       
        console.log(`server started on port ${port}`);
    });
    

    2.上記コードの実行
    Nodemonは、ソースの変更を自動的に再起動し、サーバの監視を自動的に再開するユーティリティです.
    
    $ nodemon 1.get.js
    
          localhost:8080     :
    hello express!
    
                   localhost:8080/user     :
    Cannot GET /user          404 Not Found
    

    上記のテストケースに基づいてexpressを実装
    ソース分析
    単純ルーティングの登録
    app.get('/get', function(req, res) {
        res.send('hello express');
    });

    expressソースディレクトリ構造
    ルーティングシステム
    ルーティングミドルウェアでは、Routerルーティングシステム全体にstackが1つずつlayerを格納し、layer.routeを介してrouteルーティングオブジェクトを指し、routeのstackの中にも1つのlayerが格納され、各layerには(method/handler)が含まれる.
    ソースコードには主にいくつかのクラスと方法が含まれています.
    createApplicaton
    Application(proto)
    Router
    Route
    Layer
  • express()はapp
  • を返します.
    実際にexpressは内部createApplication関数を指してappを返す
    var mixin = require('merge-descriptors'); // merge-descriptors      ,        
    var proto = require('./application'); // application.js     proto
    
    function createApplicaton() { // express()
        var app = function(req, res, next) { // app     
            app.handle(req, res, next); //     
        };
    
        mixin(app, EventEmitter.prototype, false); //  EventEmitter.prototype        app   
        mixin(app, proto, false); //  proto              app.get app.post    app 
        app.init(); //             
        return app;
    }

    アプリケーション上の多くのプロパティメソッドはアプリケーション.jsからエクスポートされたprotoオブジェクトから来ています.router/index.jsにもprotoという名前の関数オブジェクトに静的プロパティメソッドproto.handle proto.param proto.useがマウントされています.
    // router/index.js
    var proto = module.exports = function(options) {}
    

    //アプリケーション.jsがエクスポートしたprotoに、appにマージされたリクエストメソッドをマウントします.ソースコードは次のとおりです.
    // application.js
    
    // methods       ,        http          app          , app.get()、app.post()  
    methods.forEach(function(method){
      app[method] = function(path){
        if (method === 'get' && arguments.length === 1) {
          // app.get(setting)
          return this.set(path);
        }
    
        this.lazyrouter(); //     Router   this._router = new Router();
    
        var route = this._router.route(path); //   router/index    proto.route
        route[method].apply(route, slice.call(arguments, 1)); //               
        return this;
      };
    });

    この.lazyrouterは、Routerインスタンスを作成してapp._にマウントするために使用します.router
    // application.js
    methods.forEach(function(method){
      app[method] = function(path){
    
        this.lazyrouter(); //     Router   this._router = new 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));
      }
    };

    登録ミドルウェア
    2つの方法でミドルウェアを追加できます:app.useは非ルーティングミドルウェアを追加するために使用され、app[method]はルーティングミドルウェアを追加します.この2つの追加方法は、Routerの関連メソッドを内部で呼び出して実現されます.
    非ルーティングミドルウェアの登録
    // application.js
    
    app.use = function(fn) { // fn           [fn]
        var offset = 0;
        var path = '/';
    
        var fns = flatten(slice.call(arguments, offset)); //     
        // setup router
        this.lazyrouter(); //       
        var router = this._router;
    
        fns.forEach(function(fn) {
          router.use(path, function mounted_app(req, res, next) { // app.use      router.use
          var orig = req.app;
          fn.handle(req, res, function (err) {
            setPrototypeOf(req, orig.request)
            setPrototypeOf(res, orig.response)
            next(err);
          });
        });
    }
    

    app.useの下部でrouter.useが呼び出されていることがわかりました.次にrouter.useを見てみましょう.
    // router/index.js
    var Layer = require('./layer');
    
    proto.use = function(fn) {
        var offset = 0; //      
        var path = '/'; //      /     use
    
        //          path              
        if (typeof arg !== 'function') {
             offset = 1;
             path = fn;
        }
    
        //                           [fn, fn]  slice      [[fn, fn]]            
        var callbacks = flatten(slice.call(arguments, offset)); //        
    
        for(var i = 0; i < callbacks.length; i++) { //           
            var fn = callbacks[i];
            if (typeof fn !== 'function') { //     
              throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
            }
    
            //      
            //      layer   
            var layer = new Layer(path, {
                sensitive: this.caseSensitive,
                strict: false,
                end: false
            }, fn);
    
            //       ,      undefined
            layer.route = undefined;
            this.stack.push(layer); //  layer    router.stack
        }
    }

    ルーティングミドルウェアの登録
    上のアプリケーション.jsのappオブジェクトにはhttpがたくさん追加されています.要求app[method]はルーティングミドルウェアを登録するために使用されます.
    // application.js
    
    var app = exports = module.exports = {};
    
    var Router = require('./router');
    var methods = require('methods');
    
    methods.forEach(function(method){
      app[method] = function(path){ // app    app.get app.post app.put      
    
        if (method === 'get' && arguments.length === 1) {
          // app.get(setting)
          return this.set(path);
        }
    
        this.lazyrouter(); //   Router        this._router
    
        //   this._router.route => router/index.js  proto.route
        var route = this._router.route(path);
        route[method].apply(route, slice.call(arguments, 1)); //   router    method       
        return this;
      };
    });
    

    上のapp[method]でrouter[method]つまりthis.router.route[method]
    これを見てみましょうrouter.route(path); このコードに何が起こったの?
    var route = this._router.route(path); //        route      
    route[method].apply(route, slice.call(arguments, 1)); //   route     route[method]

    routerのthis.router.routeはrouteオブジェクトを作成し、layerがpathとroute.dispatchをlayerに渡し、layerのrouteがrouteオブジェクトを指してRouteとRouteを関連付け、最後にrouteオブジェクトを戻り値とする
    // router/index.js
    
    var Route = require('./route');
    var Layer = require('./layer');
    
    proto.route = function (path) {
        var route = new Route(path); // app[method]             route  
    
        var layer = new Layer(path, { //     route layer
          sensitive: this.caseSensitive,
          strict: this.strict,
          end: true
        }, route.dispatch.bind(route)); //      route   dispatch      layer  
    
        //            (    ),      Router Route     
        layer.route = route;
    
        this.stack.push(layer); //  layer   Router stack 
        return route; //     route    
    }
    

    ルーティングミドルウェアの場合、ルーティングコンテナのstack(Router.stack)のlayerはrouteフィールドを介してルーティングオブジェクトを指します.これにより、Router.stackはRoute.stackと関連付けられ、関連付けられた概略モデルは次の図に示されます.
    app[method]でroute[method].apply(route,slice.call(arguments,1))を呼び出します.
    // router/index.js
    
    //     application.js   app[method]      router      http            all  
    methods.concat('all').forEach(function(method){
      proto[method] = function(path){
        var route = this.route(path) //      route  
        route[method].apply(route, slice.call(arguments, 1)); // router[method]      route[method]
        return this;
      };
    });
    

    最後にrouter/route.jsのRouteを見てみましょう
    // router/route.js
    
    function Route(path) { // Route 
      this.path = path;
      this.stack = []; // route stack
    
      debug('new %o', path)
      this.methods = {}; //     HTTP         
    }
    
    var Layer = require('./layer');
    var methods = require('methods');
    
    //             Route      http  
    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 a callback function but got a ' + type
            throw new Error(msg);
          }
    
          debug('%s %o', method, this.path)
    
          var layer = Layer('/', {}, handle); //  route   layer       method handle
          layer.method = method;
    
          this.methods[method] = true; //        method       
          this.stack.push(layer); //  layer    route stack 
        }
    
        return this; //  route    
      };
    });

    Routeでのallメソッド
    Route.prototype.all = function all() {
      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.all() requires a callback function but got a ' + type
          throw new TypeError(msg);
        }
    
        var layer = Layer('/', {}, handle);
        layer.method = undefined; // all       
    
        this.methods._all = true; // all    
        this.stack.push(layer); //    route stack 
      }
    
      return this;
    };

    最終ルーティング登録関係チェーンapp[method]=>router[method]=>route[method]最終的にroute[method]でルーティング登録を完了
    次にLayerを見てみましょう
    // route/layer.js
    var pathRegexp = require('path-to-regexp');
    
    module.exports = Layer;
    
    function Layer(path, options, fn) {
        if (!(this instanceof Layer)) {
            return new Layer(path, options, fn);
        }
    
        this.handle = fn; //       
        this.regexp = pathRegexp(path, this.keys = [], opts); //                       
    }
    
    Layer.prototype.match = function(path) { //                   this.regexp
    
    }

    サーバの起動
    // application.js
    var http = require('http');
    
    app.listen = function listen() {
      var server = http.createServer(this); // this => express.js    app
      return server.listen.apply(server, arguments);
    };
    
    // express.js    app
    var app = function(req, res, next) {
      app.handle(req, res, next);
    };
    

    ルーティングコール
    app.handle呼び出しthis.router.handleルーティング処理
    express.js
    function createApplicaton() {
        let app = function(req, res, next) { //       
            app.handle(req, res, next); //       
        }
    }
    

    express.jsのapp.handle実際にアプリケーション.jsのapp.handle最下位からrouter.handleを呼び出す
    // application.js
    
    app.handle = function handle(req, res, callback) {
      var router = this._router;
    
      // final handler
      var done = callback || finalhandler(req, res, {
        env: this.get('env'),
        onerror: logerror.bind(this)
      });
    
      router.handle(req, res, done); //     router.handle
    };

    router.handle呼び出し内部next関数routerのstackで一致するlayerを探す
    // router/index.js
    
    
    function matchLayer(layer, path) {
      try {
        return layer.match(path);
      } catch (err) {
        return err;
      }
    }
    
    
    proto.handle = function(req, res, done) {
        // middleware and routes
        var stack = self.stack; // router stack            
        var idx = 0;
    
        next();
        function next(err) {
    
            if (idx >= stack.length) { //     
                 setImmediate(done, layerError);
                 return;
            }
    
            //       
            var path = getPathname(req);
    
            var layer;
            var match;
            var route;
            while (match !== true && idx < stack.length) { //          
              layer = stack[idx++]; //  router stack     layer
    
              match = matchLayer(layer, path); // matchLayer    layer match            
    
              route = layer.route; //      route  
              var method = req.method; //       
    
              var has_method = route._handles_method(method); //   route _handles_method  Boolean 
    
              if (match !== true) { //         
                return done(layerError);
              }
    
              if (route) { //   layer handle_request        handle
                 return layer.handle_request(req, res, next);
              }
            }
        }
    }

    ルーティング処理app.handle=>router.handle=>layer.handle_request
    layerのhandle_request呼び出しnext一次取得route stackでの処理方法
    
    Layer.prototype.handle_request = function handle(req, res, next) {
      var fn = this.handle;  //   new Layer    handle
        // function Layer() {
            // this.handle = fn;
        // }
    
      if (fn.length > 3) {
        // not a standard request handler
        return next();
      }
    
      try {
        fn(req, res, next);
      } catch (err) {
        next(err);
      }
    };

    小結
    app.useは非ルーティングミドルウェアを追加するために使用され、app[method]はルーティングミドルウェアを追加し、ミドルウェアの追加はRouterとRouteで完了する必要があり、appはfacadeに相当し、追加の詳細を包装した.
    Routerはミドルウェアを保管した容器と見なすことができる.中に格納されているルーティングミドルウェアの場合、Router.stackのlayerにはroute属性が対応するルーティングオブジェクトを指しているため、Router.stackをRoute.stackに関連付け、Routerを介してルーティングオブジェクトの各プロセッサに遍歴することができます.