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.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源码分析(下)的更多相关文章
- Node.js躬行记(19)——KOA源码分析(上)
本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...
- Node.js躬行记(17)——UmiJS版本升级
在2020年我刚到公司的时候,公司使用的版本还是1.0,之后为了引入微前端,迫不得已被动升级. 一.从 1.0 到 2.0 在官方文档中,有专门一页讲如何升级的,这个用户体验非常好. 一个清单列的非常 ...
- Node.js躬行记(21)——花10分钟入门Node.js
Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...
- Node.js躬行记(4)——自建前端监控系统
这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...
- Node.js躬行记(12)——BFF
BFF字面意思是服务于前端的后端,我的理解就是数据聚合层.我们组在维护一个后台管理系统,会频繁的与数据库交互. 过去为了增删改查会写大量的对应接口,并且还需要在Model.Service.Router ...
- Node.js躬行记(1)——Buffer、流和EventEmitter
一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...
- Node.js躬行记(2)——文件系统和网络
一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...
- Node.js躬行记(6)——自制短链系统
短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...
- Node.js躬行记(9)——微前端实践
后台管理系统使用的是umi框架,随着公司业务的发展,目前已经变成了一个巨石应用,越来越难维护,有必要对其进行拆分了. 计划是从市面上挑选一个成熟的微前端框架,首先选择的是 icestark,虽然文档中 ...
随机推荐
- java的API
一.前端 1.jsp展示数据 (1)展示在前端控制台 console.table(参数); (2)弹窗 alert(参数); (3)JSLT的<c:if>标签 <c:if test= ...
- elf,基于flexbox的响应式CSS框架
官网地址:http://jrainlau.github.io/elf/项目地址:https://github.com/jrainlau/elf 介绍 取名为"精灵"的elf,是一个 ...
- npm权限不够(安装什么都报错)
问题 Windows下使用npm安装任何包都报错, Windows下使用npm显示权限不够 如图: 解决方法 1. 方法一 使用管理员权限打开 命令窗口, 治标不治本!!!!不推荐 ...
- audio微信自动播放以及自定义样式
audio标签如下: <audio id="audioTag" src="" autoplay="autoplay" controls ...
- uni-app开发的h5 访问url自动添加 #的问题
在manifest.json配置文件修改h5的内容,添加router部分 "h5" : { "title" : "xxx", "d ...
- PyQt5 基本语法(四)
目录 2. 输入控件(一) 2.1 纯键盘 2.1.1 QLineEdit 2.1.1.1 描述 2.1.1.2 控件创建 2.1.1.3 输出模式 2.1.1.4 提示字符串 2.1.1.5 清空按 ...
- win10设置开机自启动程序
问题情境:前两天刚刚给自己的win10系统美化了一下,但发现一个问题,每次开机都需要双击启动一个程序,才能达到一个我想要的效果,所以就在思考能不能将这个程序设为开机自启动项呢? 1.首先,找到启动文件 ...
- print,printf,println的区别,以及\r,\n,\r\n的区别
1.常用的是println,就是换行输出 2.print,不换行输出 3.printf常使用于格式转化 public class Print { public static void main(Str ...
- uniapp-scroll-view纵向(竖向)滑动当scrollTop为0时卡顿问题
这个问题目前遇到的人少,所以找到答案不容易,我也是各种细节亲测才发现的解决方案.记录下来 当uniapp用scroll-view竖向滚动时,在scrollTop为0时,下拉会卡顿. 解决方法(只需要在 ...
- 【LeetCode】24.两两交换链表中的节点
24.两两交换链表中的节点 知识点:链表 题目描述 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点.你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换). 示例 示例 1 ...