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

实现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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理