Skip to content

Commit 53726bb

Browse files
authored
[#317] allow authors to provide custom error handler (including skip error handling) (#332)
1 parent 3519090 commit 53726bb

File tree

6 files changed

+178
-5
lines changed

6 files changed

+178
-5
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,38 @@ app.use('/proxy', proxy('www.google.com', {
250250
}));
251251
```
252252

253+
### proxyErrorHandler
254+
255+
By default, ```express-http-proxy``` will pass any errors except ECONNRESET to
256+
next, so that your application can handle or react to them, or just drop
257+
through to your default error handling. ECONNRESET errors are immediately
258+
returned to the user for historical reasons.
259+
260+
If you would like to modify this behavior, you can provide your own ```proxyErrorHandler```.
261+
262+
263+
```js
264+
// Example of skipping all error handling.
265+
266+
app.use(proxy('localhost:12346', {
267+
proxyErrorHandler: function(err, res, next) {
268+
next(err);
269+
}
270+
}));
271+
272+
273+
// Example of rolling your own
274+
275+
app.use(proxy('localhost:12346', {
276+
proxyErrorHandler: function(err, res, next) {
277+
switch (err && err.code) {
278+
case 'ECONNRESET': { return res.status(405).send('504 became 405'); }
279+
case 'ECONNREFUSED': { return res.status(200).send('gotcher back'); }
280+
default: { next(err); }
281+
}
282+
}}));
283+
```
284+
253285

254286

255287
#### proxyReqOptDecorator (supports Promise form)

app/steps/decorateProxyErrors.js renamed to app/steps/handleProxyErrors.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ var debug = require('debug')('express-http-proxy');
55
function connectionResetHandler(err, res) {
66
if (err && err.code === 'ECONNRESET') {
77
debug('Error: Connection reset due to timeout.');
8-
res.setHeader('X-Timout-Reason', 'express-http-proxy reset the request.');
8+
res.setHeader('X-Timeout-Reason', 'express-http-proxy reset the request.');
99
res.writeHead(504, {'Content-Type': 'text/plain'});
1010
res.end();
1111
}
1212
}
1313

1414
function handleProxyErrors(err, res, next) {
1515
switch (err && err.code) {
16-
case 'ECONNRESET': { return connectionResetHandler(err, res); }
16+
case 'ECONNRESET': { return connectionResetHandler(err, res, next); }
1717
default: { next(err); }
1818
}
1919
}

index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var decorateProxyReqBody = require('./app/steps/decorateProxyReqBody');
1515
var decorateProxyReqOpts = require('./app/steps/decorateProxyReqOpts');
1616
var decorateUserRes = require('./app/steps/decorateUserRes');
1717
var decorateUserResHeaders = require('./app/steps/decorateUserResHeaders');
18-
var decorateProxyErrors = require('./app/steps/decorateProxyErrors');
18+
var handleProxyErrors = require('./app/steps/handleProxyErrors');
1919
var maybeSkipToNextHandler = require('./app/steps/maybeSkipToNextHandler');
2020
var prepareProxyReq = require('./app/steps/prepareProxyReq');
2121
var resolveProxyHost = require('./app/steps/resolveProxyHost');
@@ -47,7 +47,10 @@ module.exports = function proxy(host, userOptions) {
4747
.then(decorateUserRes)
4848
.then(sendUserRes)
4949
.catch(function(err) {
50-
decorateProxyErrors(err, res, next);
50+
var resolver = (container.options.proxyErrorHandler) ?
51+
container.options.proxyErrorHandler :
52+
handleProxyErrors;
53+
resolver(err, res, next);
5154
});
5255
};
5356
};

lib/resolveOptions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function resolveOptions(options) {
3838
proxyReqBodyDecorator: options.proxyReqBodyDecorator,
3939
userResDecorator: options.userResDecorator || options.intercept,
4040
userResHeaderDecorator: options.userResHeaderDecorator,
41+
proxyErrorHandler: options.proxyErrorHandler,
4142
filter: options.filter || defaultFilter,
4243
// For backwards compatability, we default to legacy behavior for newly added settings.
4344
parseReqBody: isUnset(options.parseReqBody) ? true : options.parseReqBody,

test/handleProxyError.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use strict';
2+
3+
var assert = require('assert');
4+
var express = require('express');
5+
var request = require('supertest');
6+
var proxy = require('../');
7+
var proxyTarget = require('../test/support/proxyTarget');
8+
var proxyRouteFn = [{
9+
method: 'get',
10+
path: '/:errorCode',
11+
fn: function(req, res) {
12+
var errorCode = req.params.errorCode;
13+
if (errorCode === 'timeout') {
14+
return res.status(504).send('mock timeout');
15+
}
16+
return res.status(parseInt(errorCode)).send('test case error');
17+
}
18+
}];
19+
20+
describe('error handling can be over-ridden by user', function() {
21+
var app = express();
22+
var proxyServer;
23+
24+
beforeEach(function() {
25+
proxyServer = proxyTarget(12346, 100, proxyRouteFn);
26+
app = express();
27+
});
28+
29+
afterEach(function() {
30+
proxyServer.close();
31+
});
32+
33+
describe('when user provides a null function', function() {
34+
35+
describe('when author sets a timeout that fires', function() {
36+
it('passes 504 directly to client', function(done) {
37+
app.use(proxy('localhost:12346', {
38+
timeout: 1,
39+
}));
40+
41+
request(app)
42+
.get('/200')
43+
.expect(504)
44+
.expect('X-Timeout-Reason', 'express-http-proxy reset the request.')
45+
.end(done);
46+
});
47+
});
48+
49+
it('passes status code (e.g. 504) directly to the client', function(done) {
50+
app.use(proxy('localhost:12346'));
51+
request(app)
52+
.get('/504')
53+
.expect(504)
54+
.expect(function(res) {
55+
assert(res.text === 'test case error');
56+
return res;
57+
})
58+
.end(done);
59+
});
60+
61+
it('passes status code (e.g. 500) back to the client', function(done) {
62+
app.use(proxy('localhost:12346'));
63+
request(app)
64+
.get('/500')
65+
.expect(500)
66+
.end(function(err, res) {
67+
assert(res.text === 'test case error');
68+
done();
69+
});
70+
});
71+
});
72+
73+
describe('when user provides a handler function', function() {
74+
var intentionallyWeirdStatusCode = 399;
75+
var intentionallyQuirkyStatusMessage = 'monkey skunky';
76+
77+
describe('when author sets a timeout that fires', function() {
78+
it('allows author to skip handling and handle in application step', function(done) {
79+
app.use(proxy('localhost:12346', {
80+
timeout: 1,
81+
proxyErrorHandler: function(err, res, next) {
82+
next(err);
83+
}
84+
}));
85+
86+
app.use(function(err, req, res, next) { // jshint ignore:line
87+
if (err.code === 'ECONNRESET') {
88+
res.status(intentionallyWeirdStatusCode).send(intentionallyQuirkyStatusMessage);
89+
}
90+
});
91+
92+
request(app)
93+
.get('/200')
94+
.expect(function(res) {
95+
assert(res.text === intentionallyQuirkyStatusMessage);
96+
return res;
97+
})
98+
.expect(intentionallyWeirdStatusCode)
99+
.end(done);
100+
});
101+
});
102+
103+
it('allows authors to sub in their own handling', function(done) {
104+
app.use(proxy('localhost:12346', {
105+
timeout: 1,
106+
proxyErrorHandler: function(err, res, next) {
107+
console.log('bet its here');
108+
switch (err && err.code) {
109+
case 'ECONNRESET': { return res.status(405).send('504 became 405'); }
110+
case 'ECONNREFUSED': { return res.status(200).send('gotcher back'); }
111+
default: { next(err); }
112+
}
113+
}}));
114+
115+
request(app)
116+
.get('/timeout')
117+
.expect(405)
118+
.expect(function(res) {
119+
assert(res.text === '504 became 405');
120+
return res;
121+
})
122+
.end(done);
123+
});
124+
125+
it('passes status code (e.g. 500) back to the client', function(done) {
126+
app.use(proxy('localhost:12346'));
127+
request(app)
128+
.get('/500')
129+
.expect(500)
130+
.end(function(err, res) {
131+
assert(res.text === 'test case error');
132+
done();
133+
});
134+
});
135+
});
136+
137+
});

test/timeout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('honors timeout option', function() {
3131
request(http)
3232
.get('/')
3333
.expect(504)
34-
.expect('X-Timout-Reason', 'express-http-proxy reset the request.')
34+
.expect('X-Timeout-Reason', 'express-http-proxy reset the request.')
3535
.end(done);
3636
}
3737

0 commit comments

Comments
 (0)