Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Connecting through a proxy

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

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`.
For instance, if you need to send a request to the `/hello` route of your upstream server,
the `path` should be `path: 'http://upstream.server:port/hello?foo=bar'`.

If you proxy requires basic authentication, you can send it via the `proxy-authorization` header.

### Connect without authentication

```js
import { Client } from 'undici'
import { createServer } from 'http'
import proxy from 'proxy'

const server = await buildServer()
const proxy = await buildProxy()

const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`

server.on('request', (req, res) => {
console.log(req.url) // '/hello?foo=bar'
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})

const client = new Client(proxyUrl)

const response = await client.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar'
})

response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
console.log(response.statusCode) // 200
console.log(JSON.parse(data)) // { hello: 'world' }

server.close()
proxy.close()
client.close()

function buildServer () {
return new Promise((resolve, reject) => {
const server = createServer()
server.listen(0, () => resolve(server))
})
}

function buildProxy () {
return new Promise((resolve, reject) => {
const server = proxy(createServer())
server.listen(0, () => resolve(server))
})
}
```

### Connect with authentication

```js
import { Client } from 'undici'
import { createServer } from 'http'
import proxy from 'proxy'

const server = await buildServer()
const proxy = await buildProxy()

const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`

proxy.authenticate = function (req, fn) {
fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`)
}

server.on('request', (req, res) => {
console.log(req.url) // '/hello?foo=bar'
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})

const client = new Client(proxyUrl)

const response = await client.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar',
headers: {
'proxy-authorization': `Basic ${Buffer.from('user:pass').toString('base64')}`
}
})

response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
console.log(response.statusCode) // 200
console.log(JSON.parse(data)) // { hello: 'world' }

server.close()
proxy.close()
client.close()

function buildServer () {
return new Promise((resolve, reject) => {
const server = createServer()
server.listen(0, () => resolve(server))
})
}

function buildProxy () {
return new Promise((resolve, reject) => {
const server = proxy(createServer())
server.listen(0, () => resolve(server))
})
}
```
6 changes: 1 addition & 5 deletions lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ const assert = require('assert')

const kHandler = Symbol('handler')

// Borrowed from https://gist.github.com/dperini/729294
// eslint-disable-next-line no-control-regex
const REGEXP_ABSOLUTE_URL = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x00a1-\xffff0-9]+-?)*[a-z\x00a1-\xffff0-9]+)(?:\.(?:[a-z\x00a1-\xffff0-9]+-?)*[a-z\x00a1-\xffff0-9]+)*(?:\.(?:[a-z\x00a1-\xffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/ius

class Request {
constructor ({
path,
Expand All @@ -24,7 +20,7 @@ class Request {
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
} else if (path[0] !== '/' && !REGEXP_ABSOLUTE_URL.test(path)) {
} else if (path[0] !== '/' && !(path.startsWith('http://') || path.startsWith('https://'))) {
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"https-pem": "^2.0.0",
"jest": "^26.4.0",
"pre-commit": "^1.2.2",
"proxy": "^1.0.2",
"proxyquire": "^2.0.1",
"snazzy": "^8.0.0",
"standard": "^14.3.4",
Expand Down
132 changes: 132 additions & 0 deletions test/proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
'use strict'

const { test } = require('tap')
const { Client, Pool } = require('..')
const { createServer } = require('http')
const proxy = require('proxy')

test('connect through proxy', async (t) => {
t.plan(3)

const server = await buildServer()
const proxy = await buildProxy()

const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`

server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})

const client = new Client(proxyUrl)

const response = await client.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar'
})

response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
t.strictEqual(response.statusCode, 200)
t.deepEqual(JSON.parse(data), { hello: 'world' })

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

test('connect through proxy with auth', async (t) => {
t.plan(3)

const server = await buildServer()
const proxy = await buildProxy()

const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`

proxy.authenticate = function (req, fn) {
fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`)
}

server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})

const client = new Client(proxyUrl)

const response = await client.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar',
headers: {
'proxy-authorization': `Basic ${Buffer.from('user:pass').toString('base64')}`
}
})

response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
t.strictEqual(response.statusCode, 200)
t.deepEqual(JSON.parse(data), { hello: 'world' })

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

test('connect through proxy (with pool)', async (t) => {
t.plan(3)

const server = await buildServer()
const proxy = await buildProxy()

const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`

server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})

const pool = new Pool(proxyUrl)

const response = await pool.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar'
})

response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
t.strictEqual(response.statusCode, 200)
t.deepEqual(JSON.parse(data), { hello: 'world' })

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

function buildServer () {
return new Promise((resolve, reject) => {
const server = createServer()
server.listen(0, () => resolve(server))
})
}

function buildProxy () {
return new Promise((resolve, reject) => {
const server = proxy(createServer())
server.listen(0, () => resolve(server))
})
}