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