Created
June 22, 2017 13:17
-
-
Save delvedor/e4410c069af807b1f6f04f72396613b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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