diff --git a/doc/api/http.markdown b/doc/api/http.markdown index 8ab9877a2e536b..e92fd38b406707 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -640,6 +640,26 @@ passed as the second parameter to the `'request'` event. The response implements the [Writable Stream][] interface. This is an [`EventEmitter`][] with the following events: +### Event: 'beforeFlushingHead' + +`function (messageHead) {}` + +Indicates that the server is about to flush the HTTP headers to the underlying +socket. This event can be used for measuring time to first byte or customizing +the response headers and status code. + +The `messageHead` parameter allows overriding the http response code, message and headers. +It includes + +* `responseCode` {Number} +* `responseMessage` {String} +* `headers` {Object|Array} + +The headers member will be either an object of the form +`{header-names: header value}` +or an array of the form +`[['header-name', 'header-value'] ...]` + ### Event: 'close' `function () { }` diff --git a/lib/_http_server.js b/lib/_http_server.js index f337480ec2dd75..a3e03846348780 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -160,34 +160,38 @@ ServerResponse.prototype._implicitHeader = function() { ServerResponse.prototype.writeHead = function(statusCode, reason, obj) { var headers; - if (typeof reason === 'string') { - // writeHead(statusCode, reasonPhrase[, headers]) - this.statusMessage = reason; - } else { + // writeHead(statusCode, reasonPhrase[, headers]) + var args = {statusCode: statusCode, statusMessage: reason, headers: obj}; + + if (typeof args.statusMessage !== 'string') { // writeHead(statusCode[, headers]) - this.statusMessage = + args.statusMessage = this.statusMessage || STATUS_CODES[statusCode] || 'unknown'; - obj = reason; + args.headers = reason; } - this.statusCode = statusCode; + + args.headers = args.headers || {}; if (this._headers) { // Slow-case: when progressive API and header fields are passed. - if (obj) { - var keys = Object.keys(obj); + if (args.headers) { + var keys = Object.keys(args.headers); for (var i = 0; i < keys.length; i++) { var k = keys[i]; - if (k) this.setHeader(k, obj[k]); + if (k) this.setHeader(k, args.headers[k]); } } // only progressive api is used - headers = this._renderHeaders(); - } else { - // only writeHead() called - headers = obj; + args.headers = this._renderHeaders(); } - var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + + this.emit('beforeFlushingHead', args); + + this.statusMessage = args.statusMessage; + this.statusCode = args.statusCode; + headers = args.headers; + + var statusLine = 'HTTP/1.1 ' + this.statusCode.toString() + ' ' + this.statusMessage + CRLF; if (statusCode === 204 || statusCode === 304 || diff --git a/test/parallel/test-http-before-flush-head-event.js b/test/parallel/test-http-before-flush-head-event.js new file mode 100644 index 00000000000000..e5e11ab8fd9ea4 --- /dev/null +++ b/test/parallel/test-http-before-flush-head-event.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'), + assert = require('assert'), + http = require('http'); + +var testResBody = 'other stuff!\n'; + +var server = http.createServer(function(req, res) { + res.on('beforeFlushingHead', function(args) { + args.statusCode = 201; + args.statusMessage = 'changed to show we can'; + args.headers['Flush-Head'] = 'event-was-called'; + }); + res.writeHead(200, { + 'Content-Type': 'text/plain' + }); + res.end(testResBody); +}); +server.listen(common.PORT); + + +server.addListener('listening', function() { + var options = { + port: common.PORT, + path: '/', + method: 'GET' + }; + var req = http.request(options, function(res) { + assert.ok(res.statusCode === 201, + 'Response status code was not overridden from 200 to 201.'); + assert.ok(res.statusMessage === 'changed to show we can', + 'Response status message was not overridden.'); + assert.ok('flush-head' in res.headers, + 'Response headers didn\'t contain the flush-head header, ' + + 'indicating the beforeFlushingHead event was not called or ' + + 'did not allow adding headers.'); + assert.ok(res.headers['flush-head'] === 'event-was-called', + 'Response headers didn\'t contain the flush-head header ' + + 'with value event-was-called, indicating the ' + + 'beforeFlushingHead event was not called or did not allow ' + + 'adding headers.'); + res.addListener('end', function() { + server.close(); + process.exit(); + }); + res.resume(); + }); + req.end(); +});