Skip to content

Commit

Permalink
feat: add proxy-agent (nodejs#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
RafaelGSS committed Oct 24, 2021
1 parent 1e2321a commit 6c2c6ad
Show file tree
Hide file tree
Showing 11 changed files with 435 additions and 3 deletions.
100 changes: 100 additions & 0 deletions docs/api/ProxyAgent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Class: ProxyAgent

Extends: `undici.Dispatcher`

A Proxy Agent class that implements the Agent API. It allows the connection through proxy in a simple way.

## `new ProxyAgent([options])`

Arguments:

* **options** `ProxyAgentOptions` (required) - It extends the `Agent` options.

Returns: `ProxyAgent`

### Parameter: `ProxyAgentOptions`

Extends: [`AgentOptions`](docs/api/Agent.md#parameter-agentoptions)

* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.

Examples:

```js
import { ProxyAgent } from 'undici'

const proxyAgent = new ProxyAgent('my.proxy.server')
// or
const proxyAgent = new ProxyAgent({ uri: 'my.proxy.server' })
```

#### Example - Basic ProxyAgent instantiation

This will instantiate the ProxyAgent. It will not do anything until registered as the agent to use with requests.

```js
import { ProxyAgent } from 'undici'

const proxyAgent = new ProxyAgent('my.proxy.server')
```

#### Example - Basic Proxy Request with global agent dispatcher

```js
import { setGlobalDispatcher, request, ProxyAgent } from 'undici'

const proxyAgent = new ProxyAgent('my.proxy.server')
setGlobalDispatcher(proxyAgent)

const { statusCode, body } = await request('https://localhost:3000/foo')

console.log('response received', statusCode) // response received 200

for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```

#### Example - Basic Proxy Request with local agent dispatcher

```js
import { ProxyAgent, request } from 'undici'

const proxyAgent = new ProxyAgent('my.proxy.server')

const {
statusCode,
body
} = await request('https://localhost:3000/foo', { dispatcher: proxyAgent })

console.log('response received', statusCode) // response received 200

for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```

### `ProxyAgent.close()`

Closes the proxy agent and waits for registered pools and clients to also close before resolving.

Returns: `Promise<void>`

#### Example - clean up after tests are complete

```js
import { ProxyAgent, setGlobalDispatcher } from 'undici'

const proxyAgent = new ProxyAgent('my.proxy.server')
setGlobalDispatcher(proxyAgent)

await proxyAgent.close()
```

### `ProxyAgent.dispatch(options, handlers)`

Implements [`Agent.dispatch(options, handlers)`](docs/api/Agent.md#parameter-agentdispatchoptions).

### `ProxyAgent.request(options[, callback])`

See [`Dispatcher.request(options [, callback])`](docs/api/Dispatcher.md#clientrequestoptions--callback).
5 changes: 4 additions & 1 deletion docs/best-practices/proxy.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Connecting through a proxy

Connecting through a proxy is possible by properly configuring the `Client` or `Pool` constructor and request.
Connecting through a proxy is possible by:

- Using [AgentProxy](docs/api/ProxyAgent.md).
- Configuring `Client` or `Pool` constructor.

The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
should be added to every request call in the `path`.
Expand Down
1 change: 1 addition & 0 deletions docsify/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [Pool](/docs/api/Pool.md "Undici API - Pool")
* [BalancedPool](/docs/api/BalancedPool.md "Undici API - BalancedPool")
* [Agent](/docs/api/Agent.md "Undici API - Agent")
* [ProxyAgent](/docs/api/ProxyAgent.md "Undici API - ProxyAgent")
* [Connector](/docs/api/Connector.md "Custom connector")
* [Errors](/docs/api/Errors.md "Undici API - Errors")
* [MockClient](/docs/api/MockClient.md "Undici API - MockClient")
Expand Down
25 changes: 25 additions & 0 deletions examples/proxy-agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

const { request, setGlobalDispatcher, ProxyAgent } = require('../')

setGlobalDispatcher(new ProxyAgent('https://localhost:8000/'))

async function main() {
const {
statusCode,
headers,
trailers,
body
// send the request via the https://localhost:8000/ HTTP proxy
} = await request('https://localhost:3000/undici')

console.log('response received', statusCode)
console.log('headers', headers)

for await (const data of body) {
console.log('data', data)
}

console.log('trailers', trailers)
}
main()
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import MockClient = require('./types/mock-client')
import MockPool = require('./types/mock-pool')
import MockAgent = require('./types/mock-agent')
import mockErrors = require('./types/mock-errors')
import ProxyAgent from './types/proxy-agent'
import { request, pipeline, stream, connect, upgrade } from './types/api'

export * from './types/fetch'
export * from './types/file'
export * from './types/formdata'

export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors }
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
export default Undici

declare function Undici(url: string, opts: Pool.Options): Pool
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const MockClient = require('./lib/mock/mock-client')
const MockAgent = require('./lib/mock/mock-agent')
const MockPool = require('./lib/mock/mock-pool')
const mockErrors = require('./lib/mock/mock-errors')
const ProxyAgent = require('./lib/proxy-agent')

const nodeVersion = process.versions.node.split('.')
const nodeMajor = Number(nodeVersion[0])
Expand All @@ -26,6 +27,7 @@ module.exports.Client = Client
module.exports.Pool = Pool
module.exports.BalancedPool = BalancedPool
module.exports.Agent = Agent
module.exports.ProxyAgent = ProxyAgent

module.exports.buildConnector = buildConnector
module.exports.errors = errors
Expand Down
3 changes: 2 additions & 1 deletion lib/core/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ module.exports = {
kHostHeader: Symbol('host header'),
kConnector: Symbol('connector'),
kStrictContentLength: Symbol('strict content length'),
kMaxRedirections: Symbol('maxRedirections')
kMaxRedirections: Symbol('maxRedirections'),
kProxy: Symbol('proxy agent options')
}
59 changes: 59 additions & 0 deletions lib/proxy-agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict'

const { kClients, kProxy } = require('./core/symbols')
const url = require('url')
const Agent = require('./agent')
const Dispatcher = require('./dispatcher')
const { InvalidArgumentError } = require('./core/errors')

const kAgent = Symbol('proxy agent')

class ProxyAgent extends Dispatcher {
constructor (opts) {
super(opts)
this[kProxy] = buildProxyOptions(opts)

const agent = new Agent(opts)
this[kAgent] = agent

this[kClients] = agent[kClients]
}

dispatch (opts, handler) {
const { host } = url.parse(opts.origin)
return this[kAgent].dispatch(
{
...opts,
origin: this[kProxy].uri,
path: opts.origin + opts.path,
headers: {
...opts.headers,
host
},
},
handler
)
}

async close () {
await this[kAgent].close()
this[kClients].clear()
}
}

function buildProxyOptions(opts) {
if (typeof opts === 'string') {
opts = { uri: opts }
}

if (!opts || !opts.uri) {
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
}

return {
uri: opts.uri,
protocol: opts.protocol || 'https'
}
}

module.exports = ProxyAgent
Loading

0 comments on commit 6c2c6ad

Please sign in to comment.