Spaces:
Running
Running
| const HOP_BY_HOP_HEADERS = new Set([ | |
| 'connection', | |
| 'content-encoding', | |
| 'content-length', | |
| 'host', | |
| 'keep-alive', | |
| 'proxy-authenticate', | |
| 'proxy-authorization', | |
| 'te', | |
| 'trailer', | |
| 'transfer-encoding', | |
| 'upgrade', | |
| ]) | |
| function readRequestBody(req) { | |
| return new Promise((resolve, reject) => { | |
| const chunks = [] | |
| req.on('data', chunk => chunks.push(chunk)) | |
| req.on('end', () => resolve(Buffer.concat(chunks))) | |
| req.on('error', reject) | |
| }) | |
| } | |
| function buildTargetUrl(req) { | |
| const base = process.env.API_PROXY_TARGET || process.env.BACKEND_URL | |
| if (!base) { | |
| throw new Error('API_PROXY_TARGET is not configured') | |
| } | |
| const incoming = new URL(req.url, 'http://localhost') | |
| const path = incoming.searchParams.get('path') || '' | |
| incoming.searchParams.delete('path') | |
| const target = new URL(path.replace(/^\/+/, ''), `${base.replace(/\/+$/, '')}/`) | |
| incoming.searchParams.forEach((value, key) => { | |
| target.searchParams.append(key, value) | |
| }) | |
| return target | |
| } | |
| function copyResponseHeaders(response, res) { | |
| response.headers.forEach((value, key) => { | |
| const lowerKey = key.toLowerCase() | |
| if (lowerKey !== 'set-cookie' && !HOP_BY_HOP_HEADERS.has(lowerKey)) { | |
| res.setHeader(key, value) | |
| } | |
| }) | |
| const cookies = response.headers.getSetCookie?.() | |
| if (cookies?.length) { | |
| res.setHeader('set-cookie', cookies) | |
| return | |
| } | |
| const cookie = response.headers.get('set-cookie') | |
| if (cookie) { | |
| res.setHeader('set-cookie', cookie) | |
| } | |
| } | |
| export default async function handler(req, res) { | |
| let target | |
| try { | |
| target = buildTargetUrl(req) | |
| } catch (error) { | |
| res.statusCode = 500 | |
| res.setHeader('content-type', 'application/json') | |
| res.end(JSON.stringify({ detail: error.message })) | |
| return | |
| } | |
| const headers = new Headers() | |
| for (const [key, value] of Object.entries(req.headers)) { | |
| const lowerKey = key.toLowerCase() | |
| if (!HOP_BY_HOP_HEADERS.has(lowerKey) && value !== undefined) { | |
| headers.set(key, Array.isArray(value) ? value.join(', ') : value) | |
| } | |
| } | |
| const hasBody = !['GET', 'HEAD'].includes(req.method) | |
| const response = await fetch(target, { | |
| method: req.method, | |
| headers, | |
| body: hasBody ? await readRequestBody(req) : undefined, | |
| redirect: 'manual', | |
| }) | |
| res.statusCode = response.status | |
| copyResponseHeaders(response, res) | |
| res.end(Buffer.from(await response.arrayBuffer())) | |
| } | |