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

http2: handle 100-continue flow & writeContinue #15039

Closed

Conversation

apapirovski
Copy link
Member

@apapirovski apapirovski commented Aug 26, 2017

This started out as work on tests and progressed into tackling 100-continue in http2. Not sure if it's even on the right track... still finding my way around the codebase. Hopefully not stepping on any toes here.

This adds an implementation for writeContinue based on the h2 spec & the existing http implementation. ClientHttp2Stream now emits a continue event when it receives headers with :status 100.

  • The current writeContinue implementation doesn't allow a callback and I'm not really sure how to tackle it given the notes in core about additionalHeaders & nghttp2.

  • Not sure if handleHeaderContinue is in the right spot or if I need to have any other safety checks in there. It seemed like onSessionHeaders already handles most of the heavy work in that regard.

  • The test case is adapted from the one for http and should be thorough. That's the one bit that should be good to go.

Thanks in advance!

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)

http2, test

@nodejs-github-bot nodejs-github-bot added the http2 Issues or PRs related to the http2 subsystem. label Aug 26, 2017
@hiroppy hiroppy added the wip Issues and PRs that are still a work in progress. label Aug 26, 2017
@apapirovski
Copy link
Member Author

Rewrote the original test case and also added a test case for the default expected behaviour without a user-bound checkContinue listener. Feedback appreciated.

@jasnell jasnell requested a review from mcollina August 29, 2017 04:28
@jasnell jasnell self-assigned this Aug 29, 2017
@jasnell
Copy link
Member

jasnell commented Aug 29, 2017

Thank you for this! I'll take a look in detail tomorrow.

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.

Thanks for contributing! You are definitely on the right track! I've left some notes to be addressed, let us know if you have any questions.

// TODO mcollina check what is the continue flow
throw new Error('not implemented yet');
const stream = this[kStream];
if (stream === undefined) return;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, did you mean we should return false to match the old behaviour? Wanted to confirm before I go doing the wrong thing, haha.

if (stream === undefined) return;
this[kStream].additionalHeaders({
[constants.HTTP2_HEADER_STATUS]: constants.HTTP_STATUS_CONTINUE
});
Copy link
Member

Choose a reason for hiding this comment

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

this needs to have a return type, ideally true  in this case.

const testResBody = 'other stuff!\n';
const testResHeaderKey = 'abcd';
const testResHeaderVal = '1';

Copy link
Member

Choose a reason for hiding this comment

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

can you add a quick description of the test in comments here?


res.writeContinue();

setTimeout(common.mustCall(() => handler(req, res)), 100);
Copy link
Member

Choose a reason for hiding this comment

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

why the setTimeout here? If it's needed to defer for the I/O cycle, can you add a comment about it?

@jasnell do we have a better way to do this? How could we know it has finished writing it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will add a comment. Not sure there's a better way to allow 'continue' to fire on the client before we continue writing.


res.writeHead(200, {
'content-type': 'text/plain',
[testResHeaderKey]: [testResHeaderVal]
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should have these set dynamically.

const http2 = require('http2');

const testResBody = 'other stuff!\n';

Copy link
Member

Choose a reason for hiding this comment

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

can you add a description of the test?

console.error('Client sent request');

req.on('continue', common.mustCall(() => {
console.error('Client received 100-continue');
Copy link
Member

Choose a reason for hiding this comment

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

we need to verify that this event happens before the response

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, yeah. Good point. Lost that when I refactored these tests. Will fix.

assert.strictEqual(headers[':status'], 200);
}));

req.on('data', common.mustCall((chunk) => { body += chunk; }));
Copy link
Member

Choose a reason for hiding this comment

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

you will need setEncoding('utf8') to concatenate the body like this. chunk should be a Buffer here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Great point, totally missed it! Thanks.

@@ -529,9 +529,13 @@ class Http2ServerResponse extends Stream {
this.emit('finish');
}

// TODO doesn't support callbacks
Copy link
Member

Choose a reason for hiding this comment

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

IMHO callbacks are not needed here, they are not there in the HTTP1 API.

Copy link
Member Author

Choose a reason for hiding this comment

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

So the reason I added this is because the h1 API actually does support a callback, even though it's not documented.

node/lib/_http_server.js

Lines 174 to 177 in 689a643

ServerResponse.prototype.writeContinue = function writeContinue(cb) {
this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii', cb);
this._sent100 = true;
};

function _writeRaw(data, encoding, callback) {

That said, I don't really know the best way to handle this callback with h2 because the write happens on the next tick and there are no events or anything to notify me.

Copy link
Member

Choose a reason for hiding this comment

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

@jasnell do you see a way to add the callback to the C++ layer? For parity, we would need to add a callback to https://github.com/apapirovski/node/blob/cb9753692d4485249cee00e6c675a00e089f1f2e/lib/internal/http2/core.js#L2093.

@apapirovski
Copy link
Member Author

Thanks @mcollina! Will update today. I'll reply to some points individually above.

@apapirovski
Copy link
Member Author

Ok, I was able to tackle everything but the callback situation with writeContinue. See my comment above. #15039 (comment)

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.

Would you mind checking if the docs needs updating as well? I think so.

The code is ok for me, minus the callback thing (which we might want to do in another PR).

res.writeContinue();

// timeout so that we allow the client to receive continue first
setTimeout(common.mustCall(() => handler(req, res)), 100);
Copy link
Member

@mcollina mcollina Aug 29, 2017

Choose a reason for hiding this comment

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

If we are keeping the timeout, we should multiply 100 with common.platformTimeout, otherwise it might fail on slower platforms (ARM).

@apapirovski
Copy link
Member Author

apapirovski commented Aug 29, 2017

Yep, the docs for writeContinue do need updating. I was leaving it for last. Will update today, including any other feedback (like the timeout).

For the callback, I'll keep the comment in there as a reminder, unless there are any objections.

Thanks for all the feedback!

@apapirovski
Copy link
Member Author

apapirovski commented Aug 29, 2017

Re: the docs, do we want to document checkContinue as well? If so, what's the usual logic for sorting events under Http2Server and Http2SecureServer? It's not really alphabetical or anything else right now.

Also, how do you handle the 'version added' note? Should I even worry about that? Thanks in advance.

@apapirovski apapirovski changed the title WIP: http2: handle 100-continue flow & writeContinue http2: handle 100-continue flow & writeContinue Aug 29, 2017
@mcollina
Copy link
Member

@apapirovski also add checkContinue were it fits more naturally. If you want to improve the docs, e.g. make that in alphabetical order, do it in another PR.

For the "version added" it should be:

<!-- YAML
added: REPLACEME
-->

@apapirovski
Copy link
Member Author

apapirovski commented Aug 29, 2017

Btw the documentation has now been added (and the timeout issue addressed). Let me know if there's anything I should update.

I'll squash into one commit with proper commit message once everything has been reviewed, otherwise it's a pain to track the changes.

request body.

Note that when this event is emitted and handled, the [`'request'`][] event will
not be emitted.
Copy link
Member

Choose a reason for hiding this comment

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

We need to specify that this event will be emitted only if there is a 'request' event handler, or a function is passed through  createServer / createSecureServer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Any thoughts on which of these two options is more legible? Thanks!

If a 'request' listener is registered or 'http2.createServer()' is supplied a callback function, the 'checkContinue' event is emitted each time a request with an HTTP Expect: 100-continue is received. If this event is not listened for, the server will automatically respond with a status 100 Continue as appropriate.

or

Emitted each time a request with an HTTP Expect: 100-continue is received. If this event is not listened for, the server will automatically respond with a status 100 Continue as appropriate. Note that this event will be not be emitted if no 'request' listener is registered and 'http2.createServer()' was not supplied a callback function.

Copy link
Member

Choose a reason for hiding this comment

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

I prefer the first one.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. The first one

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. Wanted to make sure it wasn't weird starting with the 'if'. 👍 Updated and committed.

@mcollina
Copy link
Member

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

@apapirovski
Copy link
Member Author

apapirovski commented Aug 30, 2017

@mcollina would you like me to squash or do you want to just have whoever potentially merges do that? Not sure how you handle things usually. The first commit on this has the proper message format FWIW.

Thanks!

@jasnell
Copy link
Member

jasnell commented Aug 30, 2017

Either way works but let's let the ci run complete before squashing. Doing it mid run can cause hiccups

Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.
@apapirovski
Copy link
Member Author

Squashed commit added. Let me know if there's anything else I can do! Thanks for the review @mcollina & @jasnell.

@benjamingr
Copy link
Member

benjamingr commented Aug 30, 2017

CI failure looks unrelated. @jasnell can we land this?

jasnell pushed a commit that referenced this pull request Aug 30, 2017
Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.

PR-URL: #15039
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@jasnell
Copy link
Member

jasnell commented Aug 30, 2017

Landed in ad3d2ce

@jasnell jasnell closed this Aug 30, 2017
cjihrig pushed a commit to cjihrig/node that referenced this pull request Aug 31, 2017
Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.

PR-URL: nodejs#15039
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@apapirovski apapirovski deleted the patch-100-continue-flow branch September 4, 2017 14:35
addaleax pushed a commit to addaleax/ayo that referenced this pull request Sep 5, 2017
Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.

PR-URL: nodejs/node#15039
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit that referenced this pull request Sep 10, 2017
Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.

PR-URL: #15039
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@MylesBorins MylesBorins mentioned this pull request Sep 10, 2017
MylesBorins pushed a commit that referenced this pull request Sep 12, 2017
Adds an implementation for writeContinue based on the h2 spec & the
existing http implementation. ClientHttp2Stream now also emits
a continue event when it receives headers with :status 100.
Includes two test cases for default server continue behaviour and
for the exposed checkContinue listener.

PR-URL: #15039
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http2 Issues or PRs related to the http2 subsystem. wip Issues and PRs that are still a work in progress.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants