Skip to content
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

Closed
wants to merge 1 commit into from
Closed

http: edit _storeHeader to check for Trailer header #12990

wants to merge 1 commit into from

Conversation

arturgvieira-zz
Copy link

@arturgvieira-zz arturgvieira-zz commented May 12, 2017

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), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

http

@nodejs-github-bot nodejs-github-bot added the http Issues or PRs related to the http subsystem. label May 12, 2017
@arturgvieira-zz arturgvieira-zz changed the title Server header branch http: edit writeHead to check for 304 and Trailer May 12, 2017
// 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')) {
Copy link
Contributor

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.

Copy link
Author

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?

Copy link
Contributor

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.

Copy link
Author

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.

// (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.
Copy link
Contributor

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.

Copy link
Author

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.

@arturgvieira-zz
Copy link
Author

@mscdex I made edits to the comment, it is more specific to the actual code.

// 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)
Copy link
Contributor

@mscdex mscdex May 12, 2017

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 ?

Copy link
Author

@arturgvieira-zz arturgvieira-zz May 12, 2017

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.

@arturgvieira-zz
Copy link
Author

@mscdex All done. I updated the location of the check, thank you for pointing out _storeHeader.

@arturgvieira-zz arturgvieira-zz changed the title http: edit writeHead to check for 304 and Trailer http: edit _storeHeader to check for 304 and Trailer May 12, 2017

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?/);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the ? be a $?

Copy link
Author

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) => {
Copy link
Contributor

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.

Copy link
Author

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

@arturgvieira-zz
Copy link
Author

arturgvieira-zz commented May 12, 2017

@cjihrig All done, I corrected the errors. Thanks for the review.

@arturgvieira-zz
Copy link
Author

arturgvieira-zz commented May 17, 2017

@cjihrig Hi, if you have time, could you check if I need to make any further changes?

Copy link
Contributor

@cjihrig cjihrig left a 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.

@arturgvieira-zz
Copy link
Author

Thank you.

@mscdex
Copy link
Contributor

mscdex commented May 17, 2017

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.

@jasnell
Copy link
Member

jasnell commented May 17, 2017

That's my understanding as well @mscdex but the spec is rather vague on the topic.

@arturgvieira-zz
Copy link
Author

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.

@mscdex
Copy link
Contributor

mscdex commented May 17, 2017

I think the original linked issue seems to be suggesting that any Trailer header should be removed when Transfer-Encoding: chunked is not set since Trailer is a component of a chunk encoded response, regardless of status code. I guess that makes sense to me, but it'd definitely be a semver-major change IMHO.

@gibfahn
Copy link
Member

gibfahn commented May 20, 2017

The Bluemix story may be a concern? Thoughts.

@arturgvieira could you elaborate? Not sure what the Bluemix story is.

Ahh, the linked issue (#2842), got it!

@refack
Copy link
Contributor

refack commented May 25, 2017

@refack refack self-assigned this May 25, 2017
@refack
Copy link
Contributor

refack commented May 25, 2017

I think the original linked issue seems to be suggesting that any Trailer header should be removed when Transfer-Encoding: chunked is not set since Trailer is a component of a chunk encoded response, regardless of status code. I guess that makes sense to me, but it'd definitely be a semver-major change IMHO.

@mscdex @jasnell Just to be clear, this case (304 only) is considered semver-patch?

@mscdex
Copy link
Contributor

mscdex commented May 25, 2017

@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).

@refack refack added the blocked PRs that are blocked by other issues or PRs. label May 25, 2017
@refack
Copy link
Contributor

refack commented May 25, 2017

@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.

@refack
Copy link
Contributor

refack commented May 25, 2017

@arturgvieira go for the gold... i.e. discard the trailer for all non-chunked responses.
P.S. since headers can be added in any order I'm not sure you can do the trick you did here...

@mscdex
Copy link
Contributor

mscdex commented May 25, 2017

Sorry, I meant the Trailer header, actual trailer content should already be discarded for non-chunked responses.

@arturgvieira-zz
Copy link
Author

I'm checking for lint errors.

@arturgvieira-zz
Copy link
Author

All lint errors have been corrected.

@refack
Copy link
Contributor

refack commented May 25, 2017

@arturgvieira we don't have consensus on this fix. @mscdex and I think that you should solve the general case #12990 (comment).
That is:

  1. move the test to around L434(https://github.com/nodejs/node/pull/12990/files#diff-286202fdbdd74ede6f5f5334b6176b5cR434)
  2. Check for this.chunkedEncoding !== true && state.trailer
  3. Evaluate if response.addTrailers need modifications
  4. Document in https://github.com/nodejs/node/blob/master/doc/api/http.md#responsesetheadername-value and response.addTrailers.
  5. Expand the test.

@mscdex
Copy link
Contributor

mscdex commented May 26, 2017

I don't think addTrailers() needs any changes, since that content is already being ignored properly. All we should care about is skipping the Trailer header when chunked encoding is not being used. I don't think there is going to be a straight-forward change because a default chunked encoding header could be appended after the header is already generated in _storeHeader().

@arturgvieira-zz
Copy link
Author

I made the requested corrections.

Copy link
Contributor

@refack refack left a 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' }));
Copy link
Contributor

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

@arturgvieira-zz
Copy link
Author

All done, I added the additional information for the error as requested.

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@refack
Copy link
Contributor

refack commented Jun 11, 2017

@refack
Copy link
Contributor

refack commented Jun 12, 2017

/cc @nodejs/ctc

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@refack refack removed the wip Issues and PRs that are still a work in progress. label Jun 12, 2017
// 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');
Copy link
Contributor

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',
Copy link
Contributor

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
Copy link
Contributor

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.

@arturgvieira-zz
Copy link
Author

All done, made the changes requested

}));
server.listen(0, common.mustCall(() => {
http.get({ port: server.address().port }, common.mustCall((res) => {
server.close();
Copy link
Contributor

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.

Copy link
Author

@arturgvieira-zz arturgvieira-zz Jun 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do.

@arturgvieira-zz
Copy link
Author

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);
Copy link
Contributor

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().

Copy link
Author

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) => {
Copy link
Contributor

@mscdex mscdex Jun 13, 2017

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.

@arturgvieira-zz
Copy link
Author

@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;
Copy link
Contributor

@mscdex mscdex Jun 13, 2017

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().

Copy link
Author

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
@arturgvieira-zz
Copy link
Author

@mscdex Thank you for the review.

@mscdex
Copy link
Contributor

mscdex commented Jun 13, 2017

@mscdex
Copy link
Contributor

mscdex commented Jun 13, 2017

LGTM

@refack
Copy link
Contributor

refack commented Jun 13, 2017

@jasnell PTAL (even though we have 3 CTC LGTMs)

jasnell pushed a commit that referenced this pull request Jun 13, 2017
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]>
@jasnell
Copy link
Member

jasnell commented Jun 13, 2017

Landed in 80c9ef0

@jasnell jasnell closed this Jun 13, 2017
@refack refack removed their assignment Oct 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http Issues or PRs related to the http subsystem. semver-major PRs that contain breaking changes and should be released in the next major version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants