Skip to content

Commit d178688

Browse files
authored
feat: add http/https agent support for connection pooling (#619)
1 parent f30cf89 commit d178688

File tree

9 files changed

+419
-4
lines changed

9 files changed

+419
-4
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,47 @@ server.on('requestFailed', ({ request, error }) => {
110110
});
111111
```
112112

113+
## Use custom HTTP agents for connection pooling
114+
115+
You can provide custom HTTP/HTTPS agents to enable connection pooling and reuse with upstream proxies. This is particularly useful for maintaining sticky IP addresses or reducing connection overhead:
116+
117+
```javascript
118+
const http = require('http');
119+
const https = require('https');
120+
const ProxyChain = require('proxy-chain');
121+
122+
// Create agents with keepAlive to enable connection pooling
123+
const httpAgent = new http.Agent({
124+
keepAlive: true,
125+
maxSockets: 10,
126+
});
127+
128+
const httpsAgent = new https.Agent({
129+
keepAlive: true,
130+
maxSockets: 10,
131+
});
132+
133+
const server = new ProxyChain.Server({
134+
port: 8000,
135+
prepareRequestFunction: ({ request }) => {
136+
return {
137+
upstreamProxyUrl: 'http://proxy.example.com:8080',
138+
// Or for HTTPS upstream proxy: 'https://proxy.example.com:8080'
139+
140+
// Agents enable connection pooling to upstream proxy
141+
httpAgent, // Used for HTTP upstream proxies
142+
httpsAgent, // Used for HTTPS upstream proxies
143+
};
144+
},
145+
});
146+
147+
server.listen(() => {
148+
console.log(`Proxy server is listening on port ${server.port}`);
149+
});
150+
```
151+
152+
**Note:** Custom agents are only supported for HTTP and HTTPS upstream proxies. SOCKS upstream proxies use direct socket connections and do not support custom agents.
153+
113154
## SOCKS support
114155
SOCKS protocol is supported for versions 4 and 5, specifically: `['socks', 'socks4', 'socks4a', 'socks5', 'socks5h']`, where `socks` will default to version 5.
115156

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "proxy-chain",
3-
"version": "2.5.9",
3+
"version": "2.6.0",
44
"description": "Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining, and protocol tunneling.",
55
"main": "dist/index.js",
66
"keywords": [

src/chain.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface HandlerOpts {
2727
ipFamily?: number;
2828
dnsLookup?: typeof dns['lookup'];
2929
customTag?: unknown;
30+
httpAgent?: http.Agent;
31+
httpsAgent?: https.Agent;
3032
}
3133

3234
interface ChainOpts {
@@ -87,8 +89,12 @@ export const chain = (
8789
? https.request(proxy.origin, {
8890
...options,
8991
rejectUnauthorized: !handlerOpts.ignoreUpstreamProxyCertificate,
92+
agent: handlerOpts.httpsAgent,
9093
})
91-
: http.request(proxy.origin, options);
94+
: http.request(proxy.origin, {
95+
...options,
96+
agent: handlerOpts.httpAgent,
97+
});
9298

9399
client.once('socket', (targetSocket: SocketWithPreviousStats) => {
94100
// Socket can be re-used by multiple requests.

src/chain_socks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ const socksProtocolToVersionNumber = (protocol: string): 4 | 5 => {
3434
};
3535

3636
/**
37+
* Tunnels CONNECT requests through a SOCKS upstream proxy.
38+
*
39+
* **Note:** Custom HTTP/HTTPS agents (`httpAgent`, `httpsAgent`) from `prepareRequestFunction`
40+
* are not supported with SOCKS upstream proxies. SOCKS establishes direct TCP socket connections
41+
* using the SocksClient, which bypasses HTTP agent connection pooling.
42+
*
3743
* Client -> Apify (CONNECT) -> Upstream (SOCKS) -> Web
3844
* Client <- Apify (CONNECT) <- Upstream (SOCKS) <- Web
3945
*/

src/forward.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export interface HandlerOpts {
2929
localAddress?: string;
3030
ipFamily?: number;
3131
dnsLookup?: typeof dns['lookup'];
32+
httpAgent?: http.Agent;
33+
httpsAgent?: https.Agent;
3234
}
3335

3436
/**
@@ -118,9 +120,13 @@ export const forward = async (
118120
? https.request(origin!, {
119121
...options as unknown as https.RequestOptions,
120122
rejectUnauthorized: handlerOpts.upstreamProxyUrlParsed ? !handlerOpts.ignoreUpstreamProxyCertificate : undefined,
123+
agent: handlerOpts.httpsAgent,
121124
}, requestCallback)
122125

123-
: http.request(origin!, options as unknown as http.RequestOptions, requestCallback);
126+
: http.request(origin!, {
127+
...options as unknown as http.RequestOptions,
128+
agent: handlerOpts.httpAgent,
129+
}, requestCallback);
124130

125131
client.once('socket', (socket: SocketWithPreviousStats) => {
126132
// Socket can be re-used by multiple requests.

src/forward_socks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ export interface HandlerOpts {
2626
}
2727

2828
/**
29+
* Forwards HTTP requests through a SOCKS upstream proxy.
30+
*
31+
* **Note:** Custom HTTP/HTTPS agents (`httpAgent`, `httpsAgent`) from `prepareRequestFunction`
32+
* are not supported with SOCKS upstream proxies. SOCKS uses direct socket connections
33+
* managed by the SocksProxyAgent, which does not utilize HTTP agents.
34+
*
2935
* ```
3036
* Client -> Apify (HTTP) -> Upstream (SOCKS) -> Web
3137
* Client <- Apify (HTTP) <- Upstream (SOCKS) <- Web

src/server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Buffer } from 'node:buffer';
33
import type dns from 'node:dns';
44
import { EventEmitter } from 'node:events';
55
import http from 'node:http';
6+
import type https from 'node:https';
67
import type net from 'node:net';
78
import { URL } from 'node:url';
89
import util from 'node:util';
@@ -63,6 +64,8 @@ type HandlerOpts = {
6364
ipFamily?: number;
6465
dnsLookup?: typeof dns['lookup'];
6566
customTag?: unknown;
67+
httpAgent?: http.Agent;
68+
httpsAgent?: https.Agent;
6669
};
6770

6871
export type PrepareRequestFunctionOpts = {
@@ -86,6 +89,8 @@ export type PrepareRequestFunctionResult = {
8689
ipFamily?: number;
8790
dnsLookup?: typeof dns['lookup'];
8891
customTag?: unknown;
92+
httpAgent?: http.Agent;
93+
httpsAgent?: https.Agent;
8994
};
9095

9196
type Promisable<T> = T | Promise<T>;
@@ -453,6 +458,8 @@ export class Server extends EventEmitter {
453458
handlerOpts.dnsLookup = funcResult.dnsLookup;
454459
handlerOpts.customConnectServer = funcResult.customConnectServer;
455460
handlerOpts.customTag = funcResult.customTag;
461+
handlerOpts.httpAgent = funcResult.httpAgent;
462+
handlerOpts.httpsAgent = funcResult.httpsAgent;
456463

457464
// If not authenticated, request client to authenticate
458465
if (funcResult.requestAuthentication) {

test/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM node:18.20.8-bookworm@sha256:c6ae79e38498325db67193d391e6ec1d224d96c693a8a4d943498556716d3783
22

3-
RUN apt-get update && apt-get install -y --no-install-recommends chromium=140.0.7339.185-1~deb12u1 \
3+
RUN apt-get update && apt-get install -y --no-install-recommends chromium=142.0.7444.134-1~deb12u1 \
44
&& rm -rf /var/lib/apt/lists/*
55

66
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true

0 commit comments

Comments
 (0)