You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
3.8 KiB
148 lines
3.8 KiB
4 years ago
|
'use strict'
|
||
|
|
||
|
const http = require('http')
|
||
|
const { Writable } = require('readable-stream')
|
||
|
const util = require('util')
|
||
|
|
||
|
const setCookie = require('set-cookie-parser')
|
||
|
|
||
|
function Response (req, onEnd, reject) {
|
||
|
http.ServerResponse.call(this, {
|
||
|
method: req.method,
|
||
|
httpVersionMajor: 1,
|
||
|
httpVersionMinor: 1
|
||
|
})
|
||
|
|
||
|
this._lightMyRequest = { headers: null, trailers: {}, payloadChunks: [] }
|
||
|
// This forces node@8 to always render the headers
|
||
|
this.setHeader('foo', 'bar'); this.removeHeader('foo')
|
||
|
|
||
|
this.assignSocket(getNullSocket())
|
||
|
|
||
|
this._promiseCallback = typeof reject === 'function'
|
||
|
|
||
|
const onEndSuccess = (payload) => {
|
||
|
if (this._promiseCallback) {
|
||
|
return process.nextTick(() => onEnd(payload))
|
||
|
}
|
||
|
process.nextTick(() => onEnd(null, payload))
|
||
|
}
|
||
|
|
||
|
const onEndFailure = (err) => {
|
||
|
if (this._promiseCallback) {
|
||
|
return process.nextTick(() => reject(err))
|
||
|
}
|
||
|
process.nextTick(() => onEnd(err, null))
|
||
|
}
|
||
|
|
||
|
this.once('finish', () => {
|
||
|
const res = generatePayload(this)
|
||
|
res.raw.req = req
|
||
|
onEndSuccess(res)
|
||
|
})
|
||
|
|
||
|
this.connection.once('error', onEndFailure)
|
||
|
|
||
|
this.once('error', onEndFailure)
|
||
|
}
|
||
|
|
||
|
util.inherits(Response, http.ServerResponse)
|
||
|
|
||
|
Response.prototype.setTimeout = function (msecs, callback) {
|
||
|
this.timeoutHandle = setTimeout(() => {
|
||
|
this.emit('timeout')
|
||
|
}, msecs)
|
||
|
this.on('timeout', callback)
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
Response.prototype.writeHead = function () {
|
||
|
const result = http.ServerResponse.prototype.writeHead.apply(this, arguments)
|
||
|
|
||
|
const _headers = this.getHeaders ? this.getHeaders() : this._headers
|
||
|
this._lightMyRequest.headers = Object.assign({}, _headers)
|
||
|
|
||
|
// Add raw headers
|
||
|
;['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => {
|
||
|
const regex = new RegExp('\\r\\n' + name + ': ([^\\r]*)\\r\\n')
|
||
|
const field = this._header.match(regex)
|
||
|
if (field) {
|
||
|
this._lightMyRequest.headers[name.toLowerCase()] = field[1]
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
Response.prototype.write = function (data, encoding, callback) {
|
||
|
if (this.timeoutHandle) {
|
||
|
clearTimeout(this.timeoutHandle)
|
||
|
}
|
||
|
http.ServerResponse.prototype.write.call(this, data, encoding, callback)
|
||
|
this._lightMyRequest.payloadChunks.push(Buffer.from(data, encoding))
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
Response.prototype.end = function (data, encoding, callback) {
|
||
|
if (data) {
|
||
|
this.write(data, encoding)
|
||
|
}
|
||
|
|
||
|
http.ServerResponse.prototype.end.call(this, callback)
|
||
|
this.emit('finish')
|
||
|
}
|
||
|
|
||
|
Response.prototype.destroy = function () {}
|
||
|
|
||
|
Response.prototype.addTrailers = function (trailers) {
|
||
|
for (const key in trailers) {
|
||
|
this._lightMyRequest.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function generatePayload (response) {
|
||
|
// Prepare response object
|
||
|
const res = {
|
||
|
raw: {
|
||
|
res: response
|
||
|
},
|
||
|
headers: response._lightMyRequest.headers,
|
||
|
statusCode: response.statusCode,
|
||
|
statusMessage: response.statusMessage,
|
||
|
trailers: {},
|
||
|
get cookies () {
|
||
|
return setCookie.parse(this)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Prepare payload and trailers
|
||
|
const rawBuffer = Buffer.concat(response._lightMyRequest.payloadChunks)
|
||
|
res.rawPayload = rawBuffer
|
||
|
|
||
|
// we keep both of them for compatibility reasons
|
||
|
res.payload = rawBuffer.toString()
|
||
|
res.body = res.payload
|
||
|
res.trailers = response._lightMyRequest.trailers
|
||
|
|
||
|
// Prepare payload parsers
|
||
|
res.json = function parseJsonPayload () {
|
||
|
if (this.headers['content-type'].indexOf('application/json') < 0) {
|
||
|
throw new Error('The content-type of the response is not application/json')
|
||
|
}
|
||
|
return JSON.parse(this.payload)
|
||
|
}
|
||
|
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
// Throws away all written data to prevent response from buffering payload
|
||
|
function getNullSocket () {
|
||
|
return new Writable({
|
||
|
write (chunk, encoding, callback) {
|
||
|
setImmediate(callback)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
module.exports = Response
|