Files
trumpsignal-monorepo/apps/web/pages/api/proxy/[...path].ts
T

50 lines
1.8 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from 'next'
export const config = { api: { bodyParser: false, responseLimit: '8mb' } }
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const path = req.query.path
if (!Array.isArray(path) || path.some((p) => p === '..' || p.includes('/') || p.includes('\\')))
return res.status(400).json({ error: 'Invalid path' })
const base = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
const target = new URL('/' + path.map(encodeURIComponent).join('/'), base)
const incoming = new URL(req.url ?? '/', 'http://localhost')
target.search = incoming.search
const headers = new Headers()
const hop = new Set([
'host',
'connection',
'transfer-encoding',
'content-length',
'keep-alive',
'upgrade',
'x-forwarded-host',
'x-forwarded-proto',
])
for (const [k, v] of Object.entries(req.headers))
if (v && !hop.has(k)) headers.set(k, Array.isArray(v) ? v.join(',') : v)
headers.set('accept-encoding', 'identity')
try {
const chunks: Buffer[] = []
let bytes = 0
for await (const chunk of req) {
const b = Buffer.from(chunk)
bytes += b.length
if (bytes > 1024 * 1024) return res.status(413).end()
chunks.push(b)
}
const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Buffer.concat(chunks)
const upstream = await fetch(target, {
method: req.method,
headers,
body,
signal: AbortSignal.timeout(30_000),
redirect: 'manual',
})
upstream.headers.forEach((v, k) => {
if (!hop.has(k) && k !== 'content-encoding') res.setHeader(k, v)
})
res.status(upstream.status).send(Buffer.from(await upstream.arrayBuffer()))
} catch {
res.status(502).json({ error: 'Upstream request failed' })
}
}