fix proxy bugs

This commit is contained in:
Chris Wanstrath 2026-02-16 13:10:04 -08:00
parent 3736202020
commit fecc074757
2 changed files with 38 additions and 6 deletions

View File

@ -6,6 +6,8 @@ import syncRouter from './api/sync'
import systemRouter from './api/system'
import { Hype } from '@because/hype'
import { cleanupStalePublishers } from './mdns'
import type { Server } from 'bun'
import type { WsData } from './proxy'
import { extractSubdomain, proxySubdomain, proxyWebSocket, websocket } from './proxy'
const app = new Hype({ layout: false, logging: !!process.env.DEBUG })
@ -65,7 +67,7 @@ const defaults = app.defaults
export default {
...defaults,
maxRequestBodySize: 1024 * 1024 * 50, // 50MB
fetch(req: Request, server: any) {
fetch(req: Request, server: Server<WsData>) {
const subdomain = extractSubdomain(req.headers.get('host') ?? '')
if (subdomain) {
if (req.headers.get('upgrade')?.toLowerCase() === 'websocket') {

View File

@ -1,6 +1,9 @@
import type { ServerWebSocket } from 'bun'
import type { Server, ServerWebSocket } from 'bun'
import { getApp } from '$apps'
export type { WsData }
const pendingMessages = new Map<ServerWebSocket<WsData>, (string | ArrayBuffer | Uint8Array)[]>()
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
interface WsData {
@ -38,8 +41,8 @@ export async function proxySubdomain(subdomain: string, req: Request): Promise<R
const url = new URL(req.url)
const target = `http://localhost:${app.port}${url.pathname}${url.search}`
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
const body = hasBody ? await req.arrayBuffer() : undefined
const hasBody = ['POST', 'PUT', 'PATCH'].includes(req.method)
const body = hasBody ? req.body : undefined
const headers = new Headers(req.headers)
headers.set('host', `localhost:${app.port}`)
@ -61,7 +64,7 @@ export async function proxySubdomain(subdomain: string, req: Request): Promise<R
}
}
export function proxyWebSocket(subdomain: string, req: Request, server: any): Response | undefined {
export function proxyWebSocket(subdomain: string, req: Request, server: Server<WsData>): Response | undefined {
const app = getApp(subdomain)
if (!app || app.state !== 'running' || !app.port) {
@ -83,17 +86,39 @@ export const websocket = {
upstream.binaryType = 'arraybuffer'
upstreams.set(ws, upstream)
pendingMessages.set(ws, [])
const timeout = setTimeout(() => {
if (upstream.readyState !== WebSocket.OPEN) {
upstream.close()
ws.close()
}
}, 10_000)
upstream.addEventListener('open', () => {
clearTimeout(timeout)
const buffered = pendingMessages.get(ws)
if (buffered) {
for (const msg of buffered) upstream.send(msg)
pendingMessages.delete(ws)
}
})
upstream.addEventListener('message', e => {
// binaryType is 'arraybuffer' so data is always string | ArrayBuffer
ws.send(e.data as string | ArrayBuffer)
})
upstream.addEventListener('close', () => {
clearTimeout(timeout)
pendingMessages.delete(ws)
upstreams.delete(ws)
ws.close()
})
upstream.addEventListener('error', () => {
clearTimeout(timeout)
pendingMessages.delete(ws)
upstreams.delete(ws)
ws.close()
})
@ -101,7 +126,11 @@ export const websocket = {
message(ws: ServerWebSocket<WsData>, msg: string | ArrayBuffer | Uint8Array) {
const upstream = upstreams.get(ws)
if (!upstream || upstream.readyState !== WebSocket.OPEN) return
if (!upstream) return
if (upstream.readyState !== WebSocket.OPEN) {
pendingMessages.get(ws)?.push(msg)
return
}
upstream.send(msg)
},
@ -111,5 +140,6 @@ export const websocket = {
upstream.close()
upstreams.delete(ws)
}
pendingMessages.delete(ws)
},
}