在上一篇中,主要分析了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. CEPH-4:ceph RadowGW对象存储功能详解

    ceph RadosGW对象存储使用详解 一个完整的ceph集群,可以提供块存储.文件系统和对象存储. 本节主要介绍对象存储RadosGw功能如何灵活的使用,集群背景: $ ceph -s clust ...

  2. 什么是CSS Modules ?我们为什么需要他们

    原文地址:https://css-tricks.com/css-mo...最近我对CSS Modules比较好奇.如果你曾经听说过他们,那么这篇博客正适合你.我们将去探索它的目的和主旨.如果你同样很好 ...

  3. Amaze UI 模版中心上线丨十几款高质量优秀模版免费提供!

    Amaze UI模版中心终于上线了,目前汇聚了包含企业门户.新闻资讯.管理后台等多个领域的模版,全都可以免费下载. Amaze UI模版中心后续还会增加更多的模版以及领域,请各位持续关注. 模版中心的 ...

  4. mpvue 如何使用腾讯视频插件?

    1.在小程序微信开放平台:设置 --- 第三方服务里,申请腾讯视频插件2.申请成功后就可以在项目中使用了 具体使用步骤如下:1.在项目目录src下的main.js中加入下面代码,这里代码会被编译到ap ...

  5. 小程序图片轮播特效swiper(纯手打)

    前言 一个月前还是用vue做微信H5,后面公司业务发展,入坑小程序,做了几款小程,跑了不少坑, 也会陆续在后面几节跟大家分享. 在这节给大家分享这个 小程序图片轮播实现方案 初步的实现思路 我要实现的 ...

  6. CSS3新特性的概述

    CSS3的新特性大致分为以下六类 1.CSS3选择器 2.CSS3边框与圆角 3.CSS3背景与渐变 4.CSS3过渡 5.CSS3变换 6.CSS3动画 下面分别说一说以上六类都有哪些内容 CSS3 ...

  7. react 移动端 兼容性问题和一些小细节

    react 移动端 兼容性问题和一些小细节 使用 ES6 的浏览器兼容性问题 react 对低版本的安卓webview 兼容性 iOS下 fixed与软键盘的问题 onClick 阻止冒泡 meta对 ...

  8. python计算项目净现值和内部回报率

     代码: import numpy as np from numpy import irr import warnings def project(number, period_list): rate ...

  9. Android Studio安装问题

    安装问题可以参考:https://blog.csdn.net/y74364/article/details/96121530 但是gradle安装缓慢,需要FQ.有加速器FQ的可以开加速器安装,没有的 ...

  10. 【Android开发】富文本

    SpannableString spannableString = new SpannableString("设置文字的前景色为淡蓝色"); ForegroundColorSpan ...