Skip to content

Commit ae03c68

Browse files
committed
https-proxy-agent: WIP buffer the incoming proxy data
In case we need to re-play the data onto the socket in the case of a proxy error (so that the user can handle it). This doesn't *fully* work yet, since it seems like in the 407 Authenticate scenario, the socket never closes. Need to look into that some more...
1 parent 11bfb67 commit ae03c68

1 file changed

Lines changed: 103 additions & 16 deletions

File tree

https-proxy-agent.js

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ function connect (req, _opts, fn) {
8383
socket = net.connect(proxy);
8484
}
8585

86+
// we need to buffer any HTTP traffic that happens with the proxy before we get
87+
// the CONNECT response, so that if the response is anything other than an "200"
88+
// response code, then we can re-play the "data" events on the socket once the
89+
// HTTP parser is hooked up...
90+
var buffers = [];
91+
var buffersLength = 0;
92+
8693
function read () {
8794
var b = socket.read();
8895
if (b) ondata(b);
@@ -91,37 +98,116 @@ function connect (req, _opts, fn) {
9198

9299
function cleanup () {
93100
socket.removeListener('data', ondata);
101+
socket.removeListener('end', onend);
94102
socket.removeListener('error', onerror);
103+
socket.removeListener('close', onclose);
95104
socket.removeListener('readable', read);
96105
}
97106

107+
function onclose (err) {
108+
console.error('onclose');
109+
}
110+
111+
function onend (err) {
112+
console.error('onend');
113+
}
114+
98115
function onerror (err) {
99116
cleanup();
100117
fn(err);
101118
}
102119

103120
function ondata (b) {
121+
buffers.push(b);
122+
buffersLength += b.length;
123+
var buffered = Buffer.concat(buffers, buffersLength);
124+
var str = buffered.toString('ascii');
125+
126+
if (!~str.indexOf('\r\n\r\n')) {
127+
// keep buffering
128+
debug('have not received end of HTTP headers yet... %j');
129+
if (socket.read) {
130+
read();
131+
} else {
132+
socket.once('data', ondata);
133+
}
134+
return;
135+
}
136+
137+
var firstLine = str.substring(0, str.indexOf('\r\n'));
138+
var statusCode = +firstLine.split(' ')[1];
139+
debug('got proxy server response: "%s"', firstLine);
140+
//console.log('statusCode: %d', statusCode);
104141
//console.log(b.length, b, b.toString());
105-
// TODO: verify that the socket is properly connected, check response...
106-
107-
var sock = socket;
108-
109-
if (secureEndpoint) {
110-
// since the proxy is connecting to an SSL server, we have
111-
// to upgrade this socket connection to an SSL connection
112-
opts.socket = socket;
113-
opts.servername = opts.host;
114-
opts.host = null;
115-
opts.hostname = null;
116-
opts.port = null;
117-
sock = tls.connect(opts);
142+
143+
if (200 == statusCode) {
144+
// 200 Connected status code!
145+
var sock = socket;
146+
147+
// nullify the buffered data since we won't be needing it
148+
buffers = buffered = null;
149+
150+
if (secureEndpoint) {
151+
// since the proxy is connecting to an SSL server, we have
152+
// to upgrade this socket connection to an SSL connection
153+
debug('upgrading proxy-connected socket to TLS connection: "%s"', opts.host);
154+
opts.socket = socket;
155+
opts.servername = opts.host;
156+
opts.host = null;
157+
opts.hostname = null;
158+
opts.port = null;
159+
sock = tls.connect(opts);
160+
}
161+
162+
cleanup();
163+
fn(null, sock);
164+
} else {
165+
// some other status code that's not 200... need to re-play the HTTP header
166+
// "data" events onto the socket once the HTTP machinery is attached so that
167+
// the user can parse and handle the error status code
168+
cleanup();
169+
170+
// save a reference to the concat'd Buffer for the `onsocket` callback
171+
console.error(0, str);
172+
buffers = buffered;
173+
174+
// need to wait for the "socket" event to re-play the "data" events
175+
req.once('socket', onsocket);
176+
fn(null, socket);
118177
}
178+
}
119179

120-
cleanup();
121-
fn(null, sock);
180+
function onsocket (socket) {
181+
// replay the "buffers" Buffer onto the `socket`, since at this point
182+
// the HTTP module machinery has been hooked up for the user
183+
if ('function' == typeof socket.ondata) {
184+
// node <= v0.11.3, the `ondata` function is set on the socket
185+
socket.ondata(buffers, 0, buffers.length);
186+
} else if (socket.listeners('data').length > 0) {
187+
// node > v0.11.3, the "data" event is listened for directly
188+
socket.emit('data', buffers);
189+
} else {
190+
// never?
191+
throw new Error('should not happen...');
192+
}
193+
buffers = null;
194+
195+
// XXX: not sure if forcing "end" here is appropriate, but otherwise the
196+
// socket never closes and the tests fail since the proxy server never shuts
197+
// down...
198+
/*
199+
if ('function' == typeof socket.onend) {
200+
socket.onend();
201+
} else {
202+
socket.emit('end');
203+
}
204+
*/
205+
//socket.destroy();
122206
}
123207

124208
socket.on('error', onerror);
209+
socket.on('close', onclose);
210+
socket.on('end', onend);
125211

126212
if (socket.read) {
127213
read();
@@ -136,6 +222,7 @@ function connect (req, _opts, fn) {
136222
msg += 'Proxy-Authorization: Basic ' + new Buffer(auth).toString('base64') + '\r\n';
137223
}
138224
msg += 'Host: ' + hostname + '\r\n' +
139-
'\r\n';
225+
'Connection: close\r\n' +
226+
'\r\n';
140227
socket.write(msg);
141228
};

0 commit comments

Comments
 (0)