Introduction to the

Middleware: The essence of middleware is a function that does what we want to do in the process of receiving a request and returning it. Express official:

  1. Execute any code.
  2. Modify the request and response objects.
  3. Terminates the request-response loop.
  4. Call the next middleware in the stack.

expressThe middleware

Middleware has been used in Koa, Redux and Express. My idol Po Wrote an article on how to better understand middleware and Onion model from Koa, so THIS time I intend to mainly understand middleware from Express. First we download the Express source code, which we can download from the command line

$ npm install express --save
Copy the code

Category of Express middleware

  1. Application-level middleware
  2. Routing level middleware
  3. Error handling middleware
  4. Built-in middleware
  5. Third-party middleware

Initial use of Express middleware

Let’s look at a simple usage example. This example listens on port 3000 and we add 3 middleware to it by using the use method.

var express = require("express");

var app = express();
app.listen(3000.function () {
  console.log("listen 3000...");
});

function middlewareA(req, res, next) {
  console.log("middlewareA before next()");
  next();
  console.log("middlewareA after next()");
}

function middlewareB(req, res, next) {
  console.log("middlewareB before next()");
  next();
  console.log("middlewareB after next()");
}

function middlewareC(req, res, next) {
  console.log("middlewareC before next()");
  next();
  console.log("middlewareC after next()");
}

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);
Copy the code

The resultsWe find that after executing one piece of middleware, we go to next(), we skip the next statement, execute the next piece of middleware, and then execute the statement after next() in reverse when all the execution is complete.From the figure above, we can clearly see what middleware is and in which scenarios it can be applied. With middleware, we can precisely control the pre – and post-operations of an execution, such as the processing before AXIos sends a request and the processing after it receives a request. So how does Express accomplish this task execution?

Implementation principles of the Express middleware

First, we will output a concept: a Layer instance is a mapping entity of path and Handle, and each Layer is a middleware instance. Then let’s look at initialization of the Express middlewareFrom the above figure, we find that middleware may be nested in the middle of middleware. Faced with this situation, Express does the processing in Layer, and we mount the middleware in two cases.

  1. Through the use ofapp.userouter.useTo mount.
    1. App.use, after being processed, is eventually calledrouter.use.
  2. useapp.[Http Method],app.route,router.all,router.[Http Method],router.routeTo mount it.
    1. app.all,app.[Http Method],app.route,router.all,router.[Http Method]After a bunch of processing, it’s finally calledrouter.route.

Therefore, we mainly study router.use and router.route

router.use

We see the definition in router/index.js

proto.use = function use(fn) {
  var offset = 0;
  var path = '/';
  if (typeoffn ! = ='function') {... }if (typeofarg ! = ='function') {... }}var callbacks = flatten(slice.call(arguments, offset));
  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')}for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];
    if (typeoffn ! = ='function') {... } debug('use %o %s', path, fn.name || '<anonymous>')
    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

Summary: There is no deeply nested middleware that registers with router.use.

router.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;
};
Copy the code

Summary: Middleware with no deep nesting is registered using router.route. We found that either way, we ended up adding our middleware to the stack array. When an event is triggered, app.handle processes the stack array

app.handle = function(req, res, callback) {
	var stack = this.stack;
	var idx = 0;
	function next(err) {
		if (idx >= stack.length) {
		  callback('err') 
		  return;
		}
		var mid;
		while(idx < stack.length) {
		  mid = stack[idx++];
		  mid(req, res, next);
		}
	}
	next()
}
Copy the code

Through tail recursive calls, the next method constantly takes out the middleware in the stack to call, and passes itself to the middleware as the third parameter. The convention of each middleware is fixed in the format of (REq, RES, next) =>{}, so as long as each middleware function calls the next method, You can call the next middleware, and you can read ruan’s last call optimization for those interested in last call. It is not clear how it will trigger after next(). The overall process is that the middleware is registered and added to the stack array, and when triggered, recursive calls are made, one by one

Middleware in KOA

Koa code is more advanced and refined than Express. The code is based on ES6 implementation, supports generator (async, await) and has no built-in routing implementation and any built-in middleware. The context design is also very clever.

Middleware processing

  1. The constructorconstructorMaintains the global middleware array inthis.middlewareAnd the globalthis.contextInstance (there are request, Response objects, and some other auxiliary properties in the source code).
  2. expressDifferent, because there isn’trouterThe implementation of allthis.middlewareIs a generic “middleware” function rather than a complex onelayerThe instance

Task choreography in KOA

The order of task execution in KOA is to complete task arrangement by placing pre-execution and post-execution before and after await next(), applying the principle of async await execution order.

// Middleware that counts request processing time
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
Copy the code

Task scheduling in KOA

From the previous knowledge, we know that the middleware is stored in the middleware array. In order to complete the task, we need to take them out one by one and execute them. The related scheduling algorithm is in the compose function under koa-compose.

function compose(middleware) {
  // Omit some code
  return function (context, next) {
    // last called middleware #
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
      if (i <= index)
        return Promise.reject(new Error("next() called multiple times"));
      index = i;
      let fn = middleware[i];
      if (i === middleware.length) fn = next;
      if(! fn)return Promise.resolve();
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err); }}}; }Copy the code

Async (CTX, next) =>{} Each execution returns a promise, The value of the second argument is dispatch.bind(null, I +1), so the middleware will execute down one by one, and when the last one is done, resolve will fall, and then the method in the await queue will start executing, finally making the outermost promise resolve fall. Summary: The biggest difference between express and KOA is that the response is handled after the middleware executes and a Promise resolve is returned. Let’s take a look at some of the code in koA’s application

// some of the code in application.js
constructor() {
	super(a)this.middleware = []
	this.context = Object.create(context)
}

use(fn) {
	this.middleware.push(fn)
}

listen(. args) {
	debug('listen')
	const server = http.createServer(this.callback());
	returnserver.listen(... args); }callback() {
	// This is middleware processing code
	const fn = compose(this.middleware);
	
	const handleRequest = (req, res) = > {
	  // CTX is one of the essence of KOA. Many methods in REq and RES are delegated to CTX, making it easier to handle many problems based on CTX
	  const ctx = this.createContext(req, res);
	  return this.handleRequest(ctx, fn);
	};
	
	return handleRequest;
}

handleRequest(ctx, fnMiddleware) {
	ctx.statusCode = 404;
	const onerror = err= > ctx.onerror(err);
	const handleResponse = () = > respond(ctx);
	return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Copy the code

We found that after the listener is triggered, the callback is executed, which is where the middleware code is processed, and then the handleRequest is executed, and then the response is processed through the handleResponse, The “middleware” will set ctx.body and handleResponse will mainly handle ctx.body, so koA’s “onion ring” model will be valid and the code after await next() will affect the final response.

The onion model

Summary: We understand how to implement task choreography and scheduling for an Onion model using Express and KOA.