-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
Conversation
8ddeae5
to
ffa7ef8
Compare
Rewrote the original test case and also added a test case for the default expected behaviour without a user-bound |
Thank you for this! I'll take a look in detail tomorrow. |
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.
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.
lib/internal/http2/compat.js
Outdated
// TODO mcollina check what is the continue flow | ||
throw new Error('not implemented yet'); | ||
const stream = this[kStream]; | ||
if (stream === undefined) return; |
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 don't think we should return false
here, see https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L252-L253.
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.
Sorry, did you mean we should return false
to match the old behaviour? Wanted to confirm before I go doing the wrong thing, haha.
lib/internal/http2/compat.js
Outdated
if (stream === undefined) return; | ||
this[kStream].additionalHeaders({ | ||
[constants.HTTP2_HEADER_STATUS]: constants.HTTP_STATUS_CONTINUE | ||
}); |
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 needs to have a return type, ideally true
in this case.
const testResBody = 'other stuff!\n'; | ||
const testResHeaderKey = 'abcd'; | ||
const testResHeaderVal = '1'; | ||
|
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.
can you add a quick description of the test in comments here?
|
||
res.writeContinue(); | ||
|
||
setTimeout(common.mustCall(() => handler(req, res)), 100); |
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.
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?
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.
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] |
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 don't think we should have these set dynamically.
const http2 = require('http2'); | ||
|
||
const testResBody = 'other stuff!\n'; | ||
|
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.
can you add a description of the test?
console.error('Client sent request'); | ||
|
||
req.on('continue', common.mustCall(() => { | ||
console.error('Client received 100-continue'); |
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.
we need to verify that this event happens before the response
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.
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; })); |
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.
you will need setEncoding('utf8')
to concatenate the body like this. chunk
should be a Buffer
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.
Great point, totally missed it! Thanks.
@@ -529,9 +529,13 @@ class Http2ServerResponse extends Stream { | |||
this.emit('finish'); | |||
} | |||
|
|||
// TODO doesn't support callbacks |
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.
IMHO callbacks are not needed here, they are not there in the HTTP1 API.
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.
So the reason I added this is because the h1 API actually does support a callback, even though it's not documented.
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; | |
}; |
Line 247 in 689a643
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.
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.
@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.
Thanks @mcollina! Will update today. I'll reply to some points individually above. |
f9835ec
to
cb97536
Compare
Ok, I was able to tackle everything but the callback situation with |
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.
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); |
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.
If we are keeping the timeout, we should multiply 100
with common.platformTimeout
, otherwise it might fail on slower platforms (ARM).
Yep, the docs for For the callback, I'll keep the comment in there as a reminder, unless there are any objections. Thanks for all the feedback! |
Re: the docs, do we want to document Also, how do you handle the 'version added' note? Should I even worry about that? Thanks in advance. |
@apapirovski also add For the "version added" it should be:
|
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. |
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.
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
.
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.
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.
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 prefer the first one.
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.
Agreed. The first one
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.
Agreed. Wanted to make sure it wasn't weird starting with the 'if'. 👍 Updated and committed.
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
@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! |
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.
8c49c5a
to
bbb9f71
Compare
CI failure looks unrelated. @jasnell can we land this? |
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>
Landed in ad3d2ce |
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>
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>
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>
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>
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), orvcbuild test
(Windows) passesAffected core subsystem(s)
http2, test