Add chunked response support for large bodies
This commit is contained in:
parent
eac9a8f990
commit
4f463acccf
|
|
@ -33,6 +33,7 @@ export type TunnelResponse = {
|
|||
}
|
||||
|
||||
const BACKOFF = [1000, 2000, 4000, 8000, 16000, 30000]
|
||||
const CHUNK_SIZE = 768 * 1024 // 768KB per chunk
|
||||
|
||||
const isText = (contentType: string | null): boolean => {
|
||||
if (!contentType) return true
|
||||
|
|
@ -90,7 +91,7 @@ export function connect(options: TunnelOptions): Tunnel {
|
|||
if (msg.id) {
|
||||
onRequest?.(msg as TunnelRequest)
|
||||
const res = await proxy(msg as TunnelRequest)
|
||||
ws?.send(JSON.stringify(res))
|
||||
sendResponse(ws!, res)
|
||||
}
|
||||
} catch (err) {
|
||||
onError?.(err instanceof Error ? err : new Error(String(err)))
|
||||
|
|
@ -108,6 +109,30 @@ export function connect(options: TunnelOptions): Tunnel {
|
|||
}
|
||||
}
|
||||
|
||||
function sendResponse(ws: WebSocket, res: TunnelResponse): void {
|
||||
if (res.body.length <= CHUNK_SIZE) {
|
||||
ws.send(JSON.stringify(res))
|
||||
return
|
||||
}
|
||||
|
||||
// Send header (no body)
|
||||
ws.send(JSON.stringify({
|
||||
id: res.id,
|
||||
status: res.status,
|
||||
headers: res.headers,
|
||||
isBinary: res.isBinary,
|
||||
chunked: true,
|
||||
}))
|
||||
|
||||
// Send body in chunks
|
||||
for (let i = 0; i < res.body.length; i += CHUNK_SIZE) {
|
||||
ws.send(JSON.stringify({ id: res.id, c: res.body.slice(i, i + CHUNK_SIZE) }))
|
||||
}
|
||||
|
||||
// Send end marker
|
||||
ws.send(JSON.stringify({ id: res.id, done: true }))
|
||||
}
|
||||
|
||||
async function proxy(req: TunnelRequest): Promise<TunnelResponse> {
|
||||
try {
|
||||
const url = `${target}${req.path}`
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type Response = {
|
|||
headers: Record<string, string>,
|
||||
body: string
|
||||
isBinary?: boolean
|
||||
chunked?: boolean
|
||||
}
|
||||
|
||||
type Success = {
|
||||
|
|
@ -29,7 +30,8 @@ const REQUEST_TIMEOUT = 30_000
|
|||
|
||||
type Connection = { app: string, ws: any }
|
||||
let connections: Record<string, Connection> = {}
|
||||
const pending = new Map<string, { resolve: (res: Response) => void, subdomain: string }>
|
||||
const pending = new Map<string, { resolve: (res: Response) => void, subdomain: string }>()
|
||||
const chunked = new Map<string, { status: number, headers: Record<string, string>, isBinary?: boolean, parts: string[] }>()
|
||||
|
||||
const app = new Hono
|
||||
|
||||
|
|
@ -77,6 +79,34 @@ app.get("/tunnel", c => {
|
|||
},
|
||||
async onMessage(event, _ws) {
|
||||
const msg = JSON.parse(event.data.toString())
|
||||
|
||||
// Chunked response: header
|
||||
if (msg.chunked) {
|
||||
chunked.set(msg.id, { status: msg.status, headers: msg.headers, isBinary: msg.isBinary, parts: [] })
|
||||
return
|
||||
}
|
||||
|
||||
// Chunked response: body chunk
|
||||
if (msg.c !== undefined) {
|
||||
chunked.get(msg.id)?.parts.push(msg.c)
|
||||
return
|
||||
}
|
||||
|
||||
// Chunked response: end marker
|
||||
if (msg.done) {
|
||||
const chunk = chunked.get(msg.id)
|
||||
if (chunk) {
|
||||
chunked.delete(msg.id)
|
||||
const entry = pending.get(msg.id)
|
||||
if (entry) {
|
||||
entry.resolve({ id: msg.id, status: chunk.status, headers: chunk.headers, body: chunk.parts.join(''), isBinary: chunk.isBinary })
|
||||
pending.delete(msg.id)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Non-chunked response (backward compatible)
|
||||
const entry = pending.get(msg.id)
|
||||
if (entry) {
|
||||
entry.resolve(msg)
|
||||
|
|
@ -165,6 +195,11 @@ function randomName(): string {
|
|||
|
||||
export default {
|
||||
port: process.env.PORT || 3100,
|
||||
websocket,
|
||||
websocket: {
|
||||
...websocket,
|
||||
maxPayloadLength: 128 * 1024 * 1024,
|
||||
backpressureLimit: 128 * 1024 * 1024,
|
||||
idleTimeout: 120,
|
||||
},
|
||||
fetch: app.fetch,
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user