| | import type { IncomingMessage, ServerResponse } from 'http' |
| | import type { NextUrlWithParsedQuery } from '../../request-meta' |
| |
|
| | import url from 'url' |
| | import { stringifyQuery } from '../../server-route-utils' |
| | import { Duplex } from 'stream' |
| | import { DetachedPromise } from '../../../lib/detached-promise' |
| |
|
| | export async function proxyRequest( |
| | req: IncomingMessage, |
| | res: ServerResponse | Duplex, |
| | parsedUrl: NextUrlWithParsedQuery, |
| | upgradeHead?: Buffer, |
| | reqBody?: any, |
| | proxyTimeout?: number | null |
| | ) { |
| | const { query } = parsedUrl |
| | delete (parsedUrl as any).query |
| | parsedUrl.search = stringifyQuery(req as any, query) |
| |
|
| | const target = url.format(parsedUrl) |
| | const HttpProxy = |
| | require('next/dist/compiled/http-proxy') as typeof import('next/dist/compiled/http-proxy') |
| |
|
| | const proxy = new HttpProxy({ |
| | target, |
| | changeOrigin: true, |
| | ignorePath: true, |
| | ws: true, |
| | |
| | |
| | proxyTimeout: proxyTimeout === null ? undefined : proxyTimeout || 30_000, |
| | headers: { |
| | 'x-forwarded-host': req.headers.host || '', |
| | }, |
| | }) |
| |
|
| | let finished = false |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | proxy.on('proxyReq', (proxyReq) => { |
| | res.on('close', () => proxyReq.destroy()) |
| | }) |
| |
|
| | proxy.on('proxyRes', (proxyRes) => { |
| | if (res.destroyed) { |
| | proxyRes.destroy() |
| | } else { |
| | res.on('close', () => proxyRes.destroy()) |
| | } |
| | }) |
| |
|
| | proxy.on('proxyRes', (proxyRes, innerReq, innerRes) => { |
| | const cleanup = (err: any) => { |
| | |
| | proxyRes.removeListener('error', cleanup) |
| | proxyRes.removeListener('close', cleanup) |
| | innerRes.removeListener('error', cleanup) |
| | innerRes.removeListener('close', cleanup) |
| |
|
| | |
| | innerReq.destroy(err) |
| | proxyRes.destroy(err) |
| | } |
| |
|
| | proxyRes.once('error', cleanup) |
| | proxyRes.once('close', cleanup) |
| | innerRes.once('error', cleanup) |
| | innerRes.once('close', cleanup) |
| | }) |
| |
|
| | const detached = new DetachedPromise<boolean>() |
| |
|
| | proxy.on('error', (err) => { |
| | console.error(`Failed to proxy ${target}`, err) |
| | if (!finished) { |
| | finished = true |
| | detached.reject(err) |
| |
|
| | if (!res.destroyed) { |
| | if (!(res instanceof Duplex)) { |
| | res.statusCode = 500 |
| | } |
| |
|
| | res.end('Internal Server Error') |
| | } |
| | } |
| | }) |
| |
|
| | |
| | |
| | if (upgradeHead || res instanceof Duplex) { |
| | proxy.on('proxyReqWs', (proxyReq) => { |
| | proxyReq.on('close', () => { |
| | if (!finished) { |
| | finished = true |
| | detached.resolve(true) |
| | } |
| | }) |
| | }) |
| | proxy.ws(req, res, upgradeHead) |
| | detached.resolve(true) |
| | } else { |
| | proxy.on('proxyReq', (proxyReq) => { |
| | proxyReq.on('close', () => { |
| | if (!finished) { |
| | finished = true |
| | detached.resolve(true) |
| | } |
| | }) |
| | }) |
| | proxy.web(req, res, { |
| | buffer: reqBody, |
| | }) |
| | } |
| |
|
| | |
| | return detached.promise.finally(() => { |
| | proxy.close() |
| | }) |
| | } |
| |
|