The original address
On Friday, the students in the group discussed some fun things. Someone mentioned some ideas like “koA in 5 minutes” and “React in 100 lines”. After careful thought, it was not impossible to achieve KOA in 5 minutes, so this blog was created.
To prepare
Just go to the KOA official website and find a demo that represents the core functions of KOA
const Koa = require('koa');
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time'.`${ms}ms`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
Copy the code
The final effect is to implement a 5min-koa module, directly replace the first line of code with const koa = require(‘./5min-koa’); , the program can be executed normally.
The core of Koa
According to the KOA website, the app.listen method is actually shorthand for the following code
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
Copy the code
So we can implement app.listen first
class Koa {
constructor() {}
callback() {
return (req, res) = > {
// TODO
}
}
listen(port) {
http.createServer(this.callback()).listen(port); }}Copy the code
The core of KOA is divided into four parts
- The context context
- Middleware middleware
- Request the request
- Responce response
Context
Let’s implement a very simplified version of context, as follows
class Context {
constructor(app, req, res) {
this.app = app
this.req = req
this.res = res
// In order to minimize the implementation time, we used the native res and req directly and did not implement ctx.request ctx.response on CTX
// ctx.request ctx.response just wraps one layer over the native RES and REq
}
// Implement some of the CTX proxy methods used in demo
get set() { return this.res.setHeader }
get method() { return this.req.method }
get url() { return this.req.url }
}
Copy the code
So you’ve got a basic Context, which is pretty small, but enough. Each time a new request is made, a new CTX object is created.
Middleware
Koa’s middleware is an asynchronous function that takes two parameters, CTX and Next, where CTX is the current request context and next is the next middleware (also asynchronous function). To think of it this way, we need an array to maintain the middleware, and each call to app.use pushes one function into the array. So the use method is implemented as follows
use(middleware) {
this.middlewares.push(middleware)
}
Copy the code
Each time a new request is made, we need to inject the context of that request into each middleware in the array. It is not enough to just flood CTX, but to make each middleware call to the next middleware through the next function. When we call the next function, we usually do not need to pass the parameters, and the called middleware must receive CTX and next parameters.
Callers don’t need to pass arguments, but callers can receive arguments, which immediately reminded me of the bind method. If only CTX and next were pre-bound for each middleware, the problem would be solved. The following code converts the list of Middleware passed in by the user into a list of next functions using the bind method
let bindedMiddleware = []
for (let i = middlewares.length - 1; i >= 0; i--) {
if (middlewares.length == i + 1) {
// Finally, the next method is set to promise.resolve
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve))
} else {
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0))}}Copy the code
We end up with an array of next functions, the bindedMiddleware variable.
Request
The callback function in http.createServer is called each time a request is received, so we write code to handle the request in the TODO location of the callback method above, and put the middleware list above into the next list of functions.
function handleRequest(ctx, middlewares) {
if (middlewares && middlewares.length > 0) {
let bindedMiddleware = []
for (let i = middlewares.length - 1; i >= 0; i--) {
if (middlewares.length == i + 1) {
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve))
} else {
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0))}}return bindedMiddleware[0]()
} else {
return Promise.resolve()
}
}
Copy the code
Responce
Let’s just make it simple and send ctx.body directly to the client.
function handleResponse (ctx) {
return function() {
ctx.res.writeHead(200, { 'Content-Type': 'text/plain'}); ctx.res.end(ctx.body); }}Copy the code
Complete the Koa class implementation
Koa app instance with on, EMIT and other methods, which is the node Events module implementation of good things. Let the Koa class inherit directly from the Events module. We then put the above handleRequest and handleResponse methods into the callback method of the KOA class to obtain the final KOA, a total of 58 lines of code, as shown below
const http = require('http');
const Emitter = require('events');
class Context {
constructor(app, req, res) {
this.app = app;
this.req = req;
this.res = res;
}
get set() { return this.res.setHeader }
get method() { return this.req.method }
get url() { return this.req.url }
}
class Koa extends Emitter{
constructor(options) {
super(a);this.options = options
this.middlewares = [];
}
use(middleware) {
this.middlewares.push(middleware);
}
callback() {
return (req, res) = > {
let ctx = new Context(this, req, res);
handleRequest(ctx, this.middlewares).then(handleResponse(ctx));
}
}
listen(port) {
http.createServer(this.callback()).listen(port); }}function handleRequest(ctx, middlewares) {
if (middlewares && middlewares.length > 0) {
let bindedMiddleware = [];
for (let i = middlewares.length - 1; i >= 0; i--) {
if (middlewares.length == i + 1) {
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve));
} else {
bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])); }}return bindedMiddleware[0] (); }else {
return Promise.resolve(); }}function handleResponse (ctx) {
return function() {
ctx.res.writeHead(200, { 'Content-Type': 'text/plain'}); ctx.res.end(ctx.body); }}module.exports = Koa;
Copy the code
Try running the first Demo, no problem.
conclusion
Simple implementation, rough code is not rough, shows the core of KOA, but with less error handling, and no consideration for performance, there are many, many areas to improve.
I wrote this 5 minutes after koA to see koA source, found that the implementation of the idea is basically like this, I believe that after the baptism of this 5 minutes KOA, you go to see KOA source as a piece of cake.
Done!