-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
http: edit _storeHeader to check for Trailer header #12990
Conversation
lib/_http_server.js
Outdated
// code is always terminated by the first empty line after the | ||
// header fields, regardless of the header fields present in the | ||
// message, and thus cannot contain a message body. | ||
if (this.statusCode === 304 && headers && headers.hasOwnProperty('Trailer')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid hasOwnProperty()
for performance reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be a better choice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just try accessing the property and check if it is set to anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, will make the changes shortly.
lib/_http_server.js
Outdated
// (Informational), 204 (No Content), or 304 (Not Modified) status | ||
// code is always terminated by the first empty line after the | ||
// header fields, regardless of the header fields present in the | ||
// message, and thus cannot contain a message body. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment doesn't really describe the code below it well. The code doesn't even check for HEAD requests or 1xx/204 responses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will revise, thanks for the review.
@mscdex I made edits to the comment, it is more specific to the actual code. |
lib/_http_server.js
Outdated
// terminated by the first empty line after the header fields, | ||
// regardless of the header fields present in the message, and | ||
// thus cannot contain a message body or 'trailers' octets. | ||
if (statusCode === 304 && headers && headers['Trailer'] !== undefined) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, now that I think more about it, just checking for 'Trailer'
won't be enough because the letters could be in any case. I wonder if a better solution would be to just check for statusCode === 304 && state.trailer
inside _storeHeader()
in _http_outgoing.js? Perhaps it could be merged with the existing 204/304 check that's already being done in there.
I also don't know if we should throw or just discard the trailer header...
Thoughts @nodejs/http ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good insights. #2842 had a mention about throwing. If it is preferred to discard I'll make the change.
@mscdex All done. I updated the location of the check, thank you for pointing out _storeHeader. |
|
||
const server = http.createServer(common.mustCall(function(req, res) { | ||
assert.throws(() => res.writeHead(304, { 'Trailer': 'baz' }), | ||
/^Error: Trailer header is invalid with current status code?/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the ?
be a $
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll correct it. Thank you
server.listen(0, () => { | ||
http.get({ | ||
port: server.address().port | ||
}, common.mustCall((res) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common.mustCall()
is not needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will update it. Thanks
@cjihrig All done, I corrected the errors. Thanks for the review. |
@cjihrig Hi, if you have time, could you check if I need to make any further changes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I'd like feedback from @nodejs/http though.
Thank you. |
I've read RFC 7230 a bit more on this and my understanding/interpretation is that a Trailer header should be fine for 304 as it could indicate that a trailer would have been sent if the request was an unconditional GET (the RFC gives a similar example/explanation for a Transfer-Encoding header sent in a 304). Node already doesn't send the actual trailer for such status codes. |
That's my understanding as well @mscdex but the spec is rather vague on the topic. |
I was actually interested in closing the issue. I read over the RFC as well and I am fine with whatever you guys decide. The Bluemix story may be a concern? Thoughts. |
I think the original linked issue seems to be suggesting that any |
Ahh, the linked issue (#2842), got it! |
@mscdex @jasnell Just to be clear, this case (304 only) is considered |
@refack I think I'd rather just have one change for this particular issue and currently, discarding the trailer on a non-chunked response seems like the best option to me (and that would be semver-major). |
So I'm blocking this. |
@arturgvieira go for the gold... i.e. discard the trailer for all |
Sorry, I meant the |
I'm checking for lint errors. |
All lint errors have been corrected. |
@arturgvieira we don't have consensus on this fix. @mscdex and I think that you should solve the general case #12990 (comment).
|
I don't think |
I made the requested corrections. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good fix 🥇
const server = http.createServer(common.mustCall(function(req, res) { | ||
res.setHeader('Trailer', 'baz'); | ||
assert.throws(() => res.writeHead(200, {'Content-Length': '24'}), | ||
common.expectsError({ code: 'ERR_HTTP_TRAILER_INVALID' })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Could you add message: 'Trailers are invalid with this transfer encoding', type: Error
All done, I added the additional information for the error as requested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
/cc @nodejs/ctc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
lib/_http_outgoing.js
Outdated
// header fields, regardless of the header fields present in the | ||
// message, and thus cannot contain a message body or 'trailers'. | ||
if (this.chunkedEncoding !== true && state.trailer) { | ||
const trailerInvalidError = new errors.Error('ERR_HTTP_TRAILER_INVALID'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified to just throw new errors.Error(...)
?
const server = http.createServer(common.mustCall(function(req, res) { | ||
res.setHeader('Trailer', 'baz'); | ||
assert.throws(() => res.writeHead(200, {'Content-Length': '24'}), | ||
common.expectsError({ code: 'ERR_HTTP_TRAILER_INVALID', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps move the object literal into a separate variable and pass that to common.expectsError()
to make this more readable?:
const trailerInvalidErr = {
code: 'ERR_HTTP_TRAILER_INVALID',
message: 'Trailers are invalid with this transfer encoding',
type: Error
};
assert.throws(() => res.writeHead(200, {'Content-Length': '24'}),
common.expectsError(trailerInvalidErr));
'use strict'; | ||
const common = require('../common'); | ||
|
||
// This test ensures that when a status code of 304 is set a Trailer header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description does not agree with the code below.
All done, made the changes requested |
})); | ||
server.listen(0, common.mustCall(() => { | ||
http.get({ port: server.address().port }, common.mustCall((res) => { | ||
server.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: you might add a little extra code here to verify that the response is what you expect (both statusCode
and body content). In doing so, you'd need to make sure to adjust the Content-Length
value to 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, will do.
Made the requested changes. Thanks @mscdex |
})); | ||
server.listen(0, common.mustCall(() => { | ||
http.get({ port: server.address().port }, common.mustCall((res) => { | ||
assert.deepStrictEqual(res.statusCode, 200); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should just be .strictEqual()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
server.listen(0, common.mustCall(() => { | ||
http.get({ port: server.address().port }, common.mustCall((res) => { | ||
assert.deepStrictEqual(res.statusCode, 200); | ||
res.on('data', common.mustCall((content) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically it would be better to just buffer data in here (without common.mustCall()
) and then assert.strictEqual(buffer, 'ok')
in a common.mustCall()
-wrapped 'end'
event handler:
var buf = '';
res.on('data', (chunk) => {
buf += chunk;
}).on('end', common.mustCall(() => {
assert.strictEqual(buf, 'ok');
}));
The reason being in general you shouldn't assume anything about the number of 'data'
events for a stream.
@mscdex Take a look, I think this is what you meant. |
assert.strictEqual(res.statusCode, 200); | ||
let buffer; | ||
res.on('data', (content) => { | ||
buffer ? buffer += content : buffer = content; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this is less readable, I think it's best to just always append to a variable that is initially an empty string. Also you will be able to drop the explicit buffer.toString()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I just saw your edit. Changing now.
Test non-chunked message does not have trailer header set, message will be terminated by the first empty line after the header fields, regardless of the header fields present in the message, and thus cannot contain a message body or 'trailers'. Ref: #2842
@mscdex Thank you for the review. |
LGTM |
@jasnell PTAL (even though we have 3 CTC LGTMs) |
Test non-chunked message does not have trailer header set, message will be terminated by the first empty line after the header fields, regardless of the header fields present in the message, and thus cannot contain a message body or 'trailers'. PR-URL: #12990 Ref: #2842 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Yuta Hiroto <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Brian White <[email protected]>
Landed in 80c9ef0 |
Test non-chunked message does not have trailer header set,
message will be terminated by the first empty line after the
header fields, regardless of the header fields present in the
message, and thus cannot contain a message body or 'trailers'.
Ref: #2842
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
http