nodejs/lib/http.js

1019 lines
29 KiB
JavaScript
Raw Normal View History

2011-03-10 00:54:52 -08:00
// 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');
2010-11-29 07:41:08 -08:00
var net = require('net');
var url = require('url');
2011-01-20 02:41:16 -08:00
var EventEmitter = require('events').EventEmitter;
2010-11-29 07:41:08 -08:00
var HTTPParser = process.binding('http_parser').HTTPParser;
2011-01-27 16:59:28 -08:00
var assert = require('assert').ok;
var incoming = require('_http_incoming');
var IncomingMessage = exports.IncomingMessage = incoming.IncomingMessage;
2010-03-19 19:22:04 -07:00
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;
2010-03-15 13:48:03 -07:00
var STATUS_CODES = exports.STATUS_CODES = {
2009-06-30 18:49:56 -04:00
100 : 'Continue',
101 : 'Switching Protocols',
102 : 'Processing', // RFC 2518, obsoleted by RFC 4918
2009-06-30 18:49:56 -04:00
200 : 'OK',
201 : 'Created',
202 : 'Accepted',
203 : 'Non-Authoritative Information',
204 : 'No Content',
205 : 'Reset Content',
206 : 'Partial Content',
207 : 'Multi-Status', // RFC 4918
2009-06-30 18:49:56 -04:00
300 : 'Multiple Choices',
301 : 'Moved Permanently',
302 : 'Moved Temporarily',
303 : 'See Other',
304 : 'Not Modified',
305 : 'Use Proxy',
307 : 'Temporary Redirect',
2009-06-30 18:49:56 -04:00
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
2009-06-30 18:49:56 -04:00
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
2009-06-30 18:49:56 -04:00
};
2009-05-11 19:08:29 +02:00
2010-11-29 07:41:08 -08:00
var outgoing = require('_http_outgoing');
var OutgoingMessage = exports.OutgoingMessage = outgoing.OutgoingMessage;
2011-01-20 02:41:16 -08:00
2010-12-01 18:07:20 -08:00
function ServerResponse(req) {
2011-01-20 02:41:16 -08:00
OutgoingMessage.call(this);
2009-07-14 18:31:50 +02:00
if (req.method === 'HEAD') this._hasBody = false;
this.sendDate = true;
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te);
2010-03-23 21:31:44 -07:00
this.shouldKeepAlive = false;
}
2009-07-16 10:59:40 +02:00
}
util.inherits(ServerResponse, OutgoingMessage);
2010-11-29 07:41:08 -08:00
exports.ServerResponse = ServerResponse;
2009-07-14 18:31:50 +02:00
ServerResponse.prototype.statusCode = 200;
2010-11-29 07:41:08 -08:00
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;
};
2010-12-01 18:07:20 -08:00
ServerResponse.prototype.writeContinue = function() {
this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii');
2010-11-29 07:41:08 -08:00
this._sent100 = true;
2010-10-06 23:05:23 -04:00
};
ServerResponse.prototype._implicitHeader = function() {
this.writeHead(this.statusCode);
};
2010-11-29 07:41:08 -08:00
2010-12-01 18:07:20 -08:00
ServerResponse.prototype.writeHead = function(statusCode) {
var reasonPhrase, headers, headerIndex;
if (typeof arguments[1] == 'string') {
reasonPhrase = arguments[1];
headerIndex = 2;
} else {
2010-12-01 18:07:20 -08:00
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
2011-10-07 03:07:45 +02:00
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;
2010-11-29 07:41:08 -08:00
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) {
2010-11-29 07:41:08 -08:00
this.shouldKeepAlive = false;
}
this._storeHeader(statusLine, headers);
2009-07-14 18:31:50 +02:00
};
2010-12-01 18:07:20 -08:00
ServerResponse.prototype.writeHeader = function() {
this.writeHead.apply(this, arguments);
};
2009-07-14 18:31:50 +02:00
2010-11-29 07:41:08 -08:00
2013-04-11 14:47:15 -07:00
var agent = require('_http_agent');
2011-10-04 20:51:34 +02:00
2013-04-11 14:47:15 -07:00
var Agent = exports.Agent = agent.Agent;
var globalAgent = exports.globalAgent = agent.globalAgent;
2011-07-26 00:15:15 +02:00
2011-10-04 20:51:34 +02:00
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';
2011-07-26 00:15:15 +02:00
2011-10-04 20:51:34 +02:00
if (options.setHost === undefined) {
var setHost = true;
2011-10-04 20:51:34 +02:00
}
2011-07-26 00:15:15 +02:00
2011-10-04 20:51:34 +02:00
self.socketPath = options.socketPath;
var method = self.method = (options.method || 'GET').toUpperCase();
self.path = options.path || '/';
if (cb) {
self.once('response', cb);
2011-10-04 20:51:34 +02:00
}
if (!Array.isArray(options.headers)) {
if (options.headers) {
2011-10-04 20:51:34 +02:00
var keys = Object.keys(options.headers);
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
2011-10-04 20:51:34 +02:00
self.setHeader(key, options.headers[key]);
}
}
if (host && !this.getHeader('host') && setHost) {
var hostHeader = host;
if (port && +port !== defaultPort) {
hostHeader += ':' + port;
}
2011-10-19 16:28:44 -07:00
this.setHeader('Host', hostHeader);
}
2011-01-20 02:41:16 -08:00
}
2009-07-14 18:31:50 +02:00
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') {
2011-10-04 20:51:34 +02:00
self.useChunkedEncodingByDefault = false;
} else {
2011-10-04 20:51:34 +02:00
self.useChunkedEncodingByDefault = true;
}
2011-01-20 02:41:16 -08:00
2011-10-04 20:51:34 +02:00
if (Array.isArray(options.headers)) {
2011-10-19 16:28:44 -07:00
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
options.headers);
2011-10-04 20:51:34 +02:00
} else if (self.getHeader('expect')) {
2011-10-19 16:28:44 -07:00
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
self._renderHeaders());
2011-10-04 20:51:34 +02:00
}
if (self.socketPath) {
self._last = true;
self.shouldKeepAlive = false;
if (options.createConnection) {
self.onSocket(options.createConnection(self.socketPath));
2011-10-04 20:51:34 +02:00
} 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);
2011-10-04 20:51:34 +02:00
} 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);
2011-10-04 20:51:34 +02:00
} else {
var conn = net.createConnection({
port: port,
host: host,
localAddress: options.localAddress
});
2011-10-04 20:51:34 +02:00
}
2012-02-18 15:01:35 -08:00
self.onSocket(conn);
}
2011-07-26 00:15:15 +02:00
2011-10-19 16:28:44 -07:00
self._deferToConnect(null, null, function() {
2011-10-04 20:51:34 +02:00
self._flush();
self = null;
2011-10-19 16:28:44 -07:00
});
2011-10-04 20:51:34 +02:00
2009-07-16 10:59:40 +02:00
}
util.inherits(ClientRequest, OutgoingMessage);
2010-11-29 07:41:08 -08:00
exports.ClientRequest = ClientRequest;
2009-07-14 18:31:50 +02:00
ClientRequest.prototype._implicitHeader = function() {
2012-02-18 15:01:35 -08:00
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
this._renderHeaders());
2011-10-04 20:51:34 +02:00
};
2010-11-29 07:41:08 -08:00
2011-02-04 15:14:58 -08:00
ClientRequest.prototype.abort = function() {
2011-10-04 20:51:34 +02:00
if (this.socket) {
// in-progress
2011-10-04 20:51:34 +02:00
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', []);
}
};
2011-10-12 17:31:32 -07:00
function createHangUpError() {
var error = new Error('socket hang up');
error.code = 'ECONNRESET';
return error;
}
function socketCloseListener() {
var socket = this;
var parser = socket.parser;
var req = socket._httpMessage;
debug('HTTP socket close');
req.emit('close');
if (req.res && req.res.readable) {
// Socket closed before we emitted 'end' below.
req.res.emit('aborted');
var res = req.res;
res.on('end', function() {
res.emit('close');
});
res.push(null);
} else if (!req.res && !req._hadError) {
// This socket error fired before we started to
// receive a response. The error needs to
// fire on the request.
req.emit('error', createHangUpError());
req._hadError = true;
}
// Too bad. That output wasn't getting written.
// This is pretty terrible that it doesn't raise an error.
// Fixed better in v0.10
if (req.output)
req.output.length = 0;
if (req.outputEncodings)
req.outputEncodings.length = 0;
if (parser) {
parser.finish();
freeParser(parser, req);
}
}
function socketErrorListener(err) {
var socket = this;
var parser = socket.parser;
var req = socket._httpMessage;
debug('HTTP SOCKET ERROR: ' + err.message + '\n' + err.stack);
if (req) {
req.emit('error', err);
// For Safety. Some additional errors might fire later on
// and we need to make sure we don't double-fire the error event.
req._hadError = true;
}
if (parser) {
parser.finish();
freeParser(parser, req);
}
socket.destroy();
}
function socketOnEnd() {
var socket = this;
var req = this._httpMessage;
var parser = this.parser;
if (!req.res) {
// If we don't have a response then we know that the socket
// ended prematurely and we need to emit an error on the request.
req.emit('error', createHangUpError());
req._hadError = true;
}
if (parser) {
parser.finish();
freeParser(parser, req);
}
socket.destroy();
}
function socketOnData(d, start, end) {
var socket = this;
var req = this._httpMessage;
var parser = this.parser;
var ret = parser.execute(d, start, end - start);
if (ret instanceof Error) {
debug('parse error');
freeParser(parser, req);
socket.destroy();
req.emit('error', ret);
req._hadError = true;
} else if (parser.incoming && parser.incoming.upgrade) {
// Upgrade or CONNECT
var bytesParsed = ret;
var res = parser.incoming;
req.res = res;
socket.ondata = null;
socket.onend = null;
parser.finish();
// This is start + byteParsed
var bodyHead = d.slice(start + bytesParsed, end);
var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
if (EventEmitter.listenerCount(req, eventName) > 0) {
req.upgradeOrConnect = true;
// detach the socket
socket.emit('agentRemove');
socket.removeListener('close', socketCloseListener);
socket.removeListener('error', socketErrorListener);
req.emit(eventName, res, socket, bodyHead);
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');
});
}
}
2011-10-12 17:31:32 -07:00
2011-10-04 20:51:34 +02:00
ClientRequest.prototype.onSocket = function(socket) {
var req = this;
2011-10-19 16:28:44 -07:00
process.nextTick(function() {
2011-10-04 20:51:34 +02:00
var parser = parsers.alloc();
req.socket = socket;
req.connection = socket;
parser.reinitialize(HTTPParser.RESPONSE);
parser.socket = socket;
2011-10-04 20:51:34 +02:00
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;
2011-10-04 20:51:34 +02:00
req.emit('socket', socket);
});
2011-10-04 20:51:34 +02:00
};
2012-05-02 12:13:54 -07:00
ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
2011-10-04 20:51:34 +02:00
// 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_);
2011-10-04 20:51:34 +02:00
}
if (cb) { cb(); }
} else {
self.socket.once('connect', function() {
2011-10-04 20:51:34 +02:00
if (method) {
self.socket[method].apply(self.socket, arguments_);
2011-10-04 20:51:34 +02:00
}
if (cb) { cb(); }
});
}
}
if (!self.socket) {
self.once('socket', onSocket);
2011-02-04 15:14:58 -08:00
} else {
2011-10-04 20:51:34 +02:00
onSocket();
}
};
2012-05-02 12:13:54 -07:00
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;
2012-05-02 12:13:54 -07:00
this.socket.setTimeout(msecs, emitTimeout);
return;
}
// Set timeoutCb so that it'll get cleaned up on request end
this.timeoutCb = emitTimeout;
2012-05-02 12:13:54 -07:00
if (this.socket) {
var sock = this.socket;
2012-05-03 10:39:16 -07:00
this.socket.once('connect', function() {
sock.setTimeout(msecs, emitTimeout);
2012-05-02 12:13:54 -07:00
});
return;
}
2012-05-03 10:39:16 -07:00
this.once('socket', function(sock) {
sock.setTimeout(msecs, emitTimeout);
2012-05-02 12:13:54 -07:00
});
2011-10-04 20:51:34 +02:00
};
2012-05-02 12:13:54 -07:00
2011-10-04 20:51:34 +02:00
ClientRequest.prototype.setNoDelay = function() {
this._deferToConnect('setNoDelay', arguments);
};
ClientRequest.prototype.setSocketKeepAlive = function() {
this._deferToConnect('setKeepAlive', arguments);
};
2012-05-04 17:14:09 -07:00
ClientRequest.prototype.clearTimeout = function(cb) {
this.setTimeout(0, cb);
};
2011-10-04 20:51:34 +02:00
exports.request = function(options, cb) {
if (typeof options === 'string') {
options = url.parse(options);
} else if (options && options.path) {
options = util._extend({}, options);
options.path = encodeURI(options.path);
// encodeURI() doesn't escape quotes while url.parse() does. Fix up.
options.path = options.path.replace(/'/g, '%27');
}
if (options.protocol && options.protocol !== 'http:') {
throw new Error('Protocol:' + options.protocol + ' not supported.');
}
2011-10-04 20:51:34 +02:00
return new ClientRequest(options, cb);
2011-02-04 15:14:58 -08:00
};
2011-10-04 20:51:34 +02:00
exports.get = function(options, cb) {
var req = exports.request(options, cb);
req.end();
return req;
};
2011-02-04 15:14:58 -08:00
function ondrain() {
if (this._httpMessage) this._httpMessage.emit('drain');
}
2010-12-01 18:07:20 -08:00
function httpSocketSetup(socket) {
socket.removeListener('drain', ondrain);
socket.on('drain', ondrain);
2009-07-14 18:31:50 +02:00
}
2009-07-16 10:59:40 +02:00
2010-12-01 18:07:20 -08:00
function Server(requestListener) {
if (!(this instanceof Server)) return new Server(requestListener);
net.Server.call(this, { allowHalfOpen: true });
if (requestListener) {
2010-12-01 18:07:20 -08:00
this.addListener('request', requestListener);
}
2011-02-04 15:14:58 -08:00
// 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;
2010-12-01 18:07:20 -08:00
this.addListener('connection', connectionListener);
this.addListener('clientError', function(err, conn) {
conn.destroy(err);
});
this.timeout = 2 * 60 * 1000;
2010-03-19 19:22:04 -07:00
}
util.inherits(Server, net.Server);
2010-03-19 19:22:04 -07:00
2010-11-29 07:41:08 -08:00
Server.prototype.setTimeout = function(msecs, callback) {
this.timeout = msecs;
if (callback)
this.on('timeout', callback);
};
2010-03-19 19:22:04 -07:00
exports.Server = Server;
2010-11-29 07:41:08 -08:00
2010-12-01 18:07:20 -08:00
exports.createServer = function(requestListener) {
2010-03-19 19:22:04 -07:00
return new Server(requestListener);
2009-07-14 18:31:50 +02:00
};
2010-11-29 07:41:08 -08:00
2010-12-01 18:07:20 -08:00
function connectionListener(socket) {
2010-03-19 19:22:04 -07:00
var self = this;
2011-01-20 02:41:16 -08:00
var outgoing = [];
2011-02-04 15:14:58 -08:00
var incoming = [];
function abortIncoming() {
while (incoming.length) {
var req = incoming.shift();
req.emit('aborted');
req.emit('close');
2011-02-04 15:14:58 -08:00
}
// abort socket._httpMessage ?
}
function serverSocketCloseListener() {
debug('server socket close');
// mark this parser as reusable
if (this.parser)
freeParser(this.parser);
abortIncoming();
}
2010-12-01 18:07:20 -08:00
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;
2011-01-20 15:55:02 -08:00
parser.incoming = null;
2010-03-19 19:22:04 -07:00
// 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;
}
2010-12-01 18:07:20 -08:00
socket.addListener('error', function(e) {
self.emit('clientError', e, this);
});
2010-12-01 18:07:20 -08:00
socket.ondata = function(d, start, end) {
var ret = parser.execute(d, start, end - start);
if (ret instanceof Error) {
2010-12-01 18:07:20 -08:00
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.
2010-05-03 11:23:36 -07:00
socket.destroy();
}
}
2010-03-19 19:22:04 -07:00
};
2010-12-01 18:07:20 -08:00
socket.onend = function() {
2011-02-04 15:14:58 -08:00
var ret = parser.finish();
2011-02-04 15:14:58 -08:00
if (ret instanceof Error) {
debug('parse error');
socket.destroy(ret);
return;
}
if (!self.httpAllowHalfOpen) {
abortIncoming();
if (socket.writable) socket.end();
2011-02-04 15:14:58 -08:00
} else if (outgoing.length) {
2011-01-20 02:41:16 -08:00
outgoing[outgoing.length - 1]._last = true;
} else if (socket._httpMessage) {
socket._httpMessage._last = true;
} else {
if (socket.writable) socket.end();
}
};
socket.addListener('close', serverSocketCloseListener);
2010-03-19 19:22:04 -07:00
// 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.
2010-12-01 18:07:20 -08:00
parser.onIncoming = function(req, shouldKeepAlive) {
2011-02-04 15:14:58 -08:00
incoming.push(req);
var res = new ServerResponse(req);
2010-03-23 21:31:44 -07:00
res.shouldKeepAlive = shouldKeepAlive;
DTRACE_HTTP_SERVER_REQUEST(req, socket);
COUNTER_HTTP_SERVER_REQUEST();
2011-01-20 02:41:16 -08:00
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);
2011-02-04 15:14:58 -08:00
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();
2011-01-20 10:29:54 -08:00
res.detachSocket(socket);
2011-01-20 02:41:16 -08:00
if (res._last) {
socket.destroySoon();
} else {
// start sending the next message
var m = outgoing.shift();
if (m) {
m.assignSocket(socket);
}
}
});
2009-08-26 22:03:19 +02:00
if (req.headers.expect !== undefined &&
2010-11-29 07:41:08 -08:00
(req.httpVersionMajor == 1 && req.httpVersionMinor == 1) &&
continueExpression.test(req.headers['expect'])) {
res._expect_continue = true;
if (EventEmitter.listenerCount(self, 'checkContinue') > 0) {
2010-12-01 18:07:20 -08:00
self.emit('checkContinue', req, res);
2010-11-29 07:41:08 -08:00
} else {
res.writeContinue();
self.emit('request', req, res);
2010-11-29 07:41:08 -08:00
}
} else {
self.emit('request', req, res);
}
return false; // Not a HEAD response. (Not even a response!)
2010-03-19 19:22:04 -07:00
};
2009-07-14 18:31:50 +02:00
}
2011-01-02 01:13:56 -08:00
exports._connectionListener = connectionListener;
2011-10-04 20:51:34 +02:00
// Legacy Interface
2011-10-04 20:51:34 +02:00
function Client(port, host) {
if (!(this instanceof Client)) return new Client(port, host);
EventEmitter.call(this);
2011-10-04 20:51:34 +02:00
host = host || 'localhost';
port = port || 80;
this.host = host;
this.port = port;
this.agent = new Agent({ host: host, port: port, maxSockets: 1 });
2011-01-20 02:41:16 -08:00
}
2011-10-04 20:51:34 +02:00
util.inherits(Client, EventEmitter);
Client.prototype.request = function(method, path, headers) {
2011-01-20 02:41:16 -08:00
var self = this;
2011-10-04 20:51:34 +02:00
var options = {};
options.host = self.host;
options.port = self.port;
if (method[0] === '/') {
headers = path;
path = method;
method = 'GET';
}
options.method = method;
options.path = path;
options.headers = headers;
options.agent = self.agent;
var c = new ClientRequest(options);
c.on('error', function(e) {
self.emit('error', e);
});
2012-02-18 15:01:35 -08:00
// The old Client interface emitted 'end' on socket end.
2011-10-04 20:51:34 +02:00
// This doesn't map to how we want things to operate in the future
// but it will get removed when we remove this legacy interface.
c.on('socket', function(s) {
s.on('end', function() {
if (self._decoder) {
var ret = self._decoder.end();
if (ret)
self.emit('data', ret);
}
2011-10-04 20:51:34 +02:00
self.emit('end');
});
2011-10-04 20:51:34 +02:00
});
2010-03-19 19:22:04 -07:00
return c;
};
exports.Client = util.deprecate(Client,
'http.Client will be removed soon. Do not use it.');
exports.createClient = util.deprecate(function(port, host) {
2011-10-04 20:51:34 +02:00
return new Client(port, host);
}, 'http.createClient is deprecated. Use `http.request` instead.');