Skip to content

Commit

Permalink
[http-proxy-agent] Support dynamic headers option (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
jportner committed May 18, 2023
1 parent 25e0c93 commit 1069932
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-cows-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'http-proxy-agent': minor
---

Added "headers" option
19 changes: 19 additions & 0 deletions packages/http-proxy-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ http.get('http://nodejs.org/api/', { agent }, (res) => {
});
```

API
---

### new HttpProxyAgent(proxy: string | URL, options?: HttpProxyAgentOptions)

The `HttpProxyAgent` class implements an `http.Agent` subclass that connects
to the specified "HTTP(s) proxy server" in order to proxy HTTP requests.

The `proxy` argument is the URL for the proxy server.

The `options` argument accepts the usual `http.Agent` constructor options, and
some additional properties:

* `headers` - Object containing additional headers to send to the proxy server
in each request. This may also be a function that returns a headers object.

**NOTE:** If your proxy does not strip these headers from the request, they
will also be sent to the destination server.

License
-------

Expand Down
54 changes: 43 additions & 11 deletions packages/http-proxy-agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as tls from 'tls';
import * as http from 'http';
import createDebug from 'debug';
import { once } from 'events';
import type { OutgoingHttpHeaders } from 'http';
import { Agent, AgentConnectOpts } from 'agent-base';

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

export type HttpProxyAgentOptions<T> = ConnectOpts<T> & http.AgentOptions;
export type HttpProxyAgentOptions<T> = ConnectOpts<T> &
http.AgentOptions & {
headers?: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);
};

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

readonly proxy: URL;
proxyHeaders: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);
connectOpts: net.TcpNetConnectOpts & tls.ConnectionOptions;

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

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

// Inject the `Proxy-Authorization` header if necessary.
req._header = null;
const headers: OutgoingHttpHeaders =
typeof this.proxyHeaders === 'function'
? this.proxyHeaders()
: { ...this.proxyHeaders };
if (proxy.username || proxy.password) {
const auth = `${decodeURIComponent(
proxy.username
)}:${decodeURIComponent(proxy.password)}`;
req.setHeader(
'Proxy-Authorization',
`Basic ${Buffer.from(auth).toString('base64')}`
);
headers['Proxy-Authorization'] = `Basic ${Buffer.from(
auth
).toString('base64')}`;
}

if (!req.hasHeader('proxy-connection')) {
req.setHeader(
'Proxy-Connection',
this.keepAlive ? 'Keep-Alive' : 'close'
);
if (!headers['Proxy-Connection']) {
headers['Proxy-Connection'] = this.keepAlive
? 'Keep-Alive'
: 'close';
}
for (const name of Object.keys(headers)) {
const value = headers[name];
if (value) {
req.setHeader(name, value);
}
}

// Create a socket connection to the proxy server.
Expand Down Expand Up @@ -146,3 +160,21 @@ export class HttpProxyAgent<Uri extends string> extends Agent {
return socket;
}
}

function omit<T extends object, K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): {
[K2 in Exclude<keyof T, K[number]>]: T[K2];
} {
const ret = {} as {
[K in keyof typeof obj]: (typeof obj)[K];
};
let key: keyof typeof obj;
for (key in obj) {
if (!keys.includes(key)) {
ret[key] = obj[key];
}
}
return ret;
}
32 changes: 32 additions & 0 deletions packages/http-proxy-agent/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,38 @@ describe('HttpProxyAgent', () => {
assert(err);
expect(err.code).toEqual('ECONNREFUSED');
});

it('should allow custom proxy "headers" object', async () => {
httpServer.once('request', (req, res) => {
res.end(JSON.stringify(req.headers));
});
const agent = new HttpProxyAgent(proxyUrl, {
headers: { Foo: 'bar' },
});

const res = await req(httpServerUrl, { agent });
const body = await json(res);
expect(body.foo).toEqual('bar');
});

it('should allow custom proxy "headers" function', async () => {
let count = 1;
httpServer.on('request', (req, res) => {
res.end(JSON.stringify(req.headers));
});
const agent = new HttpProxyAgent(proxyUrl, {
headers: () => ({ number: count++ }),
});

const res = await req(httpServerUrl, { agent });
const body = await json(res);
expect(body.number).toEqual('1');

const res2 = await req(httpServerUrl, { agent });
const body2 = await json(res2);
expect(body2.number).toEqual('2');
});

it('should not send a port number for the default port', async () => {
httpServer.once('request', (req, res) => {
res.end(JSON.stringify(req.headers));
Expand Down

1 comment on commit 1069932

@vercel
Copy link

@vercel vercel bot commented on 1069932 May 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

proxy-agents – ./

proxy-agents-tootallnate.vercel.app
proxy-agents.vercel.app
proxy-agents-git-main-tootallnate.vercel.app

Please sign in to comment.