Skip to content

Commit 501f0c6

Browse files
authoredNov 3, 2022
feat: add proxyRequest util (#226)
1 parent e6a9f94 commit 501f0c6

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ H3 has concept of compasable utilities that accept `event` (from `eventHandler((
137137
- `assertMethod(event, expected, allowHead?)`
138138
- `createError({ statusCode, statusMessage, data? })`
139139
- `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
140+
- `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
140141
141142
👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
142143

‎src/utils/proxy.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
11
import type { H3Event } from '../event'
22
import type { RequestHeaders } from '../types'
3+
import { getMethod, getRequestHeaders } from './request'
4+
import { readRawBody } from './body'
35

4-
export interface SendProxyOptions {
6+
export interface ProxyOptions {
57
headers?: RequestHeaders | HeadersInit
68
fetchOptions?: RequestInit
79
fetch?: typeof fetch
810
sendStream?: boolean
911
}
1012

11-
export async function sendProxy (event: H3Event, target: string, opts: SendProxyOptions = {}) {
13+
const PayloadMethods = ['PATCH', 'POST', 'PUT', 'DELETE']
14+
const ignoredHeaders = [
15+
'transfer-encoding',
16+
'connection',
17+
'keep-alive',
18+
'upgrade',
19+
'expect'
20+
]
21+
22+
export async function proxyRequest (event: H3Event, target: string, opts: ProxyOptions = {}) {
23+
// Method
24+
const method = getMethod(event)
25+
26+
// Body
27+
let body
28+
if (PayloadMethods.includes(method)) {
29+
body = await readRawBody(event).catch(() => undefined)
30+
}
31+
32+
// Headers
33+
const headers = Object.create(null)
34+
const reqHeaders = getRequestHeaders(event)
35+
for (const name in reqHeaders) {
36+
if (!ignoredHeaders.includes(name)) {
37+
headers[name] = reqHeaders[name]
38+
}
39+
}
40+
if (opts.fetchOptions?.headers) {
41+
Object.assign(headers, opts.fetchOptions!.headers)
42+
}
43+
if (opts.headers) {
44+
Object.assign(headers, opts.headers)
45+
}
46+
47+
return sendProxy(event, target, {
48+
...opts,
49+
fetchOptions: {
50+
headers,
51+
method,
52+
body,
53+
...opts.fetchOptions
54+
}
55+
})
56+
}
57+
58+
export async function sendProxy (event: H3Event, target: string, opts: ProxyOptions = {}) {
1259
const _fetch = opts.fetch || globalThis.fetch
1360
if (!_fetch) {
1461
throw new Error('fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js.')

‎test/proxy.test.ts

+43-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
import { Server } from 'node:http'
12
import supertest, { SuperTest, Test } from 'supertest'
2-
import { describe, it, expect, beforeEach } from 'vitest'
3+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
34
import { fetch } from 'node-fetch-native'
4-
import { createApp, toNodeListener, App, eventHandler } from '../src'
5-
import { sendProxy } from '../src/utils/proxy'
5+
import { createApp, toNodeListener, App, eventHandler, getHeaders, getMethod, readRawBody } from '../src'
6+
import { sendProxy, proxyRequest } from '../src/utils/proxy'
67

78
describe('', () => {
89
let app: App
910
let request: SuperTest<Test>
1011

11-
beforeEach(() => {
12+
let server: Server
13+
let url: string
14+
15+
beforeEach(async () => {
1216
app = createApp({ debug: false })
1317
request = supertest(toNodeListener(app))
18+
server = new Server(toNodeListener(app))
19+
await new Promise((resolve) => { server.listen(0, () => resolve(null)) })
20+
url = 'http://localhost:' + (server.address() as any).port
21+
})
22+
23+
afterEach(async () => {
24+
await new Promise((resolve) => { server.close(() => resolve(null)) })
1425
})
1526

1627
describe('sendProxy', () => {
@@ -19,10 +30,36 @@ describe('', () => {
1930
return sendProxy(event, 'https://example.com', { fetch })
2031
}))
2132

22-
const result = await request
23-
.get('/')
33+
const result = await request.get('/')
2434

2535
expect(result.text).toContain('a href="https://www.iana.org/domains/example">More information...</a>')
2636
})
2737
})
38+
39+
describe('proxyRequest', () => {
40+
it('can proxy request', async () => {
41+
app.use('/debug', eventHandler(async (event) => {
42+
const headers = getHeaders(event)
43+
delete headers.host
44+
let body
45+
try { body = await readRawBody(event) } catch {}
46+
return {
47+
method: getMethod(event),
48+
headers,
49+
body
50+
}
51+
}))
52+
53+
app.use('/', eventHandler((event) => {
54+
return proxyRequest(event, url + '/debug', { fetch })
55+
}))
56+
57+
const result = await fetch(url + '/', {
58+
method: 'POST',
59+
body: 'hello'
60+
}).then(r => r.text())
61+
62+
expect(result).toMatchInlineSnapshot('"{\\"method\\":\\"POST\\",\\"headers\\":{\\"accept\\":\\"*/*\\",\\"accept-encoding\\":\\"gzip, deflate, br\\",\\"connection\\":\\"close\\",\\"content-length\\":\\"5\\",\\"content-type\\":\\"text/plain;charset=UTF-8\\",\\"user-agent\\":\\"node-fetch\\"},\\"body\\":\\"hello\\"}"')
63+
})
64+
})
2865
})

0 commit comments

Comments
 (0)
Please sign in to comment.