-
Notifications
You must be signed in to change notification settings - Fork 789
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
Ember client drop head body #7369
Ember client drop head body #7369
Conversation
1d759b5
to
5545726
Compare
5545726
to
a26d69e
Compare
Only enabled if the request is a HEAD request. This should only be an issue for HTTP/1.1 as http/2 body frames should not be generated for HEAD. Fixes http4s#7362
a26d69e
to
1c53199
Compare
ember-core/shared/src/main/scala/org/http4s/ember/core/Parser.scala
Outdated
Show resolved
Hide resolved
@danicheg @armanbilge please re-review this, @yanns confirmed that this fix works. |
It's ready to go, as far as I'm concerned. 👍🏻 But @armanbilge requested themselves to make a review. So the ball's in their court. |
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 seems reasonable.
At first I was confused why we have a discardBody
and a expectNoBody
, but expectNoBody
only considers the Status, not the Method.
ember-core/shared/src/test/scala/org/http4s/ember/core/ParserSuite.scala
Outdated
Show resolved
Hide resolved
@@ -373,6 +373,11 @@ private[ember] object Parser { | |||
def parser[F[_]: Concurrent](maxHeaderSize: Int)( | |||
buffer: Array[Byte], | |||
read: Read[F], | |||
): F[(Response[F], Drain[F])] = parser[F](maxHeaderSize, discardBody = false)(buffer, read) | |||
|
|||
def parser[F[_]: Concurrent](maxHeaderSize: Int, discardBody: Boolean)( |
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.
Is discardBody
really the right name? AFAICT there is no discarding happening, it seems more similar to expectNoBody
. Perhaps we can have requestExpectsNoBody
and statusExpectsNoBody
.
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, I did some more reading.
Warning: A response to a HEAD method should not have a body. If it has one anyway, that body must be ignored: any representation headers that might describe the erroneous body are instead assumed to describe the response which a similar GET request would have received.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
So it does seem like the body must be discarded if it's present. So now I'm not sure if it's appropriate to share a codepath with statusExpectsNoBody
.
d2fdc1d
to
1bd393a
Compare
assertEquals(body, "") | ||
assertEquals(rest, "helloeverything after the body") |
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'm pretty sure we should be assert
ing that both of these are empty. Otherwise we haven't really discarded the body, just left it on the socket.
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.
are you sure about that? then expectNoBody would also leave data on the socket.
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.
Right, but I don't think that "discard body" and "expect no body" are the same situation at all. See #7369 (comment).
For HEAD
requests the spec mandates that we discard the body, if it's present. Meanwhile, the spec for e.g. 204 No Content
makes no such mandate: it's up to us to decide how to handle that situation.
@armanbilge can you please do another pass on this? |
This issue is blocking migration from blaze to ember client. It'd be great to have the fix merged and released. Thanks all! |
@armanbilge sorry for the ping, but would you have time to check this out? |
I need to get a release out this week. I don't typically work on Ember, but given the positive feedback and that the interface changes are private, I'm inclined to merge this someone objects tomorrow. |
@@ -364,6 +364,39 @@ class ParsingSuite extends Http4sSuite { | |||
} | |||
} | |||
|
|||
test( | |||
"Parser.Response.parser return everything if the body is discarded" |
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.
Now that we've changed the behaviour to discard the rest
perhaps we should update this test name?
"Parser.Response.parser return everything if the body is discarded" | |
"Parser.Response.parser returns empty body and rest when the body is discarded" |
@@ -406,7 +411,11 @@ private[ember] object Parser { | |||
) | |||
|
|||
resp <- | |||
if (headerP.chunked) { | |||
if (discardBody) { | |||
(baseResp -> none[Array[Byte]].pure[F]).pure[F] |
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.
Disclaimer, I haven't looked at the ember parser before.
What is this doing that is different from the line two below?
The return type is F[(Response[F], Drain[F])]
. Presumably the thing on the right is what we are draining? Or is it the "drained bytes" 🤔 ?
My concern is whether or not
if (discardBody) {
(baseResp -> none[Array[Byte]].pure[F]).pure[F]
is actually draining things from the socket or just saying "there wasn't anything on the socket".
@@ -364,6 +364,39 @@ class ParsingSuite extends Http4sSuite { | |||
} | |||
} | |||
|
|||
test( | |||
"Parser.Response.parser return everything if the body is discarded" |
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 happens to the connection in this case? Discarding an ad hoc number of bytes and then continuing to parse makes me nervous about smuggling attacks.
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 path is only for response parsing, right?
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.
Is the expectation that the connection gets closed, even after a properly framed HEAD 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.
As far as I can tell, Ember is closing the connection after a successful HEAD request, both on series/0.23 and on this branch. This doesn't make that problem worse and fixes the original problem. Furthermore, if it's too aggressively closing connections, it at least relieves us of worrying about the security implications of excess bytes in a response.
Amazing work, team! Additional gratitude to @rossabaker for going an extra mile on verifying this 🙇 |
Adds a flag to ember-core Parser.Response.parse to discard the body
this is only enabled for HTTP/1.1 HEAD requests, but there may be other http methods which does not expect a body in the response.
Not sure if this is the best location for this code, but the reproducer now passes.