Skip to content

Commit 1069932

Browse files
authored
[http-proxy-agent] Support dynamic headers option (#175)
1 parent 25e0c93 commit 1069932

File tree

4 files changed

+99
-11
lines changed

4 files changed

+99
-11
lines changed

.changeset/unlucky-cows-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'http-proxy-agent': minor
3+
---
4+
5+
Added "headers" option

packages/http-proxy-agent/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@ http.get('http://nodejs.org/api/', { agent }, (res) => {
2424
});
2525
```
2626

27+
API
28+
---
29+
30+
### new HttpProxyAgent(proxy: string | URL, options?: HttpProxyAgentOptions)
31+
32+
The `HttpProxyAgent` class implements an `http.Agent` subclass that connects
33+
to the specified "HTTP(s) proxy server" in order to proxy HTTP requests.
34+
35+
The `proxy` argument is the URL for the proxy server.
36+
37+
The `options` argument accepts the usual `http.Agent` constructor options, and
38+
some additional properties:
39+
40+
* `headers` - Object containing additional headers to send to the proxy server
41+
in each request. This may also be a function that returns a headers object.
42+
43+
**NOTE:** If your proxy does not strip these headers from the request, they
44+
will also be sent to the destination server.
45+
2746
License
2847
-------
2948

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

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as tls from 'tls';
33
import * as http from 'http';
44
import createDebug from 'debug';
55
import { once } from 'events';
6+
import type { OutgoingHttpHeaders } from 'http';
67
import { Agent, AgentConnectOpts } from 'agent-base';
78

89
const debug = createDebug('http-proxy-agent');
@@ -21,7 +22,10 @@ type ConnectOpts<T> = {
2122
: never;
2223
}[keyof ConnectOptsMap];
2324

24-
export type HttpProxyAgentOptions<T> = ConnectOpts<T> & http.AgentOptions;
25+
export type HttpProxyAgentOptions<T> = ConnectOpts<T> &
26+
http.AgentOptions & {
27+
headers?: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);
28+
};
2529

2630
interface HttpProxyAgentClientRequest extends http.ClientRequest {
2731
outputData?: {
@@ -43,6 +47,7 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
4347
static protocols = ['http', 'https'] as const;
4448

4549
readonly proxy: URL;
50+
proxyHeaders: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);
4651
connectOpts: net.TcpNetConnectOpts & tls.ConnectionOptions;
4752

4853
get secureProxy() {
@@ -52,6 +57,7 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
5257
constructor(proxy: Uri | URL, opts?: HttpProxyAgentOptions<Uri>) {
5358
super(opts);
5459
this.proxy = typeof proxy === 'string' ? new URL(proxy) : proxy;
60+
this.proxyHeaders = opts?.headers ?? {};
5561
debug('Creating new HttpProxyAgent instance: %o', this.proxy.href);
5662

5763
// Trim off the brackets from IPv6 addresses
@@ -65,7 +71,7 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
6571
? 443
6672
: 80;
6773
this.connectOpts = {
68-
...opts,
74+
...(opts ? omit(opts, 'headers') : null),
6975
host,
7076
port,
7177
};
@@ -91,21 +97,29 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
9197

9298
// Inject the `Proxy-Authorization` header if necessary.
9399
req._header = null;
100+
const headers: OutgoingHttpHeaders =
101+
typeof this.proxyHeaders === 'function'
102+
? this.proxyHeaders()
103+
: { ...this.proxyHeaders };
94104
if (proxy.username || proxy.password) {
95105
const auth = `${decodeURIComponent(
96106
proxy.username
97107
)}:${decodeURIComponent(proxy.password)}`;
98-
req.setHeader(
99-
'Proxy-Authorization',
100-
`Basic ${Buffer.from(auth).toString('base64')}`
101-
);
108+
headers['Proxy-Authorization'] = `Basic ${Buffer.from(
109+
auth
110+
).toString('base64')}`;
102111
}
103112

104-
if (!req.hasHeader('proxy-connection')) {
105-
req.setHeader(
106-
'Proxy-Connection',
107-
this.keepAlive ? 'Keep-Alive' : 'close'
108-
);
113+
if (!headers['Proxy-Connection']) {
114+
headers['Proxy-Connection'] = this.keepAlive
115+
? 'Keep-Alive'
116+
: 'close';
117+
}
118+
for (const name of Object.keys(headers)) {
119+
const value = headers[name];
120+
if (value) {
121+
req.setHeader(name, value);
122+
}
109123
}
110124

111125
// Create a socket connection to the proxy server.
@@ -146,3 +160,21 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
146160
return socket;
147161
}
148162
}
163+
164+
function omit<T extends object, K extends [...(keyof T)[]]>(
165+
obj: T,
166+
...keys: K
167+
): {
168+
[K2 in Exclude<keyof T, K[number]>]: T[K2];
169+
} {
170+
const ret = {} as {
171+
[K in keyof typeof obj]: (typeof obj)[K];
172+
};
173+
let key: keyof typeof obj;
174+
for (key in obj) {
175+
if (!keys.includes(key)) {
176+
ret[key] = obj[key];
177+
}
178+
}
179+
return ret;
180+
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,38 @@ describe('HttpProxyAgent', () => {
176176
assert(err);
177177
expect(err.code).toEqual('ECONNREFUSED');
178178
});
179+
180+
it('should allow custom proxy "headers" object', async () => {
181+
httpServer.once('request', (req, res) => {
182+
res.end(JSON.stringify(req.headers));
183+
});
184+
const agent = new HttpProxyAgent(proxyUrl, {
185+
headers: { Foo: 'bar' },
186+
});
187+
188+
const res = await req(httpServerUrl, { agent });
189+
const body = await json(res);
190+
expect(body.foo).toEqual('bar');
191+
});
192+
193+
it('should allow custom proxy "headers" function', async () => {
194+
let count = 1;
195+
httpServer.on('request', (req, res) => {
196+
res.end(JSON.stringify(req.headers));
197+
});
198+
const agent = new HttpProxyAgent(proxyUrl, {
199+
headers: () => ({ number: count++ }),
200+
});
201+
202+
const res = await req(httpServerUrl, { agent });
203+
const body = await json(res);
204+
expect(body.number).toEqual('1');
205+
206+
const res2 = await req(httpServerUrl, { agent });
207+
const body2 = await json(res2);
208+
expect(body2.number).toEqual('2');
209+
});
210+
179211
it('should not send a port number for the default port', async () => {
180212
httpServer.once('request', (req, res) => {
181213
res.end(JSON.stringify(req.headers));

0 commit comments

Comments
 (0)