http: move Server and ServerResponse out
This commit is contained in:
parent
dc9f97b7b9
commit
6717fdccb4
@ -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;
|
||||
|
@ -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;
|
||||
|
455
lib/_http_server.js
Normal file
455
lib/_http_server.js
Normal file
@ -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;
|
434
lib/http.js
434
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user