koa onion model

I What is an onion model?
Look first 🌰

const Koa = require('koa');

const app = new Koa();
const PORT = 3000;

// #1
app.use(async (ctx, next)=>{
    console.log(1)
    await next();  //Pause the current program
    console.log(1)
});
// #2
app.use(async (ctx, next) => {
    console.log(2)
    await next();
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)
})

app.listen(PORT);
console.log(`http://localhost:${PORT}`);


// output
1
2
3
2
1

In koa, app The callback of the middleware registered by use is iterated according to the onion model. The middleware is divided into two parts by the next() method. The upper part of the next() method will be executed first. The following part of the next() method will be executed after the execution of the following middleware is completed. Combined with the following figure, it can be seen intuitively:

Each layer in the onion represents an independent middleware, which is used to realize different functions, such as exception handling, cache processing, etc. Each request will pass through each layer of middleware from the left. After entering the innermost layer of middleware, it will return layer by layer from the innermost layer of middleware. Therefore, for each layer of middleware, there are two timing points to add different processing logic in a request and response cycle.
Middleware execution sequence:

What can onion models do?
For example, calculating the time-consuming of a request and obtaining the information of other middleware can not be realized without the onion model.

II koa principle
Learn the principle of koa deeply. By consulting the source code of koa, we can see the following methods.

  1. use method: maintain the middleware array
use (fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
    debug('use %s', fn._name || fn.name || '-')
    this.middleware.push(fn)
    return this
}
  1. listen method: node JS native http module createServer method creates a service.
  2. Callback method: the callback of the service is the callback method, and the return is a Promise function.
listen (...args) {
    debug('listen')
    // Create a service
    const server = http.createServer(this.callback())
    return server.listen(...args)
}

callback () {
    // Returns a function
    const fn = compose(this.middleware)

    if (!this.listenerCount('error')) this.on('error', this.onerror)

    const handleRequest = (req, res) => {
      // Create ctx context
      const ctx = this.createContext(req, res)
      return this.handleRequest(ctx, fn)
    }
    // Execute the Promise function returned in the compose function and return the result.
    return handleRequest
}

Compose comes from the koa compose module. The dispatch in the method traverses the entire middleware, and then passes the context and dispatch(i + 1) to the methods in middleware.
Main purpose [core of onion model]:
1). Pass the context all the way to the middleware
2). Take the next middleware fn in middleware as the return value of next in the future

function compose(middleware) {
  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, function next() {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }

The createContext method and handleRequest method bind ctx with middleware

handleRequest (ctx, fnMiddleware) {
    const res = ctx.res
    res.statusCode = 404
    const onerror = err => ctx.onerror(err)
    const handleResponse = () => respond(ctx)
    onFinished(res, onerror)
    return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}

createContext (req, res) {
    const context = Object.create(this.context)
    const request = context.request = Object.create(this.request)
    const response = context.response = Object.create(this.response)
    context.app = request.app = response.app = this
    context.req = request.req = response.req = req
    context.res = request.res = response.res = res
    request.ctx = response.ctx = context
    request.response = response
    response.request = request
    context.originalUrl = request.originalUrl = req.url
    context.state = {}
    return context
}

III reference material

  1. https://juejin.cn/post/7012031464237694983
  2. https://juejin.cn/post/7046713578547576863
  3. https://juejin.cn/post/6890259747866411022

Keywords: node.js Front-end Middleware koa

Added by fabby on Tue, 15 Feb 2022 09:31:47 +0200