The foreword 0.

Express is the jQuery of Node. Express is the jQuery of Node. Express is the jQuery of Node. Let’s do the research based on version 4.16.2

1. Start at the entrance

1.1 the entrance

The main entry is index.js, and this file only does the require import express.js, while express.js exposes the main function createApplication, we usually var app = express(); That’s the function that’s called.

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

//var EventEmitter = require('events').EventEmitter;
//var mixin = require('merge-descriptors'); // Use the merge-descriptors package to mix two objects (including merge-descriptors)set, get), also available assign Mixin (app, EventEmitter. Prototype,false); 
  mixin(app, proto, false);

  // expose the prototype that will get setRequest = Object. Create (req, {// Add a new property to the app, and its value is an Object, which works without any additional information.true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app } }) app.init(); / / initializationreturn app;
}
Copy the code

1.2 proto

Var proto = require(‘./application’); Here’s how it works, and this file is just going to write some methods on the app object:

var app = exports = module.exports = {};
app.init = function (){}
app.handle = function (){}
app.use = function (){}
app.route = function(){// Listen,render,all,disable.enable,disabled,setParam, engine, etcCopy the code

Above we have mixed the application. Js app object with the express.js app object, that is, the express.js file app.handle and app.init are also called from this file

1.2.1 app. The init method

It’s just initialization

app.init = function init() { this.cache = {}; this.engines = {}; this.settings = {}; / / stored configuration enclosing defaultConfiguration (); // Default configuration};Copy the code

DefaultConfiguration defaultConfiguration: some code has been omitted

app.defaultConfiguration = function defaultConfiguration() {// Default Settings this.enable()'x-powered-by');
  this.set('etag'.'weak');
  this.set('env', env); // Let app inherit the parent property of the same namesetPrototypeOf(this.request, parent.request)
    setPrototypeOf(this.response, parent.response)
    setPrototypeOf(this.engines, parent.engines)
    setPrototypeOf(this.settings, parent.settings)
  });

  // setup locals
  this.locals = Object.create(null);

  // top-most app is mounted at /
  this.mountpath = '/';

  // default locals
  this.locals.settings = this.settings;

  // default configuration
  this.set('view', View);
  this.set('views', resolve('views'));
  this.set('jsonp callback name'.'callback');
};
Copy the code

Let’s look at app.set

app.set = function set(setting, val) {
  if(arguments.length === 1) {// Return the result by passing only one argumentreturnthis.settings[setting]; } this. Settings [setting] = val; Switch (setting) {case 'etag':
      break;
    case 'query parser':
      break;
    case 'trust proxy':
      break;
  }
  return this;
};
Copy the code

1.2.2 app. Handle method

Write the callback function first

app.handle = functionhandle(req, res, callback) { var router = this._router; // The route matches successfully, triggering the var callbackdone = callback || finalhandler(req, res, {
    env: this.get('env'), onerror: logerror.bind(this) }); // There is no routeif(! router) {done(a);return;
  }
  router.handle(req, res, done);
};
Copy the code

So, where does our this._router come from?

1.2.3 Processing of each method

Our this._router comes from the this.lazyRouter () method

//methods are a string array of common HTTP request and other method names.function(method){//app.get app.post etc. We use API app[method] =function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      returnthis.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); // Route. Get ()'/page',callback)
    return this;
  };
});
Copy the code

1.2.4 app.lazyRouter Generates this._router

app.lazyrouter = function lazyrouter() {
  if(! This. _router) {this._router = new Router({// Create a RoutercaseSensitive: 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)); }};Copy the code

2. The Router is a Router.

Router = require(‘./ Router ‘); “, so we open the Router folder.

  • Index.js: Router class, whose stack is used to store middleware arrays and handle all routes
  • Layer. js middleware entity Layer class, processing each layer of routing middleware or common middleware;
  • Route.js Route class, routing middleware that handles child routing and different methods (GET, POST)

2.1 the index. Js file

We also saw above the process of new a new routing, index.js is used to process the storage middleware array. In the index.js folder of the router folder, proto is exposed. The router introduced by require is also proto:

var proto = module.exports = function(options) {
  var opts = options || {};

  functionrouter(req, res, next) { router.handle(req, res, next); } // The router also gets the proto methodsetPrototypeOf(router, proto) router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive; router.mergeParams = opts.mergeParams; router.strict = opts.strict; router.stack = []; // Stack holds middlewarereturnrouter; }; // Here are some methods of proto: Proto. param, proto.handle, proto.process_params, proto.use, proto.route Methods. Concat ()'all').forEach(function(method){
  proto[method] = function(path){ //route.all, route.get, router.post
    var route = this.route(path)
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});
Copy the code

Enclosing the route method

proto.route = functionroute(path) { var route = new Route(path); Var layer = new layer (path, {//layer is sensitive: this.casesensitive, strict: this.strict, end:true}, route.dispatch.bind(route)); layer.route = route; // Notice here this.stack.push(layer); // The middleware entity is pushed onto the Router routing stackreturn route;
};
Copy the code

2.2 layer. Js

function Layer(path, options, fn) {
  if(! (this instanceof Layer)) {returnnew Layer(path, options, fn); / / there is no new call} var opts = options | | {}; this.handle = fn; this.name = fn.name ||'<anonymous>'; this.params = undefined; this.path = undefined; this.regexp = pathRegexp(path, this.keys = [], opts); This.regexp. fast_star = path ===The '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if(fn.length > 3) {// The argument is req,res,next, and the length is 3returnnext(); } try { fn(req, res, next); } catch (err) { next(err); }};Copy the code

Layer.prototype follows handle_ERROR (handling errors), match (regular matching routes) and decode_param (decodeURIComponent encapsulation)

3. The middleware

We usually use it like this:

App.use ((req,res,next)=>{// do something //next() no next or res.end, will always be suspended}); app.use(middlewareB); app.use(middlewareC);Copy the code

3.1 app. Use

After app.use(Middleware) is used, the middleware entity passed in (a function with parameters req, RES,next) is pushed to the routing stack, and the next function of the stack is called to the next() method. Middleware includes APP middleware and routing middleware, which are essentially the same. Let’s go back to the routing proto object:

proto.use = functionuse(fn) { var offset = 0; // Indicates the start number var path ='/'; // If the first argument is not a function, app.use('/page',(req,res,next)=>{})
  if(typeof fn ! = ='function') { var arg = fn; // Consider that the first argument is an arraywhile(Array.isArray(arg) && arg.length ! Arg = arg[0]; arg = arg[0]; } // first arg is the pathif(typeof arg ! = ='function') { offset = 1; // If the first argument is not a function, start with the second. //app.use('/page'}} var callbacks = flatten(slice.call(arguments, offset)); // Array flattening and callback function collectionfor(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; // It is a Route object if it is a routing middleware, otherwise it is undefined. Route = route this.stack.push(layer); // In the proto.route method of index.js, define layer.route = route this.stack.push(layer); // Push the Router onto the stack}return this;
};
Copy the code

3.2 The route.js file handles the methods array

This file is used to handle different methods, with a similar key code for methods:

methods.forEach(function(method){
  Route.prototype[method] = function(){    //app.get('/page',(req,res,next)=>{}) var handles = flatten(slice.call(arguments));for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }


    return this;
  };
});
Copy the code

3.3 Types of Middleware

Common and routing middleware

  • Common middleware: app.use, no matter what request method, executes the callback function as long as the path matches
  • Routing middleware: middleware that performs path matching and method matching according to HTTP request methods, so there are two layers:
  • Ordinary middleware Layer, which holds the route variable named, callback function already undefined.
  • Routing middleware Layer, which holds the name and callback function, route also creates a Route object.

The Router and the Route

Router class Layer instance object layer.route is undefined indicates that this Layer is common middleware; If layer.route is a Route instance object, this layer is routing middleware, but there is no method object. The route object’s Layer instance Layer has no route variable and method object, which holds the HTTP request type, that is, the routing middleware with the request method.

Therefore, the Layer instance object in the Router class is the instance that stores the common middleware or the Route of the routing middleware, while the Layer instance in the Route instance object is the real instance that stores the routing middleware.

  • The Route class is used to create routing middleware that has multiple methods (multiple methods are app.get(‘/page’,f1,f2…) The stack of callback functions f1, F2… Save the layer (app.get and app.post are two layers for the same path) to stack.
  • The main purpose of the Router class is to create a bootstrap (layer.route = route) for either normal middleware or routing middleware and save it to the stack.
  • The stack array of the Route instance holds information about middleware methods (get, POST, etc.). The Stack array of the Router instance holds information about the path.

4. Template engines

We usually do this:

app.set('views', path.join(__dirname, 'views')); // Set the view folder app.set('view engine'.'jade'); // In a request, use render res.render('index'); // App.set ()'view engine'.'jade'); , so we don't use res.render('index.jade');
Copy the code

The set method, as described earlier, applies key-value to the setting object. Then we start calling the render function

4.1 Start with res.render

Let’s go to Response.js and find this method:

res.render = function render(view, options, callback) {
  var app = this.req.app;
  var done = callback;
  var opts = options || {};
  var req = this.req;
  var self = this;
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  opts._locals = self.locals;
  done = done || function(err, STR) {// We do not write callback, such as res.render('index',{key:1}); 
    if (err) returnreq.next(err); self.send(str); // send object to string}; app.render(view, opts,done);
};
Copy the code

We find ourselves finally at App.render, so let’s simplify the code

app.render = function render(name, options, callback) {
  var cache = this.cache;
  var done= callback; var engines = this.engines; var opts = options; var renderOptions = {}; var view; // Merge renderOptions this.locals, opts._locals, opts merge(renderOptions, this.locals);if (opts._locals) {
    merge(renderOptions, opts._locals);
  }
  merge(renderOptions, opts);

  if(! Var view = this.get() {var view = this.get();'view'); View = new view (name, {// view class defaultEngine: this.get('view engine'),
      root: this.get('views'),
      engines: engines
    });

  tryRender(view, renderOptions, done); // Call view.render(options, callback) internally; };Copy the code

4.2 the js

The view.render method is found in this file, and it actually executes this.engine(this.path, options, callback) internally. The engine method is set in the View constructor:

function View(name, options) {
  var opts = options || {};
  this.defaultEngine = opts.defaultEngine;
  this.ext = extname(name);
  this.name = name;
  this.root = opts.root;
  var fileName = name;
  if(! this.ext) { this.ext = this.defaultEngine[0] ! = ='. '
      ? '. ' + this.defaultEngine
      : this.defaultEngine;
    fileName += this.ext;
  }

  if(! Opts. Engines [this.ext]) {var mod = this.ext. Substr (1); Var fn = require(mod).__expressif(typeof fn ! = ='function'// If the template engine does not support express, throw new Error('Module "' + mod + '" does not provide a view engine.'} opts.engines[this.ext] = fn // Of course, application.js has a similar setting, } this.engine = opts.engines[this.ext];} engine = opts.engines[this.ext]; // Set the template engine corresponding to the express compiler function this.path = this.lookup(fileName); // find the path}Copy the code

This. Engine (this. Path, options, callback) is required (mod).__express(this. Path, options, callback) Then play by his rules

See some articles that middleware with connect module do, I looked at connect is indeed can, and the shape is exactly the same, but I look at the source code there is no shadow of connect. Connect was probably the early Express