next
Version:
The React Framework
128 lines (127 loc) • 5.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "proxyRequest", {
enumerable: true,
get: function() {
return proxyRequest;
}
});
const _url = /*#__PURE__*/ _interop_require_default(require("url"));
const _serverrouteutils = require("../../server-route-utils");
const _stream = require("stream");
const _detachedpromise = require("../../../lib/detached-promise");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
async function proxyRequest(req, res, parsedUrl, upgradeHead, reqBody, proxyTimeout) {
const { query } = parsedUrl;
delete parsedUrl.query;
parsedUrl.search = (0, _serverrouteutils.stringifyQuery)(req, query);
// Keep in mind that a WHATWG URL's hostname and the parsedUrl's hostname
// are not strictly equal due to lowercasing, IDN translation, IPv4 and IPv6 normalization, etc.
// We just make sure this is a valid URL since httpxy would only parse it
// lazily on the `web()`/`ws()` call.
const target = new URL(_url.default.format(parsedUrl));
const { ProxyServer } = require('next/dist/compiled/httpxy');
const proxy = new ProxyServer({
target,
changeOrigin: true,
ignorePath: true,
ws: true,
// we limit proxy requests to 30s by default, in development
// we don't time out WebSocket requests to allow proxying
proxyTimeout: proxyTimeout === null ? undefined : proxyTimeout || 30000,
headers: {
'x-forwarded-host': req.headers.host || ''
}
});
let finished = false;
// httpxy does not properly detect a client disconnect in newer
// versions of Node.js. This is caused because it only listens for the
// `aborted` event on the our request object, but it also fully reads
// and closes the request object. Node **will not** fire `aborted` when
// the request is already closed. Listening for `close` on our response
// object will detect the disconnect, and we can abort the proxy's
// connection.
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)=>{
// cleanup event listeners to allow clean garbage collection
proxyRes.removeListener('error', cleanup);
proxyRes.removeListener('close', cleanup);
innerRes.removeListener('error', cleanup);
innerRes.removeListener('close', cleanup);
// destroy all source streams to propagate the caught event backward
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.DetachedPromise();
const onProxyError = (err)=>{
if (!finished) {
finished = true;
console.error(`Failed to proxy ${target}`, err);
detached.reject(err);
if (!res.destroyed) {
if (!(res instanceof _stream.Duplex)) {
res.statusCode = 500;
}
res.end('Internal Server Error');
}
}
};
proxy.on('error', onProxyError);
// If upgrade head is present or the response is a Duplex stream, treat as
// WebSocket request.
if (upgradeHead || res instanceof _stream.Duplex) {
proxy.on('proxyReqWs', (proxyReq)=>{
proxyReq.on('close', ()=>{
if (!finished) {
finished = true;
detached.resolve(true);
}
});
});
// The returned promise rejects on errors of the client socket (e.g. an
// abrupt client disconnect) even when an `error` listener is registered.
proxy.ws(req, res, {}, upgradeHead).catch(onProxyError);
detached.resolve(true);
} else {
proxy.on('proxyReq', (proxyReq)=>{
proxyReq.on('close', ()=>{
if (!finished) {
finished = true;
detached.resolve(true);
}
});
});
// The returned promise rejects on errors of the client response stream
// (e.g. an abrupt client disconnect) even when an `error` listener is
// registered.
proxy.web(req, res, {
buffer: reqBody
}).catch(onProxyError);
}
// When the proxy finishes proxying the request, shut down the proxy.
return detached.promise.finally(()=>{
proxy.close();
});
}
//# sourceMappingURL=proxy-request.js.map