This article is the continuation of the previous KOA topic, plan to write two articles, this from zero to achieve a simple version of the KOA framework (which may involve a little node knowledge, not too much, if you do not understand you can baidu view, you can also see the official website document to understand the use). Including context CTX combination REQ, RES implementation, middleware mechanism implementation. In the second part, we write down the simple realization of BodyParser and Router middleware and understand its principle.

Koa use

test.js

const Koa = require('koa') const app = new Koa() let port = 3000 app.use(async (ctx, Next) => {ctx.body = '1111' // If next is not called, 1111 await next()}) app.use(async (CTX, next) => {ctx.body = '2222'}) // App.on ('error', () = > {app. Listen (+ + port)}) app. Listen (port, () = > {the console. The log (` ${port} is listening port `)})Copy the code

From the above code we can see:

  • KoaIt’s a class. There areuselistenMethods (onListeners are obviously publish-subscribe.)
  • Because we started a service, we need to usehttpThe module
  • The page says’ Uncertainty 2222, ‘soctx.bodyIs assigned torEs. In the end () `
  • useBoth of the functions are middleware callsnextMethod to execute the next

Initialize the project structure

  • package.json

Configure the main field, the entry to the package

{... "main": "./lib/application", ... }Copy the code
  • The directory structure remains the same

writeapplicationfile

  • Initialization structure
Const EventEmitter = require('events') const HTTP = require(' HTTP ') class Koa extends EventEmitter{ constructor() { super() } use() { } listen() { } } module.exports = KoaCopy the code
  • Services are created in Listen
// es7 guarantees this. You can also use bind handleRequest = (req, res) => {} args) { const server = http.createServer(this.handleRequest) server.listen(... args) }Copy the code
  • Initialize thecontext request responseThree files,applicationThe introduction of

Request and Response files are extensions of native REQ and RES

const context = { } module.exports = context ------------------------------ const request = { } module.exports = request  --------------------------------- const response = { } module.exports = responseCopy the code
  • Create a new context, request, response
Constructor () {super() /** * create multiple Koa instances * let app1 = new Koa() * let app2 = new Koa() * Since we created the context and request response as reference types, If you change one, it will affect the other instances. * So when you create, you create a new one, __proto__ = context */ this.context = object.create (context) this.request = Object.create(request) this.response = Object.create(response) } use(middleware) { } // createContext(req, Res) {} handleRequest = (req, res) => {const CTX = this.createcontext (req, res)}Copy the code

Avoid creating context, request response conflicts, and also need to create new objects while overwriting CTX

// Overwrite CTX createContext(req, res) { const ctx = Object.create(this.context) const request = Object.create(this.request) const response = Object. Create (this.response) // res.xx req.xx are both native ctx.request = request ctx.request.req = req = ctx.req = req ctx.response = response ctx.response.res = ctx.res = res return ctx }Copy the code
  • Middleware operation

We know that middleware is injected in app.use form and can write multiple, so it is stored in array form and executed in sequence. Because KOA middleware is an Onion model, we only return the execution of the first middleware, and the rest is executed during the execution of the first middleware. We concatenate all middleware into promises and return the execution; At the same time, we should also pay attention to async await when using middleware to ensure the desired results

constructor() { ... Middlewares = []} // Middleware store use(middleware) {this.middlewares. Push (middleware)} // Compose () {let index = -1 const dispatch = I => {// The incoming I value is unchanged. If we call await next() multiple times in one middleware, then the incoming I must be less than index after the internal middleware executes the next next from the current middleware. If (I <= index) return promise.reject (' do not use multiple next') index = I // Execute until the last item exit if (I == this.middlewares.length) Return promise.resolve () let fn = this.middlewares[I] return promise.resolve (fn(CTX, () => dispatch(i + 1))) } return dispatch(0) }Copy the code

After processing the middleware (execution is complete), return to the page ctx.body

HandleRequest = (req, res) => {const CTX = this.createcontext (req, res) // default 404, Ctx. body is set in middleware. This.status = 404 this.pose (CTX).then(() => {// koA can read Stream directly if (ctx.body instanceof Stream) {ctx.body.pipe(res)  } else if(typeof ctx.body == 'object' && ctx.body) { res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(JSON.parse(ctx.body)) } else if (ctx.body) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end(ctx.body) } else { res.end('Not Found') } }).catch(err => { this.emit('error', err) }) }Copy the code

Agent CTX, Request, and Response

Add a test middleware

app.use(async (ctx, Next) => {console.log(ctx.request.url) console.log(ctx.request.req.url) console.log(ctx.request.req.url) })Copy the code

Print the result

Because the data we get are all in the native REq and RES, we want to get ctx.url, but we actually get req.rul, here we used an agent like defineProperty, those who are familiar with VUE should be familiar with it.

  • request.js
Const request = {get url() {ctx.request.req.url return this.req.url}} const request = {get url() {ctx.request.req.url return this.req.url}} Const url = require('url') const request = {get url() {return this.req.url}, get query() { return url.parse(this.url, true) }, get path() { return url.parse(this.url).pathname } }Copy the code
  • response.js

We put the body modification in the response file because ctx.body is worth res.end. The default status code is 404. Set the status code to 200 in the body modification function

Const response = {// We can set multiple ctx.body = 'XXX', which will take the last one. undefined, get body() { return this._body }, set body(val) { this.res.statusCode = 200 this._body = val } } module.exports = responseCopy the code
  • context.js
Const context = {} // function operations are used here. The three proxy methods are the same, but __defineGetter__ is not recommended. Function defineGetter(target, key) {context.__definegetter__ (key, function() { return this[target][key] }) } function defineSetter(target, key) { context.__defineSetter__(key, function(val) { this[target][key] = val }) } defineGetter('request', 'url') defineGetter('request', 'query') defineGetter('request', 'path') defineGetter('request', 'body') defineSetter('response', 'body') module.exports = contextCopy the code

At this point we can see that the test.js print is normal. If you are interested, you can post to NPM as a learning share.

Koa’s source code is relatively small and simple; Compared to Express, which has a lot of content, things like routing are encapsulated internally, whereas KOA just provides a shelf and auxiliary operations are in the form of middleware. In the next article we will introduce several middleware implementations.