关于node.js:nodejs篇实现一个koa

50次阅读

共计 5166 个字符,预计需要花费 13 分钟才能阅读完成。

实现 koa 有这几个次要步骤:

  • 封装 httpServer
  • 上下文 context 和 requestresponse 对象
  • 中间件函数 middleware
  • 错误处理

koa 外围源码外面包含这几个文件:

|-- koa
    |-- lib
        |-- application.js
        |-- context.js
        |-- request.js
        |-- response.js

入口文件 application

application.js是外围入口文件,包含导出 Koa 类函数和外围代码的实现:

const http = require("http")

class Koa {constructor() {
    // 上下文 context
    this.ctx = Object.create({});
    // 中间件函数
    this.middleware = null
  }
  use(fn) {
    // 将用户传入的函数绑定到中间件函数中
    this.middleware = fn
  }
  handleRequest(req, res) {
    let ctx = this.ctx;
    // 给上下文增加 request 和 response 对象
    ctx.req = req;
    ctx.res = res;
    // 执行中间件函数
    this.middleware(ctx);
    // 返回后果
    ctx.body ? res.end(ctx.body) : res.end("Not Found")
  }
  listen() {let server = http.createServer(this.handleRequest.bind(this))
    server.listen(...arguments)
  }
}

module.exports = Koa;

下面的代码曾经实现了 httpServer 服务的封装,有最根底的 context 上下文对象,绑定了 requestresponse属性,能够在根目录下创立 index.js 文件测试:

let Koa = require("./koa/lib/application");
let app = new Koa();

app.use((ctx) => (ctx.body = ctx.req.url))

app.listen(3000);

context、request 和 response

源码外面应用 gettersetter属性,封装上下文 contextreqres,也就是requestresponse对象。

request

创立request.js,退出上面代码:

let url = require('url');

module.exports = {get path() {return url.parse(this.req.url,true).pathname
  },
  get query() {return url.parse(this.req.url,true).query
  }
}

response

创立response.js,退出上面代码:

module.exports = {
  _body: "",
  get body() {return this._body},
  set body(value) {
    this.res.statusCode = 200
    this._body = value
  },
}

context

创立context.js,退出上面代码:

let ctx = {}

function defineGetter(property,key){
    // 相当于去 property 上取值
    ctx.__defineGetter__(key,function(){return this[property][key]
    });
}

function defineSetter(property,key){
    // 相当于给 property 赋值
    ctx.__defineSetter__(key, function (value) {this[property][key]=value
    })
}

defineGetter("request","path");
defineGetter("request","query");

defineGetter("response","body");
defineSetter("response", "body")

module.exports = ctx;

源码外面通过 __defineSetter____defineGetter__requestresponse的属性挂载到了上下文context,接下啦批改application.js,引入contextresponserequest

const context = require("./context")
const request = require("./request")
const response = require("./response")
...
    constructor() {
        ...
        // Object.create 避免用户间接批改对象,保障每次 new Koa 都是新的对象
        this.context = Object.create(context)
        this.request = Object.create(request)
        this.response = Object.create(response)
    }
    createContext(req, res) {
        let ctx = this.context
        /*
        * ctx.request.req\ctx.req\ctx.req
        * ctx.response.res\ctx.res\ctx.res
        */
        ctx.request = this.request
        ctx.response = this.response
        ctx.request.req = ctx.req = req
        ctx.response.res = ctx.res = res

        return ctx
    }
    handleRequest(req, res) {
        // 获取新的上下文
        let ctx = this.createContext(req, res)
        // 执行中间件函数
        this.middleware(ctx);
        ...
    }

中间件 middleware

下面的 middleware 函数只绑定了一个办法,咱们晓得 koa 外面是能够绑定多个中间件函数,并且中间件函数蕴含上下文 context 和是否继续执行的 next 函数,因为 koa2 中应用的是 async/await 的形式,所以中间件函数返回的都会是一个Promise

middleware 改成数组middlewares

  constructor() {
    ...
    this.middlewares = []
    ...
  }
  use(fn){
    // 先将函数保留到中间件数组中
    this.middlewares.push(fn)
  }
  ...

接下来创立 compose 办法解决中间件函数:

  compose(ctx, middlewares) {
    // 以后函数执行指针
    let exectIndex = -1
    let dispatch = async function (index) {
    // 避免同一个中间件函数呈现两个 dispatch 函数抛出异样
      if (exectIndex >= index) return Promise.reject("mulit called next();")
      exectIndex = index
      // 全副执行实现返回 Promise
      if (index === middlewares.length) return Promise.resolve()
      // 取出中间件函数解决
      let middleware = middlewares[index]
      // next 函数持续取出下一个中间件函数执行
      let next = () => dispatch(++index);
      // 返回执行的中间件函数
      return middleware(ctx, next)
    }
    return dispatch(0)
  }

批改 handleRequest 函数如下:

  handleRequest(req, res) {let ctx = this.createContext(req, res)
    res.statusCode = 404
    let p = this.compose(ctx, this.middlewares)
    p.then(() => {ctx.body ? res.end(ctx.body) : res.end("Not Found")
    })
  }

错误处理

koa 外面能够通过订阅 error 事件捕捉中间件函数运行过程中呈现的异样:

app.on("error",(error,ctx)=>{ctx.res.end(error.toString())
})

这理能够通过继承 events 对象,取得公布订阅的能力:

const EventEmiter = require("events")
class Koa extends EventEmiter {constructor() {super()
        ...
    }
    ...
    handleRequest(req,res){
        ...
        p.then(() => {ctx.body ? res.end(ctx.body) : res.end("Not Found")
        }).catch(error=>{
            // 铺货谬误后发送给 error
            this.emit("error", error, ctx)
        })
    }
}

残缺的 application.js 代码:

const http = require("http")
const Stream = require("stream")
const EventEmiter = require("events")
const context = require("./context")
const request = require("./request")
const response = require("./response")

class Koa extends EventEmiter {constructor() {super()
    this.middlewares = []

    // Object.create 避免用户间接批改对象,保障每次 new Koa 都是新的对象
    this.context = Object.create(context)
    this.request = Object.create(request)
    this.response = Object.create(response)
  }
  use(fn) {this.middlewares.push(fn)
  }
  compose(ctx, middlewares) {
    let exectIndex = -1
    let dispatch = async function (index) {if (exectIndex >= index) return Promise.reject("mulit called next();")
      exectIndex = index
      if (index === middlewares.length) return Promise.resolve()
      let middleware = middlewares[index]
      return middleware(ctx, () => dispatch(++index))
    }
    return dispatch(0)
  }
  createContext(req, res) {
    let ctx = this.context
    /*
    * ctx.request.req\ctx.req\ctx.req
    * ctx.response.res\ctx.res\ctx.res
    */
    ctx.request = this.request
    ctx.response = this.response
    ctx.request.req = ctx.req = req
    ctx.response.res = ctx.res = res

    return ctx
  }
  handleRequest(req, res) {let ctx = this.createContext(req, res)
    res.statusCode = 404
    let p = this.compose(ctx, this.middlewares)
    p.then(() => {if (ctx.body instanceof Stream) {res.setHeader("Content-Type", "application/octet-stream")
        res.setHeader("Content-Disposition", `attachment;filename=download`)
        return ctx.body.pipe(res)
      }
      if (ctx.body) {res.end(ctx.body)
      } else {res.end("Not Found")
      }
    }).catch((error) => {this.emit("error", error, ctx)
    })
  }
  listen() {let server = http.createServer(this.handleRequest.bind(this))
    server.listen(...arguments)
  }
}

module.exports = Koa

正文完
 0