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.
		
		
		
		
		
			
		
			
				
					176 lines
				
				4.3 KiB
			
		
		
			
		
	
	
					176 lines
				
				4.3 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const http = require('http')
							 | 
						||
| 
								 | 
							
								const { Writable } = require('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'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let called = false
							 | 
						||
| 
								 | 
							
								  const onEndSuccess = (payload) => {
							 | 
						||
| 
								 | 
							
								    // no need to early-return if already called because this handler is bound `once`
							 | 
						||
| 
								 | 
							
								    called = true
							 | 
						||
| 
								 | 
							
								    if (this._promiseCallback) {
							 | 
						||
| 
								 | 
							
								      return process.nextTick(() => onEnd(payload))
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    process.nextTick(() => onEnd(null, payload))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const onEndFailure = (err) => {
							 | 
						||
| 
								 | 
							
								    if (called) return
							 | 
						||
| 
								 | 
							
								    called = true
							 | 
						||
| 
								 | 
							
								    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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.once('close', 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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  copyHeaders(this)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // We need to emit 'close' otherwise stream.finished() would
							 | 
						||
| 
								 | 
							
								  // not pick it up on Node v16
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.destroy()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Response.prototype.destroy = function (error) {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) return
							 | 
						||
| 
								 | 
							
								  this.destroyed = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (error) {
							 | 
						||
| 
								 | 
							
								    process.nextTick(() => this.emit('error', error))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  process.nextTick(() => this.emit('close'))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Response.prototype.addTrailers = function (trailers) {
							 | 
						||
| 
								 | 
							
								  for (const key in trailers) {
							 | 
						||
| 
								 | 
							
								    this._lightMyRequest.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim()
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function generatePayload (response) {
							 | 
						||
| 
								 | 
							
								  if (response._lightMyRequest.headers === null) {
							 | 
						||
| 
								 | 
							
								    copyHeaders(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 (res.headers['content-type'].indexOf('application/json') < 0) {
							 | 
						||
| 
								 | 
							
								      throw new Error('The content-type of the response is not application/json')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return JSON.parse(res.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)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function copyHeaders (response) {
							 | 
						||
| 
								 | 
							
								  response._lightMyRequest.headers = Object.assign({}, response.getHeaders())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Add raw headers
							 | 
						||
| 
								 | 
							
								  ;['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => {
							 | 
						||
| 
								 | 
							
								    const regex = new RegExp('\\r\\n' + name + ': ([^\\r]*)\\r\\n')
							 | 
						||
| 
								 | 
							
								    const field = response._header.match(regex)
							 | 
						||
| 
								 | 
							
								    if (field) {
							 | 
						||
| 
								 | 
							
								      response._lightMyRequest.headers[name.toLowerCase()] = field[1]
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = Response
							 |