Merge remote-tracking branch 'ry/v0.10'
authorisaacs <i@izs.me>
Sat, 25 May 2013 00:16:08 +0000 (17:16 -0700)
committerisaacs <i@izs.me>
Mon, 27 May 2013 21:46:52 +0000 (14:46 -0700)
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

15 files changed:
1  2 
AUTHORS
ChangeLog
configure
doc/api/http.markdown
lib/_http_client.js
lib/_http_outgoing.js
lib/_http_server.js
lib/buffer.js
lib/net.js
lib/timers.js
lib/tls.js
src/node_buffer.cc
test/simple/test-buffer.js
test/simple/test-http-client-timeout-event.js
test/simple/test-http-upgrade-server.js

diff --cc AUTHORS
+++ b/AUTHORS
@@@ -454,4 -449,4 +454,5 @@@ Robert Kowalski <rok@kowalski.gd
  Benoit Vallée <github@benoitvallee.net>
  Ryuichi Okumura <okuryu@okuryu.com>
  Brandon Frohs <bfrohs@gmail.com>
 +Nick Sullivan <nick@sullivanflock.com>
+ Nathan Zadoks <nathan@nathan7.eu>
diff --cc 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
+++ 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)
-   elif sys.platform.startswith('linux'):
 +    o['variables']['uv_use_dtrace'] = o['variables']['node_use_dtrace']
 +    o['variables']['uv_parent_path'] = '/deps/uv/'
+   elif flavor == 'linux':
      o['variables']['node_use_dtrace'] = 'false'
      o['variables']['node_use_systemtap'] = b(options.with_dtrace)
      if options.systemtap_includes:
Simple merge
index 339a5c7,0000000..e02cb4f
mode 100644,000000..100644
--- /dev/null
@@@ -1,496 -1,0 +1,501 @@@
-       req.emit(eventName, res, socket, bodyHead);
 +// 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);
 +
++      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);
 +};
index 7248e5a,0000000..8455b91
mode 100644,000000..100644
--- /dev/null
@@@ -1,576 -1,0 +1,579 @@@
-   if (chunk.length === 0) return false;
 +// 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 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');
 +  }
 +};
index bbbcb37,0000000..9d1adc6
mode 100644,000000..100644
--- /dev/null
@@@ -1,454 -1,0 +1,459 @@@
-         self.emit(eventName, req, req.socket, bodyHead);
 +// 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);
++
++        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 lib/buffer.js
Simple merge
diff --cc lib/net.js
Simple merge
diff --cc lib/timers.js
Simple merge
diff --cc lib/tls.js
Simple merge
@@@ -345,10 -345,17 +345,16 @@@ Handle<Value> Buffer::StringWrite(cons
  
    Local<String> str = args[0].As<String>();
  
-   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();
Simple merge
@@@ -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);
  });