Diving in SF platform for a long time, this is also my first article, before has been mainly learning. I hope I can do some output by writing technical articles and answering questions in the future
Recently, I was asked twice about koa’s onion ring model (because I used koa2 to write several projects in the school before), but I didn’t dig into the principle of onion rings. I didn’t feel very satisfied with the answer. So this time, I have a look at the source code of KOA.
directory structure
├── lib
│ ├── application.js
│ ├── context.js
│ ├── request.js
│ └── response.js
└── package.json
At present, we downloadnode_modules/koa
This is the structure of the source file in the package. The core of KOA processing requests is the above four files, among which
-
application.js
It’s the entrance to the entire koa2. The most importantmiddleware Logic is also processed here. This is what I learned this time. -
context.js
Responsible for handling application context -
request.js
Processing HTTP requests -
response.js
Processing HTTP responses
Take a lookKOA
What methods are encapsulated in the class
Constructor
constructor(options) {
super();
options = options || {};
this.proxy = options.proxy || false;
this.subdomainOffset = options.subdomainOffset || 2;
this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
this.maxIpsCount = options.maxIpsCount || 0;
this.env = options.env Wei process.env.NODE_ Env |'development '; // environment variable
if (options.keys) this.keys = options.keys;
this.middleware =[]; // Middleware queue**
this.context = Object.create (context); // Context
this.request = Object.create (request); // request object format
this.response = Object.create (response); // response object format
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
listen
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
KOA
Is native tocreateServer
With simple encapsulation, the passed in parameters will also be passed directly to the nativeserver.listen
。 It’s just through hereKoa classMediumcallback
Method generates a configuration object and passes it to the server. From this point of view, the actual execution logic of KOA is actually implemented through thecallback
Function.
callback
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
First look at the first sentence
const fn = compose(this.middleware);
this.midlleware
This is obviously the middleware queue in the instance.
as everyone knows,compose
It means composition. A phrase we have learned before isbe composed of
-> By Composition
。 The representation in the code is probably a composite function
g() + h() => g(h())
The compose variable here comes from thekoa-compose
This bag, his role is to put all of themKoa MiddlewareMerge execution. Can be understood as beforemiddleware
Arrays are just a few scattered layers of onion that are passed throughkoa-compose
After processing, it becomes a complete onion (attached at the end of the articlekoa-compose
Principle)
Back to the above method, we get the composition function after middleware combinationfn
After that, declare ahandleRequest
Function, in the function first throughthis.createContext
Initialization message can be understood as generating a complete request message and response message, and the logic of initialization message is written inlib/request.js
andlib/response.js
in Finally, they were passedthis.handleRequest
Method to process the message.
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Here is to send the processed message to the combination function generated beforefn
After completing the whole onion ring processing logic, do some subsequent processing (return client / error handling).
As we all know, each layer of the onion ring model is a promise object, only when the onion ring upstream (according to the official document) entersresolved
After the status, the usage right of the thread is passed downstream. (note that the previous onion ring is not finished at this time) when the promise of the inner layer becomesresolved
After the status, the usage right of JS thread will continue to bubble up to handle the outer onion ringLogic after resolve。
Two parameters received by onion ring function
ctx
、next
The next method in is an asynchronous method, which means to force the current onion ringresolve
The current function will be blocked until all downstream logic is processed.
Seeing here, we can sort out the parts we have seen so far.
- The koa instance collects some required middleware(
use
Methods) - stay
this.callback
Function through thekoa-compose
The middleware is combined to generate an onion ring processorfn
(who to deal with, of course, the message object) - Format method of reference message
- Package the packet format method and onion ring processor into a factory function
handleRequest
。 This factory function is only responsible for receiving messages and returning results.
That’s what it saysuse
The method is also the core of the onion ring model, literallyRegistration Middlewaremethod.
use
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
use
The accepted function must be a function (accept two parameters, one is CTX context, one is next function), if this function is a generator(*generator
)(the middleware in koa1 only receives iterator methods, while in koa2, it uses async wait to implement all of them)koa-convert
Function to do the conversion. (I haven’t seen this one yet
Finally, it is very simple to push this method to themiddleware
In line
See here,application
The content of the file is over. It seems that there are few koa source codes. However, due to the existence of middleware, the scalability becomes very strong, which should be the reason why koa is so popular at present.
PS: koa-compose
koa-compose
The amount of code is very small, less than 50 lines (49 lines). But the design is very subtle.
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
return function (context, next) {
//The last invoked middleware subscript
let index = -1
return dispatch(0)
function dispatch (i) {
//I < = index indicates that a middleware has been repeatedly called and an error is thrown
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
//FN takes the middleware of the corresponding layer. If the next function is passed in, the priority of the next function is higher
if (i === middleware.length) fn = next
//Here FN refers to the middleware function of layer I
//If not, resolve directly
if (!fn) return Promise.resolve()
try {
//The actual execution step is here, the FN method is executed, and the execution function of the next layer middleware is also passed
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
In a nutshell, this syntax means that every middleware executesawait next()
Statement, will call the next layer of middleware. You can also think of the code as
//First half processing logic
await next()
//The latter part deals with logic
/*The equivalent of*/
//First half processing logic
Await new promise
//The latter part deals with logic
And the insertion logic isLogic of next layer MiddlewareThe execution logic of onion rings is stored in the arraykoa.middleware
Only the first step of the onion ring is executedn
The subscript will be passed only when the layer is setn+1
The next layer of processing logic is removed, and a promise is generated and inserted into the function body of the upper onion ring to form an overlapping function scope.
END