Node.js躬行记(20)——KOA源码分析(下)

博客 分享
0 162
张三
张三 2022-05-16 09:59:19
悬赏:0 积分 收藏
Node.js躬行记(20)——KOA源码分析(下)

  在上一篇中,主要分析了package.json和application.js文件,本文会分析剩下的几个文件。

一、context.js

  在context.js中,会处理错误,cookie,JSON格式化等。

1)cookie

  在处理cookie时,创建了一个Symbol类型的key,注意Symbol具有唯一性的特点,即 Symbol('context#cookies') === Symbol('context#cookies') 得到的是 false。

const Cookies = require('cookies')const COOKIES = Symbol('context#cookies')const proto = module.exports = {  get cookies () {    if (!this[COOKIES]) {      this[COOKIES] = new Cookies(this.req, this.res, {        keys: this.app.keys,        secure: this.request.secure      })    }    return this[COOKIES]  },  set cookies (_cookies) {    this[COOKIES] = _cookies  }}

  get在读取cookie,会初始化Cookies实例。

2)错误

  在默认的错误处理函数中,会配置头信息,触发错误事件,配置响应码等。

  onerror (err) {    // 可以绕过KOA的错误处理    if (err == null) return    // 在处理跨全局变量时,正常的“instanceof”检查无法正常工作    // See https://github.com/koajs/koa/issues/1466    // 一旦jest修复,可能会删除它 https://github.com/facebook/jest/issues/2549.    const isNativeError =      Object.prototype.toString.call(err) === '[object Error]' ||      err instanceof Error    if (!isNativeError) err = new Error(util.format('non-error thrown: %j', err))    let headerSent = false    if (this.headerSent || !this.writable) {      headerSent = err.headerSent = true    }    // 触发error事件,在application.js中创建过监听器    this.app.emit('error', err, this)    // nothing we can do here other    // than delegate to the app-level    // handler and log.    if (headerSent) {      return    }    const { res } = this    // 首先取消设置所有header    /* istanbul ignore else */    if (typeof res.getHeaderNames === 'function') {      res.getHeaderNames().forEach(name => res.removeHeader(name))    } else {      res._headers = {} // Node < 7.7    }    // 然后设置那些指定的    this.set(err.headers)    // 强制 text/plain    this.type = 'text'    let statusCode = err.status || err.statusCode    // ENOENT support    if (err.code === 'ENOENT') statusCode = 404    // default to 500    if (typeof statusCode !== 'number' || !statuses[statusCode]) statusCode = 500    // 响应数据    const code = statuses[statusCode]    const msg = err.expose ? err.message : code    this.status = err.status = statusCode    this.length = Buffer.byteLength(msg)    res.end(msg)  },

3)属性委托

  在package.json中依赖了一个名为delegates的库,看下面这个示例。

  request是context的一个属性,在调delegate(context, 'request')函数后,就能直接context.querystring这么调用了。

const delegate = require('delegates')const context = {  request: {    querystring: 'a=1&b=2'  }}delegate(context, 'request')  .access('querystring')console.log(context.querystring)// a=1&b=2

  在KOA中,ctx可以直接调用request与response的属性和方法就是通过delegates实现的。

delegate(proto, 'request')  .method('acceptsLanguages')  .method('acceptsEncodings')  .method('acceptsCharsets')  .method('accepts')  .method('get')  .method('is')  .access('querystring')  .access('idempotent')  .access('socket')  .access('search')  .access('method')  ...

二、request.js和response.js

  request.js和response.js就是为Node原生的req和res做一层封装。在request.js中都是些HTTP首部、IP、URL、缓存等。

  /**   * 获取 WHATWG 解析的 URL,并缓存起来   */  get URL () {    /* istanbul ignore else */    if (!this.memoizedURL) {      const originalUrl = this.originalUrl || '' // avoid undefined in template string      try {        this.memoizedURL = new URL(`${this.origin}${originalUrl}`)      } catch (err) {        this.memoizedURL = Object.create(null)      }    }    return this.memoizedURL  },  /**   * 检查请求是否新鲜(有缓存),也就是   * If-Modified-Since/Last-Modified 和 If-None-Match/ETag 是否仍然匹配   */  get fresh () {    const method = this.method    const s = this.ctx.status    // GET or HEAD for weak freshness validation only    if (method !== 'GET' && method !== 'HEAD') return false    // 2xx or 304 as per rfc2616 14.26    if ((s >= 200 && s < 300) || s === 304) {      return fresh(this.header, this.response.header)    }    return false  },

  response.js要复杂一点,会配置状态码、响应正文、读取解析的响应内容长度、302重定向等。

  /**   * 302 重定向   * Examples:   *    this.redirect('back');   *    this.redirect('back', '/index.html');   *    this.redirect('/login');   *    this.redirect('http://google.com');   */  redirect (url, alt) {    // location    if (url === 'back') url = this.ctx.get('Referrer') || alt || '/'    this.set('Location', encodeUrl(url))    // status    if (!statuses.redirect[this.status]) this.status = 302    // html    if (this.ctx.accepts('html')) {      url = escape(url)      this.type = 'text/html; charset=utf-8'      this.body = `Redirecting to <a href="${url}">${url}</a>.`      return    }    // text    this.type = 'text/plain; charset=utf-8'    this.body = `Redirecting to ${url}.`  },  /**   * 设置响应正文   */  set body (val) {    const original = this._body    this._body = val    // no content    if (val == null) {      if (!statuses.empty[this.status]) {        if (this.type === 'application/json') {          this._body = 'null'          return        }        this.status = 204      }      if (val === null) this._explicitNullBody = true      this.remove('Content-Type')      this.remove('Content-Length')      this.remove('Transfer-Encoding')      return    }    // set the status    if (!this._explicitStatus) this.status = 200    // set the content-type only if not yet set    const setType = !this.has('Content-Type')    // string    if (typeof val === 'string') {      if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text'      this.length = Buffer.byteLength(val)      return    }    // buffer    if (Buffer.isBuffer(val)) {      if (setType) this.type = 'bin'      this.length = val.length      return    }    // stream    if (val instanceof Stream) {      onFinish(this.res, destroy.bind(null, val))      if (original !== val) {        val.once('error', err => this.ctx.onerror(err))        // overwriting        if (original != null) this.remove('Content-Length')      }      if (setType) this.type = 'bin'      return    }    // json    this.remove('Content-Length')    this.type = 'json'  },

三、单元测试

  KOA的单元测试做的很细致,每个方法和属性都给出了相应的单测,它内部的写法很容易单测,非常值得借鉴。

  采用的单元测试框架是Jest,HTTP请求的测试库是SuperTest。断言使用了Node默认提供的assert模块。

const request = require('supertest')const assert = require('assert')const Koa = require('../..')// request.jsdescribe('app.request', () => {  const app1 = new Koa()  // 声明request的message属性  app1.request.message = 'hello'  it('should merge properties', () => {    app1.use((ctx, next) => {      // 判断ctx中的request.message是否就是刚刚赋的那个值      assert.strictEqual(ctx.request.message, 'hello')      ctx.status = 204    })    // 发起GET请求,地址是首页,期待响应码是204    return request(app1.listen())      .get('/')      .expect(204)  })})

 

 posted on 2022-05-16 09:20 咖啡机(K.F.J) 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    821 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员