• 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 ${url}.`
          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*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.js
    describe('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)
      })
    })
    复制代码

     

  • 相关阅读:
    芯片设计:一颗芯片到底是如何诞生的(上)
    Baklib|企业知识管理的一些建议分享
    “因遭勒索软件攻击,我被认定工作失职开除,并被老东家索赔 21.5 万元”
    如何做外贸网站的SEO优化
    如何在不失去理智的情况下调试 TensorFlow 训练程序
    《计算机校招之路》1.0.0 震撼发布
    Linux内核学习①:内核的下载、编译及过程中的问题处理
    Flutter 开发学习笔记(4):widget布局容器学习
    【端到端存储解决方案】Weka,让企业【文件存储】速度飞起来!
    ATECLOUD智能云测试平台-测试测量/仪器程控/工业控制/上位机开发软件
  • 原文地址:https://www.cnblogs.com/strick/p/16184943.html