|
|
@ -8,24 +8,24 @@ var HTTPParser = process.binding('http_parser').HTTPParser; |
|
|
|
var debug; |
|
|
|
var debugLevel = parseInt(process.env.NODE_DEBUG, 16); |
|
|
|
if (debugLevel & 0x4) { |
|
|
|
debug = function (x) { console.error('HTTP: %s', x); }; |
|
|
|
debug = function(x) { console.error('HTTP: %s', x); }; |
|
|
|
} else { |
|
|
|
debug = function () { }; |
|
|
|
debug = function() { }; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
var parsers = new FreeList('parsers', 1000, function() { |
|
|
|
var parser = new HTTPParser('request'); |
|
|
|
|
|
|
|
parser.onMessageBegin = function () { |
|
|
|
parser.onMessageBegin = function() { |
|
|
|
parser.incoming = new IncomingMessage(parser.socket); |
|
|
|
parser.field = null; |
|
|
|
parser.value = null; |
|
|
|
}; |
|
|
|
|
|
|
|
// Only servers will get URL events.
|
|
|
|
parser.onURL = function (b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start+len); |
|
|
|
parser.onURL = function(b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start + len); |
|
|
|
if (parser.incoming.url) { |
|
|
|
parser.incoming.url += slice; |
|
|
|
} else { |
|
|
@ -34,8 +34,8 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeaderField = function (b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start+len).toLowerCase(); |
|
|
|
parser.onHeaderField = function(b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start + len).toLowerCase(); |
|
|
|
if (parser.value != undefined) { |
|
|
|
parser.incoming._addHeaderLine(parser.field, parser.value); |
|
|
|
parser.field = null; |
|
|
@ -48,8 +48,8 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeaderValue = function (b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start+len); |
|
|
|
parser.onHeaderValue = function(b, start, len) { |
|
|
|
var slice = b.toString('ascii', start, start + len); |
|
|
|
if (parser.value) { |
|
|
|
parser.value += slice; |
|
|
|
} else { |
|
|
@ -57,7 +57,7 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeadersComplete = function (info) { |
|
|
|
parser.onHeadersComplete = function(info) { |
|
|
|
if (parser.field && (parser.value != undefined)) { |
|
|
|
parser.incoming._addHeaderLine(parser.field, parser.value); |
|
|
|
parser.field = null; |
|
|
@ -89,9 +89,9 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
return isHeadResponse; |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onBody = function (b, start, len) { |
|
|
|
parser.onBody = function(b, start, len) { |
|
|
|
// TODO body encoding?
|
|
|
|
var slice = b.slice(start, start+len); |
|
|
|
var slice = b.slice(start, start + len); |
|
|
|
if (parser.incoming._decoder) { |
|
|
|
var string = parser.incoming._decoder.write(slice); |
|
|
|
if (string.length) parser.incoming.emit('data', string); |
|
|
@ -100,14 +100,14 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onMessageComplete = function () { |
|
|
|
parser.onMessageComplete = function() { |
|
|
|
this.incoming.complete = true; |
|
|
|
if (parser.field && (parser.value != undefined)) { |
|
|
|
parser.incoming._addHeaderLine(parser.field, parser.value); |
|
|
|
} |
|
|
|
if (!parser.incoming.upgrade) { |
|
|
|
// For upgraded connections, also emit this after parser.execute
|
|
|
|
parser.incoming.emit("end"); |
|
|
|
parser.incoming.emit('end'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
@ -116,7 +116,7 @@ var parsers = new FreeList('parsers', 1000, function () { |
|
|
|
exports.parsers = parsers; |
|
|
|
|
|
|
|
|
|
|
|
var CRLF = "\r\n"; |
|
|
|
var CRLF = '\r\n'; |
|
|
|
var STATUS_CODES = exports.STATUS_CODES = { |
|
|
|
100 : 'Continue', |
|
|
|
101 : 'Switching Protocols', |
|
|
@ -183,7 +183,7 @@ var continueExpression = /100-continue/i; |
|
|
|
|
|
|
|
|
|
|
|
/* Abstract base class for ServerRequest and ClientResponse. */ |
|
|
|
function IncomingMessage (socket) { |
|
|
|
function IncomingMessage(socket) { |
|
|
|
stream.Stream.call(this); |
|
|
|
|
|
|
|
// TODO Remove one of these eventually.
|
|
|
@ -198,7 +198,7 @@ function IncomingMessage (socket) { |
|
|
|
this.readable = true; |
|
|
|
|
|
|
|
// request (server) only
|
|
|
|
this.url = ""; |
|
|
|
this.url = ''; |
|
|
|
|
|
|
|
this.method = null; |
|
|
|
|
|
|
@ -212,23 +212,23 @@ util.inherits(IncomingMessage, stream.Stream); |
|
|
|
exports.IncomingMessage = IncomingMessage; |
|
|
|
|
|
|
|
|
|
|
|
IncomingMessage.prototype.destroy = function (error) { |
|
|
|
IncomingMessage.prototype.destroy = function(error) { |
|
|
|
this.socket.destroy(error); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
IncomingMessage.prototype.setEncoding = function (encoding) { |
|
|
|
var StringDecoder = require("string_decoder").StringDecoder; // lazy load
|
|
|
|
IncomingMessage.prototype.setEncoding = function(encoding) { |
|
|
|
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
|
|
|
|
this._decoder = new StringDecoder(encoding); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
IncomingMessage.prototype.pause = function () { |
|
|
|
IncomingMessage.prototype.pause = function() { |
|
|
|
this.socket.pause(); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
IncomingMessage.prototype.resume = function () { |
|
|
|
IncomingMessage.prototype.resume = function() { |
|
|
|
this.socket.resume(); |
|
|
|
}; |
|
|
|
|
|
|
@ -240,13 +240,9 @@ IncomingMessage.prototype.resume = function () { |
|
|
|
// multiple values this way. If not, we declare the first instance the winner
|
|
|
|
// and drop the second. Extended header fields (those beginning with 'x-') are
|
|
|
|
// always joined.
|
|
|
|
IncomingMessage.prototype._addHeaderLine = function (field, value) { |
|
|
|
var dest; |
|
|
|
if (this.complete) { |
|
|
|
dest = this.trailers; |
|
|
|
} else { |
|
|
|
dest = this.headers; |
|
|
|
} |
|
|
|
IncomingMessage.prototype._addHeaderLine = function(field, value) { |
|
|
|
var dest = this.complete ? this.trailers : this.headers; |
|
|
|
|
|
|
|
switch (field) { |
|
|
|
// Array headers:
|
|
|
|
case 'set-cookie': |
|
|
@ -273,7 +269,7 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { |
|
|
|
|
|
|
|
|
|
|
|
default: |
|
|
|
if (field.slice(0,2) == 'x-') { |
|
|
|
if (field.slice(0, 2) == 'x-') { |
|
|
|
// except for x-
|
|
|
|
if (field in dest) { |
|
|
|
dest[field] += ', ' + value; |
|
|
@ -289,7 +285,7 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function OutgoingMessage (socket) { |
|
|
|
function OutgoingMessage(socket) { |
|
|
|
stream.Stream.call(this); |
|
|
|
|
|
|
|
// TODO Remove one of these eventually.
|
|
|
@ -317,13 +313,13 @@ util.inherits(OutgoingMessage, stream.Stream); |
|
|
|
exports.OutgoingMessage = OutgoingMessage; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype.destroy = function (error) { |
|
|
|
OutgoingMessage.prototype.destroy = function(error) { |
|
|
|
this.socket.destroy(error); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// This abstract either writing directly to the socket or buffering it.
|
|
|
|
OutgoingMessage.prototype._send = function (data, encoding) { |
|
|
|
OutgoingMessage.prototype._send = function(data, encoding) { |
|
|
|
// This is a shameful hack to get the headers and first body chunk onto
|
|
|
|
// the same packet. Future versions of Node are going to take care of
|
|
|
|
// this at a lower level and in a more general way.
|
|
|
@ -362,49 +358,49 @@ OutgoingMessage.prototype._writeRaw = function(data, encoding) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype._buffer = function (data, encoding) { |
|
|
|
OutgoingMessage.prototype._buffer = function(data, encoding) { |
|
|
|
if (data.length === 0) return; |
|
|
|
|
|
|
|
var length = this.output.length; |
|
|
|
|
|
|
|
if (length === 0 || typeof data != 'string') { |
|
|
|
this.output.push(data); |
|
|
|
encoding = encoding || "ascii"; |
|
|
|
encoding = encoding || 'ascii'; |
|
|
|
this.outputEncodings.push(encoding); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
var lastEncoding = this.outputEncodings[length-1]; |
|
|
|
var lastData = this.output[length-1]; |
|
|
|
var lastEncoding = this.outputEncodings[length - 1]; |
|
|
|
var lastData = this.output[length - 1]; |
|
|
|
|
|
|
|
if ((lastEncoding === encoding) || |
|
|
|
(!encoding && data.constructor === lastData.constructor)) { |
|
|
|
this.output[length-1] = lastData + data; |
|
|
|
this.output[length - 1] = lastData + data; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
this.output.push(data); |
|
|
|
encoding = encoding || "ascii"; |
|
|
|
encoding = encoding || 'ascii'; |
|
|
|
this.outputEncodings.push(encoding); |
|
|
|
|
|
|
|
return false; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype._storeHeader = function (firstLine, headers) { |
|
|
|
OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { |
|
|
|
var sentConnectionHeader = false; |
|
|
|
var sentContentLengthHeader = false; |
|
|
|
var sentTransferEncodingHeader = false; |
|
|
|
var sentExpect = false; |
|
|
|
|
|
|
|
// firstLine in the case of request is: "GET /index.html HTTP/1.1\r\n"
|
|
|
|
// in the case of response it is: "HTTP/1.1 200 OK\r\n"
|
|
|
|
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
|
|
|
|
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
|
|
|
|
var messageHeader = firstLine; |
|
|
|
var field, value; |
|
|
|
var self = this; |
|
|
|
|
|
|
|
function store(field, value) { |
|
|
|
messageHeader += field + ": " + value + CRLF; |
|
|
|
messageHeader += field + ': ' + value + CRLF; |
|
|
|
|
|
|
|
if (connectionExpression.test(field)) { |
|
|
|
sentConnectionHeader = true; |
|
|
@ -455,17 +451,17 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) { |
|
|
|
if (sentConnectionHeader == false) { |
|
|
|
if (this.shouldKeepAlive && |
|
|
|
(sentContentLengthHeader || this.useChunkedEncodingByDefault)) { |
|
|
|
messageHeader += "Connection: keep-alive\r\n"; |
|
|
|
messageHeader += 'Connection: keep-alive\r\n'; |
|
|
|
} else { |
|
|
|
this._last = true; |
|
|
|
messageHeader += "Connection: close\r\n"; |
|
|
|
messageHeader += 'Connection: close\r\n'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (sentContentLengthHeader == false && sentTransferEncodingHeader == false) { |
|
|
|
if (this._hasBody) { |
|
|
|
if (this.useChunkedEncodingByDefault) { |
|
|
|
messageHeader += "Transfer-Encoding: chunked\r\n"; |
|
|
|
messageHeader += 'Transfer-Encoding: chunked\r\n'; |
|
|
|
this.chunkedEncoding = true; |
|
|
|
} else { |
|
|
|
this._last = true; |
|
|
@ -481,23 +477,24 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) { |
|
|
|
|
|
|
|
// wait until the first body chunk, or close(), is sent to flush,
|
|
|
|
// UNLESS we're sending Expect: 100-continue.
|
|
|
|
if (sentExpect) this._send(""); |
|
|
|
if (sentExpect) this._send(''); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype.write = function (chunk, encoding) { |
|
|
|
OutgoingMessage.prototype.write = function(chunk, encoding) { |
|
|
|
if (!this._header) { |
|
|
|
throw new Error("You have to call writeHead() before write()"); |
|
|
|
throw new Error('You have to call writeHead() before write()'); |
|
|
|
} |
|
|
|
|
|
|
|
if (!this._hasBody) { |
|
|
|
console.error("This type of response MUST NOT have a body. Ignoring write() calls."); |
|
|
|
console.error('This type of response MUST NOT have a body. ' + |
|
|
|
'Ignoring write() calls.'); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
if (typeof chunk !== "string" && !Buffer.isBuffer(chunk) && |
|
|
|
if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk) && |
|
|
|
!Array.isArray(chunk)) { |
|
|
|
throw new TypeError("first argument must be a string, Array, or Buffer"); |
|
|
|
throw new TypeError('first argument must be a string, Array, or Buffer'); |
|
|
|
} |
|
|
|
|
|
|
|
if (chunk.length === 0) return false; |
|
|
@ -525,8 +522,8 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype.addTrailers = function (headers) { |
|
|
|
this._trailer = ""; |
|
|
|
OutgoingMessage.prototype.addTrailers = function(headers) { |
|
|
|
this._trailer = ''; |
|
|
|
var keys = Object.keys(headers); |
|
|
|
var isArray = (Array.isArray(headers)); |
|
|
|
for (var i = 0, l = keys.length; i < l; i++) { |
|
|
@ -539,16 +536,16 @@ OutgoingMessage.prototype.addTrailers = function (headers) { |
|
|
|
value = headers[key]; |
|
|
|
} |
|
|
|
|
|
|
|
this._trailer += field + ": " + value + CRLF; |
|
|
|
this._trailer += field + ': ' + value + CRLF; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
OutgoingMessage.prototype.end = function (data, encoding) { |
|
|
|
OutgoingMessage.prototype.end = function(data, encoding) { |
|
|
|
var ret; |
|
|
|
|
|
|
|
var hot = this._headerSent === false && |
|
|
|
typeof(data) === "string" && |
|
|
|
typeof(data) === 'string' && |
|
|
|
data.length > 0 && |
|
|
|
this.output.length === 0 && |
|
|
|
this.connection.writable && |
|
|
@ -562,8 +559,8 @@ OutgoingMessage.prototype.end = function (data, encoding) { |
|
|
|
if (this.chunkedEncoding) { |
|
|
|
var l = Buffer.byteLength(data, encoding).toString(16); |
|
|
|
ret = this.connection.write(this._header + l + CRLF + |
|
|
|
data + "\r\n0\r\n" + |
|
|
|
this._trailer + "\r\n", encoding); |
|
|
|
data + '\r\n0\r\n' + |
|
|
|
this._trailer + '\r\n', encoding); |
|
|
|
} else { |
|
|
|
ret = this.connection.write(this._header + data, encoding); |
|
|
|
} |
|
|
@ -596,7 +593,7 @@ OutgoingMessage.prototype.end = function (data, encoding) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function ServerResponse (req) { |
|
|
|
function ServerResponse(req) { |
|
|
|
OutgoingMessage.call(this, req.socket); |
|
|
|
|
|
|
|
if (req.method === 'HEAD') this._hasBody = false; |
|
|
@ -612,20 +609,20 @@ util.inherits(ServerResponse, OutgoingMessage); |
|
|
|
exports.ServerResponse = ServerResponse; |
|
|
|
|
|
|
|
|
|
|
|
ServerResponse.prototype.writeContinue = function () { |
|
|
|
this._writeRaw("HTTP/1.1 100 Continue" + CRLF + CRLF, 'ascii'); |
|
|
|
ServerResponse.prototype.writeContinue = function() { |
|
|
|
this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii'); |
|
|
|
this._sent100 = true; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
ServerResponse.prototype.writeHead = function (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"; |
|
|
|
reasonPhrase = STATUS_CODES[statusCode] || 'unknown'; |
|
|
|
headerIndex = 1; |
|
|
|
} |
|
|
|
|
|
|
@ -635,7 +632,7 @@ ServerResponse.prototype.writeHead = function (statusCode) { |
|
|
|
headers = {}; |
|
|
|
} |
|
|
|
|
|
|
|
var statusLine = "HTTP/1.1 " + statusCode.toString() + " " + |
|
|
|
var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + |
|
|
|
reasonPhrase + CRLF; |
|
|
|
|
|
|
|
if (statusCode === 204 || statusCode === 304 || |
|
|
@ -663,24 +660,24 @@ ServerResponse.prototype.writeHead = function (statusCode) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
ServerResponse.prototype.writeHeader = function () { |
|
|
|
ServerResponse.prototype.writeHeader = function() { |
|
|
|
this.writeHead.apply(this, arguments); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function ClientRequest (socket, method, url, headers) { |
|
|
|
function ClientRequest(socket, method, url, headers) { |
|
|
|
OutgoingMessage.call(this, socket); |
|
|
|
|
|
|
|
this.method = method = method.toUpperCase(); |
|
|
|
this.shouldKeepAlive = false; |
|
|
|
if (method === "GET" || method === "HEAD") { |
|
|
|
if (method === 'GET' || method === 'HEAD') { |
|
|
|
this.useChunkedEncodingByDefault = false; |
|
|
|
} else { |
|
|
|
this.useChunkedEncodingByDefault = true; |
|
|
|
} |
|
|
|
this._last = true; |
|
|
|
|
|
|
|
this._storeHeader(method + " " + url + " HTTP/1.1\r\n", headers); |
|
|
|
this._storeHeader(method + ' ' + url + ' HTTP/1.1\r\n', headers); |
|
|
|
} |
|
|
|
util.inherits(ClientRequest, OutgoingMessage); |
|
|
|
|
|
|
@ -688,7 +685,7 @@ util.inherits(ClientRequest, OutgoingMessage); |
|
|
|
exports.ClientRequest = ClientRequest; |
|
|
|
|
|
|
|
|
|
|
|
function outgoingFlush (socket) { |
|
|
|
function outgoingFlush(socket) { |
|
|
|
// This logic is probably a bit confusing. Let me explain a bit:
|
|
|
|
//
|
|
|
|
// In both HTTP servers and clients it is possible to queue up several
|
|
|
@ -700,7 +697,7 @@ function outgoingFlush (socket) { |
|
|
|
//
|
|
|
|
// When the user does
|
|
|
|
//
|
|
|
|
// req2.write("hello world\n");
|
|
|
|
// req2.write('hello world\n');
|
|
|
|
//
|
|
|
|
// it's possible that the first request has not been completely flushed to
|
|
|
|
// the socket yet. Thus the outgoing messages need to be prepared to queue
|
|
|
@ -731,14 +728,14 @@ function outgoingFlush (socket) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function httpSocketSetup (socket) { |
|
|
|
function httpSocketSetup(socket) { |
|
|
|
// An array of outgoing messages for the socket. In pipelined connections
|
|
|
|
// we need to keep track of the order they were sent.
|
|
|
|
socket._outgoing = []; |
|
|
|
socket.__destroyOnDrain = false; |
|
|
|
|
|
|
|
// NOTE: be sure not to use ondrain elsewhere in this file!
|
|
|
|
socket.ondrain = function () { |
|
|
|
socket.ondrain = function() { |
|
|
|
var message = socket._outgoing[0]; |
|
|
|
if (message) message.emit('drain'); |
|
|
|
if (socket.__destroyOnDrain) socket.destroy(); |
|
|
@ -746,15 +743,15 @@ function httpSocketSetup (socket) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Server (requestListener) { |
|
|
|
function Server(requestListener) { |
|
|
|
if (!(this instanceof Server)) return new Server(requestListener); |
|
|
|
net.Server.call(this, { allowHalfOpen: true }); |
|
|
|
|
|
|
|
if (requestListener) { |
|
|
|
this.addListener("request", requestListener); |
|
|
|
this.addListener('request', requestListener); |
|
|
|
} |
|
|
|
|
|
|
|
this.addListener("connection", connectionListener); |
|
|
|
this.addListener('connection', connectionListener); |
|
|
|
} |
|
|
|
util.inherits(Server, net.Server); |
|
|
|
|
|
|
@ -762,20 +759,20 @@ util.inherits(Server, net.Server); |
|
|
|
exports.Server = Server; |
|
|
|
|
|
|
|
|
|
|
|
exports.createServer = function (requestListener) { |
|
|
|
exports.createServer = function(requestListener) { |
|
|
|
return new Server(requestListener); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function connectionListener (socket) { |
|
|
|
function connectionListener(socket) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
debug("SERVER new http connection"); |
|
|
|
debug('SERVER new http connection'); |
|
|
|
|
|
|
|
httpSocketSetup(socket); |
|
|
|
|
|
|
|
socket.setTimeout(2*60*1000); // 2 minute timeout
|
|
|
|
socket.addListener('timeout', function () { |
|
|
|
socket.setTimeout(2 * 60 * 1000); // 2 minute timeout
|
|
|
|
socket.addListener('timeout', function() { |
|
|
|
socket.destroy(); |
|
|
|
}); |
|
|
|
|
|
|
@ -783,14 +780,14 @@ function connectionListener (socket) { |
|
|
|
parser.reinitialize('request'); |
|
|
|
parser.socket = socket; |
|
|
|
|
|
|
|
socket.addListener('error', function (e) { |
|
|
|
socket.addListener('error', function(e) { |
|
|
|
self.emit('clientError', e); |
|
|
|
}); |
|
|
|
|
|
|
|
socket.ondata = function (d, start, end) { |
|
|
|
socket.ondata = function(d, start, end) { |
|
|
|
var ret = parser.execute(d, start, end - start); |
|
|
|
if (ret instanceof Error) { |
|
|
|
debug("parse error"); |
|
|
|
debug('parse error'); |
|
|
|
socket.destroy(ret); |
|
|
|
} else if (parser.incoming && parser.incoming.upgrade) { |
|
|
|
var bytesParsed = ret; |
|
|
@ -803,7 +800,7 @@ function connectionListener (socket) { |
|
|
|
// in the upgradeHead from the closing lines of the headers
|
|
|
|
var upgradeHead = d.slice(start + bytesParsed + 1, end); |
|
|
|
|
|
|
|
if (self.listeners("upgrade").length) { |
|
|
|
if (self.listeners('upgrade').length) { |
|
|
|
self.emit('upgrade', req, req.socket, upgradeHead); |
|
|
|
} else { |
|
|
|
// Got upgrade header, but have no handler.
|
|
|
@ -812,25 +809,25 @@ function connectionListener (socket) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
socket.onend = function () { |
|
|
|
socket.onend = function() { |
|
|
|
parser.finish(); |
|
|
|
|
|
|
|
if (socket._outgoing.length) { |
|
|
|
socket._outgoing[socket._outgoing.length-1]._last = true; |
|
|
|
socket._outgoing[socket._outgoing.length - 1]._last = true; |
|
|
|
outgoingFlush(socket); |
|
|
|
} else { |
|
|
|
socket.end(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
socket.addListener('close', function () { |
|
|
|
socket.addListener('close', function() { |
|
|
|
// unref the parser for easy gc
|
|
|
|
parsers.free(parser); |
|
|
|
}); |
|
|
|
|
|
|
|
// At the end of each response message, after it has been flushed to the
|
|
|
|
// socket. Here we insert logic about what to do next.
|
|
|
|
socket._onOutgoingSent = function (message) { |
|
|
|
socket._onOutgoingSent = function(message) { |
|
|
|
var message = socket._outgoing.shift(); |
|
|
|
if (message._last) { |
|
|
|
// No more messages to be pushed out.
|
|
|
@ -851,7 +848,7 @@ function connectionListener (socket) { |
|
|
|
// 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) { |
|
|
|
parser.onIncoming = function(req, shouldKeepAlive) { |
|
|
|
var res = new ServerResponse(req); |
|
|
|
debug('server response shouldKeepAlive: ' + shouldKeepAlive); |
|
|
|
res.shouldKeepAlive = shouldKeepAlive; |
|
|
@ -861,8 +858,8 @@ function connectionListener (socket) { |
|
|
|
(req.httpVersionMajor == 1 && req.httpVersionMinor == 1) && |
|
|
|
continueExpression.test(req.headers['expect'])) { |
|
|
|
res._expect_continue = true; |
|
|
|
if (self.listeners("checkContinue").length) { |
|
|
|
self.emit("checkContinue", req, res); |
|
|
|
if (self.listeners('checkContinue').length) { |
|
|
|
self.emit('checkContinue', req, res); |
|
|
|
} else { |
|
|
|
res.writeContinue(); |
|
|
|
self.emit('request', req, res); |
|
|
@ -875,7 +872,7 @@ function connectionListener (socket) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Client ( ) { |
|
|
|
function Client() { |
|
|
|
if (!(this instanceof Client)) return new Client(); |
|
|
|
net.Stream.call(this, { allowHalfOpen: true }); |
|
|
|
var self = this; |
|
|
@ -890,7 +887,7 @@ function Client ( ) { |
|
|
|
|
|
|
|
function onData(d, start, end) { |
|
|
|
if (!self.parser) { |
|
|
|
throw new Error("parser not initialized prior to Client.ondata call"); |
|
|
|
throw new Error('parser not initialized prior to Client.ondata call'); |
|
|
|
} |
|
|
|
var ret = self.parser.execute(d, start, end - start); |
|
|
|
if (ret instanceof Error) { |
|
|
@ -912,30 +909,29 @@ function Client ( ) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
self.addListener("connect", function () { |
|
|
|
self.addListener('connect', function() { |
|
|
|
debug('CLIENT connected'); |
|
|
|
|
|
|
|
self.ondata = onData; |
|
|
|
self.onend = onEnd; |
|
|
|
|
|
|
|
self._state = "connected"; |
|
|
|
self._state = 'connected'; |
|
|
|
|
|
|
|
self._initParser(); |
|
|
|
debug('CLIENT requests: ' + util.inspect(self._outgoing.map(function (r) { return r.method; }))); |
|
|
|
outgoingFlush(self); |
|
|
|
}); |
|
|
|
|
|
|
|
function onEnd() { |
|
|
|
if (self.parser) self.parser.finish(); |
|
|
|
debug("CLIENT got end closing. state = " + self._state); |
|
|
|
debug('CLIENT got end closing. state = ' + self._state); |
|
|
|
self.end(); |
|
|
|
}; |
|
|
|
|
|
|
|
self.addListener("close", function (e) { |
|
|
|
self._state = "disconnected"; |
|
|
|
self.addListener('close', function(e) { |
|
|
|
self._state = 'disconnected'; |
|
|
|
if (e) return; |
|
|
|
|
|
|
|
debug("CLIENT onClose. state = " + self._state); |
|
|
|
debug('CLIENT onClose. state = ' + self._state); |
|
|
|
|
|
|
|
// finally done with the request
|
|
|
|
self._outgoing.shift(); |
|
|
@ -948,14 +944,14 @@ function Client ( ) { |
|
|
|
self.parser = null; |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
} |
|
|
|
util.inherits(Client, net.Stream); |
|
|
|
|
|
|
|
|
|
|
|
exports.Client = Client; |
|
|
|
|
|
|
|
|
|
|
|
exports.createClient = function (port, host, https, credentials) { |
|
|
|
exports.createClient = function(port, host, https, credentials) { |
|
|
|
var c = new Client(); |
|
|
|
c.port = port; |
|
|
|
c.host = host; |
|
|
@ -965,24 +961,24 @@ exports.createClient = function (port, host, https, credentials) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Client.prototype._initParser = function () { |
|
|
|
Client.prototype._initParser = function() { |
|
|
|
var self = this; |
|
|
|
if (!self.parser) self.parser = parsers.alloc(); |
|
|
|
self.parser.reinitialize('response'); |
|
|
|
self.parser.socket = self; |
|
|
|
self.parser.onIncoming = function (res) { |
|
|
|
debug("CLIENT incoming response!"); |
|
|
|
self.parser.onIncoming = function(res) { |
|
|
|
debug('CLIENT incoming response!'); |
|
|
|
|
|
|
|
var req = self._outgoing[0]; |
|
|
|
|
|
|
|
// Responses to HEAD requests are AWFUL. Ask Ryan.
|
|
|
|
// A major oversight in HTTP. Hence this nastiness.
|
|
|
|
var isHeadResponse = req.method == "HEAD"; |
|
|
|
var isHeadResponse = req.method == 'HEAD'; |
|
|
|
debug('CLIENT isHeadResponse ' + isHeadResponse); |
|
|
|
|
|
|
|
if (res.statusCode == 100) { |
|
|
|
// restart the parser, as this is a continue message.
|
|
|
|
req.emit("continue"); |
|
|
|
req.emit('continue'); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
@ -990,8 +986,8 @@ Client.prototype._initParser = function () { |
|
|
|
req.shouldKeepAlive = false; |
|
|
|
} |
|
|
|
|
|
|
|
res.addListener('end', function ( ) { |
|
|
|
debug("CLIENT request complete disconnecting. state = " + self._state); |
|
|
|
res.addListener('end', function() { |
|
|
|
debug('CLIENT request complete disconnecting. state = ' + self._state); |
|
|
|
// For the moment we reconnect for every request. FIXME!
|
|
|
|
// All that should be required for keep-alive is to not reconnect,
|
|
|
|
// but outgoingFlush instead.
|
|
|
@ -1004,7 +1000,7 @@ Client.prototype._initParser = function () { |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
req.emit("response", res); |
|
|
|
req.emit('response', res); |
|
|
|
|
|
|
|
return isHeadResponse; |
|
|
|
}; |
|
|
@ -1022,7 +1018,7 @@ Client.prototype._initParser = function () { |
|
|
|
// each request/response cycle so we cannot shift off the request from
|
|
|
|
// client._outgoing until we're completely disconnected after the response
|
|
|
|
// comes back.
|
|
|
|
Client.prototype._onOutgoingSent = function (message) { |
|
|
|
Client.prototype._onOutgoingSent = function(message) { |
|
|
|
// We've just finished a message. We don't end/shutdown the connection here
|
|
|
|
// because HTTP servers typically cannot handle half-closed connections
|
|
|
|
// (Node servers can).
|
|
|
@ -1030,27 +1026,27 @@ Client.prototype._onOutgoingSent = function (message) { |
|
|
|
// Instead, we just check if the connection is closed, and if so
|
|
|
|
// reconnect if we have pending messages.
|
|
|
|
if (this._outgoing.length) { |
|
|
|
debug("CLIENT request flush. ensure connection. state = " + this._state); |
|
|
|
debug('CLIENT request flush. ensure connection. state = ' + this._state); |
|
|
|
this._ensureConnection(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Client.prototype._ensureConnection = function () { |
|
|
|
Client.prototype._ensureConnection = function() { |
|
|
|
if (this._state == 'disconnected') { |
|
|
|
debug("CLIENT reconnecting state = " + this._state); |
|
|
|
debug('CLIENT reconnecting state = ' + this._state); |
|
|
|
this.connect(this.port, this.host); |
|
|
|
this._state = "connecting"; |
|
|
|
this._state = 'connecting'; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Client.prototype.request = function (method, url, headers) { |
|
|
|
if (typeof(url) != "string") { |
|
|
|
Client.prototype.request = function(method, url, headers) { |
|
|
|
if (typeof(url) != 'string') { |
|
|
|
// assume method was omitted, shift arguments
|
|
|
|
headers = url; |
|
|
|
url = method; |
|
|
|
method = "GET"; |
|
|
|
method = 'GET'; |
|
|
|
} |
|
|
|
var req = new ClientRequest(this, method, url, headers); |
|
|
|
this._outgoing.push(req); |
|
|
@ -1059,7 +1055,7 @@ Client.prototype.request = function (method, url, headers) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
exports.cat = function (url, encoding_, headers_) { |
|
|
|
exports.cat = function(url, encoding_, headers_) { |
|
|
|
var encoding = 'utf8', |
|
|
|
headers = {}, |
|
|
|
callback = null; |
|
|
@ -1083,7 +1079,7 @@ exports.cat = function (url, encoding_, headers_) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var url = require("url").parse(url); |
|
|
|
var url = require('url').parse(url); |
|
|
|
|
|
|
|
var hasHost = false; |
|
|
|
if (Array.isArray(headers)) { |
|
|
@ -1093,7 +1089,7 @@ exports.cat = function (url, encoding_, headers_) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (typeof headers === "Object") { |
|
|
|
} else if (typeof headers === 'Object') { |
|
|
|
var keys = Object.keys(headers); |
|
|
|
for (var i = 0, l = keys.length; i < l; i++) { |
|
|
|
var key = keys[i]; |
|
|
@ -1103,20 +1099,23 @@ exports.cat = function (url, encoding_, headers_) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!hasHost) headers["Host"] = url.hostname; |
|
|
|
if (!hasHost) headers['Host'] = url.hostname; |
|
|
|
|
|
|
|
var content = ""; |
|
|
|
var content = ''; |
|
|
|
|
|
|
|
var client = exports.createClient(url.port || 80, url.hostname); |
|
|
|
var req = client.request((url.pathname || "/")+(url.search || "")+(url.hash || ""), headers); |
|
|
|
var req = client.request((url.pathname || '/') + |
|
|
|
(url.search || '') + |
|
|
|
(url.hash || ''), |
|
|
|
headers); |
|
|
|
|
|
|
|
if (url.protocol=="https:") { |
|
|
|
if (url.protocol == 'https:') { |
|
|
|
client.https = true; |
|
|
|
} |
|
|
|
|
|
|
|
var callbackSent = false; |
|
|
|
|
|
|
|
req.addListener('response', function (res) { |
|
|
|
req.addListener('response', function(res) { |
|
|
|
if (res.statusCode < 200 || res.statusCode >= 300) { |
|
|
|
if (callback && !callbackSent) { |
|
|
|
callback(res.statusCode); |
|
|
@ -1126,8 +1125,8 @@ exports.cat = function (url, encoding_, headers_) { |
|
|
|
return; |
|
|
|
} |
|
|
|
res.setEncoding(encoding); |
|
|
|
res.addListener('data', function (chunk) { content += chunk; }); |
|
|
|
res.addListener('end', function () { |
|
|
|
res.addListener('data', function(chunk) { content += chunk; }); |
|
|
|
res.addListener('end', function() { |
|
|
|
if (callback && !callbackSent) { |
|
|
|
callback(null, content); |
|
|
|
callbackSent = true; |
|
|
@ -1135,14 +1134,14 @@ exports.cat = function (url, encoding_, headers_) { |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
client.addListener("error", function (err) { |
|
|
|
client.addListener('error', function(err) { |
|
|
|
if (callback && !callbackSent) { |
|
|
|
callback(err); |
|
|
|
callbackSent = true; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
client.addListener("close", function () { |
|
|
|
client.addListener('close', function() { |
|
|
|
if (callback && !callbackSent) { |
|
|
|
callback(new Error('Connection closed unexpectedly')); |
|
|
|
callbackSent = true; |
|
|
|