Koa is a small but beautiful Node Server framework. Its excellent middleware mechanism provides a powerful horizontal expansion capability for the whole framework.
Koa core source code only four files, [github source address](github.com/koajs/koa)… Koa’s most core HTTP server and middleware mechanism through 50 lines of code.
// application.jsconst http = require('http'); const context = {}; function compose(middlewares) { return function(context, next) { let index = -1; function dispatch(i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')); index = i; let fn = middlewares[i]; if (i === middlewares.length) fn = next if (! fn) return Promise.resolve(); try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } return dispatch(0) }}module.exports = class Application { constructor(options) { this.middleware = []; this.context = Object.create(context); } listen(... args) { const server = http.createServer(this.callback()); return server.listen(... args); } callback () { const fn = compose(this.middleware); return (req, res) => { this.context.req = req; this.context.res = res; return fn(this.context).then(() => { respond(this.context); }).catch((error) => { console.error(error); }); }; } use(fn) { this.middleware.push(fn); return this; }}function respond(ctx) { ctx.res.end(ctx.body); }Copy the code
Use the above 50 lines of source code to start a listening port 3000 HTTP server service;
const Koa = require('./application'); const app = new Koa(); app.listen(3000);Copy the code
Cut out the extra syntactic sugar for context, request, and response, simplify the respond method’s handling of the returned data, and koa-compose’s handling of the middleware. You can see the two core points of Koa:
HTTP Server function
Koa also relies on node’s native HTTP module to implement HTTP server capabilities, about node HTTP module, here will not go into depth, first dig a hole, and then write a separate article, understand the HTTP module and related NET module;
As you can see, the native HTTP module can start an HTTP service listening on port 8000 with just a few lines of code. The first parameter to createServer is a callback function. This callback function takes two parameters: a request object and a response object. The content of the response data can be determined based on the content of the request object;
const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('node server on port 8000'); }); server.listen(8000);Copy the code
In Koa, the REq and RES in the createServer callback function are saved to the CTX object for the entire life of the request. Request. js and Response. js in Koa source code add a lot of convenient methods to obtain data and set data for these two objects, such as the method of obtaining request, the path of request, setting the returned data body, setting the returned status code and other operations. These two parts are omitted in the analysis of this paper.
The middleware mechanism
The middleware mechanism is more characteristic of Koa than HTTP Server, and is also the core of the Koa framework. It is not complicated to add a middleware to koA-generated APP objects. The following code can add a middleware:
app.use((ctx, next) => { console.log('this is a koa middleware'); next(); });Copy the code
Koa appliation.js provides a use method that takes only one parameter, and that parameter must be a function. Use pushes that function into an array of middleware instances that hold the entire MIDDLEWARE.
So how does the middleware perform after storing the middleware in the middleware array? Koa source code relies on a koa-compose middleware, which connects all the middleware in the array together. Each middleware function is executed with two parameters: CTX: Koa context objects, which contain HTTP request native REQ objects, HTTP request native RES objects, and shortcuts for KOA encapsulation; Next: In fact, it is the reference of the next middleware. When the current middleware is executed to next, it will enter the next middleware function, and so on, until the last middleware is executed, and then the function code of the last middleware after next is executed, and then the next middleware is executed. Return the result of this request until the code after the first middleware next is executed.
Below is a schematic diagram of the middleware execution order provided by the KOA authors:
The execution process is abstracted as follows:
Three, middleware rules
The above describes how KOA executes all middleware in sequence. Through the middleware mechanism, the framework consumer can also develop middleware to meet the actual business needs. What requirements need to be met when adding a middleware to the Koa, and what rules have been enforced with the middleware? Answer both questions by asking the following.
Q1. What rules does middleware need to meet?
There is only one condition for middleware in KOA: it must be a function. Both synchronous and asynchronous functions can be used as middleware, but if it is a generator function, there is a warning: KoA 3.x no longer supports generators type middleware; So it’s better to have a function of the following form:
Use (async (context, next) => {console.log('middleware 1: something before next'); await db.query(); await next(); console.log('middleware 1: something after next'); }); Use ((context, next) => {console.log('middleware 2: something before next'); next(); console.log('middleware 2: something after next'); }); // For the last piece of middleware, there is no need to call next, but no error will be reported if the compose function detects that there is no next piece of middleware. App. use((context, next) => {context.body = 'response Body '; console.log("middleware 3: it's all~"); });Copy the code
Q2. What is the order of execution of middleware?
A2: The execution order of middleware depends on the call timing of app.use(), i.e. the order in which middleware is pushed to middleware;
-
Middleware that is first app.use()/push to middleware array, code before next is first executed, and code after next is last executed;
-
The middleware that is finally app.use()/push into the middleware array, the code before next is executed last, but earlier than the code after next, and the code after next is executed earlier than the code after next, but later than the code before next;
Q3. Does the second argument to the middleware function, next, have to be called? Can I call it more than once?
A3: Theoretically, the next parameter is a reference to the next middleware and can not be called, but if the next function is not actively called, the following middleware will not be executed. After the current request completes the execution of the current middleware, the code after the previous middleware next will be executed up and then returned;
The next function can only be called once in a middleware. If it is called more than once, the app will throw an Error: new Error(‘next() called multiple times’);
### Q4. Middleware has two parameters: context and next. Can I add another parameter to the middleware function?
A4: The middleware only has these two parameters, and additional parameters cannot be added; If the data processed by the previous middleware needs to be used in the later middleware, it can be attached to the context object, which is used throughout the lifetime of the request.
4. Summary of middleware practice
1. Even if a middleware contains only synchronous logic, it is still wrapped as a promise object by koa-compose. In practice, it is easy to forget whether the next middleware will execute synchronously or asynchronously, so it is better to make all middleware asynchronous functions. Add the await keyword as follows:
app.use(async (context, next) => { console.log(111); await next(); console.log(222); });
Copy the code
2. Next cannot be called more than once; If you do not need to call the next middleware, you can not call next. For example, if the user fails to authenticate or does not log in and directly returns the failure, you can judge in one of the middleware and directly return instead of calling the next middleware of next.