|
|
@ -1,110 +1,11 @@ |
|
|
|
var sys = require('sys'); |
|
|
|
var net = require('net'); |
|
|
|
var events = require('events'); |
|
|
|
|
|
|
|
var HTTPParser = process.binding('http_parser').HTTPParser; |
|
|
|
|
|
|
|
var parserFreeList = []; |
|
|
|
|
|
|
|
function newParser (type) { |
|
|
|
var parser; |
|
|
|
if (parserFreeList.length) { |
|
|
|
parser = parserFreeList.shift(); |
|
|
|
parser.reinitialize(type); |
|
|
|
} else { |
|
|
|
parser = new HTTPParser(type); |
|
|
|
|
|
|
|
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.asciiSlice(start, start+len); |
|
|
|
if (parser.incoming.url) { |
|
|
|
parser.incoming.url += slice; |
|
|
|
} else { |
|
|
|
// Almost always will branch here.
|
|
|
|
parser.incoming.url = slice; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeaderField = function (b, start, len) { |
|
|
|
var slice = b.asciiSlice(start, start+len).toLowerCase(); |
|
|
|
if (parser.value) { |
|
|
|
parser.incoming._addHeaderLine(parser.field, parser.value); |
|
|
|
parser.field = null; |
|
|
|
parser.value = null; |
|
|
|
} |
|
|
|
if (parser.field) { |
|
|
|
parser.field += slice; |
|
|
|
} else { |
|
|
|
parser.field = slice; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeaderValue = function (b, start, len) { |
|
|
|
var slice = b.asciiSlice(start, start+len); |
|
|
|
if (parser.value) { |
|
|
|
parser.value += slice; |
|
|
|
} else { |
|
|
|
parser.value = slice; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onHeadersComplete = function (info) { |
|
|
|
if (parser.field && parser.value) { |
|
|
|
parser.incoming._addHeaderLine(parser.field, parser.value); |
|
|
|
} |
|
|
|
|
|
|
|
parser.incoming.httpVersionMajor = info.versionMajor; |
|
|
|
parser.incoming.httpVersionMinor = info.versionMinor; |
|
|
|
|
|
|
|
if (info.method) { |
|
|
|
// server only
|
|
|
|
parser.incoming.method = info.method; |
|
|
|
} else { |
|
|
|
// client only
|
|
|
|
parser.incoming.statusCode = info.statusCode; |
|
|
|
} |
|
|
|
|
|
|
|
parser.onIncoming(parser.incoming, info.shouldKeepAlive); |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onBody = function (b, start, len) { |
|
|
|
// TODO body encoding?
|
|
|
|
var enc = parser.incoming._encoding; |
|
|
|
if (!enc) { |
|
|
|
parser.incoming.emit('data', b.slice(start, start+len)); |
|
|
|
} else { |
|
|
|
var string; |
|
|
|
switch (enc) { |
|
|
|
case 'utf8': |
|
|
|
string = b.utf8Slice(start, start+len); |
|
|
|
break; |
|
|
|
case 'ascii': |
|
|
|
string = b.asciiSlice(start, start+len); |
|
|
|
break; |
|
|
|
default: |
|
|
|
throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); |
|
|
|
} |
|
|
|
parser.incoming.emit('data', string); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
parser.onMessageComplete = function () { |
|
|
|
parser.incoming.emit("end"); |
|
|
|
}; |
|
|
|
} |
|
|
|
return parser; |
|
|
|
} |
|
|
|
|
|
|
|
function freeParser (parser) { |
|
|
|
if (parserFreeList.length < 1000) parserFreeList.push(parser); |
|
|
|
} |
|
|
|
// FIXME: The TCP binding isn't actually used here, but it needs to be
|
|
|
|
// loaded before the http binding.
|
|
|
|
process.binding('tcp'); |
|
|
|
|
|
|
|
var http = process.binding('http'); |
|
|
|
|
|
|
|
var CRLF = "\r\n"; |
|
|
|
var STATUS_CODES = exports.STATUS_CODES = { |
|
|
@ -155,10 +56,10 @@ var content_length_expression = /Content-Length/i; |
|
|
|
|
|
|
|
|
|
|
|
/* Abstract base class for ServerRequest and ClientResponse. */ |
|
|
|
function IncomingMessage (socket) { |
|
|
|
function IncomingMessage (connection) { |
|
|
|
events.EventEmitter.call(this); |
|
|
|
|
|
|
|
this.socket = socket; |
|
|
|
this.connection = connection; |
|
|
|
this.httpVersion = null; |
|
|
|
this.headers = {}; |
|
|
|
|
|
|
@ -169,7 +70,7 @@ function IncomingMessage (socket) { |
|
|
|
|
|
|
|
// response (client) only
|
|
|
|
this.statusCode = null; |
|
|
|
this.client = this.socket; |
|
|
|
this.client = this.connection; |
|
|
|
} |
|
|
|
sys.inherits(IncomingMessage, events.EventEmitter); |
|
|
|
exports.IncomingMessage = IncomingMessage; |
|
|
@ -179,21 +80,16 @@ IncomingMessage.prototype._parseQueryString = function () { |
|
|
|
}; |
|
|
|
|
|
|
|
IncomingMessage.prototype.setBodyEncoding = function (enc) { |
|
|
|
// TODO deprecation message?
|
|
|
|
this.setEncoding(enc); |
|
|
|
}; |
|
|
|
|
|
|
|
IncomingMessage.prototype.setEncoding = function (enc) { |
|
|
|
// TODO check values, error out on bad, and deprecation message?
|
|
|
|
this._encoding = enc.toLowerCase(); |
|
|
|
// TODO: Find a cleaner way of doing this.
|
|
|
|
this.connection.setEncoding(enc); |
|
|
|
}; |
|
|
|
|
|
|
|
IncomingMessage.prototype.pause = function () { |
|
|
|
this.socket.pause(); |
|
|
|
this.connection.pause(); |
|
|
|
}; |
|
|
|
|
|
|
|
IncomingMessage.prototype.resume = function () { |
|
|
|
this.socket.resume(); |
|
|
|
this.connection.resume(); |
|
|
|
}; |
|
|
|
|
|
|
|
IncomingMessage.prototype._addHeaderLine = function (field, value) { |
|
|
@ -206,10 +102,10 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function OutgoingMessage (socket) { |
|
|
|
events.EventEmitter.call(this, socket); |
|
|
|
function OutgoingMessage (connection) { |
|
|
|
events.EventEmitter.call(this, connection); |
|
|
|
|
|
|
|
this.socket = socket; |
|
|
|
this.connection = connection; |
|
|
|
|
|
|
|
this.output = []; |
|
|
|
this.outputEncodings = []; |
|
|
@ -230,7 +126,7 @@ exports.OutgoingMessage = OutgoingMessage; |
|
|
|
OutgoingMessage.prototype._send = function (data, encoding) { |
|
|
|
var length = this.output.length; |
|
|
|
|
|
|
|
if (length === 0 || typeof data != 'string') { |
|
|
|
if (length === 0) { |
|
|
|
this.output.push(data); |
|
|
|
encoding = encoding || "ascii"; |
|
|
|
this.outputEncodings.push(encoding); |
|
|
@ -242,7 +138,11 @@ OutgoingMessage.prototype._send = function (data, encoding) { |
|
|
|
|
|
|
|
if ((lastEncoding === encoding) || |
|
|
|
(!encoding && data.constructor === lastData.constructor)) { |
|
|
|
this.output[length-1] = lastData + data; |
|
|
|
if (lastData.constructor === String) { |
|
|
|
this.output[length-1] = lastData + data; |
|
|
|
} else { |
|
|
|
this.output[length-1] = lastData.concat(data); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
@ -328,11 +228,7 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { |
|
|
|
|
|
|
|
encoding = encoding || "ascii"; |
|
|
|
if (this.chunked_encoding) { |
|
|
|
if (typeof chunk == 'string') { |
|
|
|
this._send(process._byteLength(chunk, encoding).toString(16)); |
|
|
|
} else { |
|
|
|
this._send(chunk.length.toString(16)); |
|
|
|
} |
|
|
|
this._send(process._byteLength(chunk, encoding).toString(16)); |
|
|
|
this._send(CRLF); |
|
|
|
this._send(chunk, encoding); |
|
|
|
this._send(CRLF); |
|
|
@ -363,7 +259,7 @@ OutgoingMessage.prototype.close = function () { |
|
|
|
|
|
|
|
|
|
|
|
function ServerResponse (req) { |
|
|
|
OutgoingMessage.call(this, req.socket); |
|
|
|
OutgoingMessage.call(this, req.connection); |
|
|
|
|
|
|
|
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { |
|
|
|
this.use_chunked_encoding_by_default = false; |
|
|
@ -401,8 +297,8 @@ ServerResponse.prototype.writeHead = function (statusCode) { |
|
|
|
ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead; |
|
|
|
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; |
|
|
|
|
|
|
|
function ClientRequest (socket, method, url, headers) { |
|
|
|
OutgoingMessage.call(this, socket); |
|
|
|
function ClientRequest (connection, method, url, headers) { |
|
|
|
OutgoingMessage.call(this, connection); |
|
|
|
|
|
|
|
this.should_keep_alive = false; |
|
|
|
if (method === "GET" || method === "HEAD") { |
|
|
@ -434,19 +330,85 @@ ClientRequest.prototype.close = function () { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/* Returns true if the message queue is finished and the socket |
|
|
|
function createIncomingMessageStream (connection, incoming_listener) { |
|
|
|
var incoming, field, value; |
|
|
|
|
|
|
|
connection.addListener("messageBegin", function () { |
|
|
|
incoming = new IncomingMessage(connection); |
|
|
|
field = null; |
|
|
|
value = null; |
|
|
|
}); |
|
|
|
|
|
|
|
// Only servers will get URL events.
|
|
|
|
connection.addListener("url", function (data) { |
|
|
|
incoming.url += data; |
|
|
|
}); |
|
|
|
|
|
|
|
connection.addListener("headerField", function (data) { |
|
|
|
if (value) { |
|
|
|
incoming._addHeaderLine(field, value); |
|
|
|
field = null; |
|
|
|
value = null; |
|
|
|
} |
|
|
|
if (field) { |
|
|
|
field += data; |
|
|
|
} else { |
|
|
|
field = data; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
connection.addListener("headerValue", function (data) { |
|
|
|
if (value) { |
|
|
|
value += data; |
|
|
|
} else { |
|
|
|
value = data; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
connection.addListener("headerComplete", function (info) { |
|
|
|
if (field && value) { |
|
|
|
incoming._addHeaderLine(field, value); |
|
|
|
} |
|
|
|
|
|
|
|
incoming.httpVersion = info.httpVersion; |
|
|
|
incoming.httpVersionMajor = info.versionMajor; |
|
|
|
incoming.httpVersionMinor = info.versionMinor; |
|
|
|
|
|
|
|
if (info.method) { |
|
|
|
// server only
|
|
|
|
incoming.method = info.method; |
|
|
|
} else { |
|
|
|
// client only
|
|
|
|
incoming.statusCode = info.statusCode; |
|
|
|
} |
|
|
|
|
|
|
|
incoming_listener(incoming, info.should_keep_alive); |
|
|
|
}); |
|
|
|
|
|
|
|
connection.addListener("body", function (chunk) { |
|
|
|
incoming.emit('data', chunk); |
|
|
|
}); |
|
|
|
|
|
|
|
connection.addListener("messageComplete", function () { |
|
|
|
incoming.emit('end'); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/* Returns true if the message queue is finished and the connection |
|
|
|
* should be closed. */ |
|
|
|
function flushMessageQueue (socket, queue) { |
|
|
|
function flushMessageQueue (connection, queue) { |
|
|
|
while (queue[0]) { |
|
|
|
var message = queue[0]; |
|
|
|
|
|
|
|
while (message.output.length > 0) { |
|
|
|
if (!socket.writable) return true; |
|
|
|
if (connection.readyState !== "open" && connection.readyState !== "writeOnly") { |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
var data = message.output.shift(); |
|
|
|
var encoding = message.outputEncodings.shift(); |
|
|
|
|
|
|
|
socket.write(data, encoding); |
|
|
|
connection.write(data, encoding); |
|
|
|
} |
|
|
|
|
|
|
|
if (!message.finished) break; |
|
|
@ -460,173 +422,152 @@ function flushMessageQueue (socket, queue) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Server (requestListener) { |
|
|
|
net.Server.call(this); |
|
|
|
this.addListener("request", requestListener); |
|
|
|
this.addListener("connection", connectionListener); |
|
|
|
} |
|
|
|
sys.inherits(Server, net.Server); |
|
|
|
|
|
|
|
exports.Server = Server; |
|
|
|
|
|
|
|
exports.createServer = function (requestListener) { |
|
|
|
return new Server(requestListener); |
|
|
|
exports.createServer = function (requestListener, options) { |
|
|
|
var server = new http.Server(); |
|
|
|
//server.setOptions(options);
|
|
|
|
server.addListener("request", requestListener); |
|
|
|
server.addListener("connection", connectionListener); |
|
|
|
return server; |
|
|
|
}; |
|
|
|
|
|
|
|
function connectionListener (socket) { |
|
|
|
var self = this; |
|
|
|
// An array of responses for each socket. In pipelined connections
|
|
|
|
function connectionListener (connection) { |
|
|
|
// An array of responses for each connection. In pipelined connections
|
|
|
|
// we need to keep track of the order they were sent.
|
|
|
|
var responses = []; |
|
|
|
|
|
|
|
var parser = newParser('request'); |
|
|
|
|
|
|
|
socket.ondata = function (d, start, end) { |
|
|
|
parser.execute(d, start, end - start); |
|
|
|
}; |
|
|
|
|
|
|
|
socket.onend = function () { |
|
|
|
parser.finish(); |
|
|
|
// unref the parser for easy gc
|
|
|
|
freeParser(parser); |
|
|
|
connection.resetParser(); |
|
|
|
|
|
|
|
// is this really needed?
|
|
|
|
connection.addListener("end", function () { |
|
|
|
if (responses.length == 0) { |
|
|
|
socket.close(); |
|
|
|
connection.close(); |
|
|
|
} else { |
|
|
|
responses[responses.length-1].closeOnFinish = true; |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
parser.socket = 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 (incoming, shouldKeepAlive) { |
|
|
|
|
|
|
|
createIncomingMessageStream(connection, function (incoming, should_keep_alive) { |
|
|
|
var req = incoming; |
|
|
|
var res = new ServerResponse(req); |
|
|
|
|
|
|
|
res.shouldKeepAlive = shouldKeepAlive; |
|
|
|
res.addListener('flush', function () { |
|
|
|
if (flushMessageQueue(socket, responses)) { |
|
|
|
socket.close(); |
|
|
|
var res = new ServerResponse(req); |
|
|
|
res.should_keep_alive = should_keep_alive; |
|
|
|
res.addListener("flush", function () { |
|
|
|
if (flushMessageQueue(connection, responses)) { |
|
|
|
connection.close(); |
|
|
|
} |
|
|
|
}); |
|
|
|
responses.push(res); |
|
|
|
|
|
|
|
self.emit('request', req, res); |
|
|
|
}; |
|
|
|
connection.server.emit("request", req, res); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Client ( ) { |
|
|
|
net.Stream.call(this); |
|
|
|
|
|
|
|
var self = this; |
|
|
|
exports.createClient = function (port, host) { |
|
|
|
var client = new http.Client(); |
|
|
|
var secure_credentials={ secure : false }; |
|
|
|
|
|
|
|
var requests = []; |
|
|
|
var currentRequest; |
|
|
|
|
|
|
|
var parser = newParser('response'); |
|
|
|
parser.socket = this; |
|
|
|
client.tcpSetSecure = client.setSecure; |
|
|
|
client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) { |
|
|
|
secure_credentials.secure = true; |
|
|
|
secure_credentials.format_type = format_type; |
|
|
|
secure_credentials.ca_certs = ca_certs; |
|
|
|
secure_credentials.crl_list = crl_list; |
|
|
|
secure_credentials.private_key = private_key; |
|
|
|
secure_credentials.certificate = certificate; |
|
|
|
} |
|
|
|
|
|
|
|
self._reconnect = function () { |
|
|
|
if (self.readyState != "opening") { |
|
|
|
sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); |
|
|
|
self.connect(self.port, self.host); |
|
|
|
client._reconnect = function () { |
|
|
|
if (client.readyState != "opening") { |
|
|
|
//sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState);
|
|
|
|
client.connect(port, host); |
|
|
|
if (secure_credentials.secure) { |
|
|
|
client.tcpSetSecure(secure_credentials.format_type, |
|
|
|
secure_credentials.ca_certs, |
|
|
|
secure_credentials.crl_list, |
|
|
|
secure_credentials.private_key, |
|
|
|
secure_credentials.certificate); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
self._pushRequest = function (req) { |
|
|
|
client._pushRequest = function (req) { |
|
|
|
req.addListener("flush", function () { |
|
|
|
if (self.readyState == "closed") { |
|
|
|
sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); |
|
|
|
self._reconnect(); |
|
|
|
if (client.readyState == "closed") { |
|
|
|
//sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState);
|
|
|
|
client._reconnect(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
sys.debug("self flush readyState = " + self.readyState); |
|
|
|
if (req == currentRequest) flushMessageQueue(self, [req]); |
|
|
|
//sys.debug("client flush readyState = " + client.readyState);
|
|
|
|
if (req == currentRequest) flushMessageQueue(client, [req]); |
|
|
|
}); |
|
|
|
requests.push(req); |
|
|
|
}; |
|
|
|
|
|
|
|
this.ondata = function (d, start, end) { |
|
|
|
parser.execute(d, start, end - start); |
|
|
|
}; |
|
|
|
|
|
|
|
self.addListener("connect", function () { |
|
|
|
parser.reinitialize('response'); |
|
|
|
sys.puts('requests: ' + sys.inspect(requests)); |
|
|
|
currentRequest = requests.shift() |
|
|
|
client.addListener("connect", function () { |
|
|
|
client.resetParser(); |
|
|
|
currentRequest = requests.shift(); |
|
|
|
currentRequest.flush(); |
|
|
|
}); |
|
|
|
|
|
|
|
self.addListener("end", function () { |
|
|
|
parser.finish(); |
|
|
|
freeParser(parser); |
|
|
|
|
|
|
|
//sys.debug("self got end closing. readyState = " + self.readyState);
|
|
|
|
self.close(); |
|
|
|
client.addListener("end", function () { |
|
|
|
//sys.debug("client got end closing. readyState = " + client.readyState);
|
|
|
|
client.close(); |
|
|
|
}); |
|
|
|
|
|
|
|
self.addListener("close", function (had_error) { |
|
|
|
client.addListener("close", function (had_error) { |
|
|
|
if (had_error) { |
|
|
|
self.emit("error"); |
|
|
|
client.emit("error"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); |
|
|
|
//sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState);
|
|
|
|
|
|
|
|
// If there are more requests to handle, reconnect.
|
|
|
|
if (requests.length > 0) { |
|
|
|
self._reconnect(); |
|
|
|
client._reconnect(); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
parser.onIncoming = function (res) { |
|
|
|
sys.debug("incoming response!"); |
|
|
|
createIncomingMessageStream(client, function (res) { |
|
|
|
//sys.debug("incoming response!");
|
|
|
|
|
|
|
|
res.addListener('end', function ( ) { |
|
|
|
//sys.debug("request complete disconnecting. readyState = " + self.readyState);
|
|
|
|
self.close(); |
|
|
|
//sys.debug("request complete disconnecting. readyState = " + client.readyState);
|
|
|
|
client.close(); |
|
|
|
}); |
|
|
|
|
|
|
|
currentRequest.emit("response", res); |
|
|
|
}; |
|
|
|
}; |
|
|
|
sys.inherits(Client, net.Stream); |
|
|
|
|
|
|
|
exports.Client = Client; |
|
|
|
|
|
|
|
exports.createClient = function (port, host) { |
|
|
|
var c = new Client; |
|
|
|
c.port = port; |
|
|
|
c.host = host; |
|
|
|
return c; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
return client; |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.get = function () { |
|
|
|
http.Client.prototype.get = function () { |
|
|
|
throw new Error("client.get(...) is now client.request('GET', ...)"); |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.head = function () { |
|
|
|
http.Client.prototype.head = function () { |
|
|
|
throw new Error("client.head(...) is now client.request('HEAD', ...)"); |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.post = function () { |
|
|
|
http.Client.prototype.post = function () { |
|
|
|
throw new Error("client.post(...) is now client.request('POST', ...)"); |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.del = function () { |
|
|
|
http.Client.prototype.del = function () { |
|
|
|
throw new Error("client.del(...) is now client.request('DELETE', ...)"); |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.put = function () { |
|
|
|
http.Client.prototype.put = function () { |
|
|
|
throw new Error("client.put(...) is now client.request('PUT', ...)"); |
|
|
|
}; |
|
|
|
|
|
|
|
Client.prototype.request = function (method, url, headers) { |
|
|
|
http.Client.prototype.request = function (method, url, headers) { |
|
|
|
if (typeof(url) != "string") { // assume method was omitted, shift arguments
|
|
|
|
headers = url; |
|
|
|
url = method; |
|
|
@ -639,7 +580,7 @@ Client.prototype.request = function (method, url, headers) { |
|
|
|
|
|
|
|
|
|
|
|
exports.cat = function (url, encoding_, headers_) { |
|
|
|
var encoding = 'utf8', |
|
|
|
var encoding = 'utf8', |
|
|
|
headers = {}, |
|
|
|
callback = null; |
|
|
|
|