From 23aeb2885a3373035f5df2903f4eaecc6f8e9e7e Mon Sep 17 00:00:00 2001 From: Rowan Manning <138944+rowanmanning@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:14:01 +0100 Subject: [PATCH] feat: allow sending a custom proxy timeout error When using `proxyTimeout` it's very difficult to tell the difference between a regular socket hangup and a timeout because, in both cases, an `ECONNRESET` error is thrown. Ideally we should be able to identify when a proxy request has failed because it took too long, this would allow us to do things like send appropriate `504` status code. Suddenly throwing a different error would probably be considered a breaking change because it's possible that users of http-proxy are relying on the `ECONNRESET` error. I decided to add the custom timeout error behind a new option for now so that people can opt into using it. If you set this option: ```js var proxy = httpProxy.createProxyServer({ target: 'http://example.com', proxyTimeout: 100, proxyTimeoutCustomError: true }); ``` Then the error that gets thrown will have a message of `"The proxy request timed out"` and a code of `ETIMEDOUT` to match Node.js: https://nodejs.org/api/errors.html#common-system-errors This allows for custom error handling code like this: ```js proxy.on('error', function(err, req, res) { if (err.code === 'ETIMEDOUT') { res.writeHead(504); } else { res.writeHead(503); } // ... }); ``` Resolves #1331. --- README.md | 1 + lib/http-proxy/passes/web-incoming.js | 7 +++- ...lib-http-proxy-passes-web-incoming-test.js | 35 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b2d0b3a2..6299ac4f5 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,7 @@ proxyServer.listen(8015); ``` * **headers**: object with extra headers to be added to target requests. * **proxyTimeout**: timeout (in millis) for outgoing proxy requests +* **proxyTimeoutCustomError**: true/false, default: false - specify whether you want to throw a custom `ETIMEDOUT` error when the `proxyTimeout` is reached. If false then the default `ECONNRESET` error will be thrown. * **timeout**: timeout (in millis) for incoming requests * **followRedirects**: true/false, Default: false - specify whether you want to follow redirects * **selfHandleResponse** true/false, if set to true, none of the webOutgoing passes are called and it's your responsibility to appropriately return the response by listening and acting on the `proxyRes` event diff --git a/lib/http-proxy/passes/web-incoming.js b/lib/http-proxy/passes/web-incoming.js index 7ae735514..3784d12c1 100644 --- a/lib/http-proxy/passes/web-incoming.js +++ b/lib/http-proxy/passes/web-incoming.js @@ -138,7 +138,12 @@ module.exports = { // show an error page at the initial request if(options.proxyTimeout) { proxyReq.setTimeout(options.proxyTimeout, function() { - proxyReq.abort(); + if (options.proxyTimeoutCustomError) { + var timeoutError = new Error('The proxy request timed out'); + timeoutError.code = 'ETIMEDOUT'; + return proxyReq.destroy(timeoutError); + } + proxyReq.destroy(); }); } diff --git a/test/lib-http-proxy-passes-web-incoming-test.js b/test/lib-http-proxy-passes-web-incoming-test.js index f6553d300..53077805a 100644 --- a/test/lib-http-proxy-passes-web-incoming-test.js +++ b/test/lib-http-proxy-passes-web-incoming-test.js @@ -287,6 +287,41 @@ describe('#createProxyServer.web() using own http server', function () { }, function() {}).end(); }); + it('should proxy the request with custom timeout errors (proxyTimeoutCustomError)', function(done) { + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:45002', + proxyTimeout: 100, + proxyTimeoutCustomError: true + }); + + require('net').createServer().listen(45002); + + var proxyServer = http.createServer(requestHandler); + + var started = new Date().getTime(); + function requestHandler(req, res) { + proxy.once('error', function (err, errReq, errRes) { + proxyServer.close(); + expect(err).to.be.an(Error); + expect(errReq).to.be.equal(req); + expect(errRes).to.be.equal(res); + expect(new Date().getTime() - started).to.be.greaterThan(99); + expect(err.code).to.be('ETIMEDOUT'); + done(); + }); + + proxy.web(req, res); + } + + proxyServer.listen('8087'); + + http.request({ + hostname: '127.0.0.1', + port: '8087', + method: 'GET', + }, function() {}).end(); + }); + it('should proxy the request and handle timeout error', function(done) { var proxy = httpProxy.createProxyServer({ target: 'http://127.0.0.1:45001',