手撕koa,从零掌握koa的实现原理(7)

377 阅读3分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

手撕koa,从零掌握koa的实现原理(7)

往期更文

手撕koa,从零掌握koa的实现原理(1)

手撕koa,从零掌握koa的实现原理(2)

手撕koa,从零掌握koa的实现原理(3)

手撕koa,从零掌握koa的实现原理(4)

手撕koa,从零掌握koa的实现原理(5)

手撕koa,从零掌握koa的实现原理(6)

前言

  • 回顾昨天的内容,我们了解了koa中间件的执行顺序,到此为止,我们的koa实现到了这一步。
    • koa/koa/lib/application.js
      const http = require('http')
      const EventEmitter = require('events')
      const context = require('./context');
      const request = require('./request');
      const response = require('./response');
      class Application extends EventEmitter {
        constructor(){
          super()
          this.context = Object.create(context)
          this.request = Object.create(request)
          this.response = Object.create(response)
        }
        createContext(req, res){
          // req, res 是原生的  request 和 response 是我们自己扩展
          const ctx = Object.create(this.context)
          const request = Object.create(this.request)
          const response = Object.create(this.response)
          ctx.request = request 
          ctx.request.req = ctx.req = req
      
          ctx.response = response; 
          ctx.response.res = ctx.res = res;
      
          return ctx
        }
        handleRequest = (req, res) => {
          
          const ctx = this.createContext(req, res)
          
        }
        listen(){
          const server = http.createServer(this.handleRequest)
          server.listen(...arguments)
        }
      }
      
      module.exports = Application
      

app.use()方法实现

  • koa内部会维护一个中间件的栈,app.use()的作用就是把中间件添加到这个栈中。因此我们需要在consturctor中声明一个数组,用来存放中间件函数。
    • koa/koa/lib/application.js
      const http = require('http')
      const EventEmitter = require('events')
      const context = require('./context');
      const request = require('./request');
      const response = require('./response');
      class Application extends EventEmitter {
        constructor(){
          super()
          this.context = Object.create(context)
          this.request = Object.create(request)
          this.response = Object.create(response)
          this.middlewares = []
        }
        createContext(req, res){
          // req, res 是原生的  request 和 response 是我们自己扩展
          const ctx = Object.create(this.context)
          const request = Object.create(this.request)
          const response = Object.create(this.response)
          ctx.request = request 
          ctx.request.req = ctx.req = req
      
          ctx.response = response; 
          ctx.response.res = ctx.res = res;
      
          return ctx
        }
        use(middleware){
          this.middlewares.push(middleware)
        }
        handleRequest = (req, res) => {
          
          const ctx = this.createContext(req, res)
          
        }
        listen(){
          const server = http.createServer(this.handleRequest)
          server.listen(...arguments)
        }
      }
      
      module.exports = Application
      

实现中间件调用compose函数

  • handleRequest补充实现
  • 每当我们请求来临的时候,在我们的handleRequest中。 第一步是创建我们的上下文。 第二步是按照顺序执行我们的中间件。 我们默认给状态码404,如果ctx.body没有值就返回Not Found
    • koa/koa/lib/application.js
      handleRequest = (req, res) => {
        const ctx = this.createContext(req, res)
        res.statusCode = 404
        this.compose(ctx).then(() => {
          let body = ctx.body
          if(body){
            res.end(body)
          }else{
            res.end('Not Found')
          }
        }).catch(e => {
          this.emit('error', e)
        })
      }
      
  • compose实现
    1. index记录上次调用的中间件的索引,初始状态默认是-1
    2. i代表本次调用的中间件的索引, 如果i <= index代表本次中间件已经被调用过了,抛出异常,只能被调用一次。
    3. index = i更改index
    4. if(this.middlewares.length === i) return Promise.resolve()如果本次是最后调用的中间件那么next函数会返回一个空的成功的Promise,这也是最后一个中间件的next不用调用的原因。
    5. let middleware = this.middlewares[i]取出本次的middleware
    6. Promise.resolve(middleware(ctx, () => dispatch(i+1))执行本次的中间件函数,可以看出每次中间件函数的返回值都被Promise.resolve做了处理,并且把下次的中间件作为next函数传进去,如果在调用中间出错那么本次中间件就会返回失败的Promise,后续的中间件函数也会被中断。
    7. 默认调用第0个中间件,并且返回。
    • koa/koa/lib/application.js
      compose(ctx){
        let index = -1
        const dispatch = (i) => {
          if(i <= index){
            return Promise.reject(new Error('next() called multiple times'))
          }
          index = i
          if(this.middlewares.length === i) return Promise.resolve()
          let middleware = this.middlewares[i]
          try{
            return Promise.resolve(middleware(ctx, () => dispatch(i+1))) //这里的() => dispatch(i+1) 代表我们的next函数也就是下一次中间件的执行
          }catch(e){
            return Promise.reject(e)
          }
        }
        return dispatch(0)
      }
      

完整的Koa应用

  • koa/koa/lib/application.js
      const http = require('http')
      const EventEmitter = require('events')
      const context = require('./context');
      const request = require('./request');
      const response = require('./response');
      class Application extends EventEmitter {
        constructor(){
          super()
          this.context = Object.create(context)
          this.request = Object.create(request)
          this.response = Object.create(response)
          this.middlewares = []
        }
        createContext(req, res){
          // req, res 是原生的  request 和 response 是我们自己扩展
          const ctx = Object.create(this.context)
          const request = Object.create(this.request)
          const response = Object.create(this.response)
          ctx.request = request 
          ctx.request.req = ctx.req = req
    
          ctx.response = response; 
          ctx.response.res = ctx.res = res;
    
          return ctx
        }
        use(middleware){
          this.middlewares.push(middleware)
        }
        compose(ctx){
          let index = -1
          const dispatch = (i) => {
            if(i <= index){
              return Promise.reject(new Error('next() called multiple times'))
            }
            index = i
            if(this.middlewares.length === i) return Promise.resolve()
            let middleware = this.middlewares[i]
            try{
              return Promise.resolve(middleware(ctx, () => dispatch(i+1))) 
              //这里的() => dispatch(i+1) 代表我们的next函数也就是下一次中间件的执行
            }catch(e){
              return Promise.reject(e)
            }
          }
        }
        handleRequest = (req, res) => {
          const ctx = this.createContext(req, res)
          res.statusCode = 404
          this.compose(ctx).then(() => {
            let body = ctx.body
            if(body){
              res.end(body)
            }else{
              res.end('Not Found')
            }
          }).catch(e => {
            this.emit('error', e)
          })
        }
        listen(){
          const server = http.createServer(this.handleRequest)
          server.listen(...arguments)
        }
      }
    
      module.exports = Application
    

最后

我们的koa已经实现了,拿我们的koa去跑一下代码,测试代码完美通过。

2022-01-23-1.png

再换个测试用例,我们浏览器访问也是正确的。

  • koa/koa/src/server.js
 const Koa = require('../koa')
 const app = new Koa()

 app.use((ctx) => {
   if (ctx.path === '/get') {
     console.log(ctx.query);
     ctx.body = "Hello World get"
   }else if(ctx.path === '/post'){
     ctx.body = "Hello World post"
   }
   
 })
 app.listen(3000, () => {
   console.log('server start 3000')
 })

01-20-1.png

01-20-2.png

下期预告

下篇内容介绍一下koa的中间件用法,手写几个中间件的实现。