diff --git a/lib/http.js b/lib/http.js index 46c37ca390..476316743f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -264,9 +264,6 @@ function OutgoingMessage (socket) { this.shouldKeepAlive = true; this.useChunkedEncodingByDefault = true; - this._headerFlushed = false; - this._header = null; // to be filled by _storeHeader - this._hasBody = true; this.finished = false; @@ -277,38 +274,66 @@ exports.OutgoingMessage = OutgoingMessage; // This abstract either writing directly to the socket or buffering it. // Rename to _writeRaw() ? OutgoingMessage.prototype._send = function (data, encoding) { - if (this.connection._outgoing[0] === this && - this.connection.writable && - this.output.length === 0) - { + // This is a shameful hack to get the headers and first body chunk onto + // the same packet. Future versions of Node are going to take care of + // this at a lower level and in a more general way. + if (!this._headerSent) { + if (typeof data === 'string') { + data = this._header + data; + } else { + this.output.unshift(this._header); + this.outputEncodings.unshift('ascii'); + } + this._headerSent = true; + } + + if (this.connection._outgoing[0] === this && this.connection.writable) { + // There might be pending data in the this.output buffer. + while (this.output.length) { + if (!this.connection.writable) { + this._buffer(data, encoding); + return false; + } + var c = this.output.shift(); + var e = this.outputEncodings.shift(); + this.connection.write(c, e); + } + // Directly write to socket. return this.connection.write(data, encoding); } else { - // Buffer - var length = this.output.length; - - if (length === 0 || typeof data != 'string') { - this.output.push(data); - encoding = encoding || "ascii"; - this.outputEncodings.push(encoding); - return false; - } + this._buffer(data, encoding); + return false; + } +}; - var lastEncoding = this.outputEncodings[length-1]; - var lastData = this.output[length-1]; +OutgoingMessage.prototype._buffer = function (data, encoding) { + // Buffer + if (data.length === 0) return; - if ((lastEncoding === encoding) || - (!encoding && data.constructor === lastData.constructor)) { - this.output[length-1] = lastData + data; - return false; - } + var length = this.output.length; + if (length === 0 || typeof data != 'string') { this.output.push(data); encoding = encoding || "ascii"; this.outputEncodings.push(encoding); + return false; + } + var lastEncoding = this.outputEncodings[length-1]; + var lastData = this.output[length-1]; + + if ((lastEncoding === encoding) || + (!encoding && data.constructor === lastData.constructor)) { + this.output[length-1] = lastData + data; return false; } + + this.output.push(data); + encoding = encoding || "ascii"; + this.outputEncodings.push(encoding); + + return false; }; @@ -377,9 +402,8 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) { } } - messageHeader += CRLF; - - this._header = messageHeader; + this._header = messageHeader + CRLF; + this._headerSent = false; // wait until the first body chunk, or close(), is sent to flush. }; @@ -391,7 +415,7 @@ OutgoingMessage.prototype.sendBody = function () { OutgoingMessage.prototype.write = function (chunk, encoding) { if (!this._header) { - throw new Error("writeHead() must be called before write()") + throw new Error("You have to call writeHead() before write()"); } if (!this._hasBody) { @@ -404,13 +428,6 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { throw new TypeError("first argument must be a string, Array, or Buffer"); } - // write the header - - if (!this._headerFlushed) { - this._send(this._header); - this._headerFlushed = true; - } - if (chunk.length === 0) return false; var len, ret; @@ -451,22 +468,50 @@ OutgoingMessage.prototype.close = function (data, encoding) { OutgoingMessage.prototype.end = function (data, encoding) { var ret; - // maybe the header hasn't been sent. if not send it. - if (!this._headerFlushed) { - ret = this._send(this._header); - this._headerFlushed = true; - } - if (data) { + var hot = this._headerSent === false + && typeof(data) === "string" + && data.length > 0 + && this.output.length === 0 + && this.connection.writable + && this.connection._outgoing[0] === this + ; + + if (hot) { + // Hot path. They're doing + // res.writeHead(); + // res.end(blah); + // HACKY. + if (this.chunkedEncoding) { + var l = Buffer.byteLength(data, encoding).toString(16); + ret = this.connection.write( this._header + + l + + CRLF + + data + + "\r\n0\r\n\r\n" + , encoding + ); + } else { + ret = this.connection.write(this._header + data, encoding); + } + this._headerSent = true; + + } else if (data) { + // Normal body write. ret = this.write(data, encoding); } - this.finished = true; - - if (this.chunkedEncoding) { - ret = this._send("0\r\n\r\n"); // last chunk + if (!hot) { + if (this.chunkedEncoding) { + ret = this._send('0\r\n\r\n'); // Last chunk. + } else if (!data) { + // Force a flush, HACK. + ret = this._send(''); + } } + this.finished = true; + // There is the first message on the outgoing queue, and we've sent // everything to the socket. if (this.output.length === 0 && this.connection._outgoing[0] === this) { @@ -638,11 +683,13 @@ function httpSocketSetup (socket) { // An array of outgoing messages for the socket. In pipelined connections // we need to keep track of the order they were sent. socket._outgoing = []; + socket.__destroyOnDrain = false; // NOTE: be sure not to use ondrain elsewhere in this file! socket.ondrain = function () { var message = socket._outgoing[0]; if (message) message.emit('drain'); + if (socket.__destroyOnDrain) socket.destroy(); }; } @@ -740,7 +787,14 @@ function connectionListener (socket) { var message = socket._outgoing.shift(); if (message._last) { // No more messages to be pushed out. - socket.end(); + + // HACK: need way to do this with socket interface + if (socket._writeQueue.length) { + socket.__destroyOnDrain = true; //socket.end(); + } else { + socket.destroy(); + } + } else if (socket._outgoing.length) { // Push out the next message. outgoingFlush(socket); @@ -1002,7 +1056,7 @@ exports.cat = function (url, encoding_, headers_) { client.end(); return; } - res.setBodyEncoding(encoding); + res.setEncoding(encoding); res.addListener('data', function (chunk) { content += chunk; }); res.addListener('end', function () { if (callback && !callbackSent) { diff --git a/test/simple/test-http-304.js b/test/simple/test-http-304.js index 896c932141..8ffa07d426 100644 --- a/test/simple/test-http-304.js +++ b/test/simple/test-http-304.js @@ -7,7 +7,7 @@ var sys = require('sys'), s = http.createServer(function (request, response) { response.writeHead(304); response.end(); -}) +}); s.listen(PORT); sys.puts('Server running at http://127.0.0.1:'+PORT+'/')