The previous article wrote how to read the Koa source code, a rough look at the Koa source code, but as a did not draw a specific conclusion, the operating principle of the middleware is not clear, here we again carefully through the Koa source code.
Follow the example
So let’s go through an example
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
Copy the code
Let’s start with a Web service and a Hello World as a reencapsulation of the HTTP module, but let’s take our time to discover how it’s wrapped (I’ll delete all the irrelevant code).
The first is listen:
listen(... args) {const server = http.createServer(this.callback());
returnserver.listen(... args); }Copy the code
The HTTP module is known as http.createserver (fn).listen(port), where fn has req, res. From the encapsulation above we can be sure that this.callback must take the request and respond. So let’s look at this.callback.
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) = > {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
Copy the code
The callback returns a callback with req, RES, and so on. If you want to see what happens to handleRequest, the CTX guy shows up. When we use KOA, all the requests and responses are attached to the CTX. It looks like CTX was created using createContext, so go ahead and look at createContext:
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);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || ' ';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
Copy the code
CreateContext is a simple method of attaching useful and useless variables to the context. The code is also simple, but since it involves request and response we need to briefly look at request.js and response.js:
module.exports = {
get header() {
return this.req.headers;
},
/ /.. more items
}
Copy the code
Callback callback callback callback CTX is created and returns this.handleReques.
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err= > ctx.onerror(err);
const handleResponse = (a)= > respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Copy the code
This is a little bit more complicated, because fnMiddleware is the middleware we take out, and then we pass CTX to the middleware for execution, which is a bit like what we would normally use. Here comes the focus: middleware
The middleware
Before exploring how middleware works, let’s take a look at how it works. Here’s a simple example:
const Koa = require('koa')
const app = new Koa()
app.use(async function m1 (ctx, nex) {
console.log('m1')
await next()
console.log('m2 end')
})
app.use(async function m2 (ctx, nex) {
console.log('m2')
await next()
console.log('m2 end')
})
app.use(async function m3 (ctx, nex) {
console.log('m3')
ctx.body = 'Hello World'
})
Copy the code
The result is clear, but let’s visualize it:
M1: output M1 await1: m1 You pause for a moment and let M2 go m1:... M2: output m2 await2: m2 You also stop and let M3 go first M2:... (compromise) m3: output m3, the attention returned to a distance above the m2: output m1 m2 end note I to return to the m1: output end respond m1: CTX. Hello world is the body You tease the user returns to itCopy the code
See, ctx.body does not represent an immediate response, just a variable we will use later, which means that our CTX will go through all the middleware before responding. Not to say await magical pause effect, we need to be able to use it. So how does this middleware work? Look at compose.js:
function compose (middleware) {
/** * @param {Object} context * @return {Promise} * @api public */
return function (context, next) {
let index = - 1
return dispatch(0)
function dispatch (i) {
index = i
let fn = middleware[i]
if(! fn)return Promise.resolve()
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)}))}}Copy the code
You can see from my previous article that this is actually a recursion. But unlike the connect recursion, this is a Promise, and we all know await tastes better with Promise. After we call await next, the application must wait for the Promise to complete. Let’s simplify the middleware model:
Promise.resolve(async m1 () {
console.log(m1)
await Promise.resolve(async m2 () {
console.log(m2)
await Promise.resolve(async m3 () {
console.log(m3)
ctx.body = 'xxx'
})
console.log(m2 end)
})
console.log(m1 end)
})
Copy the code
As an application layer, we don’t need to think about how async/await is implemented, we just need to know what effect it implements.
Still got to hand it to TJ. You can talk to each other if you have any questions.