Introduction to the

Express is a popular Web development framework for Node.js. This article analyzes Express routing and how the middleware initializes and works from source code.

Let’s look at the usage first

/ / sample
const express = require('express');
const app = express();
// fn1
app.use((req, res, next) = > {
    console.log('enter');
    next()
});
// fn2
app.get('/user', (req, res, next) => {
    console.log('the user: the app get 1.1');
    next()
}, (req, res, next) => {
    // fn3
    console.log('the user: the app get 1.2');
    next()
});
// fn4
app.get('/api', (req, res, next) => {
    console.log('api: app get 1');
    res.send('api');
});
// fn5
app.get('/user', (req, res, next) => {
    console.log('the user: the app get 2.1');
    next();
}, (req, res, next) => {
    // fn6
    console.log('the user: the app get 2.2');
    res.send('user');
});
app.listen(3000);
Copy the code

When we are in the browser to http://localhost:3000/user, look at the output

Let’s take a look at the source code and study the principle of it

Initialization phase

Let’s take a look at app.getSource location

// 1. Generate a Router instance
methods.forEach(function(method){
  app[method] = function(path){
     // This function checks if there is a router attribute, and if there is no router attribute, add an instance of router to this
    this.lazyrouter();
    Router class get () {// Router class get () {// Router class get ()
    // Also path is the current /user
    // slice is array.prototype. slice so this is the handler we passed in
    var route = this._router.route(path);
    Route. route returns a route instance, generates a Layer and adds the Layer to the stack, layer.path = path, layer.handler = route.dispatch
    // 4. Then call the get method on the previously returned Route instance
    // 5. Then look at the previous layer handler, the route.dispatch method. This method calls the layer-by-layer startup method, which will be discussed later
    route[method].apply(route, slice.call(arguments.1));
    return this;
  };
});
Copy the code

Router.route method – source location

Router.route = function route(path) {
  Route initializes the stack
  // methods = {}; In order to improve the efficiency of path matching later
  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;
};
Copy the code

The Route. The get method

methods.forEach(function(method){
  // 4.1 Use the same methods to traverse binding methods
  Route.prototype[method] = function(){
    // Handles means handles method get
    var handles = flatten(slice.call(arguments));
    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      if (typeofhandle ! = ='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)
      Path = '/' layer.handler = the current routing function
      var layer = Layer('/', {}, handle);
      layer.method = method;
      // route.methods['get'] = true;
      this.methods[method] = true;
      // Push layer onto the current stack
      this.stack.push(layer);
    }
    return this;
  };
});
Copy the code

app.use

The app.use method registers middleware

app.use = function use(fn) {
  var offset = 0;
  var path = '/';

  // 1. Process parameters to initialize the router
  this.lazyrouter();
  var router = this._router;
  fns.forEach(function (fn) {
    // 2. The router.use method path defaults to '/' fn
    if(! fn || ! fn.handle || ! fn.set) {return router.use(path, fn);
    }
    debug('.use app under %s', path);
    fn.mountpath = path;
    fn.parent = this;

    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app;
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err);
      });
    });

    // mounted an app
    fn.emit('mount'.this);
  }, this);

  return this;
};
Copy the code

The router. Use method

proto.use = function use(fn) {
  var offset = 0;
  var path = '/';
  // 2.1 Processing Parameters
  if (typeoffn ! = ='function') {
    var arg = fn;
    while (Array.isArray(arg) && arg.length ! = =0) {
      arg = arg[0];
    }
    if (typeofarg ! = ='function') {
      offset = 1; path = fn; }}var callbacks = flatten(slice.call(arguments, offset));
  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')}// Add layer layer.path = path layer.handle to this
  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false.end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};
Copy the code

Initialization phase summary

  • App. _router generates a route corresponding to the path each time it calls get or POST, and at the same time generates a layer on which the Handle method iterates through all methods corresponding to the route. The next method calls the method at the next layer in the router.stack.
  • Then call route’s GET or POST methods,routeThe stack on continues to mount a layer stack, where the Handle method of the layer is the method that is passed in to handle the request, and next calls the current oneroute.stackMethod of the next layer in.
  • App. use calls router.use and does not generate route. Handle on layer calls router.stack.
  • Maybe the text is a little unclear, but look at the picture.

Processing request phase

The app.listen method calls the app.handle method

app.handle = function handle(req, res, callback) {
  var router = this._router;
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)});if(! router) { debug('no routes defined on app');
    done();
    return;
  }
// Done is an error control function
  router.handle(req, res, done);
};
Copy the code

Let’s see what router.handle does. There’s too much code here

proto.handle = function handle(req, res, out) {
  var self = this;

  var idx = 0;
  var protohost = getProtohost(req.url) || ' '
  var removed = ' ';
  var paramcalled = {};

  var stack = self.stack;

  var parentParams = req.params;
  var parentUrl = req.baseUrl || ' ';
  var done = restore(out, req, 'baseUrl'.'next'.'params');

  // setup basic req values
  req.baseUrl = parentUrl;
  req.originalUrl = req.originalUrl || req.url;

  next();

  function next(err) {
    // Error handling next('error')
    var layerError = err === 'route' ? null : err;
    // Secondary routing path processing
    if(removed.length ! = =0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = ' ';
    }
    // Skip to the next route
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }
    // The done method is used to call the next function on the router.stack
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }
    var path = getPathname(req);
    if (path == null) {
      return done(layerError);
    }

    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)continue
      if (layerError) {
        match = false;
        continue;
      }
      var method = req.method;
      // Use route's methods to check whether there are get or POST methods
      var has_method = route._handles_method(method);
      if(! has_method && method ! = ='HEAD') {
        match = false;
        continue; }}if(match ! = =true) {
      return done(layerError);
    }
    // If layer.route exists, place it on req.route
    if (route) {
      req.route = route;
    }
    // Call layer.handle_request
    // Remember that next calls the next routing or middleware function in router.stack
    req.params = self.mergeParams
      ? mergeParams(layer.params, parentParams)
      : layer.params;
    var layerPath = layer.path;
    if(! layer.keys || layer.keys.length ===0 ) {
      if(route){
        layer.handle_request(req, res, next)
      } else {
        removed = layer.path;
        req.url = req.url.slice(0,removed.length);
        if(req.url == ' '){
          req.url = '/';
          slashAdded = true;
        }
        layer.handle_request(req,res,next)
      }
    }
  }
};
Copy the code

Then look at the layer.handle_request method

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;
  if (fn.length > 3) {
    // The length of a function is the length of its parameter
    return next();
  }
  fn(req, res, next);
};
Copy the code

If app.use is used, the handle is the middleware function passed in. If app.get is called, the route.dispatch method is called, and the functions passed in by app.get are executed one by one.

The Route. The method of dispatch

// Done is the next function in router.stack.
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();
    }
    if (err && err === 'router') {
      return done(err)
    }
    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); }}};Copy the code

Processing request phase summary

It all adds up to a picture… Do forgive my poor drawing skills, will you

Thank you

Proto is a proto object used for Router express. Proto is a proto object for Router express. Proto is a proto object used for Router express. In the future, express routing matching principle will be organized into an article and sent out, thank you for reading.