Introduction
Recently, in writing koa2 related examples, by the way, look at the source code of koa2, below are some personal understandings.
koa1 core is based on generator, but relies heavily on co packaging. koa2 does not need at all. It can run directly under node v7 based on async (which is essentially the grammatical sugar call wrapping of generator).
This article does not elaborate on the grammar of async and generator. Let's first create a koa instance and then analyze it step by step based on the entry.
//koa code const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
It seems that the above code is somewhat mysterious, but its essence is the encapsulated call of the following http module.
// native code let http=require('http'); let server=http.createServer(function (req,res) { res.writeHead(200,{'Content-type':'text/plain'}); res.write('hello world gcy'); res.end(); }); //start service listen server.listen(8000,function () { console.log('listening on port 8000'); });
The following is a further analysis based on the koa constructor entry.
constructor() { super(); this.proxy = false; //Create an empty array to store middleware, the truth of the onion process, which will be analyzed below. this.middleware = []; //Decides the number of subdomains to be ignored by default of 2 this.subdomainOffset = 2; //Handling environmental variables this.env = process.env.NODE_ENV || 'development'; //Mount context,request,response on the instance this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
The above is the constructor entry, and the startup entry is as follows
//Borrow native http.createServer and add app.callback. listen() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
Explain how to build a complete web server through the above two steps. For the request parsing process received by listening, it is done by calling back function and calling a series of middleware.
The following analysis of the middleware execution process, I think the main connotation of koa is here, so do a focus to discuss.
First, the classical middleware onion graph is introduced to understand.
Look at the following code in conjunction with this picture
//// Core code application 126 lines const fn = compose(this.middleware); return function (context, next) { 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 //Return the promise instance in the resolve state immediately so that subsequent logic can continue to execute if (!fn) return Promise.resolve() try { // await next(); / / / When this sentence is executed in fn, dispatch(i+1) is executed, resulting in onion execution. // The whole process is similar to the recursive call in the process of stack execution and release, although there are differences, we can use analogy to think about its execution sequence process. return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } // Core code application 136 lines return fn(ctx) [is an immediate promise].then(handleResponse).catch(onerror); } }
If there are doubts in the process of the code mentioned above in combination with the annotations, we can refer to the following for further consideration, and ignore the other way around.
const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(async function (ctx, next) { console.log('>> two'); ctx.body = 'two'; await next(); console.log('<< two'); }); app.use(async function (ctx, next) { console.log('>> three'); await next(); console.log('<< three'); }); //If you put it in the head, it's not easy to understand the onion execution process, because the next function is not called. app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
It shows that koa is based on mid-price structure and its core is concise. Besides, there are other relatively important methods.
application.js
use(fn) / / assembling the parameters of use
createContext(req, res) creates the initialized context and mounts req and Res on the context
onerror(err) error handling, when set this.slient to true, does not output information and executes when emit triggers
Respons (ctx) HTTP response simple encapsulation, information return
context.js
Delegate (proto,'request')//Request related method delegation, so that context acts as the call entry
Exception handling logic in onerror(err)//middleware execution
In addition, there are request and response parametric parsing files, because the logic is simple and not narrative.
Although there are not many core files, there are also many require packages. Here are some of the heavier ones for example.
require
Event application inherits from Emitter, which enables event subscription and publishing.
The encapsulation of koa-compose middleware, one of the core logic, has been analyzed above.
debug error message format encapsulation processing
statuses http status code sum and corresponding information processing
koa-convert converts generator to promise
...... and so on
summary
The reason for writing this article is that in the process of writing case, the middleware selection entanglement under the same solution, especially in the process of selecting render template, in order to find the essential differences between them, to explore this.
Source code analysis is based on koa(version 2.2.0). After reading through the source code, you can develop or use other people's middleware more clearly.
If you have different understandings, please leave a message.