Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,15 @@ The `status` method allows you to set the status code that is returned to API Ga

```javascript
api.get('/users', (req, res) => {
res.status(304).send('Not Modified');
res.status(404).send('Not Found');
});
```

**NOTE:** Status codes that [must not carry content](https://datatracker.ietf.org/doc/html/rfc9110#name-overview-of-status-codes) per RFC 9110 — `1xx`, `204`, `205`, and `304` — are always sent with an empty body. Any body passed to `send()`, `json()`, `html()`, etc. is discarded for these status codes.

```javascript
api.get('/users', (req, res) => {
res.status(204).json({ foo: 'bar' }); // sent as 204 with an empty body
});
```

Expand All @@ -489,7 +497,7 @@ res.sendStatus(200); // equivalent to res.status(200).send('OK')
res.sendStatus(403); // equivalent to res.status(403).send('Forbidden')
```

Status codes that [must not carry content](https://datatracker.ietf.org/doc/html/rfc9110#name-overview-of-status-codes) per RFC 9110 — `1xx`, `204`, `205`, and `304` are sent with an empty body instead:
As with [`status()`](#statuscode), the `1xx`, `204`, `205`, and `304` status codes are sent with an empty body:

```javascript
res.sendStatus(204); // equivalent to res.status(204).send('')
Expand Down
40 changes: 40 additions & 0 deletions __tests__/responses.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ api.get('/testSendStatus403', function(req,res) {
res.sendStatus(403)
})

api.get('/testNullBodyJson', function(req,res) {
res.status(204).json({ foo: 'bar' })
})

api.get('/testNullBodySend', function(req,res) {
res.status(205).send('Reset Content')
})

api.get('/testNullBodyHtml', function(req,res) {
res.status(304).html('<div>Not Modified</div>')
})

api.get('/testNonNullBodyJson', function(req,res) {
res.status(200).json({ foo: 'bar' })
})

// Secondary route
api2.get('/testJSONPResponse', function(req,res) {
res.jsonp({ foo: 'bar' })
Expand Down Expand Up @@ -196,6 +212,30 @@ describe('Response Tests:', function() {
expect(result).toEqual({ multiValueHeaders: { 'content-type': ['application/json'] }, statusCode: 403, body: 'Forbidden', isBase64Encoded: false })
}) // end it

it('Null body status: json()', async function() {
let _event = Object.assign({},event,{ path: '/testNullBodyJson'})
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
expect(result).toEqual({ multiValueHeaders: { 'content-type': ['application/json'] }, statusCode: 204, body: '', isBase64Encoded: false })
}) // end it

it('Null body status: send()', async function() {
let _event = Object.assign({},event,{ path: '/testNullBodySend'})
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
expect(result).toEqual({ multiValueHeaders: { 'content-type': ['application/json'] }, statusCode: 205, body: '', isBase64Encoded: false })
}) // end it

it('Null body status: html()', async function() {
let _event = Object.assign({},event,{ path: '/testNullBodyHtml'})
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
expect(result).toEqual({ multiValueHeaders: { 'content-type': ['text/html'] }, statusCode: 304, body: '', isBase64Encoded: false })
}) // end it

it('Non-null body status is unaffected', async function() {
let _event = Object.assign({},event,{ path: '/testNonNullBodyJson'})
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
expect(result).toEqual({ multiValueHeaders: { 'content-type': ['application/json'] }, statusCode: 200, body: '{"foo":"bar"}', isBase64Encoded: false })
}) // end it

it('JSONP response (default callback)', async function() {
let _event = Object.assign({},event,{ path: '/testJSONPResponse' })
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
Expand Down
41 changes: 23 additions & 18 deletions __tests__/utils.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,32 @@ describe("Utility Function Tests:", function () {
}); // end it
}); // end encodeBody tests

describe("statusBodyLookup:", function () {
describe("isNullBodyStatus:", function () {
test.each([
[100, ""],
[101, ""],
[200, "OK"],
["200", "OK"],
[204, ""],
["204", ""],
[205, ""],
["205", ""],
[304, ""],
["304", ""],
[404, "Not Found"],
[502, "Bad Gateway"],
[999, "Unknown"],
["not a number", "Unknown"]
[100, true],
[101, true],
[199, true],
[204, true],
[205, true],
[304, true],
["204", true],
["304", true],
[200, false],
[201, false],
[203, false],
[206, false],
[303, false],
[305, false],
[404, false],
[502, false],
["200", false],
["foo", false],
[undefined, false],
[null, false],
])("%s", (status, expected) => {
expect(utils.statusBodyLookup(status)).toBe(expected);
expect(utils.isNullBodyStatus(status)).toBe(expected);
}); // end it
}); // end statusBodyLookup tests

}); // end isNullBodyStatus tests

describe("extractRoutes:", function () {
it("Sample routes", function () {
Expand Down
7 changes: 6 additions & 1 deletion src/lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ class RESPONSE {

// Convenience method for sending status codes
sendStatus(status) {
this.status(status).send(UTILS.statusBodyLookup(status));
this.status(status).send(UTILS.statusLookup(status));
}

// Convenience method for setting CORS headers
Expand Down Expand Up @@ -525,6 +525,11 @@ class RESPONSE {
body = '';
}

// Discard the body for status codes that must not include content
if (UTILS.isNullBodyStatus(this._statusCode)) {
body = '';
}

let headers = {};
let cookies = {};

Expand Down
15 changes: 6 additions & 9 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,13 @@ export const statusLookup = (status) => {
return status in statusCodes ? statusCodes[status] : 'Unknown';
};

export const statusBodyLookup = (status) => {
// Status codes that must not include content per RFC 9110
// https://datatracker.ietf.org/doc/html/rfc9110#name-overview-of-status-codes
export const isNullBodyStatus = (status) => {
const code = typeof status === 'string' ? Number(status) : status;

// The following status codes must not have a response body
// according to rfc 9110
if ((100 <= code && code < 200) || [204, 205, 304].includes(code)) {
return '';
}

return statusLookup(code);
return (
(code >= 100 && code < 200) || code === 204 || code === 205 || code === 304
);
Comment thread
naorpeled marked this conversation as resolved.
};

// Parses routes into readable array
Expand Down
Loading