From: isaacs Date: Sat, 25 May 2013 00:16:08 +0000 (-0700) Subject: Merge remote-tracking branch 'ry/v0.10' X-Git-Tag: v0.11.3~73 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ba048e72b02e17f0c73e0dcb7d37e76a03327c5a;p=platform%2Fupstream%2Fnodejs.git Merge remote-tracking branch 'ry/v0.10' Conflicts: AUTHORS ChangeLog configure deps/uv/ChangeLog deps/uv/src/unix/darwin.c deps/uv/src/unix/stream.c deps/uv/src/version.c deps/v8/src/isolate.cc deps/v8/src/version.cc lib/http.js src/node_version.h --- ba048e72b02e17f0c73e0dcb7d37e76a03327c5a diff --cc AUTHORS index 131b1ad,42fdacc..eb271ac --- a/AUTHORS +++ b/AUTHORS @@@ -454,4 -449,4 +454,5 @@@ Robert Kowalski Ryuichi Okumura Brandon Frohs +Nick Sullivan + Nathan Zadoks diff --cc ChangeLog index baa9a0a,2fb4557..81e7930 --- a/ChangeLog +++ b/ChangeLog @@@ -1,65 -1,26 +1,88 @@@ -2013.05.24, Version 0.10.8 (Stable) +2013.05.13, Version 0.11.2 (Unstable) + +* uv: Upgrade to 0.11.2 + +* V8: Upgrade to 3.19.0 + +* npm: Upgrade to 1.2.21 + +* build: Makefile should respect configure --prefix (Timothy J Fontaine) + +* cluster: use round-robin load balancing (Ben Noordhuis) + +* debugger, cluster: each worker has new debug port (Miroslav Bajtoš) + +* debugger: `restart` with custom debug port (Miroslav Bajtoš) + +* debugger: breakpoints in scripts not loaded yet (Miroslav Bajtoš) + +* event: EventEmitter#setMaxListeners() returns this (Sam Roberts) + +* events: add EventEmitter.defaultMaxListeners (Ben Noordhuis) + +* install: Support $(PREFIX) install target directory prefix (Olof Johansson) + +* os: Include netmask in os.networkInterfaces() (Ben Kelly) + +* path: add path.isAbsolute(path) (Ryan Doenges) + +* stream: Guarantee ordering of 'finish' event (isaacs) + +* streams: introduce .cork/.uncork/._writev (Fedor Indutny) + +* vm: add support for timeout argument (Andrew Paprocki) + + +2013.04.19, Version 0.11.1 (Unstable), 4babd2b46ebf9fbea2c9946af5cfae25a33b2b22 + +* V8: upgrade to 3.18.0 + +* uv: Upgrade to v0.11.1 + +* http: split into multiple separate modules (Timothy J Fontaine) + +* http: escape unsafe characters in request path (Ben Noordhuis) + +* url: Escape all unwise characters (isaacs) + +* build: depend on v8 postmortem-metadata if enabled (Paddy Byers) + +* etw: update prototypes to match dtrace provider (Timothy J Fontaine) + +* buffer: change output of Buffer.prototype.toJSON() (David Braun) + +* dtrace: actually use the _handle.fd value (Timothy J Fontaine) + +* dtrace: pass more arguments to probes (Dave Pacheco) + +* build: allow building with dtrace on osx (Dave Pacheco) + +* zlib: allow passing options to convenience methods (Kyle Robinson Young) + + ++2013.05.24, Version 0.10.8 (Stable), 30d9e9fdd9d4c33d3d95a129d021cd8b5b91eddb + + * v8: update to 3.14.5.9 + + * uv: upgrade to 0.10.8 + + * npm: Upgrade to 1.2.23 + + * http: remove bodyHead from 'upgrade' events (Nathan Zadoks) + + * http: Return true on empty writes, not false (isaacs) + + * http: save roundtrips, convert buffers to strings (Ben Noordhuis) + + * configure: respect the --dest-os flag consistently (Nathan Rajlich) + + * buffer: throw when writing beyond buffer (Trevor Norris) + + * crypto: Clear error after DiffieHellman key errors (isaacs) + + * string_bytes: strip padding from base64 strings (Trevor Norris) + + 2013.05.17, Version 0.10.7 (Stable), d2fdae197ac542f686ee06835d1153dd43b862e5 * uv: upgrade to v0.10.7 diff --cc configure index 7fb2588,4747dd2..012b77f --- a/configure +++ b/configure @@@ -462,11 -469,9 +463,11 @@@ def configure_node(o) # By default, enable DTrace on SunOS systems. Don't allow it on other # systems, since it won't work. (The MacOS build process is different than # SunOS, and we haven't implemented it.) - if sys.platform.startswith('sunos') or sys.platform.startswith('darwin'): + if flavor in ('solaris', 'mac'): o['variables']['node_use_dtrace'] = b(not options.without_dtrace) + o['variables']['uv_use_dtrace'] = o['variables']['node_use_dtrace'] + o['variables']['uv_parent_path'] = '/deps/uv/' - elif sys.platform.startswith('linux'): + elif flavor == 'linux': o['variables']['node_use_dtrace'] = 'false' o['variables']['node_use_systemtap'] = b(options.with_dtrace) if options.systemtap_includes: diff --cc lib/_http_client.js index 339a5c7,0000000..e02cb4f mode 100644,000000..100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@@ -1,496 -1,0 +1,501 @@@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var util = require('util'); +var net = require('net'); +var EventEmitter = require('events').EventEmitter; +var HTTPParser = process.binding('http_parser').HTTPParser; +var assert = require('assert').ok; + ++// an empty buffer for UPGRADE/CONNECT bodyHead compatibility ++var emptyBuffer = new Buffer(0); ++ +var common = require('_http_common'); + +var httpSocketSetup = common.httpSocketSetup; +var parsers = common.parsers; +var freeParser = common.freeParser; +var debug = common.debug; + +var IncomingMessage = require('_http_incoming').IncomingMessage; +var OutgoingMessage = require('_http_outgoing').OutgoingMessage; + +var agent = require('_http_agent'); +var Agent = agent.Agent; +var globalAgent = agent.globalAgent; + + +function ClientRequest(options, cb) { + var self = this; + OutgoingMessage.call(self); + + self.agent = options.agent === undefined ? globalAgent : options.agent; + + var defaultPort = options.defaultPort || 80; + + var port = options.port || defaultPort; + var host = options.hostname || options.host || 'localhost'; + + if (options.setHost === undefined) { + var setHost = true; + } + + self.socketPath = options.socketPath; + + var method = self.method = (options.method || 'GET').toUpperCase(); + self.path = options.path || '/'; + if (cb) { + self.once('response', cb); + } + + if (!Array.isArray(options.headers)) { + if (options.headers) { + var keys = Object.keys(options.headers); + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + self.setHeader(key, options.headers[key]); + } + } + if (host && !this.getHeader('host') && setHost) { + var hostHeader = host; + if (port && +port !== defaultPort) { + hostHeader += ':' + port; + } + this.setHeader('Host', hostHeader); + } + } + + if (options.auth && !this.getHeader('Authorization')) { + //basic auth + this.setHeader('Authorization', 'Basic ' + + new Buffer(options.auth).toString('base64')); + } + + if (method === 'GET' || method === 'HEAD' || method === 'CONNECT') { + self.useChunkedEncodingByDefault = false; + } else { + self.useChunkedEncodingByDefault = true; + } + + if (Array.isArray(options.headers)) { + self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n', + options.headers); + } else if (self.getHeader('expect')) { + self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n', + self._renderHeaders()); + } + if (self.socketPath) { + self._last = true; + self.shouldKeepAlive = false; + if (options.createConnection) { + self.onSocket(options.createConnection(self.socketPath)); + } else { + self.onSocket(net.createConnection(self.socketPath)); + } + } else if (self.agent) { + // If there is an agent we should default to Connection:keep-alive. + self._last = false; + self.shouldKeepAlive = true; + self.agent.addRequest(self, host, port, options.localAddress); + } else { + // No agent, default to Connection:close. + self._last = true; + self.shouldKeepAlive = false; + if (options.createConnection) { + options.port = port; + options.host = host; + var conn = options.createConnection(options); + } else { + var conn = net.createConnection({ + port: port, + host: host, + localAddress: options.localAddress + }); + } + self.onSocket(conn); + } + + self._deferToConnect(null, null, function() { + self._flush(); + self = null; + }); + +} +util.inherits(ClientRequest, OutgoingMessage); + +exports.ClientRequest = ClientRequest; + +ClientRequest.prototype._implicitHeader = function() { + this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', + this._renderHeaders()); +}; + +ClientRequest.prototype.abort = function() { + if (this.socket) { + // in-progress + this.socket.destroy(); + } else { + // haven't been assigned a socket yet. + // this could be more efficient, it could + // remove itself from the pending requests + this._deferToConnect('destroy', []); + } +}; + + +function createHangUpError() { + var error = new Error('socket hang up'); + error.code = 'ECONNRESET'; + return error; +} + + +function socketCloseListener() { + var socket = this; + var parser = socket.parser; + var req = socket._httpMessage; + debug('HTTP socket close'); + req.emit('close'); + if (req.res && req.res.readable) { + // Socket closed before we emitted 'end' below. + req.res.emit('aborted'); + var res = req.res; + res.on('end', function() { + res.emit('close'); + }); + res.push(null); + } else if (!req.res && !req._hadError) { + // This socket error fired before we started to + // receive a response. The error needs to + // fire on the request. + req.emit('error', createHangUpError()); + req._hadError = true; + } + + // Too bad. That output wasn't getting written. + // This is pretty terrible that it doesn't raise an error. + // Fixed better in v0.10 + if (req.output) + req.output.length = 0; + if (req.outputEncodings) + req.outputEncodings.length = 0; + + if (parser) { + parser.finish(); + freeParser(parser, req); + } +} + +function socketErrorListener(err) { + var socket = this; + var parser = socket.parser; + var req = socket._httpMessage; + debug('SOCKET ERROR:', err.message, err.stack); + + if (req) { + req.emit('error', err); + // For Safety. Some additional errors might fire later on + // and we need to make sure we don't double-fire the error event. + req._hadError = true; + } + + if (parser) { + parser.finish(); + freeParser(parser, req); + } + socket.destroy(); +} + +function socketOnEnd() { + var socket = this; + var req = this._httpMessage; + var parser = this.parser; + + if (!req.res) { + // If we don't have a response then we know that the socket + // ended prematurely and we need to emit an error on the request. + req.emit('error', createHangUpError()); + req._hadError = true; + } + if (parser) { + parser.finish(); + freeParser(parser, req); + } + socket.destroy(); +} + +function socketOnData(d, start, end) { + var socket = this; + var req = this._httpMessage; + var parser = this.parser; + + var ret = parser.execute(d, start, end - start); + if (ret instanceof Error) { + debug('parse error'); + freeParser(parser, req); + socket.destroy(); + req.emit('error', ret); + req._hadError = true; + } else if (parser.incoming && parser.incoming.upgrade) { + // Upgrade or CONNECT + var bytesParsed = ret; + var res = parser.incoming; + req.res = res; + + socket.ondata = null; + socket.onend = null; + parser.finish(); + + // This is start + byteParsed + var bodyHead = d.slice(start + bytesParsed, end); + + var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; + if (EventEmitter.listenerCount(req, eventName) > 0) { + req.upgradeOrConnect = true; + + // detach the socket + socket.emit('agentRemove'); + socket.removeListener('close', socketCloseListener); + socket.removeListener('error', socketErrorListener); + - req.emit(eventName, res, socket, bodyHead); ++ socket.unshift(bodyHead); ++ ++ req.emit(eventName, res, socket, emptyBuffer); + req.emit('close'); + } else { + // Got Upgrade header or CONNECT method, but have no handler. + socket.destroy(); + } + freeParser(parser, req); + } else if (parser.incoming && parser.incoming.complete && + // When the status code is 100 (Continue), the server will + // send a final response after this client sends a request + // body. So, we must not free the parser. + parser.incoming.statusCode !== 100) { + freeParser(parser, req); + } +} + + +// client +function parserOnIncomingClient(res, shouldKeepAlive) { + var parser = this; + var socket = this.socket; + var req = socket._httpMessage; + + + // propogate "domain" setting... + if (req.domain && !res.domain) { + debug('setting "res.domain"'); + res.domain = req.domain; + } + + debug('AGENT incoming response!'); + + if (req.res) { + // We already have a response object, this means the server + // sent a double response. + socket.destroy(); + return; + } + req.res = res; + + // Responses to CONNECT request is handled as Upgrade. + if (req.method === 'CONNECT') { + res.upgrade = true; + return true; // skip body + } + + // Responses to HEAD requests are crazy. + // HEAD responses aren't allowed to have an entity-body + // but *can* have a content-length which actually corresponds + // to the content-length of the entity-body had the request + // been a GET. + var isHeadResponse = req.method == 'HEAD'; + debug('AGENT isHeadResponse', isHeadResponse); + + if (res.statusCode == 100) { + // restart the parser, as this is a continue message. + delete req.res; // Clear res so that we don't hit double-responses. + req.emit('continue'); + return true; + } + + if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) { + // Server MUST respond with Connection:keep-alive for us to enable it. + // If we've been upgraded (via WebSockets) we also shouldn't try to + // keep the connection open. + req.shouldKeepAlive = false; + } + + + DTRACE_HTTP_CLIENT_RESPONSE(socket, req); + COUNTER_HTTP_CLIENT_RESPONSE(); + req.res = res; + res.req = req; + + // add our listener first, so that we guarantee socket cleanup + res.on('end', responseOnEnd); + var handled = req.emit('response', res); + + // If the user did not listen for the 'response' event, then they + // can't possibly read the data, so we ._dump() it into the void + // so that the socket doesn't hang there in a paused state. + if (!handled) + res._dump(); + + return isHeadResponse; +} + +// client +function responseOnEnd() { + var res = this; + var req = res.req; + var socket = req.socket; + + if (!req.shouldKeepAlive) { + if (socket.writable) { + debug('AGENT socket.destroySoon()'); + socket.destroySoon(); + } + assert(!socket.writable); + } else { + debug('AGENT socket keep-alive'); + if (req.timeoutCb) { + socket.setTimeout(0, req.timeoutCb); + req.timeoutCb = null; + } + socket.removeListener('close', socketCloseListener); + socket.removeListener('error', socketErrorListener); + // Mark this socket as available, AFTER user-added end + // handlers have a chance to run. + process.nextTick(function() { + socket.emit('free'); + }); + } +} + +ClientRequest.prototype.onSocket = function(socket) { + var req = this; + + process.nextTick(function() { + var parser = parsers.alloc(); + req.socket = socket; + req.connection = socket; + parser.reinitialize(HTTPParser.RESPONSE); + parser.socket = socket; + parser.incoming = null; + req.parser = parser; + + socket.parser = parser; + socket._httpMessage = req; + + // Setup "drain" propogation. + httpSocketSetup(socket); + + // Propagate headers limit from request object to parser + if (typeof req.maxHeadersCount === 'number') { + parser.maxHeaderPairs = req.maxHeadersCount << 1; + } else { + // Set default value because parser may be reused from FreeList + parser.maxHeaderPairs = 2000; + } + + socket.on('error', socketErrorListener); + socket.ondata = socketOnData; + socket.onend = socketOnEnd; + socket.on('close', socketCloseListener); + parser.onIncoming = parserOnIncomingClient; + req.emit('socket', socket); + }); + +}; + +ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) { + // This function is for calls that need to happen once the socket is + // connected and writable. It's an important promisy thing for all the socket + // calls that happen either now (when a socket is assigned) or + // in the future (when a socket gets assigned out of the pool and is + // eventually writable). + var self = this; + var onSocket = function() { + if (self.socket.writable) { + if (method) { + self.socket[method].apply(self.socket, arguments_); + } + if (cb) { cb(); } + } else { + self.socket.once('connect', function() { + if (method) { + self.socket[method].apply(self.socket, arguments_); + } + if (cb) { cb(); } + }); + } + } + if (!self.socket) { + self.once('socket', onSocket); + } else { + onSocket(); + } +}; + +ClientRequest.prototype.setTimeout = function(msecs, callback) { + if (callback) this.once('timeout', callback); + + var self = this; + function emitTimeout() { + self.emit('timeout'); + } + + if (this.socket && this.socket.writable) { + if (this.timeoutCb) + this.socket.setTimeout(0, this.timeoutCb); + this.timeoutCb = emitTimeout; + this.socket.setTimeout(msecs, emitTimeout); + return; + } + + // Set timeoutCb so that it'll get cleaned up on request end + this.timeoutCb = emitTimeout; + if (this.socket) { + var sock = this.socket; + this.socket.once('connect', function() { + sock.setTimeout(msecs, emitTimeout); + }); + return; + } + + this.once('socket', function(sock) { + sock.setTimeout(msecs, emitTimeout); + }); +}; + +ClientRequest.prototype.setNoDelay = function() { + this._deferToConnect('setNoDelay', arguments); +}; +ClientRequest.prototype.setSocketKeepAlive = function() { + this._deferToConnect('setKeepAlive', arguments); +}; + +ClientRequest.prototype.clearTimeout = function(cb) { + this.setTimeout(0, cb); +}; diff --cc lib/_http_outgoing.js index 7248e5a,0000000..8455b91 mode 100644,000000..100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@@ -1,576 -1,0 +1,579 @@@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert').ok; +var Stream = require('stream'); +var util = require('util'); + +var common = require('_http_common'); + +var CRLF = common.CRLF; +var chunkExpression = common.chunkExpression; +var continueExpression = common.continueExpression; +var debug = common.debug; + + +var connectionExpression = /Connection/i; +var transferEncodingExpression = /Transfer-Encoding/i; +var closeExpression = /close/i; +var contentLengthExpression = /Content-Length/i; +var dateExpression = /Date/i; +var expectExpression = /Expect/i; + + +var dateCache; +function utcDate() { + if (!dateCache) { + var d = new Date(); + dateCache = d.toUTCString(); + setTimeout(function() { + dateCache = undefined; + }, 1000 - d.getMilliseconds()); + } + return dateCache; +} + + +function OutgoingMessage() { + Stream.call(this); + + this.output = []; + this.outputEncodings = []; + + this.writable = true; + + this._last = false; + this.chunkedEncoding = false; + this.shouldKeepAlive = true; + this.useChunkedEncodingByDefault = true; + this.sendDate = false; + + this._hasBody = true; + this._trailer = ''; + + this.finished = false; + this._hangupClose = false; + + this.socket = null; + this.connection = null; +} +util.inherits(OutgoingMessage, Stream); + + +exports.OutgoingMessage = OutgoingMessage; + + +OutgoingMessage.prototype.setTimeout = function(msecs, callback) { + if (callback) + this.on('timeout', callback); + if (!this.socket) { + this.once('socket', function(socket) { + socket.setTimeout(msecs); + }); + } else + this.socket.setTimeout(msecs); +}; + + +// It's possible that the socket will be destroyed, and removed from +// any messages, before ever calling this. In that case, just skip +// it, since something else is destroying this connection anyway. +OutgoingMessage.prototype.destroy = function(error) { + if (this.socket) + this.socket.destroy(error); + else + this.once('socket', function(socket) { + socket.destroy(error); + }); +}; + + +// This abstract either writing directly to the socket or buffering it. +OutgoingMessage.prototype._send = function(data, encoding) { + // 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; + } + return this._writeRaw(data, encoding); +}; + + +OutgoingMessage.prototype._writeRaw = function(data, encoding) { + if (data.length === 0) { + return true; + } + + if (this.connection && + this.connection._httpMessage === this && + this.connection.writable && + !this.connection.destroyed) { + // 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 if (this.connection && this.connection.destroyed) { + // The socket was destroyed. If we're still trying to write to it, + // then we haven't gotten the 'close' event yet. + return false; + } else { + // buffer, as long as we're not destroyed. + this._buffer(data, encoding); + return false; + } +}; + + +OutgoingMessage.prototype._buffer = function(data, encoding) { + if (data.length === 0) return; + + var length = this.output.length; + + if (length === 0 || typeof data != 'string') { + this.output.push(data); + this.outputEncodings.push(encoding); + return false; + } + + var lastEncoding = this.outputEncodings[length - 1]; + var lastData = this.output[length - 1]; + + if ((encoding && lastEncoding === encoding) || + (!encoding && data.constructor === lastData.constructor)) { + this.output[length - 1] = lastData + data; + return false; + } + + this.output.push(data); + this.outputEncodings.push(encoding); + + return false; +}; + + +OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { + // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n' + // in the case of response it is: 'HTTP/1.1 200 OK\r\n' + var state = { + sentConnectionHeader: false, + sentContentLengthHeader: false, + sentTransferEncodingHeader: false, + sentDateHeader: false, + sentExpect: false, + messageHeader: firstLine + }; + + var field, value; + var self = this; + + if (headers) { + var keys = Object.keys(headers); + var isArray = (Array.isArray(headers)); + var field, value; + + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + if (isArray) { + field = headers[key][0]; + value = headers[key][1]; + } else { + field = key; + value = headers[key]; + } + + if (Array.isArray(value)) { + for (var j = 0; j < value.length; j++) { + storeHeader(this, state, field, value[j]); + } + } else { + storeHeader(this, state, field, value); + } + } + } + + // Date header + if (this.sendDate == true && state.sentDateHeader == false) { + state.messageHeader += 'Date: ' + utcDate() + CRLF; + } + + // Force the connection to close when the response is a 204 No Content or + // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked" + // header. + // + // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but + // node.js used to send out a zero chunk anyway to accommodate clients + // that don't have special handling for those responses. + // + // It was pointed out that this might confuse reverse proxies to the point + // of creating security liabilities, so suppress the zero chunk and force + // the connection to close. + var statusCode = this.statusCode; + if ((statusCode == 204 || statusCode === 304) && + this.chunkedEncoding === true) { + debug(statusCode + ' response should not use chunked encoding,' + + ' closing connection.'); + this.chunkedEncoding = false; + this.shouldKeepAlive = false; + } + + // keep-alive logic + if (state.sentConnectionHeader === false) { + var shouldSendKeepAlive = this.shouldKeepAlive && + (state.sentContentLengthHeader || + this.useChunkedEncodingByDefault || + this.agent); + if (shouldSendKeepAlive) { + state.messageHeader += 'Connection: keep-alive\r\n'; + } else { + this._last = true; + state.messageHeader += 'Connection: close\r\n'; + } + } + + if (state.sentContentLengthHeader == false && + state.sentTransferEncodingHeader == false) { + if (this._hasBody) { + if (this.useChunkedEncodingByDefault) { + state.messageHeader += 'Transfer-Encoding: chunked\r\n'; + this.chunkedEncoding = true; + } else { + this._last = true; + } + } else { + // Make sure we don't end the 0\r\n\r\n at the end of the message. + this.chunkedEncoding = false; + } + } + + this._header = state.messageHeader + CRLF; + this._headerSent = false; + + // wait until the first body chunk, or close(), is sent to flush, + // UNLESS we're sending Expect: 100-continue. + if (state.sentExpect) this._send(''); +}; + +function storeHeader(self, state, field, value) { + // Protect against response splitting. The if statement is there to + // minimize the performance impact in the common case. + if (/[\r\n]/.test(value)) + value = value.replace(/[\r\n]+[ \t]*/g, ''); + + state.messageHeader += field + ': ' + value + CRLF; + + if (connectionExpression.test(field)) { + state.sentConnectionHeader = true; + if (closeExpression.test(value)) { + self._last = true; + } else { + self.shouldKeepAlive = true; + } + + } else if (transferEncodingExpression.test(field)) { + state.sentTransferEncodingHeader = true; + if (chunkExpression.test(value)) self.chunkedEncoding = true; + + } else if (contentLengthExpression.test(field)) { + state.sentContentLengthHeader = true; + } else if (dateExpression.test(field)) { + state.sentDateHeader = true; + } else if (expectExpression.test(field)) { + state.sentExpect = true; + } +} + + +OutgoingMessage.prototype.setHeader = function(name, value) { + if (arguments.length < 2) { + throw new Error('`name` and `value` are required for setHeader().'); + } + + if (this._header) { + throw new Error('Can\'t set headers after they are sent.'); + } + + var key = name.toLowerCase(); + this._headers = this._headers || {}; + this._headerNames = this._headerNames || {}; + this._headers[key] = value; + this._headerNames[key] = name; +}; + + +OutgoingMessage.prototype.getHeader = function(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for getHeader().'); + } + + if (!this._headers) return; + + var key = name.toLowerCase(); + return this._headers[key]; +}; + + +OutgoingMessage.prototype.removeHeader = function(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for removeHeader().'); + } + + if (this._header) { + throw new Error('Can\'t remove headers after they are sent.'); + } + + if (!this._headers) return; + + var key = name.toLowerCase(); + delete this._headers[key]; + delete this._headerNames[key]; +}; + + +OutgoingMessage.prototype._renderHeaders = function() { + if (this._header) { + throw new Error('Can\'t render headers after they are sent to the client.'); + } + + if (!this._headers) return {}; + + var headers = {}; + var keys = Object.keys(this._headers); + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + headers[this._headerNames[key]] = this._headers[key]; + } + return headers; +}; + + +Object.defineProperty(OutgoingMessage.prototype, 'headersSent', { + configurable: true, + enumerable: true, + get: function() { return !!this._header; } +}); + + +OutgoingMessage.prototype.write = function(chunk, encoding) { + if (!this._header) { + this._implicitHeader(); + } + + if (!this._hasBody) { + debug('This type of response MUST NOT have a body. ' + + 'Ignoring write() calls.'); + return true; + } + + if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) { + throw new TypeError('first argument must be a string or Buffer'); + } + - if (chunk.length === 0) return false; ++ ++ // If we get an empty string or buffer, then just do nothing, and ++ // signal the user to keep writing. ++ if (chunk.length === 0) return true; + + var len, ret; + if (this.chunkedEncoding) { + if (typeof(chunk) === 'string' && + encoding !== 'hex' && + encoding !== 'base64' && + encoding !== 'binary') { + len = Buffer.byteLength(chunk, encoding); + chunk = len.toString(16) + CRLF + chunk + CRLF; + ret = this._send(chunk, encoding); + } else { + // buffer, or a non-toString-friendly encoding + len = chunk.length; + + if (this.connection) + this.connection.cork(); + this._send(len.toString(16)); + this._send(crlf_buf); + this._send(chunk); + ret = this._send(crlf_buf); + if (this.connection) + this.connection.uncork(); + } + } else { + ret = this._send(chunk, encoding); + } + + debug('write ret = ' + ret); + return ret; +}; + + +OutgoingMessage.prototype.addTrailers = function(headers) { + this._trailer = ''; + var keys = Object.keys(headers); + var isArray = (Array.isArray(headers)); + var field, value; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + if (isArray) { + field = headers[key][0]; + value = headers[key][1]; + } else { + field = key; + value = headers[key]; + } + + this._trailer += field + ': ' + value + CRLF; + } +}; + + +var zero_chunk_buf = new Buffer('\r\n0\r\n'); +var crlf_buf = new Buffer('\r\n'); + + +OutgoingMessage.prototype.end = function(data, encoding) { + if (data && typeof data !== 'string' && !Buffer.isBuffer(data)) { + throw new TypeError('first argument must be a string or Buffer'); + } + + if (this.finished) { + return false; + } + if (!this._header) { + this._implicitHeader(); + } + + if (data && !this._hasBody) { + debug('This type of response MUST NOT have a body. ' + + 'Ignoring data passed to end().'); + data = false; + } + + if (this.connection && data) + this.connection.cork(); + + var ret; + if (data) { + // Normal body write. + ret = this.write(data, encoding); + } + + if (this.chunkedEncoding) { + ret = this._send('0\r\n' + this._trailer + '\r\n'); // Last chunk. + } else { + // Force a flush, HACK. + ret = this._send(''); + } + + if (this.connection && data) + this.connection.uncork(); + + this.finished = true; + + // There is the first message on the outgoing queue, and we've sent + // everything to the socket. + debug('outgoing message end.'); + if (this.output.length === 0 && this.connection._httpMessage === this) { + this._finish(); + } + + return ret; +}; + + +var ServerResponse, ClientRequest; + +OutgoingMessage.prototype._finish = function() { + assert(this.connection); + + if (!ServerResponse) + ServerResponse = require('_http_server').ServerResponse; + + if (!ClientRequest) + ClientRequest = require('_http_client').ClientRequest; + + if (this instanceof ServerResponse) { + DTRACE_HTTP_SERVER_RESPONSE(this.connection); + COUNTER_HTTP_SERVER_RESPONSE(); + } else { + assert(this instanceof ClientRequest); + DTRACE_HTTP_CLIENT_REQUEST(this, this.connection); + COUNTER_HTTP_CLIENT_REQUEST(); + } + this.emit('finish'); +}; + + +OutgoingMessage.prototype._flush = function() { + // This logic is probably a bit confusing. Let me explain a bit: + // + // In both HTTP servers and clients it is possible to queue up several + // outgoing messages. This is easiest to imagine in the case of a client. + // Take the following situation: + // + // req1 = client.request('GET', '/'); + // req2 = client.request('POST', '/'); + // + // When the user does + // + // req2.write('hello world\n'); + // + // it's possible that the first request has not been completely flushed to + // the socket yet. Thus the outgoing messages need to be prepared to queue + // up data internally before sending it on further to the socket's queue. + // + // This function, outgoingFlush(), is called by both the Server and Client + // to attempt to flush any pending messages out to the socket. + + if (!this.socket) return; + + var ret; + while (this.output.length) { + + if (!this.socket.writable) return; // XXX Necessary? + + var data = this.output.shift(); + var encoding = this.outputEncodings.shift(); + + ret = this.socket.write(data, encoding); + } + + if (this.finished) { + // This is a queue to the server or client to bring in the next this. + this._finish(); + } else if (ret) { + // This is necessary to prevent https from breaking + this.emit('drain'); + } +}; diff --cc lib/_http_server.js index bbbcb37,0000000..9d1adc6 mode 100644,000000..100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@@ -1,454 -1,0 +1,459 @@@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var util = require('util'); +var net = require('net'); +var EventEmitter = require('events').EventEmitter; +var HTTPParser = process.binding('http_parser').HTTPParser; +var assert = require('assert').ok; + ++// an empty buffer for UPGRADE/CONNECT bodyHead compatibility ++var emptyBuffer = new Buffer(0); ++ +var common = require('_http_common'); +var parsers = common.parsers; +var freeParser = common.freeParser; +var debug = common.debug; +var CRLF = common.CRLF; +var continueExpression = common.continueExpression; +var chunkExpression = common.chunkExpression; +var httpSocketSetup = common.httpSocketSetup; + +var OutgoingMessage = require('_http_outgoing').OutgoingMessage; + + +var STATUS_CODES = exports.STATUS_CODES = { + 100 : 'Continue', + 101 : 'Switching Protocols', + 102 : 'Processing', // RFC 2518, obsoleted by RFC 4918 + 200 : 'OK', + 201 : 'Created', + 202 : 'Accepted', + 203 : 'Non-Authoritative Information', + 204 : 'No Content', + 205 : 'Reset Content', + 206 : 'Partial Content', + 207 : 'Multi-Status', // RFC 4918 + 300 : 'Multiple Choices', + 301 : 'Moved Permanently', + 302 : 'Moved Temporarily', + 303 : 'See Other', + 304 : 'Not Modified', + 305 : 'Use Proxy', + 307 : 'Temporary Redirect', + 400 : 'Bad Request', + 401 : 'Unauthorized', + 402 : 'Payment Required', + 403 : 'Forbidden', + 404 : 'Not Found', + 405 : 'Method Not Allowed', + 406 : 'Not Acceptable', + 407 : 'Proxy Authentication Required', + 408 : 'Request Time-out', + 409 : 'Conflict', + 410 : 'Gone', + 411 : 'Length Required', + 412 : 'Precondition Failed', + 413 : 'Request Entity Too Large', + 414 : 'Request-URI Too Large', + 415 : 'Unsupported Media Type', + 416 : 'Requested Range Not Satisfiable', + 417 : 'Expectation Failed', + 418 : 'I\'m a teapot', // RFC 2324 + 422 : 'Unprocessable Entity', // RFC 4918 + 423 : 'Locked', // RFC 4918 + 424 : 'Failed Dependency', // RFC 4918 + 425 : 'Unordered Collection', // RFC 4918 + 426 : 'Upgrade Required', // RFC 2817 + 428 : 'Precondition Required', // RFC 6585 + 429 : 'Too Many Requests', // RFC 6585 + 431 : 'Request Header Fields Too Large',// RFC 6585 + 500 : 'Internal Server Error', + 501 : 'Not Implemented', + 502 : 'Bad Gateway', + 503 : 'Service Unavailable', + 504 : 'Gateway Time-out', + 505 : 'HTTP Version Not Supported', + 506 : 'Variant Also Negotiates', // RFC 2295 + 507 : 'Insufficient Storage', // RFC 4918 + 509 : 'Bandwidth Limit Exceeded', + 510 : 'Not Extended', // RFC 2774 + 511 : 'Network Authentication Required' // RFC 6585 +}; + + +function ServerResponse(req) { + OutgoingMessage.call(this); + + if (req.method === 'HEAD') this._hasBody = false; + + this.sendDate = true; + + if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { + this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te); + this.shouldKeepAlive = false; + } +} +util.inherits(ServerResponse, OutgoingMessage); + + +exports.ServerResponse = ServerResponse; + +ServerResponse.prototype.statusCode = 200; + +function onServerResponseClose() { + // EventEmitter.emit makes a copy of the 'close' listeners array before + // calling the listeners. detachSocket() unregisters onServerResponseClose + // but if detachSocket() is called, directly or indirectly, by a 'close' + // listener, onServerResponseClose is still in that copy of the listeners + // array. That is, in the example below, b still gets called even though + // it's been removed by a: + // + // var obj = new events.EventEmitter; + // obj.on('event', a); + // obj.on('event', b); + // function a() { obj.removeListener('event', b) } + // function b() { throw "BAM!" } + // obj.emit('event'); // throws + // + // Ergo, we need to deal with stale 'close' events and handle the case + // where the ServerResponse object has already been deconstructed. + // Fortunately, that requires only a single if check. :-) + if (this._httpMessage) this._httpMessage.emit('close'); +} + +ServerResponse.prototype.assignSocket = function(socket) { + assert(!socket._httpMessage); + socket._httpMessage = this; + socket.on('close', onServerResponseClose); + this.socket = socket; + this.connection = socket; + this.emit('socket', socket); + this._flush(); +}; + +ServerResponse.prototype.detachSocket = function(socket) { + assert(socket._httpMessage == this); + socket.removeListener('close', onServerResponseClose); + socket._httpMessage = null; + this.socket = this.connection = null; +}; + +ServerResponse.prototype.writeContinue = function() { + this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii'); + this._sent100 = true; +}; + +ServerResponse.prototype._implicitHeader = function() { + this.writeHead(this.statusCode); +}; + +ServerResponse.prototype.writeHead = function(statusCode) { + var reasonPhrase, headers, headerIndex; + + if (typeof arguments[1] == 'string') { + reasonPhrase = arguments[1]; + headerIndex = 2; + } else { + reasonPhrase = STATUS_CODES[statusCode] || 'unknown'; + headerIndex = 1; + } + this.statusCode = statusCode; + + var obj = arguments[headerIndex]; + + if (obj && this._headers) { + // Slow-case: when progressive API and header fields are passed. + headers = this._renderHeaders(); + + if (Array.isArray(obj)) { + // handle array case + // TODO: remove when array is no longer accepted + var field; + for (var i = 0, len = obj.length; i < len; ++i) { + field = obj[i][0]; + if (headers[field] !== undefined) { + obj.push([field, headers[field]]); + } + } + headers = obj; + + } else { + // handle object case + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (k) headers[k] = obj[k]; + } + } + } else if (this._headers) { + // only progressive api is used + headers = this._renderHeaders(); + } else { + // only writeHead() called + headers = obj; + } + + var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + + reasonPhrase + CRLF; + + if (statusCode === 204 || statusCode === 304 || + (100 <= statusCode && statusCode <= 199)) { + // RFC 2616, 10.2.5: + // The 204 response MUST NOT include a message-body, and thus is always + // terminated by the first empty line after the header fields. + // RFC 2616, 10.3.5: + // The 304 response MUST NOT contain a message-body, and thus is always + // terminated by the first empty line after the header fields. + // RFC 2616, 10.1 Informational 1xx: + // This class of status code indicates a provisional response, + // consisting only of the Status-Line and optional headers, and is + // terminated by an empty line. + this._hasBody = false; + } + + // don't keep alive connections where the client expects 100 Continue + // but we sent a final status; they may put extra bytes on the wire. + if (this._expect_continue && !this._sent100) { + this.shouldKeepAlive = false; + } + + this._storeHeader(statusLine, headers); +}; + +ServerResponse.prototype.writeHeader = function() { + this.writeHead.apply(this, arguments); +}; + + +function Server(requestListener) { + if (!(this instanceof Server)) return new Server(requestListener); + net.Server.call(this, { allowHalfOpen: true }); + + if (requestListener) { + this.addListener('request', requestListener); + } + + // Similar option to this. Too lazy to write my own docs. + // http://www.squid-cache.org/Doc/config/half_closed_clients/ + // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F + this.httpAllowHalfOpen = false; + + this.addListener('connection', connectionListener); + + this.addListener('clientError', function(err, conn) { + conn.destroy(err); + }); + + this.timeout = 2 * 60 * 1000; +} +util.inherits(Server, net.Server); + + +Server.prototype.setTimeout = function(msecs, callback) { + this.timeout = msecs; + if (callback) + this.on('timeout', callback); +}; + + +exports.Server = Server; + + +function connectionListener(socket) { + var self = this; + var outgoing = []; + var incoming = []; + + function abortIncoming() { + while (incoming.length) { + var req = incoming.shift(); + req.emit('aborted'); + req.emit('close'); + } + // abort socket._httpMessage ? + } + + function serverSocketCloseListener() { + debug('server socket close'); + // mark this parser as reusable + if (this.parser) + freeParser(this.parser); + + abortIncoming(); + } + + debug('SERVER new http connection'); + + httpSocketSetup(socket); + + // If the user has added a listener to the server, + // request, or response, then it's their responsibility. + // otherwise, destroy on timeout by default + if (self.timeout) + socket.setTimeout(self.timeout); + socket.on('timeout', function() { + var req = socket.parser && socket.parser.incoming; + var reqTimeout = req && !req.complete && req.emit('timeout', socket); + var res = socket._httpMessage; + var resTimeout = res && res.emit('timeout', socket); + var serverTimeout = self.emit('timeout', socket); + + if (!reqTimeout && !resTimeout && !serverTimeout) + socket.destroy(); + }); + + var parser = parsers.alloc(); + parser.reinitialize(HTTPParser.REQUEST); + parser.socket = socket; + socket.parser = parser; + parser.incoming = null; + + // Propagate headers limit from server instance to parser + if (typeof this.maxHeadersCount === 'number') { + parser.maxHeaderPairs = this.maxHeadersCount << 1; + } else { + // Set default value because parser may be reused from FreeList + parser.maxHeaderPairs = 2000; + } + + socket.addListener('error', function(e) { + self.emit('clientError', e, this); + }); + + socket.ondata = function(d, start, end) { + var ret = parser.execute(d, start, end - start); + if (ret instanceof Error) { + debug('parse error'); + socket.destroy(ret); + } else if (parser.incoming && parser.incoming.upgrade) { + // Upgrade or CONNECT + var bytesParsed = ret; + var req = parser.incoming; + + socket.ondata = null; + socket.onend = null; + socket.removeListener('close', serverSocketCloseListener); + parser.finish(); + freeParser(parser, req); + + var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; + if (EventEmitter.listenerCount(self, eventName) > 0) { + // This is start + byteParsed + var bodyHead = d.slice(start + bytesParsed, end); - self.emit(eventName, req, req.socket, bodyHead); ++ ++ socket.unshift(bodyHead); ++ self.emit(eventName, req, req.socket, emptyBuffer); + } else { + // Got upgrade header or CONNECT method, but have no handler. + socket.destroy(); + } + } + }; + + socket.onend = function() { + var ret = parser.finish(); + + if (ret instanceof Error) { + debug('parse error'); + socket.destroy(ret); + return; + } + + if (!self.httpAllowHalfOpen) { + abortIncoming(); + if (socket.writable) socket.end(); + } else if (outgoing.length) { + outgoing[outgoing.length - 1]._last = true; + } else if (socket._httpMessage) { + socket._httpMessage._last = true; + } else { + if (socket.writable) socket.end(); + } + }; + + socket.addListener('close', serverSocketCloseListener); + + // The following callback is issued after the headers have been read on a + // new message. In this callback we setup the response object and pass it + // to the user. + parser.onIncoming = function(req, shouldKeepAlive) { + incoming.push(req); + + var res = new ServerResponse(req); + + res.shouldKeepAlive = shouldKeepAlive; + DTRACE_HTTP_SERVER_REQUEST(req, socket); + COUNTER_HTTP_SERVER_REQUEST(); + + if (socket._httpMessage) { + // There are already pending outgoing res, append. + outgoing.push(res); + } else { + res.assignSocket(socket); + } + + // When we're finished writing the response, check if this is the last + // respose, if so destroy the socket. + res.on('finish', function() { + // Usually the first incoming element should be our request. it may + // be that in the case abortIncoming() was called that the incoming + // array will be empty. + assert(incoming.length == 0 || incoming[0] === req); + + incoming.shift(); + + // if the user never called req.read(), and didn't pipe() or + // .resume() or .on('data'), then we call req._dump() so that the + // bytes will be pulled off the wire. + if (!req._consuming) + req._dump(); + + res.detachSocket(socket); + + if (res._last) { + socket.destroySoon(); + } else { + // start sending the next message + var m = outgoing.shift(); + if (m) { + m.assignSocket(socket); + } + } + }); + + if (req.headers.expect !== undefined && + (req.httpVersionMajor == 1 && req.httpVersionMinor == 1) && + continueExpression.test(req.headers['expect'])) { + res._expect_continue = true; + if (EventEmitter.listenerCount(self, 'checkContinue') > 0) { + self.emit('checkContinue', req, res); + } else { + res.writeContinue(); + self.emit('request', req, res); + } + } else { + self.emit('request', req, res); + } + return false; // Not a HEAD response. (Not even a response!) + }; +} +exports._connectionListener = connectionListener; diff --cc src/node_buffer.cc index 4b5fee6,8153c83..7da3fec --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@@ -345,10 -345,17 +345,16 @@@ Handle Buffer::StringWrite(cons Local str = args[0].As(); - if (encoding == HEX && str->Length() % 2 != 0) + int length = str->Length(); + + if (length == 0) { - constructor_template->GetFunction()->Set(chars_written_sym, - Integer::New(0)); + return scope.Close(Integer::New(0)); + } + + if (encoding == HEX && length % 2 != 0) return ThrowTypeError("Invalid hex string"); + size_t offset = args[1]->Int32Value(); size_t max_length = args[2]->IsUndefined() ? buffer->length_ - offset : args[2]->Uint32Value(); diff --cc test/simple/test-http-client-timeout-event.js index 1064e15,9e0e8bd..e0fb16e --- a/test/simple/test-http-client-timeout-event.js +++ b/test/simple/test-http-client-timeout-event.js @@@ -53,8 -53,6 +53,8 @@@ server.listen(options.port, options.hos setTimeout(function () { req.destroy(); assert.equal(timeout_events, 1); -- }, 10); - req.end(); ++ }, 100); + setTimeout(function () { + req.end(); - }, 5); ++ }, 50); });