The first article introduced the generator directory design.

Next, learn about Koa2 middleware.

Koa2 itself is a minimalist HTTP server with no middleware built in, but a middleware kernel. Middleware is at the heart of Koa2 and therefore requires proficiency.

What is middleware?

You can think of an HTTP request as a flow of water, and various types of middleware act like pipes that process the flow. Each middleware will rewrite the request, response and other data during HTTP requests.

This is what we call the Onion model.

We can understand the above process with an example:


const Koa = require('koa')
const app = new Koa()



// Log middleware
app.use(async (ctx, next) => {
  console.log('middleware before await');
  const start = new Date(a)await next() // Function control transfer
  console.log('middleware after await');
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

app.use(async(ctx, next) => {
  console.log('response');
  ctx.body = "hello koa2"
})

module.exports = app

Copy the code

Terminal print

middleware before await
response
middleware after await
Copy the code

Middleware writing

Universal functional middleware

// Log middleware
app.use((ctx, next) = > {
  console.log('middleware before await');
  const start = new Date(a)return next().then(() = > {
    console.log('middleware after await');
    const ms = new Date() - start
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)})})Copy the code

That’s not a neat way to write it.

Generator function middleware


const convert = require('koa-convert');

// Log middleware
app.use(function* log(next) {
  console.log('middleware before await');
  const start = new Date(a)yield next
  const ms = new Date() - start
  console.log(`The ${this.method} The ${this.url} - ${ms}ms`)})Copy the code

There is a small detail here, because our middleware does not use arrow functions, so this is actually the context object CTX. This also explains why Koa2 needs to display CTX objects when using async arrow functional middleware, in order to solve the problem of using this reference here.

Async function middleware

app.use(async ctx => {
  ctx.body = 'Hello World';
});
Copy the code

Context object

In Koa2, CTX is the context of a complete HTTP request that lasts throughout the lifetime of the request. That is, it is shared throughout the request phase.


  createContext(req, res) {
    const context = Object.create(this.context);
    // Request and Response are Koa2 built-in objects
    // Ctx. request and ctx.response are generally used to access services
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    // Hang in the app itself
    context.app = request.app = response.app = this;
    // Hang on node native built-in objects
    // req: http://nodejs.cn/api/http.html#http_class_http_incomingmessage
    // res: http://nodejs.cn/api/http.html#http_class_http_serverresponse
    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;
    // The original URL
    context.originalUrl = request.originalUrl = req.url;
    // Common storage space for the lifetime of a middleware
    context.state = {};
    return context;
  }
Copy the code

ctx.body

Ctx. body is primarily Koa2’s method of returning data to the client.


// context.js
/** * Response delegation. */

delegate(proto, 'response')
  .access('body')
Copy the code

You can see that ctx.body is actually an assignment to the body in Response.js.

Some features of ctx.body:

  • You can return a text directly
  • You can return an HTML text
  • You can return JSON

ctx.body = 'hello'

ctx.body = '<h2>h2</h2>'

ctx.body = {
  name: 'kobe'
}
Copy the code

get status() {
    return this.res.statusCode;
  },

  /**
   * Set response status code.
   *
   * @param {Number} code
   * @api public* /

  set status(code) {
    if (this.headerSent) return;

    assert(Number.isInteger(code), 'status code must be a number');
    assert(code >= 100 && code <= 999.`invalid status code: ${code}`);
    this._explicitStatus = true;
    this.res.statusCode = code;
    // For the HTTP version sent by the client, message.httpVersionMajor is the first integer and message.httpVersionMinor is the second integer.
    // http://nodejs.cn/api/http.html#http_message_httpversion
    / / set the status message at http://nodejs.cn/api/http.html#http_response_statusmessage
    if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code];
    if (this.body && statuses.empty[code]) this.body = null;
  }, 

/**
   * Set response body.
   *
   * @param {String|Buffer|Object|Stream} val
   * @api public* /

  set body(val) {
    // this._body is a real body property or proxy property
    const original = this._body;
    this._body = val;

    // no content
    if (null == val) {
      // 204 "no content"
      if(! statuses.empty[this.status]) this.status = 204;
      if (val === null) this._explicitNullBody = true;
      this.remove('Content-Type');
      this.remove('Content-Length');
      this.remove('Transfer-Encoding');
      return;
    }

    // Set the status code
    if (!this._explicitStatus) this.status = 200;

    / / set the content-type
    const setType = !this.has('Content-Type');

    // string
    if ('string'= = =typeof val) {
      // text/html or text/plain
      if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
      this.length = Buffer.byteLength(val);
      return;
    }

    // buffer
    if (Buffer.isBuffer(val)) {
      if (setType) this.type = 'bin';
      this.length = val.length;
      return;
    }

    // stream
    if (val instanceof Stream) {
      onFinish(this.res, destroy.bind(null, val));
      if(original ! = val) { val.once('error'.err= > this.ctx.onerror(err));
        // overwriting
        if (null! = original)this.remove('Content-Length');
      }

      if (setType) this.type = 'bin';
      return;
    }

    // json
    this.remove('Content-Length');
    this.type = 'json';
  },
Copy the code

Ctx. body works by processing the content-Type header based on its assignment Type and writing the data to the browser via res.end based on the content-Type value.

ctx.redirect

Browser redirection is usually either forward or backward.


redirect(url, alt) {
    // location
    if ('back' === url) url = this.ctx.get('Referrer') || alt || '/';
    this.set('Location', encodeUrl(url));

    // status
    if(! statuses.redirect[this.status]) this.status = 302;

    // html
    if (this.ctx.accepts('html')) {
      url = escape(url);
      this.type = 'text/html; charset=utf-8';
      this.body = `Redirecting to <a href="${url}">${url}</a>.`;
      return;
    }

    // text
    this.type = 'text/plain; charset=utf-8';
    this.body = `Redirecting to ${url}. `;
  },
Copy the code

Next article in the middleware source code series.

Ps: Don’t mind you can follow my public number XYz_ programming diary, add friends, click 👍, click to see.