在上一篇中,主要分析了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躬行记(4)——自建前端监控系统

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

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

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

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

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

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

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

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

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

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

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

  9. Node.js躬行记(15)——活动规则引擎

    在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...

随机推荐

  1. Swagger+AutoRest 生成web api客户端(.Net)

    简介 对于.net来说,用web api来构建服务是一个不错的选择,都是http请求,调用简单,但是如果真的要在程序中调用,则还有些工作要做,比如我们需要手写httpClient调用,并映射Model ...

  2. 学习Git

    参考廖雪峰老师的个人网站:http://www.liaoxuefeng.com 版本库初始化 通过git init命令将这个命令变为git可以管理的仓库 添加文件 用命令git add file告诉G ...

  3. BZOJ 1054 广搜

    1054: [HAOI2008]移动玩具 在一个4*4的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动 时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩 ...

  4. JavaScript escape() 函数

    JavaScript escape() 函数 JavaScript 全局对象 定义和用法 escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串. 语法 escape(str ...

  5. C#多线程的用法1-简单示例

    写在前面:阅读本系列文章即表示你已经知道什么是线程等理论知识,现在正想了解如何正确的使用线程进行编程工作. /// <summary> /// 单线程工作示例 /// </summa ...

  6. Markdown文本测试

    一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 1. 这是一 2. 这是二 这是无序符号 My Github 这是着重表示 这是斜体 一级粗体 二级斜体 cin >> a; c ...

  7. 重构&mdash;&mdash;一个小例子

    菜鸟区域,老鸟绕路! 原代码,这是一个可以借阅影片的小程序,你可以想象成某个大型系统,我想代码应该都能很容易看懂: using System; using System.Collections.Gen ...

  8. 关于 使用python向qq好友发送消息(对爬虫的作用----当程序执行完毕或者报错无限给自己qq发送消息,直到关闭)

    以前看到网上一些小程序,在处理完事物后会自动发送qq消息,但是一直搞不懂是说明原理.也在网上找过一些python登陆qq发送消息的文字,但是都太复杂了.今天偶然看到一篇文章,是用python调用win ...

  9. SQL Server 2000使用链接服务器

    执行:安装盘\SQL2KSP4\install\instcat.sql 文件下载地址:http://download.csdn.net/detail/taomanman/5680765

  10. java-深克隆和浅克隆

    文章参考 https://www.cnblogs.com/acode/p/6306887.html 一.前提 1.使用clone()方法的类,必须实现Cloneable接口, 否则调用clone()方 ...