grpc-java
grpc-java copied to clipboard
Allow override for `Content-Type` header in order to support encoder selection based on Content Sub-Type
Is your feature request related to a problem?
grpc-java
with Netty
transport currently considers Content-Type
header as a reserved header and disregards the value of Content-Type
provided by user (Can find a similar issue with User-Agent
here at #5874).
This is problematic in cases where content sub-type based encoder selection is required at server side. (Check #7321)
Describe the solution you'd like
grpc-netty
must not consider Content-Type
header as a reserved header since a core gRPC functionality is not working because override of Content-Type
from currently forced application/grpc
to application/grpc+<content sub-type>
is not possible.
This would require removal of: https://github.com/grpc/grpc-java/blob/0ffcd40ab6192a4cb1b61f44d1e1aa485e61f9cb/netty/src/main/java/io/grpc/netty/Utils.java#L232 and https://github.com/grpc/grpc-java/blob/0ffcd40ab6192a4cb1b61f44d1e1aa485e61f9cb/netty/src/main/java/io/grpc/netty/Utils.java#L247
Further, additional validations can be added to check if value of Content-Type
is valid according to gRPC protocol specification. (Basically checking if value of Content-Type
starts with application/grpc
).
Describe alternatives you've considered
An alternative would be to deprecate registering global encoders in gRPC implementations for various languages (like Go) in favour of registering Marshaller
s like grpc-java
's MethodDescriptor.Marshaller<T>
which is not currently supported in gRPC implementations for these languages. This would be rather tedious to incorporate.
Additional context
This issue with not being able to set a custom override for Content-Type
header is posing a problem for us as we need to communicate with a Go gRPC application using JSON content sub-type. The way encoding works on go-grpc side is that it chooses one of the registered encoders based on content subtype from Content-Type
header, so application/grpc+json
will use the registered encoder with name json
. However since Netty
transport library is overriding it with application/grpc
(considering it a reserved header), go-grpc
is falling back on using the default encoder with content sub-type name of proto
.
Further, the Go server needs to support both protobuf
and json
based encoding.
Confirming the need for it, see #11023
You are requesting full control over Content-Type
, but then say gRPC can require values be prefixed with application/grpc
. So you are simply suggesting an API to provide sub-type support. I don't think we want an API looking like that, though. I think we'd want something plumbed through Marshaller like I mentioned before.
Further, the Go server needs to support both protobuf and json based encoding.
If the Go server supports both, why are you wanting to use JSON? (In other words, why can't you use protobuf?)
The sub-type format is very broken. It is being used for the opposite of what the standard says it means and its name is very confusing. "subtype" in Content-Type is the part after the "/", so for "application/grpc" "grpc" would be the subtype and for "text/plain" "plain" would be the subtype. Clearly "subtype" in the gRPC wire spec is not that.
The +
syntax is described in RFC 6839. If something is marked +xml
, then that means an XML parser can process it without knowing the rest of the type. Same with +json
. It describes the outer structure of the document, not something nested within. application/grpc+json
is thus completely broken, because a JSON parser can't parse grpc's 5-byte framing header.
Yes, grpc-go is using it. And yes, it is in the grpc protocol spec. But it is a bad idea to propagate it further than Go.
I think the easiest solution here is a separate header that holds the message content type. Something like "grpc-message-content-type".
(This could potentially also replace "grpc-message-type", which to my knowledge is unused in practice, as content-type could specify the message schema. For protobuf maybe application/x-protobuf; messageType="google.protobuf.Empty"
, and there's been similar proposals for json; this is non-standard though and I don't know how bad the parsing would be.)
(Another option would be to use parameters in grpc itself, like application/grpc; msg=application/x-protobuf
. This might have compatibility issues, as some implementations might be only ignoring content after the +
; we'd need to do a survey. And I don't know how much parsing would be necessary there, like if we need to wrap values in double-quotes and such.)
I have spoken to grpc-go folks about this, and it may be possible to migrate to a better approach while keeping the current API. But clearly there is some work involved.