KOA take you ten minutes to read the source code

Some time ago saw koa source, thanks to the good koa abstract, not only provides a simple api, but also makes a considerable source of simplicity and elegance. Today, take a moment to draw a block diagram of a koa source code to analyze its source, while summary, I hope can help to relevant classmates.

Note: Source is based on version 2.x, 1.x fully consistent with the structure of the source code a bit more succinct and intuitive.

Basics

Used node of any of the following people are not familiar with the code, as follows:

const http = require('http');

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});

server.listen(3000);

The above code is very simple, createServer http method creates a http.Server instance, http.Sever inherited EventEmitter class. We passed a function, in fact, added to the request event server, the equivalent of a quick way. So you can not pass any parameters, and then manually request to monitor events. Finally, the server code to bind to port 3000, starts listening to all requests from port 3000.

Then we look at the monitor function request events, which function accepts two parameters, namely req and res. Wherein req is a readable stream, res is a writable stream. We get that all the information requested by http request req, res while writing data to respond to the request.

These are all the basics you need to know, is not very simple?

koa source Interpretation

Before analyzing the source code, koa first intuitive look at how to create a server:

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

app.use(ctx => {
ctx.body = 'Hello Koa';
});

app.listen(3000);

Now, we made this simple code above to start deep koa source code, source code analysis before I put directly mentioned at the beginning of the schematic, so we can combine schematic analysis.

image
constructor
First we create an instance of Koa app, its constructor is very simple, as follows:

constructor() {
super();
this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}

Binding schematic, this code is very simple, in which context, request and response is simply a literal form three objects created above method for encapsulating a series (in fact, most of the attributes are assigned (the setter) and the value unit (getter )), temporarily do not control them. As shown in a schematic view, they will be the corresponding attribute as a prototype app.

It is worth mentioning that the Application is inherited from EventEmitter, as follows:

class Application extends Emitter { //... }

Middleware
Next Middleware using a registration method use, in fact, a simple push to their mideware this array. as follows:

use (Fn) {
// code omitted parameter calibration properties a little
this.middleware.push (Fn);
return the this;
}

Start the application

The core code listen to this method, but it is also very simple to do events. We already know that is created by passing a function to http.createServer as an example of its formal parameters, in fact, app.listen which to do this one thing, the source code is still very simple:

listen() {
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}

The only concern we have is this this.callback, but also to understand the core of koa applications.

Implementation of the results this.callback () is certainly a function, according to the basis of our knowledge, this function is nothing more than access to information based on req, simultaneously writing data to the res in it.

Then this function specifically how to do it? First, it is based on req and res objects encapsulate ctx middleware we used, then a nested function to transfer ctx combined into the middleware. Middleware combined nested function returns a Promise of example, the combining function performed until End (Resolve), res data is written by the ctx information (e.g. ctx.body) want, during execution error occurred (Reject) it calls the default error handler.

Principle is very simple, look at the code:

callback() {

const fn = compose(this.middleware);

if (!this.listeners('error').length) this.on('error', this.onerror);

return (req, res) => {
res.statusCode = 404;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
fn(ctx).then(() => respond(ctx)).catch(onerror);
};

}

As we analyzed the same, callback will first call to our middleware combined into a function returns a nested function for execution. The req and res createContext ctx intermediate packaging required. onFinished ensure a closed flow, and will complete the execution of a callback function corresponding error. onerror is our error handler, the final analysis. ctx respond according to the data that we, and then writes the data to the centralized res, in response to the http request. The code is simple but a bit long, do not stick out.

CreateContext is now the core of how the package is a ctx directly look at the source code, because too simple:

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);

response.app = = = request.app context.app the this;
context.req response.req = = = request.req The REQ;
context.res response.res = = = request.res RES;
request.ctx = response.ctx = context;
// code omitted irrelevant little
return context;
}

Simply put, it is to create three simple object, and specify their prototypes for the corresponding object of our app. The native then req and the res assigned to the corresponding attribute, completed.

As we diagram of the structure of the entire Koa is complete.

However, ctx is not exposed on many attributes? Where are they? They are on the far right of our schematic, we skip the beginning of three simple object. In the form of prototype chain, we ctx.request can access properties and methods in the vast majority of its corresponding object on top of this simple request. request is how to package it? I just need to simply stick a little source code, we would understand the second.

···
module.exports = {
//...
get method() {
return this.req.method;
},

set method(val) {
this.req.method = val;
}
//...
}
···

So you visit http://ctx.request.xxx attributes are defined resquest evaluators property (setter) on the simple object and the value is (getter) on the right.

Similarly response, we will not repeat them.

It is worth mentioning that the right context object above method provides only minimal, other attributes are simple proxy to their request and the response of these two objects.

Error Handling

Finally, a brief analysis about error handling, we can see the source code from the previous callback, app will register a default error handler.

if (!this.listeners('error').length) this.on('error', this.onerror);

But every http request we actually pay a ctx.onerror error handling:

const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
fn(ctx).then(() => respond(ctx)).catch(onerror);

onFinished ensure a closed flow, and will complete the execution of a callback function corresponding error. ctx.onerror This function is empty or null in the parameter when the direct return, will not do anything.

if (null == err) return;

Otherwise, the app will trigger an error event.

this.app.emit('error', err, this);

Then determine if the request is still not the end of the process, which is registered app onerror event is not the end of the request, the client will try to generate a 500 error end. Way to judge:

if (this.headerSent || !this.writable) {
err.headerSent = true;
return;
}

To sum up, we can handle errors at different levels of abstraction. For example, we can be wrong all the middleware generated captured and processed in the top of the middleware, so the error will not be captured top. We can also cover ctx.onerror way to catch all exceptions, and can not trigger the error event app. Finally, of course, we can also directly monitor the way the app error event to handle errors.

The final words
as the beginning of the article said, koa appropriate abstractions, not only provides a simple and practical api, but also makes a considerable source of simplicity and elegance. While reading the article titled koa source, but not actually on the minutiae of response and request a detailed exposition of the package, if you read the article here, then you should go to the source handy.

Finally, if elaborate unclear, or not just a place to welcome feedback.

Guess you like

Origin blog.51cto.com/14516511/2433318