line_push/node_modules/node-res/index.js
2022-07-17 13:16:16 +08:00

648 lines
14 KiB
JavaScript

'use strict'
/*
* node-res
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const mime = require('mime-types')
const etag = require('etag')
const vary = require('vary')
const onFinished = require('on-finished')
const destroy = require('destroy')
const methods = require('./methods')
const returnContentAndType = function (body) {
/**
* Return the body and it's type when
* body is a string.
*/
if (typeof (body) === 'string') {
return {
body,
type: /^\s*</.test(body) ? 'text/html' : 'text/plain'
}
}
/**
* If body is a buffer, return the exact copy
* and type as bin.
*/
if (Buffer.isBuffer(body)) {
return { body, type: 'application/octet-stream' }
}
/**
* If body is a number or boolean. Convert it to
* a string and return the type as text.
*/
if (typeof (body) === 'number' || typeof (body) === 'boolean') {
return { body: String(body), type: 'text/plain' }
}
/**
* Otherwise check whether body is an object or not. If yes
* stringify it and otherwise return the exact copy.
*/
return typeof (body) === 'object'
? { body: JSON.stringify(body), type: 'application/json' }
: { body }
}
/**
* A simple IO module to make consistent HTTP response, without
* worrying about underlying details.
*
* @module Response
*/
const Response = exports = module.exports = {}
/**
* Copying all the descriptive methods to the response object.
*/
Response.descriptiveMethods = Object.keys(methods).map((method) => {
Response[method] = function (req, res, body) {
Response.status(res, methods[method])
Response.send(req, res, body)
}
return method
})
/**
* Returns the value of an existing header on
* the response object
*
* @method getHeader
*
* @param {ServerResponse} res
* @param {String} key
*
* @return {Array|String} Return type depends upon the header existing value
*
* @example
* ```js
* nodeRes.getHeader(res, 'Content-type')
* ```
*/
Response.getHeader = function (res, key) {
return res.getHeader(key)
}
/**
* Sets header on the response object. This method will wipe off
* existing values. To append to existing values, use `append`.
*
* @method header
*
* @param {http.ServerResponse} res
* @param {String} key
* @param {String|Array} value
*
* @return {void}
*
* @example
* ```js
* nodeRes.header(res, 'Content-type', 'application/json')
*
* // or set an array of headers
* nodeRes.header(res, 'Link', ['<http://localhost/>', '<http://localhost:3000/>'])
* ```
*/
Response.header = function (res, key, value) {
const values = Array.isArray(value) ? value.map(String) : value
res.setHeader(key, values)
}
/**
* Appends value to the header existing values.
*
* @method append
*
* @param {http.ServerResponse} res
* @param {String} key
* @param {String|Array} value
*
* @return {void}
*
* @example
* ```js
* nodeRes.append(res, 'Content-type', 'application/json')
*
* // or append an array of headers
* nodeRes.append(res, 'Link', ['<http://localhost/>', '<http://localhost:3000/>'])
* ```
*/
Response.append = function (res, key, value) {
const previousValue = Response.getHeader(res, key)
const headers = previousValue
? (Array.isArray(previousValue) ? previousValue.concat(value) : [previousValue].concat(value))
: value
Response.header(res, key, headers)
}
/**
* Set status on the HTTP res object
*
* @method status
*
* @param {http.ServerResponse} res
* @param {Number} code
*
* @return {void}
*
* @example
* ```js
* nodeRes.status(res, 200)
* ```
*/
Response.status = function (res, code) {
res.statusCode = code
}
/**
* Sets the header on response object, only if it
* does not exists.
*
* @method safeHeader
*
* @param {http.ServerResponse} res
* @param {String} key
* @param {String|Array} value
*
* @return {void}
*
* @example
* ```js
* nodeRes.safeHeader(res, 'Content-type', 'application/json')
* ```
*/
Response.safeHeader = function (res, key, value) {
if (!res.getHeader(key)) {
Response.header(res, key, value)
}
}
/**
* Removes the header from response
*
* @method removeHeader
*
* @param {http.ServerResponse} res
* @param {String} key
*
* @return {void}
*
* @example
* ```js
* nodeRes.removeHeader(res, 'Content-type')
* ```
*/
Response.removeHeader = function (res, key) {
res.removeHeader(key)
}
/**
* Write string or buffer to the response object.
*
* @method write
*
* @param {http.ServerResponse} res
* @param {String|Buffer} body
*
* @return {void}
*
* @example
* ```js
* nodeRes.write(res, 'Hello world')
* ```
*/
Response.write = function (res, body) {
res.write(body)
}
/**
* Explictly end HTTP response
*
* @method end
*
* @param {http.ServerResponse} res
* @param {String|Buffer} [payload]
*
* @return {void}
*
* @example
* ```js
* nodeRes.end(res, 'Hello world')
* ```
*/
Response.end = function (res, payload) {
res.end(payload)
}
/**
* Send body as the HTTP response and end it. Also
* this method will set the appropriate `Content-type`
* and `Content-length`.
*
* If body is set to null, this method will end the response
* as 204.
*
* @method send
*
* @param {http.ServerRequest} req
* @param {http.ServerResponse} res
* @param {String|Buffer|Object|Stream} body
* @param {Boolean} [generateEtag = true]
*
* @return {void}
*
* @example
* ```js
* nodeRes.send(req, res, 'Hello world')
*
* // or html
* nodeRes.send(req, res, '<h2> Hello world </h2>')
*
* // or JSON
* nodeRes.send(req, res, { greeting: 'Hello world' })
*
* // or Buffer
* nodeRes.send(req, res, Buffer.from('Hello world', 'utf-8'))
*
* // Ignore etag
* nodeRes.send(req, res, 'Hello world', false)
* ```
*/
Response.send = function (req, res, body = null, generateEtag = true) {
/**
* Handle streams
*/
if (body && typeof (body.pipe) === 'function') {
Response
.stream(res, body)
.catch((error) => {
Response.status(res, error.code === 'ENOENT' ? 404 : 500)
Response.send(req, res, error.message, generateEtag)
})
return
}
const chunk = Response.prepare(res, body)
if (chunk === null || req.method === 'HEAD') {
Response.end(res)
return
}
/**
* Generate etag when instructured for
*/
if (generateEtag) {
Response.etag(res, chunk)
}
Response.end(res, chunk)
}
/**
* Sets the Etag header for a given body chunk
*
* @method etag
*
* @param {http.ServerResponse} res
* @param {String|Buffer} body
*
* @return {void}
*
* @example
* ```js
* nodeRes.etag(res, 'Hello world')
* ```
*/
Response.etag = function (res, body) {
Response.header(res, 'ETag', etag(body))
}
/**
* Prepares the response body by encoding it properly. Also
* sets appropriate headers based upon the body content type.
*
* This method is used internally by `send`, so you should
* never use it when calling `send`.
*
* It is helpful when you want to get the final payload and end the
* response at a later stage.
*
* @method prepare
*
* @param {http.ServerResponse} res
* @param {Mixed} body
*
* @return {String}
*
* @example
* ```js
* const chunk = nodeRes.prepare(res, '<h2> Hello </h2>')
*
* if (chunk) {
* nodeRes.etag(res, chunk)
*
* if (nodeReq.fresh(req, res)) {
* chunk = null
* nodeRes.status(304)
* }
*
* nodeRes.end(chunk)
* }
* ```
*/
Response.prepare = function (res, body) {
if (body === null) {
Response.status(res, 204)
Response.removeHeader(res, 'Content-Type')
Response.removeHeader(res, 'Content-Length')
Response.removeHeader(res, 'Transfer-Encoding')
return null
}
let { body: chunk, type } = returnContentAndType(body)
/**
* Remove unwanted headers when statuscode is 204 or 304
*/
if (res.statusCode === 204 || res.statusCode === 304) {
Response.removeHeader(res, 'Content-Type')
Response.removeHeader(res, 'Content-Length')
Response.removeHeader(res, 'Transfer-Encoding')
return chunk
}
const headers = typeof res.getHeaders === 'function' ? res.getHeaders() : (res._headers || {})
/**
* Setting content type. Ideally we can use `Response.type`, which
* sets the right charset too. But we will be doing extra
* processing for no reasons.
*/
if (type && !headers['content-type']) {
Response.header(res, 'Content-Type', `${type}; charset=utf-8`)
}
/**
* setting up content length as response header
*/
if (chunk && !headers['content-length']) {
Response.header(res, 'Content-Length', Buffer.byteLength(chunk))
}
return chunk
}
/**
* Prepares response for JSONP
*
* @method prepareJsonp
*
* @param {http.ServerResponse} res
* @param {Object} body
* @param {String} callbackFn
*
* @return {String}
*
* @example
* ```js
* const chunk = nodeRes.prepareJsonp(res, '<h2> Hello </h2>', 'callback')
*
* if (chunk) {
* nodeRes.etag(res, chunk)
*
* if (nodeReq.fresh(req, res)) {
* chunk = null
* nodeRes.status(304)
* }
*
* nodeRes.end(chunk)
* }
* ```
*/
Response.prepareJsonp = function (res, body, callbackFn) {
Response.header(res, 'X-Content-Type-Options', 'nosniff')
Response.safeHeader(res, 'Content-Type', 'text/javascript; charset=utf-8')
const parsedBody = JSON
.stringify(body)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
/**
* setting up callbackFn on response body , typeof will make
* sure not to throw error of client if callbackFn is not
* a function
*/
return '/**/ typeof ' + callbackFn + " === 'function' && " + callbackFn + '(' + parsedBody + ');'
}
/**
* Returns the HTTP response with `Content-type`
* set to `application/json`.
*
* @method json
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {Object} body
* @param {Boolean} [generateEtag = true]
*
* @return {void}
*
* @example
* ```js
* nodeRes.json(req, res, { name: 'virk' })
* nodeRes.json(req, res, [ 'virk', 'joe' ])
* ```
*/
Response.json = function (req, res, body, generateEtag) {
Response.safeHeader(res, 'Content-Type', 'application/json; charset=utf-8')
Response.send(req, res, body, generateEtag)
}
/**
* Make JSONP response with `Content-type` set to
* `text/javascript`.
*
* @method jsonp
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {Object} body
* @param {String} [callbackFn = 'callback']
* @param {Boolean} [generateEtag = true]
*
* @return {void}
*
* @example
* ```js
* nodeRes.jsonp(req, res, { name: 'virk' }, 'callback')
* ```
*/
Response.jsonp = function (req, res, body, callbackFn = 'callback', generateEtag) {
Response.send(req, res, Response.prepareJsonp(res, body, callbackFn), generateEtag)
}
/**
* Set `Location` header on the HTTP response.
*
* @method location
*
* @param {http.ServerResponse} res
* @param {String} url
*
* @return {void}
*/
Response.location = function (res, url) {
Response.header(res, 'Location', url)
}
/**
* Redirect the HTTP request to the given url.
*
* @method redirect
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {String} url
* @param {Number} [status = 302]
*
* @return {void}
*
* @example
* ```js
* nodeRes.redirect(req, res, '/')
* ```
*/
Response.redirect = function (req, res, url, status = 302) {
const body = ''
Response.status(res, status)
Response.location(res, url)
Response.send(req, res, body)
}
/**
* Add vary header to the HTTP response.
*
* @method vary
*
* @param {http.ServerResponse} res
* @param {String} field
*
* @return {void}
*/
Response.vary = function (res, field) {
vary(res, field)
}
/**
* Set content type header by looking up the actual
* type and setting charset to utf8.
*
* ### Note
* When defining custom charset, you must set pass the complete
* content type, otherwise `false` will be set as the
* content-type header.
*
* @method type
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {String} [charset]
* @return {void}
*
* @example
* ```js
* nodeRes.type(res, 'html')
*
* nodeRes.type(res, 'json')
*
* nodeRes.type(res, 'text/html', 'ascii')
* ```
*/
Response.type = function (res, type, charset) {
type = charset ? `${type}; charset=${charset}` : type
Response.safeHeader(res, 'Content-Type', mime.contentType(type))
}
/**
* Pipe stream to the response. Also this method will make sure
* to destroy the stream, if request gets cancelled.
*
* The promise resolve when response finishes and rejects, when
* stream raises errors.
*
* @method stream
*
* @param {Object} res
* @param {Stream} body
*
* @returns {Promise}
*
* @example
* ```js
* Response.stream(res, fs.createReadStream('foo.txt'))
*
* // handle stream errors
* Response
* .stream(res, fs.createReadStream('foo.txt'))
* .catch((error) => {
* })
* ```
*/
Response.stream = function (res, body) {
return new Promise((resolve, reject) => {
if (typeof (body.pipe) !== 'function') {
reject(new Error('Body is not a valid stream'))
return
}
let finished = false
/**
* Error in stream
*/
body.on('error', (error) => {
if (finished) {
return
}
finished = true
destroy(body)
reject(error)
})
/**
* Consumed stream
*/
body.on('end', resolve)
/**
* Written response
*/
onFinished(res, function () {
finished = true
destroy(body)
})
/**
* Pipe to res
*/
body.pipe(res)
})
}