response源码分析
依赖模块
const contentDisposition = require('content-disposition')// 创建和解析 HTTP 头 Content-Dispositionconst 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')// 百分号编码 urlconst 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}