response
源码分析
依赖模块
const contentDisposition = require('content-disposition')
// 创建和解析 HTTP 头 Content-Disposition
const getType = require('cache-content-type')
// 创建 Content-Type 值并缓存
const onFinish = require('on-finished')
// 当 HTTP 请求终止时执行回调
const escape = require('escape-html')
// 转义 HTML 中特殊符号为实体编码
const typeis = require('type-is').is
// 类型检查
const statuses = require('statuses')
// 提供状态码与消息的一一对应关系
const destroy = require('destroy')
// 释放流
const assert = require('assert')
// 提供了一组验证不变量的断言函数
const extname = require('path').extname
// 获取文件拓展名
const vary = require('vary')
// 操作 HTTP 头
const only = require('only')
// 设置对象白名单属性
const util = require('util')
// 工具模块,支持 Node.js 内部 API 的需求
const encodeUrl = require('encodeurl')
// 百分号编码 url
const Stream = require('stream')
// 提供了用于实现流接口的 API
导出对象
module.exports = {
// 内容如下
}
socket
get socket () {
return this.res.socket
}
header
get header () {
const { res } = this
return typeof res.getHeaders === 'function'
? res.getHeaders()
: res._headers || {} // Node < 7.7
}
get headers () {
return this.header
}
status
get status () {
return this.res.statusCode
}
set status (code) {
if (this.headerSent) return
assert(Number.isInteger(code), 'status code must be a number')
assert(code >= 100 && code <= 999, `invalid status code: ${code}`)
this._explicitStatus = true
this.res.statusCode = code
if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code]
if (this.body && statuses.empty[code]) this.body = null
}
message
get message () {
return this.res.statusMessage || statuses[this.status]
}
set message (msg) {
this.res.statusMessage = msg
}
body
get body () {
return this._body
}
set body (val) {
const original = this._body
this._body = val
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
}
if (!this._explicitStatus) this.status = 200
const setType = !this.has('Content-Type')
if (typeof val === 'string') {
if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text'
this.length = Buffer.byteLength(val)
return
}
if (Buffer.isBuffer(val)) {
if (setType) this.type = 'bin'
this.length = val.length
return
}
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
}
this.remove('Content-Length')
this.type = 'json'
}
length
set length (n) {
if (!this.has('Transfer-Encoding')) {
this.set('Content-Length', n)
}
}
get length () {
if (this.has('Content-Length')) {
return parseInt(this.get('Content-Length'), 10) || 0
}
const { body } = this
if (!body || body instanceof Stream) return undefined
if (typeof body === 'string') return Buffer.byteLength(body)
if (Buffer.isBuffer(body)) return body.length
return Buffer.byteLength(JSON.stringify(body))
}
headerSent
get headerSent () {
return this.res.headersSent
}
type
set type (type) {
type = getType(type)
if (type) {
this.set('Content-Type', type)
} else {
this.remove('Content-Type')
}
}
get type () {
const type = this.get('Content-Type')
if (!type) return ''
return type.split(';', 1)[0]
}
lastModified
set lastModified (val) {
if (typeof val === 'string') val = new Date(val)
this.set('Last-Modified', val.toUTCString())
}
get lastModified () {
const date = this.get('last-modified')
if (date) return new Date(date)
}
etag
set etag (val) {
if (!/^(W\/)?"/.test(val)) val = `"${val}"`
this.set('ETag', val)
}
get etag () {
return this.get('ETag')
}
writable
get writable () {
if (this.res.writableEnded || this.res.finished) return false
const socket = this.res.socket
if (!socket) return true
return socket.writable
}
vary
设置响应头
vary (field) {
if (this.headerSent) return
vary(this.res, field)
}
redirect
重定向
redirect (url, alt) {
if (url === 'back') url = this.ctx.get('Referrer') || alt || '/'
this.set('Location', encodeUrl(url))
if (!statuses.redirect[this.status]) this.status = 302
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
}
this.type = 'text/plain; charset=utf-8'
this.body = `Redirecting to ${url}.`
}
attachment
设置响应头 Content-Disposition
attachment (filename, options) {
if (filename) this.type = extname(filename)
this.set('Content-Disposition', contentDisposition(filename, options))
}
is
类型判断
is (type, ...types) {
return typeis(this.type, type, ...types)
}
get
获取响应头
get (field) {
return this.res.getHeader(field)
}
has
判断是否存在响应头
has (field) {
return typeof this.res.hasHeader === 'function'
? this.res.hasHeader(field)
// Node < 7.7
: field.toLowerCase() in this.headers
}
set
设置响应头
set (field, val) {
if (this.headerSent) return
if (arguments.length === 2) {
if (Array.isArray(val)) val = val.map(v => typeof v === 'string' ? v : String(v))
else if (typeof val !== 'string') val = String(val)
this.res.setHeader(field, val)
} else {
for (const key in field) {
this.set(key, field[key])
}
}
}
append
附加响应头字段
append (field, val) {
const prev = this.get(field)
if (prev) {
val = Array.isArray(prev)
? prev.concat(val)
: [prev].concat(val)
}
return this.set(field, val)
}
remove
移除响应头
remove (field) {
if (this.headerSent) return
this.res.removeHeader(field)
}
inspect
检查函数
inspect () {
if (!this.res) return
const o = this.toJSON()
o.body = this.body
return o
}
toJSON
格式化函数
toJSON () {
return only(this, [
'status',
'message',
'header'
])
}
flushHeaders
刷新响应头
flushHeaders () {
this.res.flushHeaders()
}
自定义检查函数
if (util.inspect.custom) {
module.exports[util.inspect.custom] = module.exports.inspect
}