在上一篇中,主要分析了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.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)
})
})

Node.js躬行记(20)——KOA源码分析(下)的更多相关文章

  1. Node.js躬行记(19)——KOA源码分析(上)

    本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...

  2. Node.js躬行记(17)——UmiJS版本升级

    在2020年我刚到公司的时候,公司使用的版本还是1.0,之后为了引入微前端,迫不得已被动升级. 一.从 1.0 到 2.0 在官方文档中,有专门一页讲如何升级的,这个用户体验非常好. 一个清单列的非常 ...

  3. Node.js躬行记(21)——花10分钟入门Node.js

    Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...

  4. Node.js躬行记(4)——自建前端监控系统

    这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...

  5. Node.js躬行记(12)——BFF

    BFF字面意思是服务于前端的后端,我的理解就是数据聚合层.我们组在维护一个后台管理系统,会频繁的与数据库交互. 过去为了增删改查会写大量的对应接口,并且还需要在Model.Service.Router ...

  6. Node.js躬行记(1)——Buffer、流和EventEmitter

    一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...

  7. Node.js躬行记(2)——文件系统和网络

    一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...

  8. Node.js躬行记(6)——自制短链系统

    短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...

  9. Node.js躬行记(9)——微前端实践

    后台管理系统使用的是umi框架,随着公司业务的发展,目前已经变成了一个巨石应用,越来越难维护,有必要对其进行拆分了. 计划是从市面上挑选一个成熟的微前端框架,首先选择的是 icestark,虽然文档中 ...

随机推荐

  1. SpringCloud个人笔记-03-Config初体验

    sb-cloud-config 配置中心 <?xml version="1.0" encoding="UTF-8"?> <project xm ...

  2. Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议

    本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...

  3. python中模块制作、发布、安装

    模块的发布 需要在当前目录下 模块的安装 真实制作发布一个包 包的制作 (1)将写好的包放在moudelTest目录下 (2)moudelTest目录下创建一个setup.py文件(格式上面有介绍) ...

  4. 还能这样?把 Python 自动翻译成 C++

    作者:byronhe,腾讯 WXG 开发工程师 一.问题背景 随着深度学习的广泛应用,在搜索引擎/推荐系统/机器视觉等业务系统中,越来越多的深度学习模型部署到线上服务. 机器学习模型在离线训练时,一般 ...

  5. 该如何选择 background-image 和 img 标签

    用img标签 如果你希望别人打印页面时候包含这张图片请使用 img 标签 当这张图片有非常有意义的语义,比如警告图标,请使用img标签及它的alt属性.这样意味着你可以向所有的用户终端现实他的意义. ...

  6. 常用 adb 命令总结

    1. 显示当前运行的全部模拟器:    adb devices    2.  安装应用程序:      adb install -r 应用程序.apk    3.  获取模拟器中的文件:      a ...

  7. PAT B1051 复数乘法

    输入样例: 2.3 3.5 5.2 0.4 输出样例: -8.68-8.23i 解题思路: 1.读入R1.P1.R2.P2. 2.A=(R1*R2)cos(P1+P2),B=(R1*R2)sin(P1 ...

  8. 如何在jsp界面进行判断再输出不同的值

    C标签的out <td> <c:if test="${nowtime eq returntime}"> <c:out value="逾期&q ...

  9. vue-cropper裁剪上传

    效果图: 全部代码: npm install vue-cropper //首先 安装vue-cropper main.js全局引用: import VueCropper from 'vue-cropp ...

  10. 面向对象编程-终结篇 es6新增语法

    各位,各位,终于把js完成了一个段落了,这次的章节一过我还没确定下面要学的内容可能是vue也可能是前后端交互,但无论是哪个都挺兴奋的,因为面临着终于可以做点看得过去的大点的案例项目了,先憋住激动地情绪 ...