Skip to content

Commit

Permalink
Allow post-fastboot middlewares to recover from fastboot failure
Browse files Browse the repository at this point in the history
  • Loading branch information
Arjan Singh committed Sep 1, 2016
1 parent b975bb3 commit 5cbeaf5
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 75 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ provide to the middleware to specify which Ember app to load and render.

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

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

Resilient mode still calls `next(err)` to propagate your error to any subsequent
middleware that you apply after this one.
You can use this feature to track errors or log analytics.

However, because FastBoot is reslient still sends the response to the client.
***You cannot alter the `response`*** with any of your post-fastboot middleware.

## Custom FastBoot Instance

For more control over the FastBoot instance that is created to render
Expand Down
15 changes: 4 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,16 @@ function fastbootExpressMiddleware(distPath, options) {
})
.catch(error => {
log(500, error.stack);
res.status(500);
next(error);
res.sendStatus(500);
});
}

function failure(error) {
if (error.name === "UnrecognizedURLError") {
next();
} else {
next(error);
log(500, "Unknown Error: " + error.stack);
if (error.stack) {
res.status(500).send(error.stack);
} else {
res.sendStatus(500);
}
if (error.name !== "UnrecognizedURLError") {
res.status(500);
}
next(error);
}
};
}
Expand Down
10 changes: 9 additions & 1 deletion test/helpers/test-http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ class TestHTTPServer {
if (options.errorHandling) {
app.use((err, req, res, next) => {
res.set('x-test-error', 'error handler called');
next();
next(err);
});
}

if (options.recoverErrors) {
app.use((err, req, res, next) => {
res.set('x-test-recovery', 'recovered response');
res.status(200);
res.send('hello world');
});
}

Expand Down
161 changes: 99 additions & 62 deletions test/middleware-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,68 +65,6 @@ describe("FastBoot", function() {
});
});

it("renders no FastBoot markup if the resilient flag is set", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true
});
server = new TestHTTPServer(middleware);

return server.start()
.then(() => server.request('/'))
.then(html => {
expect(html).to.not.match(/error/);
});
});

it("propagates to error handling middleware if the resilient flag is set", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true
});
server = new TestHTTPServer(middleware, { errorHandling: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-error']).to.match(/error handler called/);
expect(body).to.match(/hello world/);
});
});

it("propagates to error handling middleware if the resilient flag is not set", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: false,
});
server = new TestHTTPServer(middleware, { errorHandling: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.catch(({ statusCode, response: { headers } }) => {
expect(statusCode).to.equal(500);
expect(headers['x-test-error']).to.match(/error handler called/);
});
});

it("is does not propagate errors when the reslient flag is set and there is no error handling middleware", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true,
});
server = new TestHTTPServer(middleware, { errorHandling: false });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-error']).to.not.match(/error handler called/);
expect(body).to.not.match(/error/);
expect(body).to.match(/hello world/);
});
});

it("can be provided with a custom FastBoot instance", function() {
let fastboot = new FastBoot({
distPath: fixture('basic-app')
Expand Down Expand Up @@ -181,4 +119,103 @@ describe("FastBoot", function() {
});
}
});

describe('when reslient mode is enabled', function () {
it("renders no FastBoot markup", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true
});
server = new TestHTTPServer(middleware);

return server.start()
.then(() => server.request('/'))
.then(html => {
expect(html).to.not.match(/error/);
});
});

it("propagates to error handling middleware", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true
});
server = new TestHTTPServer(middleware, { errorHandling: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-error']).to.match(/error handler called/);
expect(body).to.match(/hello world/);
});
});

it("is does not propagate errors when and there is no error handling middleware", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true,
});
server = new TestHTTPServer(middleware, { errorHandling: false });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-error']).to.not.match(/error handler called/);
expect(body).to.not.match(/error/);
expect(body).to.match(/hello world/);
});
});

it("allows post-fastboot middleware to recover the response when it fails", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: true
});
server = new TestHTTPServer(middleware, { recoverErrors: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-recovery']).to.match(/recovered response/);
expect(body).to.match(/hello world/);
});
});
});

describe('when reslient mode is disabled', function () {
it("propagates to error handling middleware", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: false,
});
server = new TestHTTPServer(middleware, { errorHandling: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.catch(({ statusCode, response: { headers } }) => {
expect(statusCode).to.equal(500);
expect(headers['x-test-error']).to.match(/error handler called/);
});
});

it("allows post-fastboot middleware to recover the response when it fails", function() {
let middleware = fastbootMiddleware({
distPath: fixture('rejected-promise'),
resilient: false
});
server = new TestHTTPServer(middleware, { recoverErrors: true });

return server.start()
.then(() => server.request('/', { resolveWithFullResponse: true }))
.then(({ body, statusCode, headers }) => {
expect(statusCode).to.equal(200);
expect(headers['x-test-recovery']).to.match(/recovered response/);
expect(body).to.match(/hello world/);
});
});
});

});

0 comments on commit 5cbeaf5

Please sign in to comment.