diff --git a/README.md b/README.md index 35fcd23..3415aaf 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ The content type of the response depends on the request's `Accepts` header. | debug | Boolean    | `false` | If `true`, HTTP responses include all error properties, including sensitive data such as file paths, URLs and stack traces. See [Example output](#example) below. | | log | Boolean | `true` | If `true`, all errors are printed via `console.error`, including an array of fields (custom error properties) that are safe to include in response messages (both 4xx and 5xx).
If `false`, sends only the error back in the response. | | safeFields | [String] | `[]` | Specifies property names on errors that are allowed to be passed through in 4xx and 5xx responses. See [Safe error fields](#safe-error-fields) below. | +| defaultType | String | `"json"` | Specify the default response content type to use when the client does not provide any Accepts header. +| negotiateContentType | Boolean | true | Negotiate the response content type via Accepts request header. When disabled, strong-error-handler will always use the default content type when producing responses. Disabling content type negotiation is useful if you want to see JSON-formatted error responses in browsers, because browsers usually prefer HTML and XML over other content types. ### Customizing log format diff --git a/lib/content-negotiation.js b/lib/content-negotiation.js index 17e56e6..2246874 100644 --- a/lib/content-negotiation.js +++ b/lib/content-negotiation.js @@ -56,6 +56,18 @@ function negotiateContentProducer(req, logWarning, options) { debug('Resolved content-type', resolvedContentType); var contentType = resolvedContentType || defaultType; + if (options.negotiateContentType === false) { + if (SUPPORTED_TYPES.indexOf(options.defaultType) > -1) { + debug('Forcing options.defaultType `%s`', + options.defaultType); + contentType = options.defaultType; + } else { + debug('contentType: `%s` is not supported, ' + + 'falling back to contentType: `%s`', + options.defaultType, contentType); + } + } + // to receive _format from user's url param to overide the content type // req.query (eg /api/Users/1?_format=json will overide content negotiation // https://github.com/strongloop/strong-remoting/blob/ac3093dcfbb787977ca0229b0f672703859e52e1/lib/http-context.js#L643-L645 diff --git a/test/handler.test.js b/test/handler.test.js index 1e533ed..9dd6c80 100644 --- a/test/handler.test.js +++ b/test/handler.test.js @@ -628,6 +628,41 @@ describe('strong-error-handler', function() { .expect('Content-Type', /^application\/json/, done); }); + it('disables content-type negotiation when negotiateContentType=false', + function(done) { + givenErrorHandlerForError(new Error('Some error'), { + negotiateContentType: false, + defaultType: 'application/json', + }); + request.get('/') + .set('Accept', 'text/html') + .expect('Content-Type', /^application\/json/, done); + } + ); + + it('chooses resolved type when negotiateContentType=false + not-supported', + function(done) { + givenErrorHandlerForError(new Error('Some error'), { + negotiateContentType: false, + defaultType: 'unsupported/type', + }); + request.get('/') + .set('Accept', 'text/html') + .expect('Content-Type', /^text\/html/, done); + } + ); + + it('chooses default type when negotiateContentType=false + not-supported ', + function(done) { + givenErrorHandlerForError(new Error('Some error'), { + negotiateContentType: false, + defaultType: 'unsupported/type', + }); + request.get('/') + .expect('Content-Type', /^application\/json/, done); + } + ); + it('honors order of accepted content-types of text/html', function(done) { givenErrorHandlerForError(new Error('Some error'), { defaultType: 'application/json',