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

Support http2 #36

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open

Support http2 #36

wants to merge 16 commits into from

Conversation

sogaani
Copy link

@sogaani sogaani commented Aug 2, 2018

Support http2 to enable express tests with http2.
expressjs/express#3390.

body-parser cannot parse http2 body stream, because hasBody with http2ServerRequest always return false. A http2ServerRequest does not have any information about body had. I think hasBody with http2ServerRequest should return always true.

Copy link
Contributor

@dougwilson dougwilson left a comment

Choose a reason for hiding this comment

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

There is definitely something wrong here. For example a HEAD request, even in HTTP2, will never have a Body, so hasBody should return false. Same with almost all GET requests which would never have a body.

@jshttp jshttp deleted a comment from coveralls Aug 2, 2018
@jshttp jshttp deleted a comment from coveralls Aug 2, 2018
@sogaani
Copy link
Author

sogaani commented Aug 2, 2018

@dougwilson Thank you for feedback. you are right. I will change hasBody to return false if request is OPTIONS, TRACE, CONNECT, HEADor GET method.

@dougwilson
Copy link
Contributor

In HTTP1 a GET request may or may not have a body. I assume this is still the same in http/2 as I have used a body in a GET request to a http/2 server. Also that logic doesn't seem robust for custom methods or new future methods. There should be somewhere in the http/2 rfc that states exactly how to know if a request includes a body or not. That should be the implemented logic here.

@sogaani
Copy link
Author

sogaani commented Aug 2, 2018

I dig in http/2 rfc next week and implement this.

@dougwilson
Copy link
Contributor

Skipping around through the rfc you linked,, from what I can gather is that a body = data frames from the client on a stream. So to know if there is a body in http/2 you would say "will this steam send data frames?" and it seems that answer would be yes for the following condition: you get a header or continuation frame with end headers flag but no end stream flag set.

@sogaani
Copy link
Author

sogaani commented Aug 2, 2018

So to know if there is a body in http/2 you would say "will this steam send data frames?" and it seems that answer would be yes for the following condition: you get a header or continuation frame with end headers flag but no end stream flag set.

Definitely yes. And I had checked some nodejs code how get end stream flag. But I cannot found... At least, stream still not ended when express get http2ServerRequest even if no body. If you know that, it is great!

@dougwilson
Copy link
Contributor

It's possible that new API needs to be added to Node.js to get that information. User land code should be able to get at all the data at the http/2 level from my understanding. If it's purposely hidden though the API then at the very least have an API to know if the http/2 request actually has a body or not.

@sogaani
Copy link
Author

sogaani commented Aug 6, 2018

I dig in nodejs code and found parameter that indicate already get end stream flag.

When Nodejs get end stream flag with HeaderFrame, Nodejs push null in order to end a readableStream and set stream._readableStream.ended to true.
https://github.com/nodejs/node/blob/master/lib/internal/http2/core.js#L301

I fixed this PR.

Copy link
Contributor

@dougwilson dougwilson left a comment

Choose a reason for hiding this comment

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

Awesome! Just the note about testing.

test/test.js Outdated
'content-type': type || ''
},
stream: {
_readableState: {
Copy link
Contributor

Choose a reason for hiding this comment

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

At this point if we are depending on Node.js internals for the state, we need to test against the actual Node.js implementation instead of a simple mock.

Copy link
Contributor

Choose a reason for hiding this comment

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

Either that or there is specific documentation in Node.js documenting this exact behavior, though I didn't see that anywhere. Without one of these we won't know when Node.js changes it's behavior as our tests won't fail.

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 fix tests tomorrow.

test/test.js Outdated
assert.strictEqual(typeis(req, ['image/*']), false)
assert.strictEqual(typeis(req, ['text/*', 'image/*']), false)
})
if (!process.env.HTTP2_TEST) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this excludes from HTTP/2 tests? To me this seems like it should work in http/2.

Copy link
Author

Choose a reason for hiding this comment

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

I agree with you. I just cannot found how to unset content-type in superagent. I'm checking superagent code.

callback(req)
})

server = server.listen(function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't forget to close the server, otherwise every test leaves a new server open, eventually going to starve the test memory over time.

callback(req)
})

server = server.listen(function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't forget to close the server, otherwise every test leaves a new server open, eventually going to starve the test memory over time.

@sogaani
Copy link
Author

sogaani commented Aug 9, 2018

@dougwilson I addressed review comment. I think this is good for now. 👍

.travis.yml Outdated
@@ -26,6 +26,7 @@ before_install:
- "test $TRAVIS_NODE_VERSION != '0.6' || npm rm --save-dev istanbul"
- "test $TRAVIS_NODE_VERSION != '0.8' || npm rm --save-dev istanbul"
- "test $(echo $TRAVIS_NODE_VERSION | cut -d. -f1) -ge 4 || npm rm --save-dev $(grep -E '\"eslint\\S*\"' package.json | cut -d'\"' -f2)"
- "test -z $(echo $HTTP2_TEST) || npm install --only=dev https://github.com/visionmedia/superagent.git"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please only install deps from npm.

Copy link
Author

Choose a reason for hiding this comment

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

Unfortunately, superagent with http2 have not released. I will wait for release.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also what does the only flag do? According to https://docs.npmjs.com/cli/install it only does something if you do not provide a module name.

Copy link
Author

Choose a reason for hiding this comment

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

This is my mistake. --only=dev flag does nothing here. I should delete this flag.

index.js Outdated
* @param {Object} request
* @return {Boolean}
* @public
*/

function hasbody (req) {
return req.headers['transfer-encoding'] !== undefined ||
return (ishttp2(req) && !req.stream._readableState.ended) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Some testing seems like this may have an issue. If you add a req.on('data' listener and read the body, this then returns false for http/2 but true for http/1. The current interface it is expected to be true for both, as hasBody determines if the request has body, regardless of it you happened to have read it already or not.

Copy link
Author

Choose a reason for hiding this comment

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

I checked and happens an issue you said. I address this issue and add a test.

index.js Outdated
* @param {Object} request
* @return {Boolean}
* @public
*/

function hasbody (req) {
return req.headers['transfer-encoding'] !== undefined ||
return (ishttp2(req) && (req.stream.readable || req.stream._readableState.endEmitted)) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like this still has some kind of issue. I would expect the following test to pass:

      it('should not indicate body', function (done) {
        createBodylessRequest('', function (req) {
          var data = ''
          req.on('data', function (chunk) {
            data += chunk
          })
          req.on('end', function (chunk) {
            process.nextTick(function () {
              assert.strictEqual(typeis.hasBody(req), false)
              done()
            })
          })
        })
      })

It seems like just attaching a data event will make this module claim the request has a body, even if the original request did not have a body.

Copy link
Author

Choose a reason for hiding this comment

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

I addressed issue. I found a parameter _readableState.sync which is default true and became false after reading data.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! What does that flag actually mean / is it documented so we can rely on some kind of behavior? The underscore prefix just has me worries about the long term.

Copy link
Author

Choose a reason for hiding this comment

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

It means 'readable'/'data' event is emitted immediately or not.

See also comment in source.
https://github.com/nodejs/node/blob/master/lib/_stream_readable.js#L104
// a flag to be able to tell if the event 'readable'/'data' is emitted
// immediately, or on a later tick. We set this to true at first, because
// any actions that shouldn't happen until "later" should generally also
// not happen before the first read call.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think Node.js considers docs in source code as promised behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm also unclear on how It means 'readable'/'data' event is emitted immediately or not. has any indication on if the http/2 stream has data frames or not? It seems to be unrelated to the unlying state of the http/2 layer to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Searching the Node.js issue tracker for the underscore property pulls up nodejs/node#445 which sounds like it shouldn't be used inside Node.js code, let alone third-party things.

Copy link
Author

Choose a reason for hiding this comment

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

I don't think Node.js considers docs in source code as promised behavior.

Yes. However, current Nodejs has no other parameter to indicate hasBody after event 'end'. Should we suggest new API for Nodejs?

I'm also unclear on how It means 'readable'/'data' event is emitted immediately or not. has any indication on if the http/2 stream has data frames or not? It seems to be unrelated to the unlying state of the http/2 layer to me.

sync relates to readableStream rather than http/2 layer state. When http/2 layer get DataFrame, readableStream(ServerHttp2Request) emit 'data' event immediately or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. However, current Nodejs has no other parameter to indicate hasBody after event 'end'. Should we suggest new API for Nodejs?

Based on the discussion in nodejs/node#445 it sounds like the answer is it's a must, as the activity seems to indicate that this will break sooner rather than later, and they want to get everything public.

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 will file a issue in nodejs repo and pending this PR.

@dougwilson
Copy link
Contributor

OK, I will file a issue in nodejs repo and pending this PR.

Did you ever make this issue / can you provide a link I can look at?

@sogaani
Copy link
Author

sogaani commented Sep 13, 2018

Yes, I already make the issue.
See also nodejs/node#22497

@dougwilson
Copy link
Contributor

Thanks! I left a comment there. Let's give 30 days from now on that issue if it's not resolved and then we'll go with the reaching into internals solution of this PR 👍

@sogaani
Copy link
Author

sogaani commented Sep 21, 2018

I fix PR to use request.stream.endAfterHeaders released on node 10.11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants