Skip to content

Commit a6b9587

Browse files
shulaodasapphi-red
andauthored
feat: support async for proxy.bypass (#18940)
Co-authored-by: 翠 / green <[email protected]>
1 parent 05b005f commit a6b9587

File tree

4 files changed

+76
-21
lines changed

4 files changed

+76
-21
lines changed

packages/vite/src/node/server/middlewares/proxy.ts

+44-20
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ export interface ProxyOptions extends HttpProxy.ServerOptions {
2727
/** undefined for WebSocket upgrade requests */
2828
res: http.ServerResponse | undefined,
2929
options: ProxyOptions,
30-
) => void | null | undefined | false | string
30+
) =>
31+
| void
32+
| null
33+
| undefined
34+
| false
35+
| string
36+
| Promise<void | null | undefined | boolean | string>
3137
/**
3238
* rewrite the Origin header of a WebSocket request to match the target
3339
*
@@ -158,7 +164,7 @@ export function proxyMiddleware(
158164
})
159165

160166
if (httpServer) {
161-
httpServer.on('upgrade', (req, socket, head) => {
167+
httpServer.on('upgrade', async (req, socket, head) => {
162168
const url = req.url!
163169
for (const context in proxies) {
164170
if (doesProxyContextMatchUrl(context, url)) {
@@ -169,14 +175,26 @@ export function proxyMiddleware(
169175
opts.target?.toString().startsWith('wss:')
170176
) {
171177
if (opts.bypass) {
172-
const bypassResult = opts.bypass(req, undefined, opts)
173-
if (typeof bypassResult === 'string') {
174-
req.url = bypassResult
175-
debug?.(`bypass: ${req.url} -> ${bypassResult}`)
176-
return
177-
} else if (bypassResult === false) {
178-
debug?.(`bypass: ${req.url} -> 404`)
179-
socket.end('HTTP/1.1 404 Not Found\r\n\r\n', '')
178+
try {
179+
const bypassResult = await opts.bypass(req, undefined, opts)
180+
if (typeof bypassResult === 'string') {
181+
debug?.(`bypass: ${req.url} -> ${bypassResult}`)
182+
req.url = bypassResult
183+
return
184+
}
185+
if (bypassResult === false) {
186+
debug?.(`bypass: ${req.url} -> 404`)
187+
socket.end('HTTP/1.1 404 Not Found\r\n\r\n', '')
188+
return
189+
}
190+
} catch (err) {
191+
config.logger.error(
192+
`${colors.red(`ws proxy bypass error:`)}\n${err.stack}`,
193+
{
194+
timestamp: true,
195+
error: err,
196+
},
197+
)
180198
return
181199
}
182200
}
@@ -194,23 +212,29 @@ export function proxyMiddleware(
194212
}
195213

196214
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
197-
return function viteProxyMiddleware(req, res, next) {
215+
return async function viteProxyMiddleware(req, res, next) {
198216
const url = req.url!
199217
for (const context in proxies) {
200218
if (doesProxyContextMatchUrl(context, url)) {
201219
const [proxy, opts] = proxies[context]
202220
const options: HttpProxy.ServerOptions = {}
203221

204222
if (opts.bypass) {
205-
const bypassResult = opts.bypass(req, res, opts)
206-
if (typeof bypassResult === 'string') {
207-
req.url = bypassResult
208-
debug?.(`bypass: ${req.url} -> ${bypassResult}`)
209-
return next()
210-
} else if (bypassResult === false) {
211-
debug?.(`bypass: ${req.url} -> 404`)
212-
res.statusCode = 404
213-
return res.end()
223+
try {
224+
const bypassResult = await opts.bypass(req, res, opts)
225+
if (typeof bypassResult === 'string') {
226+
debug?.(`bypass: ${req.url} -> ${bypassResult}`)
227+
req.url = bypassResult
228+
return next()
229+
}
230+
if (bypassResult === false) {
231+
debug?.(`bypass: ${req.url} -> 404`)
232+
res.statusCode = 404
233+
return res.end()
234+
}
235+
} catch (e) {
236+
debug?.(`bypass: ${req.url} -> ${e}`)
237+
return next(e)
214238
}
215239
}
216240

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import { expect, test, vi } from 'vitest'
2-
import { browserLogs } from '~utils'
2+
import { browserLogs, isServe, page, serverLogs } from '~utils'
33

44
test('proxy-bypass', async () => {
55
await vi.waitFor(() => {
66
expect(browserLogs.join('\n')).toContain('status of 404 (Not Found)')
77
})
88
})
9+
10+
test('async-proxy-bypass', async () => {
11+
const content = await page.frame('async-response').content()
12+
expect(content).toContain('Hello after 4 ms (async timeout)')
13+
})
14+
15+
test.runIf(isServe)('async-proxy-bypass-with-error', async () => {
16+
await vi.waitFor(() => {
17+
expect(serverLogs.join('\n')).toContain('bypass error')
18+
})
19+
})

playground/proxy-bypass/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
root app<br />
22
<iframe src="/nonExistentApp" style="border: 0"></iframe>
3+
<iframe src="/asyncResponse" name="async-response"></iframe>
4+
<iframe src="/asyncThrowingError"></iframe>

playground/proxy-bypass/vite.config.js

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { defineConfig } from 'vite'
22

3+
const timeout = (ms) => new Promise((r) => setTimeout(r, ms))
4+
35
export default defineConfig({
46
server: {
57
port: 9606,
@@ -10,6 +12,22 @@ export default defineConfig({
1012
return false
1113
},
1214
},
15+
'/asyncResponse': {
16+
bypass: async (_, res) => {
17+
await timeout(4)
18+
res.writeHead(200, {
19+
'Content-Type': 'text/plain',
20+
})
21+
res.end('Hello after 4 ms (async timeout)')
22+
return '/asyncResponse'
23+
},
24+
},
25+
'/asyncThrowingError': {
26+
bypass: async () => {
27+
await timeout(4)
28+
throw new Error('bypass error')
29+
},
30+
},
1331
},
1432
},
1533
})

0 commit comments

Comments
 (0)