Skip to content

Commit

Permalink
Add support for proxyRequestOptions (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbargiel authored Oct 7, 2022
1 parent 1c23a5b commit 3848d23
Show file tree
Hide file tree
Showing 7 changed files with 411 additions and 7 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ http.get('http://localhost:9200', { agent })
.end()
```

You can also pass custom options intended only for the proxy CONNECT request with the `proxyConnectOptions` option,
such as headers or `tls.connect()` options:

```js
const fs = require('fs')
const http = require('http')
const { HttpProxyAgent } = require('hpagent')

const agent = new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
proxy: 'https://localhost:8080',
proxyConnectOptions: {
headers: {
'Proxy-Authorization': 'Basic YWxhZGRpbjpvcGVuc2VzYW1l',
},
ca: [ fs.readFileSync('custom-proxy-cert.pem') ]
}
})

http.get('http://localhost:9200', { agent })
.on('response', console.log)
.end()
```

## Integrations

Following you can find the list of userland http libraries that are tested with this agent.
Expand Down
15 changes: 12 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,29 @@ declare class HttpProxyAgent extends http.Agent {
}

interface HttpProxyAgentOptions extends http.AgentOptions {
proxy: string | URL
proxy: string | URL,
proxyRequestOptions?: ProxyAgentRequestOptions
}

declare class HttpsProxyAgent extends https.Agent {
constructor(options: HttpsProxyAgentOptions)
}

interface HttpsProxyAgentOptions extends https.AgentOptions {
proxy: string | URL
proxy: string | URL,
proxyRequestOptions?: ProxyAgentRequestOptions
}

interface ProxyAgentRequestOptions {
ca?: string[],
headers?: Object,
rejectUnauthorized?: boolean
}

export {
HttpProxyAgent,
HttpProxyAgentOptions,
HttpsProxyAgent,
HttpsProxyAgentOptions
HttpsProxyAgentOptions,
ProxyAgentRequestOptions,
}
12 changes: 8 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ const { URL } = require('url')

class HttpProxyAgent extends http.Agent {
constructor (options) {
const { proxy, ...opts } = options
const { proxy, proxyRequestOptions, ...opts } = options
super(opts)
this.proxy = typeof proxy === 'string'
? new URL(proxy)
: proxy
this.proxyRequestOptions = proxyRequestOptions || {}
}

createConnection (options, callback) {
const requestOptions = {
...this.proxyRequestOptions,
method: 'CONNECT',
host: this.proxy.hostname,
port: this.proxy.port,
path: `${options.host}:${options.port}`,
setHost: false,
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
headers: { ...this.proxyRequestOptions.headers, connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
agent: false,
timeout: options.timeout || 0
}
Expand Down Expand Up @@ -60,21 +62,23 @@ class HttpProxyAgent extends http.Agent {

class HttpsProxyAgent extends https.Agent {
constructor (options) {
const { proxy, ...opts } = options
const { proxy, proxyRequestOptions, ...opts } = options
super(opts)
this.proxy = typeof proxy === 'string'
? new URL(proxy)
: proxy
this.proxyRequestOptions = proxyRequestOptions || {}
}

createConnection (options, callback) {
const requestOptions = {
...this.proxyRequestOptions,
method: 'CONNECT',
host: this.proxy.hostname,
port: this.proxy.port,
path: `${options.host}:${options.port}`,
setHost: false,
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
headers: { ...this.proxyRequestOptions.headers, connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
agent: false,
timeout: options.timeout || 0
}
Expand Down
91 changes: 91 additions & 0 deletions test/http-http.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,94 @@ test('Username and password should not be encoded', async t => {
server.close()
proxy.close()
})

test('Proxy request options should be passed to the CONNECT request only', async t => {
const server = await createServer()
const proxy = await createProxy()
let serverCustomHeaderReceived
let proxyCustomHeaderReceived
server.on('request', (req, res) => {
serverCustomHeaderReceived = req.headers['x-custom-header']
return res.end('ok')
})
proxy.on('connect', (req) => {
proxyCustomHeaderReceived = req.headers['x-custom-header']
})

const response = await request({
method: 'GET',
hostname: server.address().address,
port: server.address().port,
path: '/',
agent: new HttpProxyAgent({
proxyRequestOptions: {
headers: {
'x-custom-header': 'value'
}
},
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: `http://${proxy.address().address}:${proxy.address().port}`
})
})

let body = ''
response.setEncoding('utf8')
for await (const chunk of response) {
body += chunk
}

t.is(body, 'ok')
t.is(response.statusCode, 200)
t.falsy(serverCustomHeaderReceived)
t.is(proxyCustomHeaderReceived, 'value')

server.close()
proxy.close()
})

test('Proxy request options should not override internal default options for CONNECT request', async t => {
const server = await createServer()
const proxy = await createProxy()
let proxyConnectionHeaderReceived
server.on('request', (req, res) => res.end('ok'))
proxy.on('connect', (req) => {
proxyConnectionHeaderReceived = req.headers.connection
})

const response = await request({
method: 'GET',
hostname: server.address().address,
port: server.address().port,
path: '/',
agent: new HttpProxyAgent({
proxyRequestOptions: {
headers: {
connection: 'close'
}
},
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: `http://${proxy.address().address}:${proxy.address().port}`
})
})

let body = ''
response.setEncoding('utf8')
for await (const chunk of response) {
body += chunk
}

t.is(body, 'ok')
t.is(response.statusCode, 200)
t.is(proxyConnectionHeaderReceived, 'keep-alive')

server.close()
proxy.close()
})
91 changes: 91 additions & 0 deletions test/http-https.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,94 @@ test('Timeout', async t => {
server.close()
proxy.close()
})

test('Proxy request options should be passed to the CONNECT request only', async t => {
const server = await createSecureServer()
const proxy = await createProxy()
let serverCustomHeaderReceived
let proxyCustomHeaderReceived
server.on('request', (req, res) => {
serverCustomHeaderReceived = req.headers['x-custom-header']
return res.end('ok')
})
proxy.on('connect', (req) => {
proxyCustomHeaderReceived = req.headers['x-custom-header']
})

const response = await request({
method: 'GET',
hostname: SERVER_HOSTNAME,
port: server.address().port,
path: '/',
agent: new HttpsProxyAgent({
proxyRequestOptions: {
headers: {
'x-custom-header': 'value'
}
},
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: `http://${proxy.address().address}:${proxy.address().port}`
})
})

let body = ''
response.setEncoding('utf8')
for await (const chunk of response) {
body += chunk
}

t.is(body, 'ok')
t.is(response.statusCode, 200)
t.falsy(serverCustomHeaderReceived)
t.is(proxyCustomHeaderReceived, 'value')

server.close()
proxy.close()
})

test('Proxy request options should not override internal default options for CONNECT request', async t => {
const server = await createSecureServer()
const proxy = await createProxy()
let proxyConnectionHeaderReceived
server.on('request', (req, res) => res.end('ok'))
proxy.on('connect', (req) => {
proxyConnectionHeaderReceived = req.headers.connection
})

const response = await request({
method: 'GET',
hostname: SERVER_HOSTNAME,
port: server.address().port,
path: '/',
agent: new HttpsProxyAgent({
proxyRequestOptions: {
headers: {
connection: 'close'
}
},
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: `http://${proxy.address().address}:${proxy.address().port}`
})
})

let body = ''
response.setEncoding('utf8')
for await (const chunk of response) {
body += chunk
}

t.is(body, 'ok')
t.is(response.statusCode, 200)
t.is(proxyConnectionHeaderReceived, 'keep-alive')

server.close()
proxy.close()
})
Loading

0 comments on commit 3848d23

Please sign in to comment.