Functions overview

  • Application: Create the context, merge the middleware, and start the service
  • Request: An extension to the native HTTP module REq
  • Response: Extension to the native HTTP module RES
  • Context: Merge and broker request and response

Service startup :listen

use


const Koa=require('./application')
const app=new Koa()
app.listen(3000, () = > {console.log('run server__')})Copy the code

implementation

// The HTTP module is directly used internally
const http=require('http')
class Application{
    listen(. args){
       constserver= http.createServer() server.listen(... args) } }Copy the code

Request handling :handleRequest

use


const Koa=require('./application')
const app=new Koa()

app.use((ctx) = >{
    // The following four expressions are the same
    // Why
    console.log(ctx.url)
    console.log(ctx.request.url)
    console.log(ctx.req.url)
    console.log(ctx.request.req.url)
    
})

app.listen(3000, () = > {console.log('run server__')})Copy the code

implementation



class Application{

    // Function registration (middleware is also a function)
    use(fn){
        this.fn=fn
    }

    // Request processing
    handleRequest(req,res){
        // Create context: merge req,res is CTX
        // This method implementation is described below
        const ctx=this.createContext(req,res)
        // CTX is the first argument for external use
        // app.use(ctx=>{})
        this.fn(ctx)
    }


    / /... Start the service
     listen(. args){
        // Bind ensures that this points to the custom Application
       const server= http.createServer(this.handleRequest.bind(this)) server.listen(... args) } }Copy the code

CreateContext :createContext

  1. Think about it. Why did KOA merge REQ and RES into one CTX? Blind guessing is for ease of use. One is better than two.
  2. What’s the difference between REQ, RES and CTX? The req, res some,CTX is, req,res doesn’t,CTX and.
  3. You ask me what is the West factory? One word: east factory tube of I want tube, east factory tube of I want tube more!

implementation



/** * create application.js request.js response.js context.js */


//request.js

const request={}
module.exports=request

//response.js

const response={}
module.exports=response

//context.js

const context={}
module.exports=context



//application.js
class Application{


    constructor(){
        // These three are imported from outside
        this.context=context;
        this.request=request;
        this.response=response;
    }

 // createContext will eventually return a merged context;
    createContext(req,res){

    Object. Create is used to extend without interfering with the original module, and is also a layer of inheritance
 
    const context=Object.create(this.context)
    const request=Object.create(this.request)
    const response=Object.create(this.response)

    // Context association and merge
    // context.req=req;
    // context.request.req=req
    // context.res=res;
    // context.response.res=res

    // The above code can be shortened to the following form
     context.req=context.request.req=req    
     context.res=context.response.res=res

    // There is one thing you should know:
    //ctx.req.url and ctx.request.req.url are the same because ctx.req and ctx.request
    // Ctx.res and ctx.response.res also point to the same
	// Another thing to think about in the meantime: what about ctx.url and ctx.request.url?

    / / return the context
    return context


    }
}

Copy the code

Request enhancement: Request

Request is koA’s enhancement of the native HTTP module REq, which is why the source code does not use getters and setters directly as context does.

Url = ctx.request.req.url = ctx.request.req.url = ctx.request.url = ctx.request.url = ctx.request.url = ctx.request.url = ctx.request.url = ctx.request.url


const request={

    get url() {

        // This refers to request
        // Request in createContext happens to have a REQ attached to it
        // Ctx.request. Req is essentially the same pointer as ctx.req
        // Ctx.request.url is the same as ctx.req.url, ctx.request.req.url
        return this.req.url; }},module.exports=request

Copy the code

Context: the agent

Ok, now the only thing missing is ctx.url, which is essentially ctx.request.url

Why do you say that? Let’s take a look inside the context implementation

const context = {}

function delegateGet(prop, key) {
    The __defineGetter__ method executes a callback when a key of an object is accessed
    context.__defineGetter__(key, function () {
        return this[prop][key]
    })
}

// ctx.url=>ctx.request.url
delegateGet('request'.'url')

module.exports = context

Copy the code

For __defineGetter__, see MDN

Developer.mozilla.org/zh-CN/docs/… __

If you go to the KOA source code, it uses a third party package :delegates, which is actually implemented __defineGetter__

Response enhancement :response

use


const Koa=require('./application')
const app=new Koa()

app.use((ctx) = >{
   ctx.body='hello lengyuexin';
   console.log(ctx.body)
})

app.listen(3000, () = > {console.log('run server__')})Copy the code

implementation


// CTX is a proxy, CTX is a proxy, CTX is a proxy
// So CTX must be given to Response
// If CTX does a request, it must be sent to Request
/ / it is: CTX. Body = > CTX.. The response of the body


const context = {}


function delegateGet(prop, key) {
    context.__defineGetter__(key, function () {
        return this[prop][key]
    })
}

function delegateSet(prop, key) {
    context.__defineSetter__(key, function (newValue) {
        this[prop][key] = newValue
    })
}

delegateGet('response'.'body')/ / access CTX. Body
delegateSet('response'.'body')/ / set the CTX. Body = 'XXX'


module.exports = context

Copy the code

So response, like request, is a getter setter


const response = {

    _body: ' '.get body() {
        return this._body
    },

    set body(newBody) {
        this._body = newBody
    }

}

module.exports = response

Copy the code

Merge middleware :compose

  • In KOA, the use function can be called multiple times, and only the first one is executed by default.
  • To execute the rest, call the next function, the second argument to the use function.
  • Also, if asynchronous operations are involved, async,await can be used.

Onion diagram


use


// Print 123 in sequence
const app = new (require('koa'))

app.use((ctx, next) = > {
    console.log(1)
    next()
})
app.use((ctx, next) = > {
    console.log(2)
    next()
})
app.use((ctx, next) = > {
    console.log(3)})// Error listening

app.on('error'.(err) = >{
    console.error(err)
})

app.listen(3000)



Copy the code

implementation


// To facilitate asynchronous processing and error catching
The compose method returns a promise
// Middleware also needs to store it in an array


const EventEmitter = require('events')
const Stream = require('stream')
class Application extends EventEmitter {


// Middlewares are added to application constructor to initialize an empty array
  constructor() {
        super(a)this.context = context
        this.request = request
        this.response = response
        this.middlewares = []// Multiple use calls, save
    }

// The use method just pushes the function directly to middlewares

    use(fn){
        this.middlewares.push(fn)
    }



    
     handleRequest(req, res) {

        const ctx = this.createContext(req, res)

      // Compose the middleware and execute the returned promise
      // Get the _body response

        this.compose(ctx).then(() = > {
             // Only buffers and strings can be processed by default
            let _body = ctx.body;

            if (_body === ' ') {
                // Give the default value if the body is not set
                // Set the status code to 404
                res.statusCode = 404
                _body = 'not found'
                return res.end(_body)
            } else if (_body instanceof Stream) {
                // KOA also supports returning a file stream directly, via PIPE
                // Handle the convection
                return _body.pipe(res)
            } else if (typeof_body ! = ='null' && typeof _body === 'object') {
                // Processing of objects
                return res.end(JSON.stringify(_body))
            } else if (_body == null) {
                //null and undefined are output as strings
                // toString cannot be called directly
                return res.end(_body + ' ')}else {
                // Other types of direct toString
                return res.end(_body.toString())
            }
        }).catch(err= > {
            // Add error listener for app
            // Application inherits the Events module
            this.emit('error', err)
         })
    }


    / / compose
    // Core logic three things: 1. Transboundary processing 2. Implementation of the first middleware 3. Execute the following middleware in turn

   compose(ctx) {

       // Dispatch uses the arrow function here
       // The inner this refers to the custom Application
        const dispatch = (index) = > {
            // Handle handleRequest and then

            if (index === this.middlewares.length) return Promise.resolve()


            // Getting the current middleware is initially the first
            const middleware = this.middlewares[index]

            Middleware execution requires two parameters
            const exec = middleware(ctx, () = > dispatch(++index))
            // It is possible that this method does not have an async wrapper
            // Return a promise
            // The handleRequest then function will not report an error

            return Promise.resolve(exec)

        }

        return dispatch(0)}}Copy the code

At the end of the text read

The interview was asked what you understand about KOA.

My first thought was to say something about the Onion model and handwriting compose.

When I calm down and realize, wow, I’ve forgotten how to write…


Love is fickle,

But move already hurt.

Thank you so much for reading my article,

I’m Cold Moon Heart. See you next time.