-
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: near full http1 compatibility, add tests #15702
Conversation
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 is a lot to digest, I'll do my best to review asap, but it might happen in a while. Can you also update the docs accordingly? I think we should document what you are doing with the Proxy.
👍 on the Express compatibility.
26de916
to
892a2a5
Compare
Thanks @mcollina! Pushed a tiny change that got rid of the unnecessary Will work on docs and add a commit either later today or sometime the coming week. |
892a2a5
to
b2fbd3e
Compare
|
||
get code() { | ||
return this[kState].closedCode; | ||
get complete() { |
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's this? I can't see this is a part of the official h1 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.
It's not documented but libraries use it.
@@ -387,9 +477,16 @@ class Http2ServerResponse extends Stream { | |||
if (typeof name !== 'string') | |||
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'name', 'string'); | |||
|
|||
if (this[kStream].headersSent) | |||
throw new errors.Error('ERR_HTTP2_HEADERS_SENT'); | |||
|
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 check that name
is valid but not value
?
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.
assertValidHeader(name, value);
takes care of value. We're going to properly validate name & value further downstream at some point (outside of compat), not yet implemented.
@@ -71,19 +84,24 @@ function statusMessageWarn() { | |||
} | |||
|
|||
function onStreamData(chunk) { | |||
if (!this[kRequest].push(chunk)) | |||
const request = this[kRequest]; | |||
if (request !== undefined && !request.push(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.
Should request === undefined
even be possible 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.
Technically? Yes. Likely? No.
b2fbd3e
to
aa6b0d0
Compare
lib/internal/http2/core.js
Outdated
@@ -823,6 +822,44 @@ class Http2Session extends EventEmitter { | |||
return settings; | |||
} | |||
|
|||
// Proxies for socket address-related getters so users don't | |||
// ever need to reach through to the socket | |||
get remoteAddress() { |
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.
These need to be added to the docs in /doc/api/http2.md
. Should be done in this PR but can be done in a separate one if necessary
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.
Code changes LGTM with green CI.
Would prefer docs to be updated in this PR tho.
Really great to see this!
@jasnell I've been thinking over the socket warning a bit more and I'm wondering if we really should have it or not... There are some legitimate reasons to access the socket, such as with the address-related stuff but also for TLS, for stuff like Realistically, it's not like we want to maintain the whole range of socket-related getters, setters and methods on the session. And it seems somewhat arbitrary as to what we actually expose on the session and what we don't. I'm personally almost leaning towards removing the 2nd commit ( |
Yeah, I know getting to this information is important, it's just a really bad idea to expose the socket easily with http2 connections. We've seen things like that come back and bite us in the past. Let me stew on this a bit more to see if I can think of a reasonable alternative... if there's not, then I'm good with dropping the warning for now. |
Sounds good. I think my preferred option is to have the warning just for certain methods as mentioned above (the ones that are actually destructive) but I realize that would only apply to compatibility mode and anyone using the proper http2 API wouldn't hear a peep. |
I'm 👍 on having the warning on some of the stream-related APIs. In fact, I would not make it a warning, but I'd rather throw. If you are calling one of those methods you definitely have a bug. |
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: nodejs#15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js
Ok, I've got an updated version ready to go just in case. It throws
if the user tries to It also removes the socket warning and the socket address related getters. Will commit or adjust further once there's consensus. Working on the docs at the moment so should have those ready sometime tomorrow. |
aa6b0d0
to
eaec69a
Compare
eaec69a
to
9eff92c
Compare
Ok, I've force pushed a commit with updates as per above and a new commit with the doc changes (plus some formatting fixes as we had the wrong heading levels for a lot of the compatibility stuff). |
Still LGTM with green CI :-) |
Landed as 2da7d9b. |
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: #15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js PR-URL: #15702 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: #15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js PR-URL: #15702 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: #15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js PR-URL: #15702 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: nodejs/node#15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js PR-URL: nodejs/node#15702 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
As a preface, this is a lot of changes but I had no way to separate them out into small chunks due to the order in which they happened. I started with working on support for HEAD requests and other minor http2 tweaks but as I got the express & on-finished test suites to work, I had to change a ton of that code and as a result it's hard for me to separate it out since some of these changes have broader effect on the compatibility layer.
With this PR we pass 100% of tests in
express
&on-finished
that are applicable tohttp2
. (There are tests in on-finished that don't apply re: handling upgrade and there are 4 tests in express that use a 3rd party module that we can't do anything about because it uses http1 prototype directly.)Full or near full list of changes follows:
complete
prop forHttp2ServerRequest
method
setter since some user-land modules use thissocket
andconnection
onHttp2ServerResponse
Http2Stream
for all the most commonly used aspects of the socket in http1writeHead
,write
,end
,addHeader
andremoveHeader
. A lot of user-land modules, including express, rely on the fact that in http1 all write methods callwriteHead
if headers haven't been written yet.end
call and triggers callback, even ifwriteHead
already happened)kFinish
runs if the user hasn't accessed it yet, this is what h1 doeskStream
and instead unsetkResponse
&kRequest
error
event for the CANCEL rst code (8) — this is done mostly for h1 compatibility but also because a lot of clients seem to use this to signify client-requested abort (similar to how you could just trigger shutdown with code 0). The code is still emitted onstreamClosed
if anyone wants to know. Since theerror
emits on stream aren't actionable inhttp2
, it seems like this has very minor implications and it allows us to support the user code already out there much easier. I could potentially just do some manipulation in my proxy socket instead but it really hampers its performance and as a result the performance of express & on-finished.address
related info on the session instead of needing to access the socket.Performance has actually improved by about 5% with all of the changes in here, even when accessing the socket — despite the use of a Proxy.
Please let me know if I can provide more info for any of these changes. It came a long way since I started this work.
cc @jasnell, @mcollina & @dougwilson
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
http2, test