Initial commit
[yaffs-website] / node_modules / tunnel-agent / index.js
1 'use strict'
2
3 var net = require('net')
4   , tls = require('tls')
5   , http = require('http')
6   , https = require('https')
7   , events = require('events')
8   , assert = require('assert')
9   , util = require('util')
10   ;
11
12 exports.httpOverHttp = httpOverHttp
13 exports.httpsOverHttp = httpsOverHttp
14 exports.httpOverHttps = httpOverHttps
15 exports.httpsOverHttps = httpsOverHttps
16
17
18 function httpOverHttp(options) {
19   var agent = new TunnelingAgent(options)
20   agent.request = http.request
21   return agent
22 }
23
24 function httpsOverHttp(options) {
25   var agent = new TunnelingAgent(options)
26   agent.request = http.request
27   agent.createSocket = createSecureSocket
28   agent.defaultPort = 443
29   return agent
30 }
31
32 function httpOverHttps(options) {
33   var agent = new TunnelingAgent(options)
34   agent.request = https.request
35   return agent
36 }
37
38 function httpsOverHttps(options) {
39   var agent = new TunnelingAgent(options)
40   agent.request = https.request
41   agent.createSocket = createSecureSocket
42   agent.defaultPort = 443
43   return agent
44 }
45
46
47 function TunnelingAgent(options) {
48   var self = this
49   self.options = options || {}
50   self.proxyOptions = self.options.proxy || {}
51   self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets
52   self.requests = []
53   self.sockets = []
54
55   self.on('free', function onFree(socket, host, port) {
56     for (var i = 0, len = self.requests.length; i < len; ++i) {
57       var pending = self.requests[i]
58       if (pending.host === host && pending.port === port) {
59         // Detect the request to connect same origin server,
60         // reuse the connection.
61         self.requests.splice(i, 1)
62         pending.request.onSocket(socket)
63         return
64       }
65     }
66     socket.destroy()
67     self.removeSocket(socket)
68   })
69 }
70 util.inherits(TunnelingAgent, events.EventEmitter)
71
72 TunnelingAgent.prototype.addRequest = function addRequest(req, options) {
73   var self = this
74
75    // Legacy API: addRequest(req, host, port, path)
76   if (typeof options === 'string') {
77     options = {
78       host: options,
79       port: arguments[2],
80       path: arguments[3]
81     };
82   }
83
84   if (self.sockets.length >= this.maxSockets) {
85     // We are over limit so we'll add it to the queue.
86     self.requests.push({host: options.host, port: options.port, request: req})
87     return
88   }
89
90   // If we are under maxSockets create a new one.
91   self.createConnection({host: options.host, port: options.port, request: req})
92 }
93
94 TunnelingAgent.prototype.createConnection = function createConnection(pending) {
95   var self = this
96
97   self.createSocket(pending, function(socket) {
98     socket.on('free', onFree)
99     socket.on('close', onCloseOrRemove)
100     socket.on('agentRemove', onCloseOrRemove)
101     pending.request.onSocket(socket)
102
103     function onFree() {
104       self.emit('free', socket, pending.host, pending.port)
105     }
106
107     function onCloseOrRemove(err) {
108       self.removeSocket(socket)
109       socket.removeListener('free', onFree)
110       socket.removeListener('close', onCloseOrRemove)
111       socket.removeListener('agentRemove', onCloseOrRemove)
112     }
113   })
114 }
115
116 TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
117   var self = this
118   var placeholder = {}
119   self.sockets.push(placeholder)
120
121   var connectOptions = mergeOptions({}, self.proxyOptions, 
122     { method: 'CONNECT'
123     , path: options.host + ':' + options.port
124     , agent: false
125     }
126   )
127   if (connectOptions.proxyAuth) {
128     connectOptions.headers = connectOptions.headers || {}
129     connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
130         new Buffer(connectOptions.proxyAuth).toString('base64')
131   }
132
133   debug('making CONNECT request')
134   var connectReq = self.request(connectOptions)
135   connectReq.useChunkedEncodingByDefault = false // for v0.6
136   connectReq.once('response', onResponse) // for v0.6
137   connectReq.once('upgrade', onUpgrade)   // for v0.6
138   connectReq.once('connect', onConnect)   // for v0.7 or later
139   connectReq.once('error', onError)
140   connectReq.end()
141
142   function onResponse(res) {
143     // Very hacky. This is necessary to avoid http-parser leaks.
144     res.upgrade = true
145   }
146
147   function onUpgrade(res, socket, head) {
148     // Hacky.
149     process.nextTick(function() {
150       onConnect(res, socket, head)
151     })
152   }
153
154   function onConnect(res, socket, head) {
155     connectReq.removeAllListeners()
156     socket.removeAllListeners()
157
158     if (res.statusCode === 200) {
159       assert.equal(head.length, 0)
160       debug('tunneling connection has established')
161       self.sockets[self.sockets.indexOf(placeholder)] = socket
162       cb(socket)
163     } else {
164       debug('tunneling socket could not be established, statusCode=%d', res.statusCode)
165       var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)
166       error.code = 'ECONNRESET'
167       options.request.emit('error', error)
168       self.removeSocket(placeholder)
169     }
170   }
171
172   function onError(cause) {
173     connectReq.removeAllListeners()
174
175     debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)
176     var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)
177     error.code = 'ECONNRESET'
178     options.request.emit('error', error)
179     self.removeSocket(placeholder)
180   }
181 }
182
183 TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
184   var pos = this.sockets.indexOf(socket)
185   if (pos === -1) return
186   
187   this.sockets.splice(pos, 1)
188
189   var pending = this.requests.shift()
190   if (pending) {
191     // If we have pending requests and a socket gets closed a new one
192     // needs to be created to take over in the pool for the one that closed.
193     this.createConnection(pending)
194   }
195 }
196
197 function createSecureSocket(options, cb) {
198   var self = this
199   TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
200     // 0 is dummy port for v0.6
201     var secureSocket = tls.connect(0, mergeOptions({}, self.options, 
202       { servername: options.host
203       , socket: socket
204       }
205     ))
206     self.sockets[self.sockets.indexOf(socket)] = secureSocket
207     cb(secureSocket)
208   })
209 }
210
211
212 function mergeOptions(target) {
213   for (var i = 1, len = arguments.length; i < len; ++i) {
214     var overrides = arguments[i]
215     if (typeof overrides === 'object') {
216       var keys = Object.keys(overrides)
217       for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
218         var k = keys[j]
219         if (overrides[k] !== undefined) {
220           target[k] = overrides[k]
221         }
222       }
223     }
224   }
225   return target
226 }
227
228
229 var debug
230 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
231   debug = function() {
232     var args = Array.prototype.slice.call(arguments)
233     if (typeof args[0] === 'string') {
234       args[0] = 'TUNNEL: ' + args[0]
235     } else {
236       args.unshift('TUNNEL:')
237     }
238     console.error.apply(console, args)
239   }
240 } else {
241   debug = function() {}
242 }
243 exports.debug = debug // for test