The original link

Introduction to the

Koa was built by the original Express team to be a smaller, more expressive, and more robust Web framework that would fit as a cornerstone of a Web services framework.

Koa1 avoids nesting hell and greatly improves error handling by combining different generators. Koa2 uses the latest async await Generator syntax sugar to make development more efficient.

Koa does not bind any middleware in the kernel methods, but it does integrate middleware very easily, just passing in a middleware function with the use method to easily get the context information such as the request response and the next middleware, keeping the use of middleware in order.

An overview of

The koA source is in four files in the lib file, and the contents of each module file are described below.

Lib / ├ ─ ─ application. Js ├ ─ ─ the context, js ├ ─ ─ request. Js └ ─ ─ response. JsCopy the code
  • Application.js exports a class function that generates an instance of KOA. This class derives Node Events to facilitate error handling.
  1. use()Add subscription middleware, internally using an array to maintain middleware;
  2. listen()Node HTTP starts a service;
  3. callback()Returns an HTTP service callback function cb.
    1. Compose handles the middleware array and returns a function, fnMiddleware. Internal promise-oriented middleware, recursive calls make the middleware take the context CTX and the next middleware next and execute them sequentially;
    2. CreateContext receives the HTTP request callback parameters req and RES in cb, enabling the application instance, Context, Request, and Response to access each other’s REQ and RES and return a new context each time.
    3. HandleRequest finally executes fnMiddleware, and the middleware calls the private function respond when there is no error.
  • Context.js exports an object with error handling, cookie handling, and proxy properties and methods on Request.js and Response.js (for example: Ctx. url (request. Url, node HTTP req.url).
  • Request. js exports an object that encapsulates and processes the node’s native HTTP request REQ, facilitating the acquisition of the set REQ and avoiding direct contact with the REQ.
  • Response.js exports an object that encapsulates and processes the node’s native HTTP response RES, so as to conveniently obtain the set RES and avoid dealing with RES directly.

Using the example

  • Start a simple service
const Koa = require('koa');
const app = new Koa();

app.listen(3000);
Copy the code

In fact, the following grammar sugar

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

http.createServer(app.callback()).listen(3000);
Copy the code
  • Use middleware to handle Node HTTP requests and responses
const Koa = require('koa');
const app = new Koa();

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

// response
app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
Copy the code

The Logger middleware await next() suspends execution of the following code until the Response middleware completes execution.

Note that Response does not execute next, and there is no next middleware at this point, but even if it does, it will not report an error, because the internal processing is a Promise. Resolve Promise.

Note that executing next() multiple times (2 or more) in one middleware will result in an error.

If logger middleware does not execute next, response middleware will not execute. HTTP res.statusCode = 404; NPM statuses maintain the usual code text prompt sound. For example, 404: Not Found.

Ctx. body actually calls the body set method of the KOA Response object, assigns the _body property and sets the HTTP status code based on the value. The node HTTP res.end() function is called after resolve.

Start implementing a lean KOA

skeleton

  • Application. Js needs to serve, so it needs to introduce the Node HTTP module; You need to publish and subscribe to some messages, so you need to inherit the Node Events module. The remaining modules import the other three files.
const http = require('http');
const Emitter = require('events');
const context = require('./context');
const request = require('./request');
const response = require('./response');

class Koa extends Emitter {
  constructor() {
    super(a); } listen() {} use() {} callback() {} handleRequest() {} createContext() {} }module.exports = Koa;
Copy the code
  • context.js
let proto = {};

module.exports = proto;
Copy the code
  • request.js
const request = {};

module.exports = request;
Copy the code
  • response.js
const response = {};

module.exports = response;
Copy the code

The first step is to receive an intermediate function

  • Constructor, the other three objects can all be accessed by the app instance.
constructor() {
  super(a);this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
  this.fn = null;
}
Copy the code

To briefly mention why object. create is used, for example, avoid altering this.context.x to affect context.x (unless you this.context.__proto__.x, obviously no one would do that intentionally).

if(!Object.create) {
  Object.create = function(proto) {
    function F(){}
    F.prototype = proto;
    return newF; }}Copy the code
  • Listen syntax sugar facilitates HTTP services.
listen(... args) {const server = http.createServer(this.callback());
  returnserver.listen(... args); }Copy the code
  • Use, subscribe to middleware, can only subscribe to one for now.
use(fn) {
  this.fn = fn;
  return this;
}
Copy the code
  • Callback, which handles the middleware, and returns a callback function that receives node HTTP REq, RES. Each time an HTTP request is received, koA createContext is used to create a new context based on the current request environment.
callback() {
  return (req, res) = > {
    const ctx = this.createContext(req, res);
    return this.handleRequest(ctx);
  };
}
Copy the code
  • HandleRequest, executing middleware, and responding to HTTP requests.
handleRequest(ctx) {
  this.fn(ctx);
  ctx.res.end(ctx.body);
}
Copy the code
  • CreateContext, each time an HTTP request is processed, the content is updated based on the reQ and RES of the current request. A series of assignment operations, mainly in order to generate a new context, request, response can access each other, and can access the KOA app instance and HTTP REq, RES.
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;
  return context;
}
Copy the code
  • Request.js, which simply adds a few url handling methods to the KOA Request object
const parse = require('parseurl');

const request = {
  get url() {
    return this.req.url;
  },
  get path() {
    return parse(this.req).pathname;
  },
  get query() {
    return parse(this.req).query; }};Copy the code
  • Response.js, here just add a method that sets the response body
const response = {
  get body() {
    return this._body;
  },
  set body(val) {
    this.res.statusCode = 200;
    this._body = val; }};Copy the code
  • Master file index. Js
const Koa = require('./application');
const app = new Koa();

app.use(ctx= > {
  console.log(ctx.req.url);
  console.log(ctx.request.req.url);
  console.log(ctx.response.req.url);
  console.log(ctx.request.url);
  console.log(ctx.request.path);
  console.log(ctx.request.query);
  console.log(ctx.url);
  console.log(ctx.path);
  console.log(ctx.query);
  ctx.body = 'hello world';
});

app.listen(3000);
Copy the code
  • Nodeindex.js, browser inputlocalhost:3000/path? x=1&y=2The console output,
/path? x=1&y=2 /path? x=1&y=2 /path? x=1&y=2 /path? x=1&y=2 /path x=1&y=2 undefined undefined undefinedCopy the code

As you can see, you can use koa Context, Request, and Response to access node req properties, as well as directly access methods defined on the Request object.

The recommendation is to avoid req or RES for Node HTTP.

As you know, KOA allows context instance proxies to access methods on KOA Request and Response.

The second step is to implement the Context proxy

  • Context.js, where the proxy accesses methods on KOA Request and response

Koa uses __defineSetter__ and __defineGetter__ to indicate that these methods are deprecated by the standard, and Object. DefineProperty is used instead.

Object. DefineProperty sets enumerable and signals to false.

function defineGetter(prop, name) {
  Object.defineProperty(proto, name, {
    get() {
      return this[prop][name];
    },
    enumerable: true.configurable: true}); }function defineSetter(prop, name) {
  Object.defineProperty(proto, name, {
    set(val) {
      this[prop][name] = val;
    },
    enumerable: true.configurable: true}); } defineGetter('request'.'url');
defineGetter('request'.'path');
defineGetter('request'.'query');

defineGetter('response'.'body');
defineSetter('response'.'body');
Copy the code
  • Restart the service, console.log output
/path? x=1&y=2 /path? x=1&y=2 /path? x=1&y=2 /path? x=1&y=2 /path x=1&y=2 /path? x=1&y=2 /path x=1&y=2Copy the code

Ctx. body = ‘Hello world’ is also not a new addition, but instead accesses the body set method on Response.

The third step is to receive multiple synchronous middleware

constructor() {
- this.fn = null;
+ this.middleware = [];
}

use(fn) {
- this.fn = fn;
+ this.middleware.push(fn);
}
Copy the code
  • Added compose to implement the Onion ring model
function compose(middleware) {
  return function (context, next) {
    let index = - 1;
    return dispatch(0);
    function dispatch(i) {
      if(i <= index) throw new Error('Next () is called more than 2 times in middleware');
      index = i;
      let fn = middleware[i];
      if(i === middleware.length) fn = next;
      if(! fn)return;
      return fn(context, dispatch.bind(null, i + 1)); }}}Copy the code
callback() {
+ const fn = compose(this.middleware);
  return (req, res) => {
    const ctx = this.createContext(req, res);
- return this.handleRequest(ctx);
+ return this.handleRequest(ctx, fn);
  };
}

- handleRequest(ctx) {
+ handleRequest(ctx, fnMiddleware) {
- this.fn(ctx);
+ fnMiddleware(ctx);
  ctx.res.statusCode = 200;
  ctx.res.end(ctx.body);
}
Copy the code
  • Index.js, you can use multiple middleware and next
app.use((ctx, next) = > {
  console.log(ctx.url);
  next();
});

app.use((ctx, next) = > {
  ctx.body = 'hello world';
  next();
});
Copy the code

Step 4: Asynchronous onion ring model

  • Modify Compose to support asynchrony
function compose(middleware) {
  return function (context, next) {
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
-if (I <= index) throw new Error('next() is called more than 2 times in middleware ');
+ if(I <= index) return promise.reject (new Error('next() is called more than 2 times in middleware '));
      index = i;
      let fn = middleware[i];
      if(i === middleware.length) fn = next;
- if(! fn) return;
+ if(! fn) return Promise.resolve();
- return fn(context, dispatch.bind(null, i + 1));
+ try {
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
+ } catch (err) {
+ return Promise.reject(err);
+}}}}Copy the code
handleRequest(ctx, fnMiddleware) {
- fnMiddleware(ctx);
- ctx.res.statusCode = 200;
- ctx.res.end(ctx.body);
+ fnMiddleware(ctx).then(() => {
+ ctx.res.statusCode = 200;
+ ctx.res.end(ctx.body);
+});
}
Copy the code
  • Index. js Asynchronous onion rings
app.use(async (ctx, next) => {
  await new Promise(resolve= > {
    setTimeout((a)= > {
      console.log(ctx.url);
      resolve();
    }, 500);
  });
  next();
});

app.use((ctx, next) = > {
  ctx.body = 'hello world';
  next();
});
Copy the code

After the

The main functions of this simple KOA are realized, and many details such as error handling are omitted for the sake of simplicity, which is a big no-no in a formal product, so be careful.