'use strict'
/**
* Module dependencies.
*/
const assert = require('node:assert')
const extname = require('node:path').extname
const util = require('node:util')
const contentDisposition = require('content-disposition')
const onFinish = require('on-finished')
const escape = require('escape-html')
const typeis = require('type-is').is
const statuses = require('statuses')
const destroy = require('destroy')
const encodeUrl = require('encodeurl')
const vary = require('vary')
const getType = require('mime-types').contentType
const isStream = require('./is-stream.js')
const only = require('./only.js')
/**
* Prototype.
*/
module.exports = {
/**
* Return the request socket.
*
* @return {Connection}
* @api public
*/
get socket () {
return this.res.socket
},
/**
* Return response header.
*
* @return {Object}
* @api public
*/
get header () {
const { res } = this
return typeof res.getHeaders === 'function'
? res.getHeaders()
: res._headers || {} // Node < 7.7
},
/**
* Return response header, alias as response.header
*
* @return {Object}
* @api public
*/
get headers () {
return this.header
},
/**
* Get response status code.
*
* @return {Number}
* @api public
*/
get status () {
return this.res.statusCode
},
/**
* Set response status code.
*
* @param {Number} code
* @api public
*/
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.message[code]
if (this.body && statuses.empty[code]) this.body = null
},
/**
* Get response status message
*
* @return {String}
* @api public
*/
get message () {
return this.res.statusMessage || statuses.message[this.status]
},
/**
* Set response status message
*
* @param {String} msg
* @api public
*/
set message (msg) {
this.res.statusMessage = msg
},
/**
* Get response body.
*
* @return {Mixed}
* @api public
*/
get body () {
return this._body
},
/**
* Set response body.
*
* @param {String|Buffer|Object|Stream|ReadableStream|Blob|Response} val
* @api public
*/
set body (val) {
const original = this._body
this._body = val
const cleanupPreviousStream = () => {
if (original && isStream(original)) {
original.once('error', () => {})
// Only destroy if the new value is not a stream
if (!isStream(val)) {
destroy(original)
}
}
}
// 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')
cleanupPreviousStream()
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* "text/plain"
*
* this.get('content-type');
* // => "text/plain"
*
* @param {String} field
* @return {any}
* @api public
*/
get (field) {
return this.res.getHeader(field)
},
/**
* Returns true if the header identified by name is currently set in the outgoing headers.
* The header name matching is case-insensitive.
*
* Examples:
*
* this.has('Content-Type');
* // => true
*
* this.get('content-type');
* // => true
*
* @param {String} field
* @return {boolean}
* @api public
*/
has (field) {
return typeof this.res.hasHeader === 'function'
? this.res.hasHeader(field)
// Node < 7.7
: field.toLowerCase() in this.headers
},
/**
* Set header `field` to `val` or pass
* an object of header fields.
*
* Examples:
*
* this.set('Foo', ['bar', 'baz']);
* this.set('Accept', 'application/json');
* this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
*
* @param {String|{ [k: string]: any }} field
* @param {any} [val]
* @api public
*/
set (field, val) {
if (this.headerSent || !field) return
if (typeof field === 'string') {
this.res.setHeader(field, val)
} else {
Object.keys(field).forEach(header => this.res.setHeader(header, field[header]))
}
},
/**
* Append additional header `field` with value `val`.
*
* Examples:
*
* ```
* this.append('Link', ['', '']);
* this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
* this.append('Warning', '199 Miscellaneous warning');
* ```
*
* @param {String} field
* @param {*} val
* @api public
*/
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 header `field`.
*
* @param {String} field
* @api public
*/
remove (field) {
if (this.headerSent) return
this.res.removeHeader(field)
},
/**
* Checks if the request is writable.
* Tests for the existence of the socket
* as node sometimes does not set it.
*
* @return {Boolean}
* @api private
*/
get writable () {
// can't write any more after response finished
// response.writableEnded is available since Node > 12.9
// https://nodejs.org/api/http.html#http_response_writableended
// response.finished is undocumented feature of previous Node versions
// https://stackoverflow.com/questions/16254385/undocumented-response-finished-in-node-js
if (this.res.writableEnded || this.res.finished) return false
const socket = this.res.socket
// There are already pending outgoing res, but still writable
// https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
if (!socket) return true
return socket.writable
},
/**
* Inspect implementation.
*
* @return {Object}
* @api public
*/
inspect () {
if (!this.res) return
const o = this.toJSON()
o.body = this.body
return o
},
/**
* Return JSON representation.
*
* @return {Object}
* @api public
*/
toJSON () {
return only(this, [
'status',
'message',
'header'
])
},
/**
* Flush any set headers and begin the body
*/
flushHeaders () {
this.res.flushHeaders()
}
}
/**
* Custom inspection implementation for node 6+.
*
* @return {Object}
* @api public
*/
/* istanbul ignore else */
if (util.inspect.custom) {
module.exports[util.inspect.custom] = module.exports.inspect
}