Skip to content

Instantly share code, notes, and snippets.

@delvedor
Created June 22, 2017 13:17
Show Gist options
  • Save delvedor/e4410c069af807b1f6f04f72396613b7 to your computer and use it in GitHub Desktop.
Save delvedor/e4410c069af807b1f6f04f72396613b7 to your computer and use it in GitHub Desktop.
/* eslint-disable no-useless-return */
'use strict'
const pump = require('pump')
const safeStringify = require('fast-safe-stringify')
const xtend = require('xtend')
const validation = require('./validation')
const serialize = validation.serialize
const statusCodes = require('./status-codes.json')
const stringify = JSON.stringify
const runHooks = require('fastseries')()
function Reply (req, res, handle) {
this.res = res
this.handle = handle // aka Store
this._req = req
this.sent = false
this._serializer = null
}
/**
* Instead of using directly res.end(), we are using setImmediate(…)
* This because we have observed that with this technique we are faster at responding to the various requests,
* since the setImmediate forwards the res.end at the end of the poll phase of libuv in the event loop.
* So we can gather multiple requests and then handle all the replies in a different moment,
* causing a general improvement of performances, ~+10%.
*/
Reply.prototype.send = function (payload) {
if (this.sent) {
throw new Error('Reply already sent')
}
if (!payload) {
if (!this.res.statusCode) {
this.res.statusCode = 204
}
this.res.setHeader('Content-Length', '0')
setImmediate(handleReplyEnd, this, '')
return
}
if (payload && payload.isBoom) {
this._req.log.error(payload)
this.res.statusCode = payload.output.statusCode
this.headers(payload.output.headers)
setImmediate(
handleReplyEnd,
this,
safeStringify(payload.output.payload)
)
return
}
if (payload instanceof Error) {
if (!this.res.statusCode || this.res.statusCode < 400) {
this.res.statusCode = 500
}
this._req.log.error(payload)
setImmediate(
handleReplyEnd,
this,
stringify(xtend({
error: statusCodes[this.res.statusCode + ''],
message: payload.message,
statusCode: this.res.statusCode
}, this._extendServerError && this._extendServerError()))
)
return
}
if (typeof payload.then === 'function') {
return payload.then(wrapReplySend(this)).catch(wrapReplySend(this))
}
if (payload._readableState) {
if (!this.res.getHeader('Content-Type')) {
this.res.setHeader('Content-Type', 'application/octet-stream')
}
return pump(payload, this.res, wrapPumpCallback(this))
}
if (!this.res.getHeader('Content-Type') || this.res.getHeader('Content-Type') === 'application/json') {
this.res.setHeader('Content-Type', 'application/json')
// Here we are assuming that the custom serializer is a json serializer
const str = this._serializer ? this._serializer(payload) : serialize(this.handle, payload, this.res.statusCode)
if (!this.res.getHeader('Content-Length')) {
this.res.setHeader('Content-Length', Buffer.byteLength(str))
}
// onResponse hook
handleReplyEnd(this, str)
return
}
// All the code below must have a 'content-type' setted
if (this._serializer) {
setImmediate(wrapReplyEnd, this, this._serializer(payload))
return
}
setImmediate(wrapReplyEnd, this, payload)
return
}
Reply.prototype.header = function (key, value) {
this.res.setHeader(key, value)
return this
}
Reply.prototype.headers = function (headers) {
var keys = Object.keys(headers)
for (var i = 0; i < keys.length; i++) {
this.header(keys[i], headers[keys[i]])
}
return this
}
Reply.prototype.code = function (code) {
this.res.statusCode = code
return this
}
Reply.prototype.serializer = function (fn) {
this._serializer = fn
return this
}
function wrapPumpCallback (reply) {
return function pumpCallback (err) {
if (err) {
reply._req.log.error(err)
} else {
console.log('CALL THE HOOK!!!!!!')
}
}
}
function handleReplyEnd (that, payload) {
setImmediate(
runHooks,
new State(that, payload),
hookIterator,
that.handle.hooks.onResponse,
wrapReplyEnd
)
}
function wrapReplyEnd (err) {
if (err) {
this._req.log.error(err)
}
this.sent = true
this.res.end(this.payload)
}
function wrapReplySend (reply, payload) {
return function send (payload) {
return handleReplyEnd(reply._req, reply._res, payload)
}
}
function buildReply (R) {
function _Reply (req, res, handle) {
this.res = res
this.handle = handle
this._req = req
this.sent = false
this._serializer = null
}
_Reply.prototype = new R()
return _Reply
}
function State (that, payload) {
this.req = that._req
this.res = that.res
this.sent = that.sent
this.payload = payload
}
function hookIterator (fn, cb) {
fn(this._req, this.res, cb)
}
module.exports = Reply
module.exports.buildReply = buildReply
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment