summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy J Fontaine <tjfontaine@gmail.com>2013-04-11 16:00:19 -0700
committerBen Noordhuis <info@bnoordhuis.nl>2013-04-17 00:08:28 +0200
commit6717fdccb4a3ebf55d87ae5df1e24ebebd4081f1 (patch)
treec53e593b3c999773d09d00ee48f4af9ee2046719
parentdc9f97b7b99f27eaf21faf29c7df8c9d823ef863 (diff)
downloadnode-new-6717fdccb4a3ebf55d87ae5df1e24ebebd4081f1.tar.gz
http: move Server and ServerResponse out
-rw-r--r--lib/_http_common.js12
-rw-r--r--lib/_http_outgoing.js2
-rw-r--r--lib/_http_server.js455
-rw-r--r--lib/http.js434
-rw-r--r--node.gyp1
5 files changed, 475 insertions, 429 deletions
diff --git a/lib/_http_common.js b/lib/_http_common.js
index 40a4b11840..8c97886272 100644
--- a/lib/_http_common.js
+++ b/lib/_http_common.js
@@ -220,3 +220,15 @@ function freeParser(parser, req) {
}
}
exports.freeParser = freeParser;
+
+
+function ondrain() {
+ if (this._httpMessage) this._httpMessage.emit('drain');
+}
+
+
+function httpSocketSetup(socket) {
+ socket.removeListener('drain', ondrain);
+ socket.on('drain', ondrain);
+}
+exports.httpSocketSetup = httpSocketSetup;
diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index 4fe0927cc7..1902cd9b38 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -585,7 +585,7 @@ OutgoingMessage.prototype._finish = function() {
assert(this.connection);
if (!ServerResponse)
- ServerResponse = require('http').ServerResponse;
+ ServerResponse = require('_http_server').ServerResponse;
if (!ClientRequest)
ClientRequest = require('http').ClientRequest;
diff --git a/lib/_http_server.js b/lib/_http_server.js
new file mode 100644
index 0000000000..945baf3226
--- /dev/null
+++ b/lib/_http_server.js
@@ -0,0 +1,455 @@
+// 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;
+
+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);
+
+ // This is start + byteParsed
+ var bodyHead = d.slice(start + bytesParsed, end);
+
+ var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+ if (EventEmitter.listenerCount(self, eventName) > 0) {
+ 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;
diff --git a/lib/http.js b/lib/http.js
index e0d3e339eb..3476e45740 100644
--- a/lib/http.js
+++ b/lib/http.js
@@ -35,218 +35,15 @@ var common = require('_http_common');
var parsers = exports.parsers = common.parsers;
var freeParser = common.freeParser;
var debug = common.debug;
-var CRLF = common.CRLF;
-var continueExpression = common.continueExpression;
-var chunkExpression = common.chunkExpression;
-
-
-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
-};
var outgoing = require('_http_outgoing');
var OutgoingMessage = exports.OutgoingMessage = outgoing.OutgoingMessage;
-
-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);
-};
+var server = require('_http_server');
+exports.ServerResponse = server.ServerResponse;
+exports.STATUS_CODES = server.STATUS_CODES;
var agent = require('_http_agent');
@@ -734,235 +531,16 @@ exports.get = function(options, cb) {
};
-function ondrain() {
- if (this._httpMessage) this._httpMessage.emit('drain');
-}
-
-
-function httpSocketSetup(socket) {
- socket.removeListener('drain', ondrain);
- socket.on('drain', ondrain);
-}
-
-
-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;
+var httpSocketSetup = common.httpSocketSetup;
+exports._connectionListener = server._connectionListener;
+var Server = exports.Server = server.Server;
exports.createServer = function(requestListener) {
return new Server(requestListener);
};
-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);
-
- // This is start + byteParsed
- var bodyHead = d.slice(start + bytesParsed, end);
-
- var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
- if (EventEmitter.listenerCount(self, eventName) > 0) {
- 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;
-
// Legacy Interface
function Client(port, host) {
diff --git a/node.gyp b/node.gyp
index 210dcd8cd7..74ef1eb6c8 100644
--- a/node.gyp
+++ b/node.gyp
@@ -38,6 +38,7 @@
'lib/_http_common.js',
'lib/_http_incoming.js',
'lib/_http_outgoing.js',
+ 'lib/_http_server.js',
'lib/https.js',
'lib/module.js',
'lib/net.js',