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
Do not require Trailer
header to parse trailers in HTTP2
#92
Conversation
How should we handle HTTP/2 -> HTTP/1 proxy. Should this header be added? |
This may be wrong, but given that you have to wait all trailers before being able to decide what to put in the |
That would break streaming? |
I looked at other proxies, like envoy grpc proxy, and they don't really support streaming all together (I guess partially because of trailers). |
I'm fine with something like your proposed change provided it doesn't go against any RFCs, I agree we would like to support gRPC. I need to review this in detail to make sure this is the right behaviour change. I think it's probably acceptable. |
This is great @Maaarcocr, and should fix some issues with GRPC. Might be worthwhile adding a test to document the behavior. Also, I can't really tell what's up with the CI failures but it seems like other branches are failing as well, so it might not be related to this change in particular. |
I'm trying to find a basis or reason for allowing this. https://datatracker.ietf.org/doc/html/rfc7540 page 58 shows an example of an HTTP/1 request upgraded to an HTTP/2 request which includes a direct 1:1 usage of
This seems to imply at least it's compatible, but not that it's required. On page 51, it explicitly states:
The only advice I can find proximal to that section is:
I could not find anywhere that states that Does this mean semantically we should accept the same logic in HTTP/1? |
It seems like it's a SHOULD which is what I've also seen around. I was unsure about HTTP1.1 but I guess even if chunk encoded you need the trailer header only to do some integrity checks, not really for any other reason. A better solution, for both HTTP2 and HTTP1 would be to let the user decide what to do when the trailer header is present and the trailers don't match it (also maybe what to do if the header is missing all together). I do think that not requiring it is the right thing to do. We could approach it this way:
|
Trailers as a semantic are quite tricky to fit within the current model for HTTP request-response and need explicit support in the headers semantics. So I need to make sure it's going to work as expected. |
Ok, that makes sense. Do you reckon we could merge the http2 changes sooner than the whole overall changes? Mostly asking because, internally, we do rely on async-http and this change would unlock us. I know it's not really something that concerns you, but it would help us plan a bit if we knew when we could expect the http2 changes to get merged. (The work we're doing internally may also get open sourced and it's related to gRPC, but all of that is TBD) |
Does gRPC optionally use trailers as a header or not at all? |
afaik not at all. Maybe some implementations may, but the ones that us the shared C implementation (Ruby, Python, C#) don't. I'm pretty sure the go one doesn't. |
Does gRPC use any of the same semantics for headers? like, |
I will try to get this merged by the weekend. There are a couple of other changes that need to be made to |
yes grpc is built on top of HTTP2, so when it comes to it, in order to talk to it you need a fairly basic HTTP2 client that sends the right headers and packs the body message the right way. So yes, it uses fairly standard http headers. It also has its own trailers (like grpc-status), which are still just normal headers but only have their own semantics within grpc connections. |
While HTTP requires:
It seems like gRPC does not require any such negotiations. The semantics of headers and trailers are very similar but different enough that it's tricky to support both semantics in one package. HTTP semantics "SHOULD":
While the lower level protocols support this, it's hard to know what the design trade offs are, i.e. sacrifice strict HTTP semantics to allow gRPC to live in the same code base. My understanding is gRPC uses the same protocol as HTTP/2 but it's not sharing the same semantics. Because it has trailers with none of the semantics required for HTTP to work correctly and this includes proxies and the ability to correctly proxy requests with trailers, from HTTP/2 to HTTP/1. I'm hesitant to adopt a change here which violates "SHOULD" recommendations in the RFCs. Of course, As such, I think I'll cut a new gem, Basically it boils down to, is gRPC really compatible with HTTP semantics? Or is it just using the same protocol/wire format? |
I've asked this question here: https://groups.google.com/g/grpc-io/c/Mw-HeAV_ZZc |
Another angle is, do we relax HTTP/2 protocol checks because it's acceptable in the RFCs? I need to know if HTTP/2 actually relaxes the semantic requirements around |
@mnot if you have any input on this it would be most appreciated. |
https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
"over HTTP2 framing" |
Trailer handling is one of the things that's changed in the newest HTTP specs (about to be published Any Day Now) -- see here. If you have any questions, glad to try to help. |
I don't fully agree that grpc is changing HTTP2 semantics, as grpc is pretty much built on top of it and it just sees it as a transport layer. Some gRPC server implementations error back to you if you do not send the TE=trailers header. I've learnt this the hard way while trying to reimplement a grpc client, precisely the one that share the C implementation, so they are compliant with the HTTP spec. I think the go implementation actually allows you to either set or not set TE=trailers and it will do the right thing. There was a medium article (that now got deleted for some reason) where the author described how to send grpc requests using cURL and its http2 client. All in all, I think grpc doesn't violate the HTTP2 spec and it's actually one of the few cases where trailers are used. |
Okay so the expectation is to use |
Does:
Apply to gRPC or not? A well formed HTTP response "SHOULD" generate a "trailer" header according to RFCs. I guess it won't hurt gRPC implementations and makes it compatible with HTTP semantics. The question is: How should the client behave when receiving an HTTP/2 response which includes trailers, but does not include a |
Currently, an HTTP/2 response which:
By this PR only, a HTTP/2 response which:
The solution would be to assume that HTTP/2 can always generate trailers, but there is some extra overhead involved in this. It will force HTTP/2 -> HTTP/1 to always use chunked encoding even if it was otherwise unnecessary. |
Basically, to be completely compatible with HTTP semantics, gRPC should specify:
Looking at https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses I wonder if that was implied by
But I'm assuming servers don't implement this? @ejona86 do you have any feedback/insight into this spec/design? |
This was an interesting interpretation of the relevant docs: httpwg/http-core#18 (comment) |
I understand your concerns about grpc possibly skewing away from proper HTTP semantics and just adhering to its framing. At the same time, would it make sense to maintain 2 http implementations whose only difference is that one errors out when the "Trailer" header is not sent and the other doesn't? I have a working demo of using async-http as a grpc client and the only change I had to make for it to work was to allow trailers without the "Trailer" header. |
After sleeping on this, and reflecting on the relevant RFCs, I believe that the It's hard for HTTP/1 since there are clear trade offs (fixed Therefore, I think we can accept this PR and we just have to accept the semantic complexities that come from it. |
Yes, this PR is obviously the right thing to do. A requirement on a sender (especially a SHOULD) does not place any onus on a recipient to check that the sender is doing the right thing -- it's encouraging good behaviour (hence, SHOULD). If it were necessary for the recipient to check it, that would have been said explicitly in the spec. |
See also this, under 'interpreting requirements'. |
@mnot thanks for your feedback.
Sorry to nitpick, but it really wasn't obvious to me hahah. How does a client/server in this case meaningfully do anything w.r.t. a SHOULD requirement? Emit warnings? How about gRPC in this case where it's essentially ignoring the SHOULD on a systematic basis. If standard X says SHOULD and standard Y building on X ignores that, it feels like a mistake to me. |
A recipient doesn't need to do anything. This requirement is there to encourage senders to make the information available, nothing more.
gRPC isn't a standard, and doesn't use HTTP particularly well. That's on them. |
Okay, I made updated releases of all the relevant gems with this new behaviour. |
Fixes #91