How many ways can middleware be implemented?

Time:2021-7-20

Welcome to my official account.Rui talk, get my latest articles:
How many ways can middleware be implemented?

1、 Preface

Middleware is a general and independent system software service program which is located on the operating system of the server and manages the computing resources and network communication. Distributed application software uses this software to share resources among different technologies. In the field of large front-end, the meaning of middleware is much simpler, which generally refers to the software that provides common and independent functionsData processing function。 Typical middleware includes logging, data overlay and error handling. In this paper, we will compare the use scenarios and implementation principles of Middleware in the large front-end domain, includingExpress, Koa, ReduxandAxios

2、 Middleware in big front end field

The big front-end fields mentioned here naturally include server-side and client-side. The concept of middleware was first proposedExpressAnd then made by the original teamKoaIt not only follows the architecture design of middleware, but also more thoroughly defines itself asMiddlewares Frame-works

Expressive HTTP middleware framework for node.js

In the client domain,ReduxIt also introduces the concept of middleware, which is convenient for independent function to process action.AxiosAlthough there is no middleware, it has many advantagesInterceptorThe usage of is very similar to that of middleware, which is also compared by the way. The following table compares the usage of middleware or class middleware of several frameworks horizontally.

frame Use registration Next scheduling Composition Processing objects
Express Y Y N req & res
Koa Y Y Y ctx
Redux N Y Y action
Axios Y N N config/data

Let’s break down the internal implementation of these frameworks.

3、 Express

3.1 usage

app.use(function logMethod(req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

ExpressThere are many levels of registration methods for middleware. Here we take application level middleware as an example. Here are two keywords,useandnextExpressadoptuseRegistration,nextThe way of triggering the next middleware execution establishes the standard usage of middleware architecture.

3.2 principle

In the principle part, the source code will be extremely simplified, leaving only the core.

Middleware registration (use)
var stack = [];
function use(fn) {
  stack.push(fn);
}
Middleware scheduling (next)
function handle(req, res) {
  var idx = 0;
  next();
  function next() {
    var fn = stack[idx++];
    fn(req, res, next)
  }
}

When the request arrives, it triggershandlemethod. nextnextFunction takes the middleware out of the queue and executes it.

4、 KOA

4.1 usage

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

WithExpresscomparison,KoaMiddleware registration has nothing to do with routing. All requests go through the registered middleware. meanwhileKoaBorn to supportasync/awaitAsynchronous programming mode, the code style is more concise. As for the onion model and so on, we all know, no nonsense.

4.2 principle

Middleware registration (use)
var middleware = [];
function use(fn) {
  middleware.push(fn);
}
Middleware (KOA compose)
function compose (middleware) {
  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      //The follow-up operation performed by middleware, combined with the source code of KOA, here's next = undefined
      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)
      }
    }
  }
}

WithExpresssimilar,KoaThe middleware of is also executed in sequencedispatchFunction. The writing mode of the code is also very similarCall dispatch / next > define dispatch / next > dispatch / next as a callback call recursively。 Here’s one thing to note. For middleware, theirawait next()In fact, it isawait dispatch(i)。 When the last middleware is executed, the condition is triggeredif (i === middleware.length) fn = nexthereOfnextyesundefined, will trigger a barif (!fn) return Promise.resolve(), continue with the last middlewareawait next()The following code is also the time point for the onion model to execute from the inside out.

5、 Redux

ReduxIt is the first front-end framework that I know to apply the concept of middleware to the client. Its source code reflects the idea of functional programming everywhere and makes people shine.

5.1 usage

const logger = store => next => action => {
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}
const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
  }
}
const store = createStore(appReducer, applyMiddleware(logger, crashReporter))

ReduxThe parameters of middleware have been corized,storeyesapplyMiddlewareFrom the inside,nextyescomposeIt came in later,actionyesdispatchIt’s coming in. The design here is really very clever, and we will analyze it with the source code.

5.2 principle

Middleware choreography (applymiddleware)
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState)
    let dispatch = store.dispatch
    let chain = []
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    //First, execute middleware and pass in the first parameter store
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    //Pass in the original dispatch
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}

herecomposeThe return value of is reassigned todispatch, which indicates that we call in the applicationdispatchNot at allstoreIt comes with an upgraded version after middleware processing.

Middleware composition
function compose (...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

composeThere is only one line of the core code, like dolls, which will cover the middleware layer by layer, the bottom layerargsnamelystore.dispatch

6、 Axios

AxiosThere is no concept of Middleware in, but interceptors with similar functions provide independent, configurable and superimposable additional functions between two points of data processing link in essence.

6.1 usage

//Request interceptor
axios.interceptors.request.use(function (config) {
  config.headers.token = 'added by interceptor';
  return config;
});
//Response interceptor
axios.interceptors.response.use(function (data) {
  data.data = data.data + ' - modified by interceptor';
  return data;
});

AxiosThere are two types of interceptors: request and response. After registration, the interceptors will be executed automatically in the order of registration, and there is no need to call them manually like other frameworksnext()

6.2 principle

Interceptors registration (use)
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
function InterceptorManager() {
  this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

You can see thatAxiosInternally, two interceptors are maintained, which have independent handlers array.useIt’s just adding elements to an array. Different from other frameworks, the array element here is not a function, but an object that containsfulfilledandrejected2 attributes. When the second parameter is not transferredrejectedIt’s undefined.

Task arrangement
//Reduced code
Axios.prototype.request = function request(config) {
  config = mergeConfig(this.defaults, config);
  //Add elements in pairs
  var requestInterceptorChain = [];
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  
  var responseInterceptorChain = [];
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });
  
  var chain = [dispatchRequest, undefined];
  
  Array.prototype.unshift.apply(chain, requestInterceptorChain);
  chain.concat(responseInterceptorChain);
  
  promise = Promise.resolve(config);
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  return promise;
}

Here, the interceptors are concatenated through the chain call of promise. The execution order is as follows:requestInterceptorChain -> chain -> responseInterceptorChain。 There is a default convention here. The elements in the chain follow the[fulfilled1, rejected1, fulfilled2, rejected2]So if you do not provide a second parameter when registering interceptors, there will be a default value of undefined.

7、 Horizontal comparison of each frame

After looking at the implementation of middleware, we can summarize the following features:

  • Middleware mechanism can be used for both server and client
  • The essence of middleware mechanism is to open one or more points on the data processing link to the framework users to enhance the data processing capability of the framework
  • Most of middleware are reusable functions independent of specific business
  • Multiple middleware can be combined to achieve complex functions

Let’s summarize the essence of the implementation of middleware system in each framework

frame Implementation mode
Express Recursive callnext
Koa Recursive calldispatch
Redux Array.reduceRealize function nesting
Axios promise.thencall chaining

The most subtle and difficult thing to understand isArray.reduceThis kind of form needs repeated deliberation.promise.thenThe task scheduling method of chain call is also very ingenious, and the data processed before will be automatically transferred to the next onethen。 The form of recursive calls is best understood,KoastayExpressBased on the implementation, it naturally supports asynchronous call, which is more in line with the server-side scenario.

8、 Summary

In this paper, starting from the way of use, combined with the source code to explain the implementation of Middleware in the major front-end framework, horizontal comparison of the similarities and differences between them. The skills of recursive call, function nesting and promise chain call are worth learning.

Recommended Today

Lua language novice simple tutorial

1、 Foreword Lua is a lightweight and compact scripting language, which is written in standard C language and open in the form of source code. Its design purpose is to be embedded in the application, so as to provide flexible expansion and customization functions for the application. Lua can be applied in game development, independent […]