diff options
author | isaacs <i@izs.me> | 2012-03-02 03:48:29 +0400 |
---|---|---|
committer | isaacs <i@izs.me> | 2012-03-02 03:48:29 +0400 |
commit | 02094835d138b48c0733ebdb40dd0888c4a630ab (patch) | |
tree | 43986fb248405cd0a4a382a07761eed520bcd740 | |
parent | abbda96de7725f89c497e727518c1e7c4dc26eeb (diff) |
Update request
-rw-r--r-- | node_modules/request/forever.js | 21 | ||||
-rw-r--r-- | node_modules/request/main.js | 158 | ||||
-rw-r--r-- | node_modules/request/package.json | 58 | ||||
-rw-r--r-- | node_modules/request/tunnel.js | 229 |
4 files changed, 420 insertions, 46 deletions
diff --git a/node_modules/request/forever.js b/node_modules/request/forever.js index e6531a21b..ac853c0d2 100644 --- a/node_modules/request/forever.js +++ b/node_modules/request/forever.js @@ -1,8 +1,11 @@ module.exports = ForeverAgent +ForeverAgent.SSL = ForeverAgentSSL var util = require('util') , Agent = require('http').Agent , net = require('net') + , tls = require('tls') + , AgentSSL = require('https').Agent function ForeverAgent(options) { var self = this @@ -34,12 +37,14 @@ function ForeverAgent(options) { socket.destroy(); } }) - self.createConnection = net.createConnection + } util.inherits(ForeverAgent, Agent) ForeverAgent.defaultMinSockets = 5 + +ForeverAgent.prototype.createConnection = net.createConnection ForeverAgent.prototype.addRequestNoreuse = Agent.prototype.addRequest ForeverAgent.prototype.addRequest = function(req, host, port) { var name = host + ':' + port @@ -82,3 +87,17 @@ ForeverAgent.prototype.removeSocket = function(s, name, host, port) { this.createSocket(name, host, port).emit('free'); } } + +function ForeverAgentSSL (options) { + ForeverAgent.call(this, options) +} +util.inherits(ForeverAgentSSL, ForeverAgent) + +ForeverAgentSSL.prototype.createConnection = createConnectionSSL +ForeverAgentSSL.prototype.addRequestNoreuse = AgentSSL.prototype.addRequest + +function createConnectionSSL (port, host, options) { + options.port = port + options.host = host + return tls.connect(options) +} diff --git a/node_modules/request/main.js b/node_modules/request/main.js index 5c1426416..f65120274 100644 --- a/node_modules/request/main.js +++ b/node_modules/request/main.js @@ -26,6 +26,7 @@ var http = require('http') , Cookie = require('./vendor/cookie') , CookieJar = require('./vendor/cookie/jar') , cookieJar = new CookieJar + , tunnel = require('./tunnel') ; if (process.logging) { @@ -133,6 +134,20 @@ Request.prototype.init = function (options) { } if (self.proxy) { if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy) + + // do the HTTP CONNECT dance using koichik/node-tunnel + if (http.globalAgent && self.uri.protocol === "https:") { + self.tunnel = true + var tunnelFn = self.proxy.protocol === "http:" + ? tunnel.httpsOverHttp : tunnel.httpsOverHttps + + var tunnelOptions = { proxy: { host: self.proxy.hostname + , port: +self.proxy.port } + , ca: this.ca } + + self.agent = tunnelFn(tunnelOptions) + self.tunnel = true + } } self._redirectsFollowed = self._redirectsFollowed || 0 @@ -163,7 +178,7 @@ Request.prototype.init = function (options) { else if (self.uri.protocol == 'https:') {self.uri.port = 443} } - if (self.proxy) { + if (self.proxy && !self.tunnel) { self.port = self.proxy.port self.host = self.proxy.hostname } else { @@ -177,9 +192,12 @@ Request.prototype.init = function (options) { } self.clientErrorHandler = function (error) { + if (self._aborted) return + if (self.setHost) delete self.headers.host - if (self.req._reusedSocket && error.code === 'ECONNRESET') { - self.agent = {addRequest: ForeverAgent.prototype.addRequestNoreuse.bind(self.agent)} + if (self.req._reusedSocket && error.code === 'ECONNRESET' + && self.agent.addRequestNoreuse) { + self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) } self.start() self.req.end() return @@ -204,7 +222,7 @@ Request.prototype.init = function (options) { if (self.uri.auth && !self.headers.authorization) { self.headers.authorization = "Basic " + toBase64(self.uri.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) } - if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization']) { + if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization'] && !self.tunnel) { self.headers['proxy-authorization'] = "Basic " + toBase64(self.proxy.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) } @@ -218,7 +236,7 @@ Request.prototype.init = function (options) { if (self.path.length === 0) self.path = '/' - if (self.proxy) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) + if (self.proxy && !self.tunnel) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) if (options.json) { self.json(options.json) @@ -247,7 +265,7 @@ Request.prototype.init = function (options) { } } - var protocol = self.proxy ? self.proxy.protocol : self.uri.protocol + var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol , defaultModules = {'http:':http, 'https:':https} , httpModules = self.httpModules || {} ; @@ -255,17 +273,30 @@ Request.prototype.init = function (options) { if (!self.httpModule) throw new Error("Invalid protocol") + if (options.ca) self.ca = options.ca + + if (!self.agent) { + if (options.agentOptions) self.agentOptions = options.agentOptions + + if (options.agentClass) { + self.agentClass = options.agentClass + } else if (options.forever) { + self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL + } else { + self.agentClass = self.httpModule.Agent + } + } + if (self.pool === false) { self.agent = false } else { + self.agent = self.agent || self.getAgent() if (self.maxSockets) { // Don't use our pooling if node has the refactored client - self.agent = self.agent || self.httpModule.globalAgent || self.getAgent(self.host, self.port) self.agent.maxSockets = self.maxSockets } if (self.pool.maxSockets) { // Don't use our pooling if node has the refactored client - self.agent = self.agent || self.httpModule.globalAgent || self.getAgent(self.host, self.port) self.agent.maxSockets = self.pool.maxSockets } } @@ -295,6 +326,8 @@ Request.prototype.init = function (options) { }) process.nextTick(function () { + if (self._aborted) return + if (self.body) { if (Array.isArray(self.body)) { self.body.forEach(function(part) { @@ -314,19 +347,61 @@ Request.prototype.init = function (options) { self.ntick = true }) } -Request.prototype.getAgent = function (host, port) { - if (!this.pool[host+':'+port]) { - this.pool[host+':'+port] = new this.httpModule.Agent({host:host, port:port}) + +Request.prototype.getAgent = function () { + var Agent = this.agentClass + var options = {} + if (this.agentOptions) { + for (var i in this.agentOptions) { + options[i] = this.agentOptions[i] + } + } + if (this.ca) options.ca = this.ca + + var poolKey = '' + + // different types of agents are in different pools + if (Agent !== this.httpModule.Agent) { + poolKey += Agent.name + } + + if (!this.httpModule.globalAgent) { + // node 0.4.x + options.host = this.host + options.port = this.port + if (poolKey) poolKey += ':' + poolKey += this.host + ':' + this.port } - return this.pool[host+':'+port] + + if (options.ca) { + if (poolKey) poolKey += ':' + poolKey += options.ca + } + + if (!poolKey && Agent === this.httpModule.Agent && this.httpModule.globalAgent) { + // not doing anything special. Use the globalAgent + return this.httpModule.globalAgent + } + + // already generated an agent for this setting + if (this.pool[poolKey]) return this.pool[poolKey] + + return this.pool[poolKey] = new Agent(options) } + Request.prototype.start = function () { var self = this + + if (self._aborted) return + self._started = true self.method = self.method || 'GET' self.href = self.uri.href if (log) log('%method %href', self) self.req = self.httpModule.request(self, function (response) { + if (self._aborted) return + if (self._paused) response.pause() + self.response = response response.request = self @@ -353,7 +428,7 @@ Request.prototype.start = function () { if (response.statusCode >= 300 && response.statusCode < 400 && (self.followAllRedirects || - (self.followRedirect && (self.method !== 'PUT' && self.method !== 'POST'))) && + (self.followRedirect && (self.method !== 'PUT' && self.method !== 'POST' && self.method !== 'DELETE'))) && response.headers.location) { if (self._redirectsFollowed >= self.maxRedirects) { self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop.")) @@ -423,6 +498,8 @@ Request.prototype.start = function () { bodyLen += chunk.length }) self.on("end", function () { + if (self._aborted) return + if (buffer.length && Buffer.isBuffer(buffer[0])) { var body = new Buffer(bodyLen) var i = 0 @@ -439,7 +516,7 @@ Request.prototype.start = function () { response.body = buffer.join('') } - if (self.json) { + if (self._json) { try { response.body = JSON.parse(response.body) } catch (e) {} @@ -461,20 +538,36 @@ Request.prototype.start = function () { // Set additional timeout on socket - in case if remote // server freeze after sending headers - self.req.setTimeout(self.timeout, function(){ - if (self.req) { - self.req.abort() - var e = new Error("ESOCKETTIMEDOUT") - e.code = "ESOCKETTIMEDOUT" - self.emit("error", e) - } - }); + if (self.req.setTimeout) { // only works on node 0.6+ + self.req.setTimeout(self.timeout, function(){ + if (self.req) { + self.req.abort() + var e = new Error("ESOCKETTIMEDOUT") + e.code = "ESOCKETTIMEDOUT" + self.emit("error", e) + } + }) + } } self.req.on('error', self.clientErrorHandler) self.emit('request', self.req) } + +Request.prototype.abort = function() { + this._aborted = true; + + if (this.req) { + this.req.abort() + } + else if (this.response) { + this.response.abort() + } + + this.emit("abort") +} + Request.prototype.pipeDest = function (dest) { var response = this.response // Called after the response is received @@ -554,6 +647,7 @@ Request.prototype.multipart = function (multipart) { Request.prototype.json = function (val) { this.setHeader('content-type', 'application/json') this.setHeader('accept', 'application/json') + this._json = true if (typeof val === 'boolean') { if (typeof this.body === 'object') this.body = JSON.stringify(this.body) } else { @@ -659,22 +753,20 @@ Request.prototype.pipe = function (dest, opts) { } Request.prototype.write = function () { if (!this._started) this.start() - if (!this.req) throw new Error("This request has been piped before http.request() was called.") this.req.write.apply(this.req, arguments) } Request.prototype.end = function (chunk) { if (chunk) this.write(chunk) if (!this._started) this.start() - if (!this.req) throw new Error("This request has been piped before http.request() was called.") this.req.end() } Request.prototype.pause = function () { - if (!this.response) throw new Error("This request has been piped before http.request() was called.") - this.response.pause.apply(this.response, arguments) + if (!this.response) this._paused = true + else this.response.pause.apply(this.response, arguments) } Request.prototype.resume = function () { - if (!this.response) throw new Error("This request has been piped before http.request() was called.") - this.response.resume.apply(this.response, arguments) + if (!this.response) this._paused = false + else this.response.resume.apply(this.response, arguments) } Request.prototype.destroy = function () { if (!this._ended) this.end() @@ -735,12 +827,13 @@ request.defaults = function (options) { request.forever = function (agentOptions, optionsArg) { var options = {} - if (agentOptions) { + if (optionsArg) { for (option in optionsArg) { options[option] = optionsArg[option] } } - options.agent = new ForeverAgent(agentOptions) + if (agentOptions) options.agentOptions = agentOptions + options.forever = true return request.defaults(options) } @@ -758,7 +851,10 @@ request.put = function (uri, options, callback) { request.head = function (uri, options, callback) { var params = initParams(uri, options, callback); params.options.method = 'HEAD' - if (options.body || options.requestBodyStream || options.json || options.multipart) { + if (params.options.body || + params.options.requestBodyStream || + (params.options.json && typeof params.options.json !== 'boolean') || + params.options.multipart) { throw new Error("HTTP HEAD requests MUST NOT include a request body.") } return request(params.uri, params.options, params.callback) diff --git a/node_modules/request/package.json b/node_modules/request/package.json index 305cf167b..4a62c5a46 100644 --- a/node_modules/request/package.json +++ b/node_modules/request/package.json @@ -1,15 +1,45 @@ -{ "name" : "request" -, "description" : "Simplified HTTP request client." -, "tags" : ["http", "simple", "util", "utility"] -, "version" : "2.9.151" -, "author" : "Mikeal Rogers <mikeal.rogers@gmail.com>" -, "repository" : - { "type" : "git" - , "url" : "http://github.com/mikeal/request.git" - } -, "bugs" : - { "url" : "http://github.com/mikeal/request/issues" } -, "engines" : ["node >= 0.3.6"] -, "main" : "./main" -, "scripts": { "test": "node tests/run.js" } +{ + "name": "request", + "description": "Simplified HTTP request client.", + "tags": [ + "http", + "simple", + "util", + "utility" + ], + "version": "2.9.153", + "author": { + "name": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com" + }, + "repository": { + "type": "git", + "url": "git://github.com/mikeal/request.git" + }, + "bugs": { + "url": "http://github.com/mikeal/request/issues" + }, + "engines": [ + "node >= 0.3.6" + ], + "main": "./main", + "scripts": { + "test": "node tests/run.js" + }, + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "_id": "request@2.9.153", + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.2", + "_nodeVersion": "v0.7.6-pre", + "_defaultsLoaded": true, + "dist": { + "shasum": "d1f4855a90a9e69bf6cd2a68503e253cc4a27a71" + }, + "_from": "request@~2.9" } diff --git a/node_modules/request/tunnel.js b/node_modules/request/tunnel.js new file mode 100644 index 000000000..453786c5e --- /dev/null +++ b/node_modules/request/tunnel.js @@ -0,0 +1,229 @@ +'use strict'; + +var net = require('net'); +var tls = require('tls'); +var http = require('http'); +var https = require('https'); +var events = require('events'); +var assert = require('assert'); +var util = require('util'); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port) { + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === host && pending.port === port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port) { + var self = this; + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push({host: host, port: port, request: req}); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket({host: host, port: port, request: req}, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, host, port); + } + + function onCloseOrRemove(err) { + self.removeSocket(); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false + }); + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode === 200) { + assert.equal(head.length, 0); + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + cb(socket); + } else { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + var error = new Error('tunneling socket could not be established, ' + + 'sutatusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, mergeOptions({}, self.options, { + socket: socket + })); + cb(secureSocket); + }); +} + + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +exports.debug = debug; // for test |