Skip to content

Commit 9a59b0f

Browse files
author
Julien Gilli
committed
BREAKING CHANGE: do not override content-type in res.send
1 parent 11d6458 commit 9a59b0f

File tree

5 files changed

+65
-48
lines changed

5 files changed

+65
-48
lines changed

docs/guides/server.md

+23-9
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,18 @@ server.get('/websocket/attach', function upgradeRoute(req, res, next) {
427427

428428
## Content Negotiation
429429

430-
If you're using `res.send()` restify will automatically select the content-type
431-
to respond with, by finding the first registered `formatter` defined. Note in
432-
the examples above we've not defined any formatters, so we've been leveraging
433-
the fact that restify ships with `application/json`, `text/plain` and
430+
If you're using `res.send()` restify will determine the content-type to respond
431+
with by:
432+
433+
1. negotiating the content-type by matching available formatters with the
434+
request's `accept` header
435+
1. otherwise, using `application/json` if the body is an object that is not a
436+
Buffer instance
437+
1. otherwise, using the value of `res.contentType` if present
438+
1. otherwise, using the value of the `Content-Type` response header if set
439+
440+
Note in the examples above we've not defined any formatters, so we've been
441+
leveraging the fact that restify ships with `application/json`, `text/plain` and
434442
`application/octet-stream` formatters. You can add additional formatters to
435443
restify by passing in a hash of content-type -> parser at server creation time:
436444

@@ -450,9 +458,14 @@ var server = restify.createServer({
450458
});
451459
```
452460

453-
If a content-type can't be negotiated, then restify will default to using the
454-
`application/octet-stream` formatter. For example, attempting to send a
455-
content-type that does not have a defined formatter:
461+
If a content-type can't be determined, then restify will respond with an error.
462+
463+
If a content-type can be negotiated, but that content-type doesn't have a
464+
corresponding formatter, restify will not format the response with any
465+
formatter.
466+
467+
For example, attempting to send a content-type that does not have a defined
468+
formatter:
456469

457470
```js
458471
server.get('/foo', function(req, res, next) {
@@ -462,12 +475,13 @@ server.get('/foo', function(req, res, next) {
462475
});
463476
```
464477

465-
Will result in a response with a content-type of `application/octet-stream`:
478+
Will result in a response with a content-type of `text/css` even though no
479+
`'text/css'` formatter is present:
466480

467481
```sh
468482
$ curl -i localhost:3000/
469483
HTTP/1.1 200 OK
470-
Content-Type: application/octet-stream
484+
Content-Type: text/css
471485
Content-Length: 2
472486
Date: Thu, 02 Jun 2016 06:50:54 GMT
473487
Connection: keep-alive

docs/index.md

+23-9
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,18 @@ server.get('/websocket/attach', function upgradeRoute(req, res, next) {
437437

438438
## Content Negotiation
439439

440-
If you're using `res.send()` restify will automatically select the content-type
441-
to respond with, by finding the first registered `formatter` defined. Note in
442-
the examples above we've not defined any formatters, so we've been leveraging
443-
the fact that restify ships with `application/json`, `text/plain` and
440+
If you're using `res.send()` restify will determine the content-type to respond
441+
with by:
442+
443+
1. negotiating the content-type by matching available formatters with the
444+
request's `accept` header
445+
1. otherwise, using `application/json` if the body is an object that is not a
446+
Buffer instance
447+
1. otherwise, using the value of `res.contentType` if present
448+
1. otherwise, using the value of the `Content-Type` response header if set
449+
450+
Note in the examples above we've not defined any formatters, so we've been
451+
leveraging the fact that restify ships with `application/json`, `text/plain` and
444452
`application/octet-stream` formatters. You can add additional formatters to
445453
restify by passing in a hash of content-type -> parser at server creation time:
446454

@@ -460,9 +468,14 @@ var server = restify.createServer({
460468
});
461469
```
462470

463-
If a content-type can't be negotiated, then restify will default to using the
464-
`application/octet-stream` formatter. For example, attempting to send a
465-
content-type that does not have a defined formatter:
471+
If a content-type can't be determined, then restify will respond with an error.
472+
473+
If a content-type can be negotiated, but that content-type doesn't have a
474+
corresponding formatter, restify will not format the response with any
475+
formatter.
476+
477+
For example, attempting to send a content-type that does not have a defined
478+
formatter:
466479

467480
```js
468481
server.get('/foo', function(req, res, next) {
@@ -472,12 +485,13 @@ server.get('/foo', function(req, res, next) {
472485
});
473486
```
474487

475-
Will result in a response with a content-type of `application/octet-stream`:
488+
Will result in a response with a content-type of `text/css` even though no
489+
`'text/css'` formatter is present:
476490

477491
```sh
478492
$ curl -i localhost:3000/
479493
HTTP/1.1 200 OK
480-
Content-Type: application/octet-stream
494+
Content-Type: text/css
481495
Content-Length: 2
482496
Date: Thu, 02 Jun 2016 06:50:54 GMT
483497
Connection: keep-alive

lib/response.js

+10-21
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,14 @@ function patch(Response) {
448448
// Derive type if not provided by the user
449449
type = type || self.req.accepts(self.acceptable);
450450

451-
// Check to see if we can find a valid formatter
451+
// Check to see if we could find a content type to use for the response.
452452
if (!type) {
453453
return formatterError(
454454
self,
455455
new errors.NotAcceptableError({
456-
message: 'could not find suitable formatter'
456+
message:
457+
'could not find suitable content-type to use ' +
458+
'for the response'
457459
})
458460
);
459461
}
@@ -464,33 +466,20 @@ function patch(Response) {
464466
type = mime.lookup(type);
465467
}
466468

467-
// If we were unable to derive a valid type, default to treating it as
468-
// arbitrary binary data per RFC 2046 Section 4.5.1
469-
if (!self.formatters[type] && self.acceptable.indexOf(type) === -1) {
470-
type = 'application/octet-stream';
471-
}
472-
473469
formatter = self.formatters[type] || self.formatters['*/*'];
474470

475-
// If after the above attempts we were still unable to derive a
476-
// formatter, provide a meaningful error message
477-
if (!formatter) {
478-
return formatterError(
479-
self,
480-
new errors.InternalServerError({
481-
message:
482-
'could not find formatter for application/octet-stream'
483-
})
484-
);
485-
}
486-
487471
if (self._charSet) {
488472
type = type + '; charset=' + self._charSet;
489473
}
490474

491-
// Update header to the derived content type for our formatter
475+
// Update Content-Type header to the one originally set or to the type
476+
// inferred from the most relevant formatter found.
492477
self.setHeader('Content-Type', type);
493478

479+
if (!formatter) {
480+
return flush(self, body);
481+
}
482+
494483
// Finally, invoke the formatter and flush the request with it's results
495484
return flush(self, formatter(self.req, self, body));
496485
};

test/formatter.test.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ test(
189189
);
190190

191191
test(
192-
'GH-937 should return 500 when no default formatter found ' +
193-
'and octet-stream is not available',
192+
'GH-937 should return 200 even when no formatter matching client found ' +
193+
'but content-type set manually',
194194
function(t) {
195195
// ensure client accepts only a type not specified by server
196196
var opts = {
@@ -201,10 +201,11 @@ test(
201201
};
202202

203203
CLIENT.get(opts, function(err, req, res, data) {
204-
t.ok(err);
204+
t.ifError(err);
205205
t.ok(req);
206206
t.ok(res);
207-
t.equal(res.statusCode, 500);
207+
t.equal(res.statusCode, 200);
208+
t.equal(res.contentType(), 'text/html');
208209
t.end();
209210
});
210211
}

test/response.test.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,9 @@ test('writeHead should emit a header event', function(t) {
497497
});
498498
});
499499

500-
test('should fail to set header due to missing formatter', function(t) {
501-
// when a formatter is not set up for a specific content-type, restify will
502-
// default to octet-stream.
503-
500+
test('should send 200 when formatter missing', function(t) {
501+
// res.send still sends a response even when a formatter is not set up for a
502+
// specific content-type.
504503
SERVER.get('/11', function handle(req, res, next) {
505504
res.header('content-type', 'application/hal+json');
506505
res.send(200, JSON.stringify({ hello: 'world' }));
@@ -510,7 +509,7 @@ test('should fail to set header due to missing formatter', function(t) {
510509
CLIENT.get(join(LOCALHOST, '/11'), function(err, _, res) {
511510
t.ifError(err);
512511
t.equal(res.statusCode, 200);
513-
t.equal(res.headers['content-type'], 'application/octet-stream');
512+
t.equal(res.headers['content-type'], 'application/hal+json');
514513
t.end();
515514
});
516515
});

0 commit comments

Comments
 (0)