| 7 | import { DetachedPromise } from '../../../lib/detached-promise' |
| 8 | |
| 9 | export async function proxyRequest( |
| 10 | req: IncomingMessage, |
| 11 | res: ServerResponse | Duplex, |
| 12 | parsedUrl: NextUrlWithParsedQuery, |
| 13 | upgradeHead?: Buffer, |
| 14 | reqBody?: any, |
| 15 | proxyTimeout?: number | null |
| 16 | ) { |
| 17 | const { query } = parsedUrl |
| 18 | delete (parsedUrl as any).query |
| 19 | parsedUrl.search = stringifyQuery(req as any, query) |
| 20 | |
| 21 | const target = url.format(parsedUrl) |
| 22 | const HttpProxy = |
| 23 | require('next/dist/compiled/http-proxy') as typeof import('next/dist/compiled/http-proxy') |
| 24 | |
| 25 | const proxy = new HttpProxy({ |
| 26 | target, |
| 27 | changeOrigin: true, |
| 28 | ignorePath: true, |
| 29 | ws: true, |
| 30 | // we limit proxy requests to 30s by default, in development |
| 31 | // we don't time out WebSocket requests to allow proxying |
| 32 | proxyTimeout: proxyTimeout === null ? undefined : proxyTimeout || 30_000, |
| 33 | headers: { |
| 34 | 'x-forwarded-host': req.headers.host || '', |
| 35 | }, |
| 36 | }) |
| 37 | |
| 38 | let finished = false |
| 39 | |
| 40 | // http-proxy does not properly detect a client disconnect in newer |
| 41 | // versions of Node.js. This is caused because it only listens for the |
| 42 | // `aborted` event on the our request object, but it also fully reads |
| 43 | // and closes the request object. Node **will not** fire `aborted` when |
| 44 | // the request is already closed. Listening for `close` on our response |
| 45 | // object will detect the disconnect, and we can abort the proxy's |
| 46 | // connection. |
| 47 | proxy.on('proxyReq', (proxyReq) => { |
| 48 | res.on('close', () => proxyReq.destroy()) |
| 49 | }) |
| 50 | |
| 51 | proxy.on('proxyRes', (proxyRes) => { |
| 52 | if (res.destroyed) { |
| 53 | proxyRes.destroy() |
| 54 | } else { |
| 55 | res.on('close', () => proxyRes.destroy()) |
| 56 | } |
| 57 | }) |
| 58 | |
| 59 | proxy.on('proxyRes', (proxyRes, innerReq, innerRes) => { |
| 60 | const cleanup = (err: any) => { |
| 61 | // cleanup event listeners to allow clean garbage collection |
| 62 | proxyRes.removeListener('error', cleanup) |
| 63 | proxyRes.removeListener('close', cleanup) |
| 64 | innerRes.removeListener('error', cleanup) |
| 65 | innerRes.removeListener('close', cleanup) |
| 66 | |