diff --git a/lib/ServerResponse.js b/lib/ServerResponse.js index 1a8c16a..a8df30c 100644 --- a/lib/ServerResponse.js +++ b/lib/ServerResponse.js @@ -6,7 +6,19 @@ function ServerResponse(socket) { this._socket = socket; this.sendDate = true; var self = this; + // Pass on internal socket events up to listeners on the Response object + // (which is what's exposed in the connect's http pipeline) + this._socket.on('finish', function () { self.emit('finish'); }); this._socket.on('close', function (had_error) { self.emit('close', had_error); }); + // Socket will raise 'header' event when headers have been committed. This is + // consistent with how 'connect' library patches ServerResponse. + this._socket.on('header', function () { + // Additionally it patches the .headerSent property, so we don't set it directly but via ._header + // (see 'patch.js' in 'connect' module lib folder). + self.headersSent = self._header = true; + // _headers is also assumed on the response object by 'connect' module + self._headers = self._socket._requestContext.headers; + }); }; util.inherits(ServerResponse, events.EventEmitter); @@ -21,7 +33,12 @@ ServerResponse.prototype.destroy = function (error) { }; ServerResponse.prototype.write = function(chunk, encoding, isEnd) { - this._socket._requestContext.responseStarted = true; + // ensure statusCode property is set on the Response object (as + // per node's documentation), and allow middleware to set it + if (!this._socket._requestContext.responseStarted) { + this._socket._requestContext.responseStarted = true; + this._socket._requestContext.statusCode = this.statusCode = this.statusCode || this._socket._requestContext.statusCode; + } return this._socket.write(chunk, encoding, isEnd); }; diff --git a/lib/Socket.js b/lib/Socket.js index 8c22349..446fa5a 100644 --- a/lib/Socket.js +++ b/lib/Socket.js @@ -341,6 +341,12 @@ Socket.prototype._queue_body_chunk = function (chunk, encoding, isEnd) }; Socket.prototype._on_written = function () { + // When all chunks have been sent, and we are at the end (isLastChunk is true), + // then this is the last callback from the native module, and we should emit + // a 'finish' event for whoever is listening (e.g. connect.logger middleware). + if (!this._requestContext.chunks && this._requestContext.isLastChunk) + this.emit('finish'); + if (this._requestContext.drainEventPending && !this._requestContext.chunks) { delete this._requestContext.drainEventPending; if (!this._closed) { @@ -373,6 +379,7 @@ Socket.prototype._initiate_send_next = function () { // Initiate sending HTTP response headers and body, if any. this._requestContext.headersWritten = true; + this.emit('header'); try { this._requestContext.asyncPending('res', httpsys.httpsys_write_headers(this._requestContext)); diff --git a/test/111_connect_stack.js b/test/111_connect_stack.js new file mode 100644 index 0000000..8341532 --- /dev/null +++ b/test/111_connect_stack.js @@ -0,0 +1,86 @@ +var http = require('../lib/httpsys.js').http() + , https = require('../lib/httpsys.js').https() + , fs = require('fs') + , assert = require('assert') + , connect = require('connect'); + +var port = process.env.PORT || 3102; +var server; + +describe('111_connect_stack.js: connect stack integration', function () { + + afterEach(function (done) { + if (server) { + server.close(function () { + done(); + server = undefined; + }); + } + else { + done(); + } + }); + + it('works with connect.logger', function (done) { + var log = ''; + server = http.createServer(connect() + .use(connect.logger({ + stream: { + write: function(data) { log += data; } + } + })) + .use(function(req, res, next) { + res.setHeader('content-type', 'text/plain'); + if (req.url === '/test') { + res.statusCode = 304; + res.end(); + } else { + res.statusCode = 200; + res.setHeader('content-length', 14); + res.end('This is a test'); + } + }) + ); + + server.listen(port); + + log = ''; + sendAndCheck('/', 'hello', function(res, body) { + assert.equal(res.statusCode, 200); + assert.equal(res.headers['content-type'], 'text/plain'); + assert.equal(body, 'This is a test'); + assert(log.indexOf('\"GET / HTTP/1.1\" 200 14') >= 0, 'Expecting proper log message: ' + log); + + log = ''; + sendAndCheck('/test', '', function(res, body) { + assert.equal(res.statusCode, 304); + assert.equal(res.headers['content-type'], 'text/plain'); + assert.equal(body, ''); + assert(log.indexOf('\"GET /test HTTP/1.1\" 304 -') >= 0, 'Expecting proper log message: ' + log); + + done(); + }); + }); + }); + +}); + +function sendAndCheck(reqPath, reqBody, resCallback) { + var options = { + hostname: 'localhost', + port: port, + path: reqPath, + method: 'GET' + }; + + var request = http.request(options, function (res) { + var body = ''; + res.on('data', function (chunk) { body += chunk; }); + res.on('end', function () { + resCallback(res, body); + }); + }); + + request.on('error', assert.ifError); + request.end(); +} \ No newline at end of file