-2013.06.18, Version 0.10.12 (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.03.28, Version 0.11.0 (Unstable), bce38b3d74e64fcb7d04a2dd551151da6168cdc5
++
++* V8: update to 3.17.13
++
++* os: use %SystemRoot% or %windir% in os.tmpdir() (Suwon Chae)
++
++* util: fix util.inspect() line width calculation (Marcin Kostrzewa)
++
++* buffer: remove _charsWritten (Trevor Norris)
++
++* fs: uv_[fl]stat now reports subsecond resolution (Timothy J Fontaine)
++
++* fs: Throw if error raised and missing callback (bnoordhuis)
++
++* tls: expose SSL_CTX_set_timeout via tls.createServer (Manav Rathi)
++
++* tls: remove harmful unnecessary bounds checking (Marcel Laverdet)
++
++* buffer: write ascii strings using WriteOneByte (Trevor Norris)
++
++* dtrace: fix generation of v8 constants on freebsd (Fedor Indutny)
++
++* dtrace: x64 ustack helper (Fedor Indutny)
++
++* readline: handle wide characters properly (Nao Iizuka)
++
++* repl: Use a domain to catch async errors safely (isaacs)
++
++* repl: emit 'reset' event when context is reset (Sami Samhuri)
++
++* util: custom `inspect()` method may return an Object (Nathan Rajlich)
++
++* console: `console.dir()` bypasses inspect() methods (Nathan Rajlich)
++
++
++2013.06.18, Version 0.10.12 (Stable), a088cf4f930d3928c97d239adf950ab43e7794aa
+
+ * npm: Upgrade to 1.2.32
+
+ * readline: make `ctrl + L` clear the screen (Yuan Chuan)
+
+ * v8: add setVariableValue debugger command (Ben Noordhuis)
+
+ * net: Do not destroy socket mid-write (isaacs)
+
+ * v8: fix build for mips32r2 architecture (Andrei Sedoi)
+
+ * configure: fix cross-compilation host_arch_cc() (Andrei Sedoi)
+
+
+ 2013.06.13, Version 0.10.11 (Stable), d9d5bc465450ae5d60da32e9ffcf71c2767f1fad
+
+ * uv: upgrade to 0.10.11
+
+ * npm: Upgrade to 1.2.30
+
+ * openssl: add missing configuration pieces for MIPS (Andrei Sedoi)
+
+ * Revert "http: remove bodyHead from 'upgrade' events" (isaacs)
+
+ * v8: fix pointer arithmetic undefined behavior (Trevor Norris)
+
+ * crypto: fix utf8/utf-8 encoding check (Ben Noordhuis)
+
+ * net: Fix busy loop on POLLERR|POLLHUP on older linux kernels (Ben Noordhuis, isaacs)
+
+
-
2013.06.04, Version 0.10.10 (Stable), 25e51c396aa23018603baae2b1d9390f5d9db496
* uv: Upgrade to 0.10.10
* src: tie process.versions.uv to uv_version_string() (Ben Noordhuis)
- 2013.03.28, Version 0.11.0 (Unstable), bce38b3d74e64fcb7d04a2dd551151da6168cdc5
-
- * V8: update to 3.17.13
-
- * os: use %SystemRoot% or %windir% in os.tmpdir() (Suwon Chae)
-
- * util: fix util.inspect() line width calculation (Marcin Kostrzewa)
-
- * buffer: remove _charsWritten (Trevor Norris)
-
- * fs: uv_[fl]stat now reports subsecond resolution (Timothy J Fontaine)
-
- * fs: Throw if error raised and missing callback (bnoordhuis)
-
- * tls: expose SSL_CTX_set_timeout via tls.createServer (Manav Rathi)
-
- * tls: remove harmful unnecessary bounds checking (Marcel Laverdet)
-
- * buffer: write ascii strings using WriteOneByte (Trevor Norris)
-
- * dtrace: fix generation of v8 constants on freebsd (Fedor Indutny)
-
- * dtrace: x64 ustack helper (Fedor Indutny)
-
- * readline: handle wide characters properly (Nao Iizuka)
-
- * repl: Use a domain to catch async errors safely (isaacs)
-
- * repl: emit 'reset' event when context is reset (Sami Samhuri)
-
- * util: custom `inspect()` method may return an Object (Nathan Rajlich)
-
- * console: `console.dir()` bypasses inspect() methods (Nathan Rajlich)
-
-
-2013.03.28, Version 0.10.2 (Stable)
+2013.03.28, Version 0.10.2 (Stable), 1e0de9c426e07a260bbec2d2196c2d2db8eb8886
* npm: Upgrade to 1.2.15
--- /dev/null
- // an empty buffer for UPGRADE/CONNECT bodyHead compatibility
- var emptyBuffer = new Buffer(0);
-
+// 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;
+
- socket.unshift(bodyHead);
-
- req.emit(eventName, res, socket, emptyBuffer);
+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.socket._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.socket._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.socket._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 && !req.socket._hadError) {
+ // 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.socket._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.socket._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);
+ 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);
+};
--- /dev/null
- // an empty buffer for UPGRADE/CONNECT bodyHead compatibility
- var emptyBuffer = new Buffer(0);
-
+// 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;
+
- socket.unshift(bodyHead);
- self.emit(eventName, req, req.socket, emptyBuffer);
+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);
+ } 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;