Skip to content

Commit eb6906b

Browse files
[http-proxy-agent] Fix keepAlive: true (#190)
Based on #169. Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
1 parent b9ac154 commit eb6906b

File tree

3 files changed

+62
-13
lines changed

3 files changed

+62
-13
lines changed

.changeset/four-carrots-help.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'http-proxy-agent': patch
3+
---
4+
5+
Fix `keepAlive: true`

packages/http-proxy-agent/src/index.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,18 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
7777
};
7878
}
7979

80-
async connect(
80+
addRequest(req: HttpProxyAgentClientRequest, opts: AgentConnectOpts): void {
81+
req._header = null;
82+
this.setRequestProps(req, opts);
83+
// @ts-expect-error `addRequest()` isn't defined in `@types/node`
84+
super.addRequest(req, opts);
85+
}
86+
87+
setRequestProps(
8188
req: HttpProxyAgentClientRequest,
8289
opts: AgentConnectOpts
83-
): Promise<net.Socket> {
90+
): void {
8491
const { proxy } = this;
85-
8692
const protocol = opts.secureEndpoint ? 'https:' : 'http:';
8793
const hostname = req.getHeader('host') || 'localhost';
8894
const base = `${protocol}//${hostname}`;
@@ -96,7 +102,7 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
96102
req.path = String(url);
97103

98104
// Inject the `Proxy-Authorization` header if necessary.
99-
req._header = null;
105+
100106
const headers: OutgoingHttpHeaders =
101107
typeof this.proxyHeaders === 'function'
102108
? this.proxyHeaders()
@@ -121,15 +127,16 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
121127
req.setHeader(name, value);
122128
}
123129
}
130+
}
124131

125-
// Create a socket connection to the proxy server.
126-
let socket: net.Socket;
127-
if (this.secureProxy) {
128-
debug('Creating `tls.Socket`: %o', this.connectOpts);
129-
socket = tls.connect(this.connectOpts);
130-
} else {
131-
debug('Creating `net.Socket`: %o', this.connectOpts);
132-
socket = net.connect(this.connectOpts);
132+
async connect(
133+
req: HttpProxyAgentClientRequest,
134+
opts: AgentConnectOpts
135+
): Promise<net.Socket> {
136+
req._header = null;
137+
138+
if (!req.path.includes('://')) {
139+
this.setRequestProps(req, opts);
133140
}
134141

135142
// At this point, the http ClientRequest's internal `_header` field
@@ -140,7 +147,6 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
140147
debug('Regenerating stored HTTP header string for request');
141148
req._implicitHeader();
142149
if (req.outputData && req.outputData.length > 0) {
143-
// Node >= 12
144150
debug(
145151
'Patching connection write() output buffer with updated header'
146152
);
@@ -151,6 +157,16 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
151157
debug('Output buffer: %o', req.outputData[0].data);
152158
}
153159

160+
// Create a socket connection to the proxy server.
161+
let socket: net.Socket;
162+
if (this.secureProxy) {
163+
debug('Creating `tls.Socket`: %o', this.connectOpts);
164+
socket = tls.connect(this.connectOpts);
165+
} else {
166+
debug('Creating `net.Socket`: %o', this.connectOpts);
167+
socket = net.connect(this.connectOpts);
168+
}
169+
154170
// Wait for the socket's `connect` event, so that this `callback()`
155171
// function throws instead of the `http` request machinery. This is
156172
// important for i.e. `PacProxyAgent` which determines a failed proxy

packages/http-proxy-agent/test/test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as fs from 'fs';
22
import * as http from 'http';
33
import * as https from 'https';
44
import assert from 'assert';
5+
import { once } from 'events';
56
import { createProxy, ProxyServer } from 'proxy';
67
import { listen } from 'async-listen';
78
import { json, req } from 'agent-base';
@@ -219,5 +220,32 @@ describe('HttpProxyAgent', () => {
219220
const body = await json(res);
220221
expect(body.host).toEqual(httpServerUrl.hostname);
221222
});
223+
224+
it('should work with `keepAlive: true`', async () => {
225+
httpServer.on('request', (req, res) => {
226+
res.end(JSON.stringify(req.headers));
227+
});
228+
229+
const agent = new HttpProxyAgent(proxyUrl, { keepAlive: true });
230+
231+
try {
232+
const res = await req(httpServerUrl, { agent });
233+
expect(res.headers.connection).toEqual('keep-alive');
234+
expect(res.statusCode).toEqual(200);
235+
res.resume();
236+
const s1 = res.socket;
237+
await once(s1, 'free');
238+
239+
const res2 = await req(httpServerUrl, { agent });
240+
expect(res2.headers.connection).toEqual('keep-alive');
241+
expect(res2.statusCode).toEqual(200);
242+
res2.resume();
243+
const s2 = res2.socket;
244+
assert(s1 === s2);
245+
await once(s2, 'free');
246+
} finally {
247+
agent.destroy();
248+
}
249+
});
222250
});
223251
});

0 commit comments

Comments
 (0)