What is the KOA framework?

Koa is a new Web framework based on the Node implementation, which was built by the original Express framework. It is characterized by elegance, simplicity, strong expression and high degree of freedom. It is a lighter Node framework than Express because all functions are implemented through plug-ins, which are very much in line with the Unix philosophy.

Koa framework is now updated to 2.x version, this article from scratch, step by step, explain the KOA2 framework source code structure and implementation principle, show and explain koA2 framework source code in a few of the most important concepts, and then hand in hand to teach you to personally achieve a simple KOA2 framework, to help you learn and a deeper understanding of KOA2, After reading this article, go to check koA2 source code, I believe that your ideas will be very smooth.

The framework used in this article is KOA2, which is different from KOA1. Koa1 uses generator+co.js execution while KOA2 uses async/await. Therefore, the code and demo in this article need to run on Node 8 or above. It is recommended that you upgrade or install babel-CLI and use babel-Node to run the code covered in this article.

Koa source code structure

Above is the lib folder of koA2’s source directory structure. The lib folder contains four koA2 core files: application.js, Context.js, request.js, response.js.

application.js

Application.js is a koA entry file that exports the constructor that creates the class instance. It inherits Events, which gives the framework the ability to listen for events and trigger events. Application also exposes some common apis, such as toJSON, Listen, use, and so on.

Listen encapsulates HTTP. CreateServer. The callback passed in to this function includes middleware merging, context processing, and special res processing.

Use is a collection of middleware that puts multiple middleware into a cache queue and then makes a recursive combination of calls to those columns of middleware through the koa-compose plug-in.

context.js

This part is the koA application context CTX, which is a simple object exposure, and the focus is on the delegate. This is the delegate, and this is designed for the convenience of the developer. For example, if we want to access ctx.repsponse.status, You can access it directly by accessing ctx.status.

Request. Js, response. Js

Use es6’s get and set syntax, fetch headers, set headers, and set body. These are not detailed, interested readers can look at the source code.

Achieve the four modules of KOA2

Above briefly described the koA2 source framework structure, next we will achieve a KOA2 framework, THE author believes that understanding and implementation of a KOA framework needs to achieve four big modules, respectively:

  • Encapsulate node HTTP Server and create Koa class constructor

  • Construct request, Response, and context objects

  • Middleware mechanism and implementation of peeling onion model

  • Error capture and error handling

Let’s analyze and implement one by one.

Module 1: Encapsulates node HTTP Server and creates Koa class constructors

Read the source code of KOA2, realize koA server application and port listening, in fact, is based on the native code of Node for encapsulation, the code in the following figure is through the node native code to achieve the server listening.

let http = require('http');
let server = http.createServer((req, res) = > {
    res.writeHead(200);
    res.end('hello world');
});
server.listen(3000, () = > {console.log('listenning on 3000');
});
Copy the code

We need to implement the above Node native code wrapper into koA’s pattern:

const http = require('http');
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
Copy the code

The first step to implementing KOA is to encapsulate the above process. To do this, we need to create application.js to implement an Application class constructor:

let http = require('http');
class Application {    
    constructor() {        
        this.callbackFunc;
    }
    listen(port) {        
        let server = http.createServer(this.callback());
        server.listen(port);
    }
    use(fn) {
        this.callbackFunc = fn;
    }
    callback() {
        return (req, res) = > {
            this.callbackFunc(req, res); }; }}module.exports = Application;
Copy the code

Then create example.js, import application.js, and run the server instance to start the listening code:

let Koa = require('./application');
let app = new Koa();
app.use((req, res) = > {
    res.writeHead(200);
    res.end('hello world');
});
app.listen(3000, () = > {console.log('listening on 3000');
});
Copy the code

Now type localhost:3000 in your browser to see “Hello World” displayed in your browser. Now that we have completed the first step, we have simply wrapped the HTTP server and created a class that generates koA instances. This class also implements app.use to register middleware and register callback functions. App.listen is used to open server instances and pass in callback functions. The first module mainly implements typical KOA style and sets up a simple KOA shelf. Next we’ll start writing and explaining module 2.

Module 2: Construct request, Response, and Context objects

According to the source code of KOA2, context.js, request.js and Response. js are code files of request, response and context modules respectively. Context is the CTX when we write KOA code. It is equivalent to a global KOA instance context this, which connects the request and Response function modules and is exposed to the parameters of callback functions such as THE KOA instance and middleware to play the role of connecting the preceding and the following.

Request and Response function modules encapsulate node’s native Request and response respectively, using getter and setter attributes. Node-based objects req/ RES encapsulates KOA’s Request/Response object. Request.js and Response.js are simply implemented based on this principle. First create request.js file and then write the following code:

let url = require('url');
module.exports = {
    get query() {
        return url.parse(this.req.url, true).query; }};Copy the code

Parse (this.req.url, true).query will be returned when you use ctx.query in the KOA instance. See source code, based on getter and setter, in request.js also encapsulated header, URL, origin, path and other methods, are on the original request with getter and setter encapsulated, I will not be implemented here one by one.

Next, we implement the response.js file code module, which, like request, encapsulates the original response based on getter and setter. Ctx. body and ctx.status are used as examples to briefly describe how to implement koA’s response module. We first create response.js file and then enter the following code:

module.exports = {
    get body() {
        return this._body;
    },
    set body(data) {
        this._body = data;
    },
    get status() {
        return this.res.statusCode;
    },
    set status(statusCode) {
        if (typeofstatusCode ! = ='number') {
            throw new Error('something wrong! ');
        }
        this.res.statusCode = statusCode; }};Copy the code

The above code reads and sets the status of koA. When reading, it returns the statusCode property based on the native Response object, and when reading the body, it reads and operates on this._body. We don’t use the native this.res.end for the body, because the body will be read and modified multiple times when we write koA code, so the actual action that returns the browser information is wrapped and manipulated in application.js.

Now that we’ve implemented Request.js and Response.js, we’ve got the Request and response objects and their wrapped methods, and we’re going to implement context.js, Context is used to mount request and Response objects onto CTX so that koA instances and code can easily use methods in Request and Response objects. Now we create the context.js file and enter the following code:

let proto = {};

function delegateSet(property, name) {
    proto.__defineSetter__(name, function (val) {
        this[property][name] = val;
    });
}

function delegateGet(property, name) {
    proto.__defineGetter__(name, function () {
        return this[property][name];
    });
}

let requestSet = [];
let requestGet = ['query'];

let responseSet = ['body'.'status'];
let responseGet = responseSet;

requestSet.forEach(ele= > {
    delegateSet('request', ele);
});

requestGet.forEach(ele= > {
    delegateGet('request', ele);
});

responseSet.forEach(ele= > {
    delegateSet('response', ele);
});

responseGet.forEach(ele= > {
    delegateGet('response', ele);
});

module.exports = proto;
Copy the code

The context.js file is used to mount and proxy common request and response methods. The context.query file is used to directly proxy context.request.query. Context. body and context.status proxy context.response.body and context.response.status. Context. request and context.response will be mounted in application.js

We could have used simple setters and getters to set each method, but because the context object defines methods that are relatively simple and canonical, as you can see in the KOA source code, The KOA source code uses __defineSetter__ and __defineSetter__ instead of setters/getters for each property read setting. This makes it easier to expand and simplify writing. When we need to delegate more RES and REq methods, You can add the corresponding method and property names to the array objects in context.js.

So far, we have three module objects: Request, Response, and context. The next step is to mount all the request and response methods into the context, and let the context realize its function as a link between the preceding and the following. Modify the application.js file and add the following code:

let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

createContext(req, res) {       
   let ctx = Object.create(this.context);
   ctx.request = Object.create(this.request);
   ctx.response = Object.create(this.response);
   ctx.req = ctx.request.req = req;
   ctx.res = ctx.response.res = res; 
   return ctx;
}
Copy the code

As you can see, we added the createContext method, which is the key. It creates the CTX through Object.create and mounts request and Response to the CTX, and native REq and RES to the CTX child properties. Back to look at the context/request/response. Js files, will be able to know when to use this. Res or enclosing the response such as where it came from, turned out to be mounted in the createContext method to the corresponding instance, After building the runtime context CTX, our app.use callback parameters are based on CTX.

Module three: implementation of middleware mechanism and peeling onion model

So far we have successfully implemented the context object, the request object, and the response object modules. The most important module is the koA middleware module. The KOA middleware mechanism is a peeling onion model. Multiple middleware are put into an array queue through use and then execute from the outer layer. When it meets next, it enters the next middleware in the queue. After all the middleware executes, it returns the frame and executes the unexecuted code in the previous middleware in the queue.

The peeling onion model of KOA is implemented with generator + co.js in KOA1, while KOA2 is implemented with async/await + Promise. Next, we implement the middleware mechanism in KOA2 based on async/await + Promise. First, assume that when koA’s middleware mechanism is in place, it can successfully run the following code:

let Koa = require('.. /src/application');

let app = new Koa();

app.use(async (ctx, next) => {
    console.log(1);
    await next();
    console.log(6);
});

app.use(async (ctx, next) => {
    console.log(2);
    await next();
    console.log(5);
});

app.use(async (ctx, next) => {
    console.log(3);
    ctx.body = "hello world";
    console.log(4);
});

app.listen(3000, () = > {console.log('listenning on 3000');
});
Copy the code

After successful operation, 123456 will be output in the terminal, which can verify that our KOA peeling onion model is correct. Next we’ll start the implementation by modifying the application.js file and adding the following code:

    compose() {
        return async ctx => {
            function createNext(middleware, oldNext) {
                return async() = > {awaitmiddleware(ctx, oldNext); }}let len = this.middlewares.length;
            let next = async() = > {return Promise.resolve();
            };
            for (let i = len - 1; i >= 0; i--) {
                let currentMiddleware = this.middlewares[i];
                next = createNext(currentMiddleware, next);
            }
            await next();
        };
    }

    callback() {
        return (req, res) = > {
            let ctx = this.createContext(req, res);
            let respond = (a)= > this.responseBody(ctx);
            let onerror = (err) = > this.onerror(err, ctx);
            let fn = this.compose();
            return fn(ctx);
        };
    }
Copy the code

Koa uses the use function to push all the middleware into an internal array queue this.middlewares. The onion model can make all the middleware execute in turn. The most critical code for peeling the onion model is the compose function:

compose() {
        return async ctx => {
            function createNext(middleware, oldNext) {
                return async() = > {awaitmiddleware(ctx, oldNext); }}let len = this.middlewares.length;
            let next = async() = > {return Promise.resolve();
            };
            for (let i = len - 1; i >= 0; i--) {
                let currentMiddleware = this.middlewares[i];
                next = createNext(currentMiddleware, next);
            }
            await next();
        };
    }
Copy the code

The createNext function passes next from the previous middleware as a parameter to the next middleware and binds the context CTX to the current middleware. When the middleware completes execution, next() is called to execute the next middleware.

for (let i = len - 1; i >= 0; i--) {
        let currentMiddleware = this.middlewares[i];
        next = createNext(currentMiddleware, next);
 }
Copy the code

The code above is a chain of reverse recursive model implementation, I started the cycle of maximum number, the middleware since last encapsulation, each time it is his own execution function is encapsulated as a middleware next on the next as parameters, such as cycle to the first middleware, only need to perform a next (), Can chain recursively call all middleware, this is koA peeling onion core code mechanism.

The middleware passed in through use is a callback function whose parameters are CTX context and next. Next is actually the transfer baton of control. The function of next is to stop running the current middleware and hand over control to the next middleware. The code before next() of the next middleware is executed, the code run by the next middleware encounters next(), and the code execution is handed over to the next middleware. When the last middleware is executed, the control is reversed and the remaining code of the previous middleware is executed. The whole process is a bit like pseudo recursion. When the middleware is finished, it will return a Promise object, because our compose function returns an async function, and the async function returns a Promise, so we can synchronize all the middleware asynchronously. Response functions and error handlers can be executed through THEN.

When the middleware mechanism code is written, running our above example will output 123456. At this point, our basic FRAMEWORK of KOA is basically done, but a framework can not only implement functions, for the framework and server instance robustness, but also need to add error handling mechanism.

Module 4: Error capture and error handling

In order to achieve a basic framework, error handling, and capture the essential, a robust framework, must guarantee that in the event of a fault, can capture the error and the exception thrown, and feedback, the error feedback on the information sent to the monitoring and control system, at present, we realize the simple framework of koa also failed to achieve this, Next we add error handling and catching mechanisms.

throw new Error('oooops');
Copy the code

If the application. Js callback function returns an error, the error cannot be caught. If the application.

return fn(ctx).then(respond);
Copy the code

As can be seen, FN is the execution function of the middleware, and every middleware code is wrapped with async, and the execution function compose of the middleware also returns an async function. According to the specification of ES7, async returns an object instance of a promise. If we want to catch the errors of promise, we only need to use the catch method of Promise to catch all middleware exceptions. The return code of callback after modification is as follows:

return fn(ctx).then(respond).catch(onerror);
Copy the code

Now we have achieved our middleware error exception handling, but we still lack of capture mechanism of error framework layer, we hope we can have the server instance of error event monitoring mechanism, through on monitoring function can subscribe to and monitored framework level error, it is easy to implement this mechanism, using nodejs native events module can, Events module provides us with event listening on function and event triggering EMIT behavior function, one for emitting event and one for receiving event. We only need to inherit koA’s constructor from events module, and the constructed pseudocode is as follows:

let EventEmitter = require('events');
class Application extends EventEmitter {}
Copy the code

After inheriting the Events module, when creating the KOA instance, add the on listener function as follows:

let app = new Koa();

app.on('error', err => {
    console.log('error happends: ', err.stack);
});
Copy the code

This allows us to implement error catching and listening mechanisms at the framework level. To sum up, error handling and catching are divided into two parts: the fault handling of middleware and the fault handling of framework layer. The fault handling of middleware uses promise catch, and the fault handling of framework layer uses NodeJS native module Events. In this way, we can catch all errors and exceptions on a server instance. At this point, we have fully implemented a lightweight version of the KOA framework.

At the end

So far, we have implemented a lightweight VERSION of the KOA framework, we have implemented the node HTTP server encapsulation, create koA class constructor, construct the request, response, context object, middleware mechanism and the implementation of the onion stripping model, error capture and error handling four big modules. Understand the lightweight version of koA implementation principle, then go to see koA2 source code, you will find everything suddenly revealed, KOA2 source code is nothing more than on the basis of the lightweight version of a lot of tools and functions and details of the processing, limited to the length of the author will no longer be introduced.


IVWEB Technology Weekly shocked online, pay attention to the public number: IVWEB community, weekly timing push quality articles.

  • Collection of weekly articles: weekly
  • Team open source project: Feflow