This is the first article in Yamatsuki’s hands-on series on Node. In addition, I have set up a new warehouse on Github with one question per day and one interview question per day. Welcome to exchange.
- Front end test notes
- Front end big factory surface through big complete set
- Computer basic interview questions subtotal
When we are learning more about a framework or library, it is sometimes necessary to explore source code in order to understand its thinking and design, as well as to better use and avoid unintended bugs. For a framework/library like KOA, which is extremely simple and widely used, you should know its source code.
And to see if we know enough about it, we can implement a mini-library with only core functionality. As the saying goes, although the sparrow is small, it has all five organs.
This is just 40 lines of code to implement a small koA with its core functionality.
Source code implementation:Github.com/shfshanyue/…
Github address: github.com/shfshanyue/…
Here is a simplified example with almost all of koA’s core features:
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
console.log('Middleware 1 Start')
await next()
console.log('Middleware 1 End')
})
app.use(async (ctx, next) => {
console.log('Middleware 2 Start')
await next()
console.log('Middleware 2 End')
ctx.body = 'hello, world'
})
app.listen(3000)
// output
// Middleware 1 Start
// Middleware 2 Start
// Middleware 2 End
// Middleware 1 End
Copy the code
In the simplest example, you can see that there are three clear modules, as follows:
- Application: Basic server framework
- Context: Encapsulates the basic data structure of the server framework for HTTP request parsing and response
- Middleware: Middleware and the central mechanism of the Onion model
We started implementing these three modules step by step:
Forget the framework and write a simple server
Let’s start with an HTTP service based on Node’s basic HTTP API and implement the simplest version of KOA:
const http = require('http')
const server = http.createServer((req, res) = > {
res.end('hello, world')
})
server.listen(3000)
Copy the code
Here’s an example of implementing the simplest version of KOA, which I’ll call KOA-Mini
const Koa = require('koa-mini')
const app = new Koa()
app.use(async (ctx, next) => {
console.log('Middleware 1 Start')
await next()
console.log('Middleware 1 End')
})
app.use(async (ctx, next) => {
console.log('Middleware 2 Start')
await next()
console.log('Middleware 2 End')
ctx.body = 'hello, world'
})
app.listen(3000)
Copy the code
Build the Application
Start with the Appliacation framework:
app.listen
: handles requests and responses, and listens on portsapp.use
: Middleware function that processes the request and completes the response
A simple dozen or so lines of code, as shown below:
const http = require('http')
class Application {
constructor () {
this.middleware = null} listen (... args) {const server = http.createServer(this.middleware) server.listen(... args) }// The native http.createserver callback is still called
use (middleware) {
this.middleware = middleware
}
}
Copy the code
The code that calls Application to start the service is as follows:
const app = new Appliacation()
app.use((req, res) = > {
res.end('hello, world')
})
app.listen(3000)
Copy the code
Since the app.use callback is still the native HTTP. crateServer callback, in KOA the callback argument is a Context object.
The next step will be to build the Context object.
Building the Context
In KOA, app.use’s callback argument is a CTX object, not the native REq/RES. So in this step you build a Context object and use ctx.body to build the response:
app.use(ctx => ctx.body = 'hello, world')
: by thehttp.createServer
Further encapsulation in the callback functionContext
implementationContext(req, res)
To:request/response
The data structure constructs the Context object for the subject
The core code is as follows, note the comment part:
const http = require('http')
class Application {
constructor() {} use () {} listen (... args) {const server = http.createServer((req, res) = > {
// Construct the Context object
const ctx = new Context(req, res)
// The app.use function of the koA-compatible Context is handled
this.middleware(ctx)
// ctx.body is the response contentctx.res.end(ctx.body) }) server.listen(... args) } }// Construct a Context class
class Context {
constructor (req, res) {
this.req = req
this.res = res
}
}
Copy the code
At this point, KOA is modified as follows and app.use can work normally:
const app = new Application()
app.use(ctx= > {
ctx.body = 'hello, world'
})
app.listen(7000)
Copy the code
All of the above code is simple, leaving only one of the most important and core features: the Onion model
Onion model and middleware transformation
The above work is only a simple middleware, but in reality there will be many middleware, such as error handling, permission verification, routing, logging, traffic limiting and so on. So we’re going to revamp app. Middlewares
app.middlewares
: Collects an array of middleware callback functions and uses itcompose
together
The compose function is abstracted for all middleware functions, which take the Context object as an argument to receive the request and process the response:
// this.middlewares represents all middleware
// Through the compose abstraction
const fn = compose(this.middlewares)
await fn(ctx)
// Of course, it can also be written in this form, as long as you take the CTX argument
await compose(this.middlewares, ctx)
Copy the code
The complete code is as follows:
const http = require('http')
class Application {
constructor () {
this.middlewares = [] } listen (... args) {const server = http.createServer(async (req, res) => {
const ctx = new Context(req, res)
// Concatenate middleware callbacks to form the onion model
const fn = compose(this.middlewares)
awaitfn(ctx) ctx.res.end(ctx.body) }) server.listen(... args) } use (middleware) {// Middleware callbacks become arrays
this.middlewares.push(middleware)
}
}
Copy the code
Next, focus on the compose function
Complete the compose function wrapper
Koa’s onion model states that each middleware layer is like each layer of an onion, with each layer going in and out twice as it passes through the center of the onion, and the layer that goes in first comes out last.
middleware
: The first middleware will executenext
: Each middleware will execute the next middleware through next
How do we implement the Onion model for all middleware?
Let’s take a look at each of the Middleware apis below
middleware(ctx, next)
Copy the code
Next in each middleware means to execute the next middleware. We extract the next function, and next has next in the next function. How do we deal with this?
const next = (a)= > nextMiddleware(ctx, next)
middleware(ctx, next(0))
Copy the code
Yes, use a recursion to complete the transformation of the middleware and connect the middleware as follows:
// Dispatch (I) means to execute the i-th middleware
const dispatch = (i) = > {
return middlewares[i](ctx, () => dispatch(i+1))
}
dispatch(0)
Copy the code
Dispatch (I) represents the execution of the i-th middleware, and the next() function will execute the next middleware Dispatch (I +1), so we can easily complete the Onion model using recursion
At this point, add the recursive termination condition: when the last middleware function executes next(), it returns directly
const dispatch = (i) = > {
const middleware = middlewares[i]
if (i === middlewares.length) {
return
}
return middleware(ctx, () => dispatch(i+1))}return dispatch(0)
Copy the code
The final compose function code looks like this:
function compose (middlewares) {
return ctx= > {
const dispatch = (i) = > {
const middleware = middlewares[i]
if (i === middlewares.length) {
return
}
return middleware(ctx, () => dispatch(i+1))}return dispatch(0)}}Copy the code
Now that the onion model, the core functionality of KOA, is complete, write an example to try it out:
const app = new Application()
app.use(async (ctx, next) => {
ctx.body = 'hello, one'
await next()
})
app.use(async (ctx, next) => {
ctx.body = 'hello, two'
await next()
})
app.listen(7000)
Copy the code
There is one small, but not globally significant, disadvantage: exception handling, and the next step will be to complete the exception catching code
Exception handling
What if your backend service crashes because of a single error?
We only need to do one exception on the middleware execution function:
try {
const fn = compose(this.middlewares)
await fn(ctx)
} catch (e) {
console.error(e)
ctx.res.statusCode = 500
ctx.res.write('Internel Server Error')}Copy the code
However, when it is used in daily projects, we must capture it before the exception capture of the framework layer to do some tasks of exception structuring and exception reporting. At this time, an exception processing middleware will be used:
// Error handling middleware
app.use(async (ctx, next) => {
try {
await next();
}
catch (err) {
// 1. Exception structuring
// 2. Exception classification
// 3. Exception level
// 4. Exceptions are reported}})Copy the code
summary
The core code of KOA is very simple, if you are a Node engineer, it is highly recommended to study the source code of KOA in your spare time, and implement a minimal version of KOA yourself.
My source code implementation of the warehouse for: KOA-mini
Pay attention to my
Scan code to add my wechat, remarks into the group, join the advanced front-end advanced group
In addition, welcome to pay attention to the public number [Internet factory recruitment] to receive recruitment information, direct to the factory responsible person. If you are the technical person in charge, you are welcome to send in the promotion information.