mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
@@ -83,6 +83,28 @@ describe('SystemProxyBridge', () => {
|
||||
.toBe('request-fallback')
|
||||
})
|
||||
|
||||
it('terminates the downstream response when a selected proxy resets after sending headers', async () => {
|
||||
const resettingProxy = net.createServer(socket => {
|
||||
socket.once('data', () => {
|
||||
socket.write(
|
||||
'HTTP/1.1 200 OK\r\nContent-Length: 100\r\nConnection: close\r\n\r\npartial',
|
||||
() => setTimeout(() => socket.resetAndDestroy(), 25),
|
||||
)
|
||||
})
|
||||
})
|
||||
servers.push(resettingProxy)
|
||||
const resettingPort = await listen(resettingProxy)
|
||||
const bridge = await startBridge(async () => `PROXY 127.0.0.1:${resettingPort}`)
|
||||
|
||||
const outcome = await Promise.race([
|
||||
requestOutcomeThroughProxy(bridge, 'http://foreign.example/after-reset'),
|
||||
new Promise<'timed-out'>(resolve => setTimeout(() => resolve('timed-out'), 500)),
|
||||
])
|
||||
|
||||
expect(outcome).not.toBe('timed-out')
|
||||
expect(outcome).not.toBe('end')
|
||||
})
|
||||
|
||||
it('does not replay a non-idempotent request after its selected proxy drops it', async () => {
|
||||
let firstProxyRequests = 0
|
||||
const droppingProxy = net.createServer(socket => {
|
||||
@@ -314,6 +336,28 @@ function requestThroughProxy(
|
||||
})
|
||||
}
|
||||
|
||||
function requestOutcomeThroughProxy(
|
||||
proxyUrl: string,
|
||||
targetUrl: string,
|
||||
): Promise<'aborted' | 'response-error' | 'request-error' | 'end'> {
|
||||
const proxy = new URL(proxyUrl)
|
||||
return new Promise(resolve => {
|
||||
const request = http.request({
|
||||
host: proxy.hostname,
|
||||
port: Number(proxy.port),
|
||||
path: targetUrl,
|
||||
headers: { Host: new URL(targetUrl).host },
|
||||
}, response => {
|
||||
response.resume()
|
||||
response.once('aborted', () => resolve('aborted'))
|
||||
response.once('error', () => resolve('response-error'))
|
||||
response.once('end', () => resolve('end'))
|
||||
})
|
||||
request.once('error', () => resolve('request-error'))
|
||||
request.end()
|
||||
})
|
||||
}
|
||||
|
||||
function connectAndEcho(
|
||||
proxyUrl: string,
|
||||
authority: string,
|
||||
|
||||
@@ -4,6 +4,7 @@ import net from 'node:net'
|
||||
import tls from 'node:tls'
|
||||
import { lookup } from 'node:dns/promises'
|
||||
import type { Duplex } from 'node:stream'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
|
||||
export const SYSTEM_PROXY_BRIDGE_HOST = '127.0.0.1'
|
||||
const CONNECT_TIMEOUT_MS = 10_000
|
||||
@@ -163,9 +164,13 @@ export class SystemProxyBridge implements SystemProxyBridgeLike {
|
||||
onSocket,
|
||||
)
|
||||
response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.statusMessage, upstreamResponse.headers)
|
||||
upstreamResponse.pipe(response)
|
||||
await pipeline(upstreamResponse, response)
|
||||
} catch (error) {
|
||||
if (!response.headersSent) response.writeHead(502, { Connection: 'close' })
|
||||
if (response.headersSent) {
|
||||
response.destroy()
|
||||
return
|
||||
}
|
||||
response.writeHead(502, { Connection: 'close' })
|
||||
response.end(`System proxy bridge failed: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user