Files
hermes-web-ui/server/src/routes/proxy-handler.ts
T
ekko ee9f56dfbd feat: add Koa2 BFF server, CLI management, sessions CLI integration, and logs page
- Add Koa2 BFF layer for API proxy, file upload, session management
- Auto-check and enable api_server in ~/.hermes/config.yaml on startup
- Integrate sessions with Hermes CLI (list, get, delete)
- Add Logs page with level filtering, log file selection, and search
- Add CLI commands: start/stop/restart/status for daemon management
- Unify package.json for frontend and server dependencies
- Default port changed to 8648

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:33:04 +08:00

82 lines
2.3 KiB
TypeScript

import type { Context } from 'koa'
import { config } from '../config'
export async function proxy(ctx: Context) {
const upstream = config.upstream.replace(/\/$/, '')
const url = `${upstream}${ctx.path}${ctx.search || ''}`
console.log(`[PROXY] ${ctx.method} ${ctx.path} -> ${url}`)
// Build headers — forward most, strip browser-specific ones
const headers: Record<string, string> = {}
for (const [key, value] of Object.entries(ctx.headers)) {
if (value == null) continue
const lower = key.toLowerCase()
if (lower === 'host') {
headers['host'] = new URL(upstream).host
} else if (lower !== 'origin' && lower !== 'referer' && lower !== 'connection') {
const v = Array.isArray(value) ? value[0] : value
if (v) headers[key] = v
}
}
// Add SSE-friendly headers
if (ctx.path.match(/\/events$/)) {
headers['x-accel-buffering'] = 'no'
headers['cache-control'] = 'no-cache'
}
try {
// Build request body from raw body
let body: string | undefined
if (ctx.req.method !== 'GET' && ctx.req.method !== 'HEAD') {
body = (ctx as any).request.rawBody as string | undefined
}
const res = await fetch(url, {
method: ctx.req.method,
headers,
body,
})
// Set response headers
const resHeaders: Record<string, string> = {}
res.headers.forEach((value, key) => {
const lower = key.toLowerCase()
if (lower !== 'transfer-encoding' && lower !== 'connection') {
resHeaders[key] = value
}
})
if (ctx.path.match(/\/events$/)) {
resHeaders['x-accel-buffering'] = 'no'
resHeaders['cache-control'] = 'no-cache'
}
ctx.status = res.status
ctx.set(resHeaders)
// Stream response body
if (res.body) {
const reader = res.body.getReader()
const pump = async () => {
while (true) {
const { done, value } = await reader.read()
if (done) break
ctx.res.write(value)
}
ctx.res.end()
}
await pump()
} else {
ctx.res.end()
}
} catch (err: any) {
if (!ctx.res.headersSent) {
ctx.status = 502
ctx.set('Content-Type', 'application/json')
ctx.body = { error: { message: `Proxy error: ${err.message}` } }
} else {
ctx.res.end()
}
}
}