Skip to content

Commit db8fdbc

Browse files
authored
Merge pull request #11 from dollarshaveclub/resilient-error-handling
Forward errors along to error handling middlewares
2 parents 3be164a + 28e3ee2 commit db8fdbc

File tree

4 files changed

+143
-28
lines changed

4 files changed

+143
-28
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ provide to the middleware to specify which Ember app to load and render.
4949

5050
By default, errors during render will cause the middleware to send an
5151
HTTP 500 status code as the response. In order to swallow errors and
52-
return a `200 OK` with an empty HTML page, set the `resilient` flag to
52+
return a `200` status code with an empty HTML page, set the `resilient` flag to
5353
true:
5454

5555
```js
@@ -58,6 +58,13 @@ app.get('/*', fastbootMiddleware('/path/to/dist', {
5858
}));
5959
```
6060

61+
Resilient mode still calls `next(err)` to propagate your error to any subsequent
62+
middleware that you apply after this one.
63+
You can use this feature to track errors or log analytics.
64+
65+
However, because FastBoot is reslient still sends the response to the client.
66+
***You cannot alter the `response`*** with any of your post-fastboot middleware.
67+
6168
## Custom FastBoot Instance
6269

6370
For more control over the FastBoot instance that is created to render

src/index.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,32 @@ function fastbootExpressMiddleware(distPath, options) {
3636
result.html()
3737
.then(html => {
3838
let headers = result.headers;
39+
let statusMessage = result.error ? 'NOT OK ' : 'OK ';
3940

4041
for (var pair of headers.entries()) {
4142
res.set(pair[0], pair[1]);
4243
}
4344

44-
log(result.statusCode, 'OK ' + path);
45+
if (result.error) {
46+
log("RESILIENT MODE CAUGHT:", result.error.stack);
47+
next(result.error);
48+
}
49+
50+
log(result.statusCode, statusMessage + path);
4551
res.status(result.statusCode);
4652
res.send(html);
4753
})
4854
.catch(error => {
49-
console.log(error.stack);
50-
res.sendStatus(500);
55+
res.status(500);
56+
next(error);
5157
});
5258
}
5359

5460
function failure(error) {
55-
if (error.name === "UnrecognizedURLError") {
56-
next();
57-
} else {
58-
log(500, "Unknown Error: " + error.stack);
59-
if (error.stack) {
60-
res.status(500).send(error.stack);
61-
} else {
62-
res.sendStatus(500);
63-
}
61+
if (error.name !== "UnrecognizedURLError") {
62+
res.status(500);
6463
}
64+
next(error);
6565
}
6666
};
6767
}

test/helpers/test-http-server.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ class TestHTTPServer {
2121

2222
app.get('/*', this.middleware);
2323

24+
if (options.errorHandling) {
25+
app.use((err, req, res, next) => {
26+
res.set('x-test-error', 'error handler called');
27+
next(err);
28+
});
29+
}
30+
31+
if (options.recoverErrors) {
32+
app.use((err, req, res, next) => {
33+
res.set('x-test-recovery', 'recovered response');
34+
res.status(200);
35+
res.send('hello world');
36+
});
37+
}
38+
2439
return new Promise((resolve, reject) => {
2540
let port = options.port || 3000;
2641
let host = options.host || 'localhost';
@@ -42,9 +57,17 @@ class TestHTTPServer {
4257
});
4358
}
4459

45-
request(urlPath) {
60+
request(urlPath, options) {
4661
let info = this.info;
4762
let url = 'http://[' + info.host + ']:' + info.port;
63+
64+
if (options && options.resolveWithFullResponse) {
65+
return request({
66+
resolveWithFullResponse: options.resolveWithFullResponse,
67+
uri: url + urlPath
68+
});
69+
}
70+
4871
return request(url + urlPath);
4972
}
5073

test/middleware-test.js

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,6 @@ describe("FastBoot", function() {
6565
});
6666
});
6767

68-
it("renders an empty page if the resilient flag is set", function() {
69-
let middleware = fastbootMiddleware({
70-
distPath: fixture('rejected-promise'),
71-
resilient: true
72-
});
73-
server = new TestHTTPServer(middleware);
74-
75-
return server.start()
76-
.then(() => server.request('/'))
77-
.then(html => {
78-
expect(html).to.not.match(/error/);
79-
});
80-
});
81-
8268
it("can be provided with a custom FastBoot instance", function() {
8369
let fastboot = new FastBoot({
8470
distPath: fixture('basic-app')
@@ -133,4 +119,103 @@ describe("FastBoot", function() {
133119
});
134120
}
135121
});
122+
123+
describe('when reslient mode is enabled', function () {
124+
it("renders no FastBoot markup", function() {
125+
let middleware = fastbootMiddleware({
126+
distPath: fixture('rejected-promise'),
127+
resilient: true
128+
});
129+
server = new TestHTTPServer(middleware);
130+
131+
return server.start()
132+
.then(() => server.request('/'))
133+
.then(html => {
134+
expect(html).to.not.match(/error/);
135+
});
136+
});
137+
138+
it("propagates to error handling middleware", function() {
139+
let middleware = fastbootMiddleware({
140+
distPath: fixture('rejected-promise'),
141+
resilient: true
142+
});
143+
server = new TestHTTPServer(middleware, { errorHandling: true });
144+
145+
return server.start()
146+
.then(() => server.request('/', { resolveWithFullResponse: true }))
147+
.then(({ body, statusCode, headers }) => {
148+
expect(statusCode).to.equal(200);
149+
expect(headers['x-test-error']).to.match(/error handler called/);
150+
expect(body).to.match(/hello world/);
151+
});
152+
});
153+
154+
it("is does not propagate errors when and there is no error handling middleware", function() {
155+
let middleware = fastbootMiddleware({
156+
distPath: fixture('rejected-promise'),
157+
resilient: true,
158+
});
159+
server = new TestHTTPServer(middleware, { errorHandling: false });
160+
161+
return server.start()
162+
.then(() => server.request('/', { resolveWithFullResponse: true }))
163+
.then(({ body, statusCode, headers }) => {
164+
expect(statusCode).to.equal(200);
165+
expect(headers['x-test-error']).to.not.match(/error handler called/);
166+
expect(body).to.not.match(/error/);
167+
expect(body).to.match(/hello world/);
168+
});
169+
});
170+
171+
it("allows post-fastboot middleware to recover the response when it fails", function() {
172+
let middleware = fastbootMiddleware({
173+
distPath: fixture('rejected-promise'),
174+
resilient: true
175+
});
176+
server = new TestHTTPServer(middleware, { recoverErrors: true });
177+
178+
return server.start()
179+
.then(() => server.request('/', { resolveWithFullResponse: true }))
180+
.then(({ body, statusCode, headers }) => {
181+
expect(statusCode).to.equal(200);
182+
expect(headers['x-test-recovery']).to.match(/recovered response/);
183+
expect(body).to.match(/hello world/);
184+
});
185+
});
186+
});
187+
188+
describe('when reslient mode is disabled', function () {
189+
it("propagates to error handling middleware", function() {
190+
let middleware = fastbootMiddleware({
191+
distPath: fixture('rejected-promise'),
192+
resilient: false,
193+
});
194+
server = new TestHTTPServer(middleware, { errorHandling: true });
195+
196+
return server.start()
197+
.then(() => server.request('/', { resolveWithFullResponse: true }))
198+
.catch(({ statusCode, response: { headers } }) => {
199+
expect(statusCode).to.equal(500);
200+
expect(headers['x-test-error']).to.match(/error handler called/);
201+
});
202+
});
203+
204+
it("allows post-fastboot middleware to recover the response when it fails", function() {
205+
let middleware = fastbootMiddleware({
206+
distPath: fixture('rejected-promise'),
207+
resilient: false
208+
});
209+
server = new TestHTTPServer(middleware, { recoverErrors: true });
210+
211+
return server.start()
212+
.then(() => server.request('/', { resolveWithFullResponse: true }))
213+
.then(({ body, statusCode, headers }) => {
214+
expect(statusCode).to.equal(200);
215+
expect(headers['x-test-recovery']).to.match(/recovered response/);
216+
expect(body).to.match(/hello world/);
217+
});
218+
});
219+
});
220+
136221
});

0 commit comments

Comments
 (0)