response源码分析

依赖模块

  1. const contentDisposition = require('content-disposition')
  2. // 创建和解析 HTTP 头 Content-Disposition
  3. const getType = require('cache-content-type')
  4. // 创建 Content-Type 值并缓存
  5. const onFinish = require('on-finished')
  6. // 当 HTTP 请求终止时执行回调
  7. const escape = require('escape-html')
  8. // 转义 HTML 中特殊符号为实体编码
  9. const typeis = require('type-is').is
  10. // 类型检查
  11. const statuses = require('statuses')
  12. // 提供状态码与消息的一一对应关系
  13. const destroy = require('destroy')
  14. // 释放流
  15. const assert = require('assert')
  16. // 提供了一组验证不变量的断言函数
  17. const extname = require('path').extname
  18. // 获取文件拓展名
  19. const vary = require('vary')
  20. // 操作 HTTP 头
  21. const only = require('only')
  22. // 设置对象白名单属性
  23. const util = require('util')
  24. // 工具模块,支持 Node.js 内部 API 的需求
  25. const encodeUrl = require('encodeurl')
  26. // 百分号编码 url
  27. const Stream = require('stream')
  28. // 提供了用于实现流接口的 API

导出对象

  1. module.exports = {
  2. // 内容如下
  3. }

socket

  1. get socket () {
  2. return this.res.socket
  3. }

header

  1. get header () {
  2. const { res } = this
  3. return typeof res.getHeaders === 'function'
  4. ? res.getHeaders()
  5. : res._headers || {} // Node < 7.7
  6. }
  7. get headers () {
  8. return this.header
  9. }

status

  1. get status () {
  2. return this.res.statusCode
  3. }
  4. set status (code) {
  5. if (this.headerSent) return
  6. assert(Number.isInteger(code), 'status code must be a number')
  7. assert(code >= 100 && code <= 999, `invalid status code: ${code}`)
  8. this._explicitStatus = true
  9. this.res.statusCode = code
  10. if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code]
  11. if (this.body && statuses.empty[code]) this.body = null
  12. }

message

  1. get message () {
  2. return this.res.statusMessage || statuses[this.status]
  3. }
  4. set message (msg) {
  5. this.res.statusMessage = msg
  6. }

body

  1. get body () {
  2. return this._body
  3. }
  4. set body (val) {
  5. const original = this._body
  6. this._body = val
  7. if (val == null) {
  8. if (!statuses.empty[this.status]) {
  9. if (this.type === 'application/json') {
  10. this._body = 'null'
  11. return
  12. }
  13. this.status = 204
  14. }
  15. if (val === null) this._explicitNullBody = true
  16. this.remove('Content-Type')
  17. this.remove('Content-Length')
  18. this.remove('Transfer-Encoding')
  19. return
  20. }
  21. if (!this._explicitStatus) this.status = 200
  22. const setType = !this.has('Content-Type')
  23. if (typeof val === 'string') {
  24. if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text'
  25. this.length = Buffer.byteLength(val)
  26. return
  27. }
  28. if (Buffer.isBuffer(val)) {
  29. if (setType) this.type = 'bin'
  30. this.length = val.length
  31. return
  32. }
  33. if (val instanceof Stream) {
  34. onFinish(this.res, destroy.bind(null, val))
  35. if (original !== val) {
  36. val.once('error', err => this.ctx.onerror(err))
  37. // overwriting
  38. if (original != null) this.remove('Content-Length')
  39. }
  40. if (setType) this.type = 'bin'
  41. return
  42. }
  43. this.remove('Content-Length')
  44. this.type = 'json'
  45. }

length

  1. set length (n) {
  2. if (!this.has('Transfer-Encoding')) {
  3. this.set('Content-Length', n)
  4. }
  5. }
  6. get length () {
  7. if (this.has('Content-Length')) {
  8. return parseInt(this.get('Content-Length'), 10) || 0
  9. }
  10. const { body } = this
  11. if (!body || body instanceof Stream) return undefined
  12. if (typeof body === 'string') return Buffer.byteLength(body)
  13. if (Buffer.isBuffer(body)) return body.length
  14. return Buffer.byteLength(JSON.stringify(body))
  15. }

headerSent

  1. get headerSent () {
  2. return this.res.headersSent
  3. }

type

  1. set type (type) {
  2. type = getType(type)
  3. if (type) {
  4. this.set('Content-Type', type)
  5. } else {
  6. this.remove('Content-Type')
  7. }
  8. }
  9. get type () {
  10. const type = this.get('Content-Type')
  11. if (!type) return ''
  12. return type.split(';', 1)[0]
  13. }

lastModified

  1. set lastModified (val) {
  2. if (typeof val === 'string') val = new Date(val)
  3. this.set('Last-Modified', val.toUTCString())
  4. }
  5. get lastModified () {
  6. const date = this.get('last-modified')
  7. if (date) return new Date(date)
  8. }

etag

  1. set etag (val) {
  2. if (!/^(W\/)?"/.test(val)) val = `"${val}"`
  3. this.set('ETag', val)
  4. }
  5. get etag () {
  6. return this.get('ETag')
  7. }

writable

  1. get writable () {
  2. if (this.res.writableEnded || this.res.finished) return false
  3. const socket = this.res.socket
  4. if (!socket) return true
  5. return socket.writable
  6. }

vary 设置响应头

  1. vary (field) {
  2. if (this.headerSent) return
  3. vary(this.res, field)
  4. }

redirect 重定向

  1. redirect (url, alt) {
  2. if (url === 'back') url = this.ctx.get('Referrer') || alt || '/'
  3. this.set('Location', encodeUrl(url))
  4. if (!statuses.redirect[this.status]) this.status = 302
  5. if (this.ctx.accepts('html')) {
  6. url = escape(url)
  7. this.type = 'text/html; charset=utf-8'
  8. this.body = `Redirecting to <a href="${url}">${url}</a>.`
  9. return
  10. }
  11. this.type = 'text/plain; charset=utf-8'
  12. this.body = `Redirecting to ${url}.`
  13. }

attachment 设置响应头 Content-Disposition

  1. attachment (filename, options) {
  2. if (filename) this.type = extname(filename)
  3. this.set('Content-Disposition', contentDisposition(filename, options))
  4. }

is 类型判断

  1. is (type, ...types) {
  2. return typeis(this.type, type, ...types)
  3. }

get 获取响应头

  1. get (field) {
  2. return this.res.getHeader(field)
  3. }

has 判断是否存在响应头

  1. has (field) {
  2. return typeof this.res.hasHeader === 'function'
  3. ? this.res.hasHeader(field)
  4. // Node < 7.7
  5. : field.toLowerCase() in this.headers
  6. }

set 设置响应头

  1. set (field, val) {
  2. if (this.headerSent) return
  3. if (arguments.length === 2) {
  4. if (Array.isArray(val)) val = val.map(v => typeof v === 'string' ? v : String(v))
  5. else if (typeof val !== 'string') val = String(val)
  6. this.res.setHeader(field, val)
  7. } else {
  8. for (const key in field) {
  9. this.set(key, field[key])
  10. }
  11. }
  12. }

append 附加响应头字段

  1. append (field, val) {
  2. const prev = this.get(field)
  3. if (prev) {
  4. val = Array.isArray(prev)
  5. ? prev.concat(val)
  6. : [prev].concat(val)
  7. }
  8. return this.set(field, val)
  9. }

remove 移除响应头

  1. remove (field) {
  2. if (this.headerSent) return
  3. this.res.removeHeader(field)
  4. }

inspect 检查函数

  1. inspect () {
  2. if (!this.res) return
  3. const o = this.toJSON()
  4. o.body = this.body
  5. return o
  6. }

toJSON 格式化函数

  1. toJSON () {
  2. return only(this, [
  3. 'status',
  4. 'message',
  5. 'header'
  6. ])
  7. }

flushHeaders 刷新响应头

  1. flushHeaders () {
  2. this.res.flushHeaders()
  3. }

自定义检查函数

  1. if (util.inspect.custom) {
  2. module.exports[util.inspect.custom] = module.exports.inspect
  3. }