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

make available header and trailer metadata #93

Merged
merged 11 commits into from
Feb 18, 2016

Conversation

kazegusuri
Copy link
Contributor

gRPC recommends application-level error information should be returned as Trailer metadata. We have to use grpc.CallOption for each rpc call in order to get the metadata at client side. This PR makes protoc-gen-grpc-gateway generates codes with grpc.CallOption. And the metadata are set in context, so handlers can extract the metadata by runtime.CallInfoFromContext.

Actually I had a plan to return those metadata as HTTP header or body in JSON especially in DefaultHTTPError handler. However metadata possibly includes binary or values must be escaped, so default handler doesn't care metadata by default.
EDIT: So I recommend to define a handler which utilizes metadata and overwrite default handlers.

Any ideas or comments are welcome.

@@ -208,7 +208,7 @@ var (
}
{{end}}

return client.{{.Method.GetName}}(ctx, &protoReq)
return client.{{.Method.GetName}}(ctx, &protoReq, grpc.Header(&callInfo.HeaderMD), grpc.Trailer(&callInfo.TrailerMD))
Copy link
Member

Choose a reason for hiding this comment

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

These call options are effective only if .Method.GetServerStreaming and .Method.GetClientStreaming are false.
The caller of this request_xxx function must use grpc.ClientStream.Header() and grpc.ClientStream.Trailer if otherwise.

IIUC, it would get easier to handle this case if you have let this request_xxx function allocate the metadata.MDs and let the function return the MDs in addition to response and errors.

How about renaming runtime.CallInfo to runtime.ServerMetadata and declaring the ! .Method.GetServerStreaming() version of this function as something like this?

func request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ctx context.Context, client {{.Method.Service.GetName}}Client, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error)

With this approach forward_xxx function does not need to care about .GetClientStreaming.

@kazegusuri
Copy link
Contributor Author

Thank you for the comment. I haven't take care streaming because I have never touched.
It sounds good to rename to runtime.ServerMetadata and return it from function.
Event after that, the caller of request_xxx set the metdata to context to my understanding, right?
If this is okay, I will populate the changes.

@yugui
Copy link
Member

yugui commented Jan 28, 2016

the caller of request_xxx set the metdata to context

Yes.

Also I am thinking of forwarding headers and trailers in the forwarder functions. What do you think about it?

@kazegusuri
Copy link
Contributor Author

Also I am thinking of forwarding headers and trailers in the forwarder functions. What do you think about it?

Sorry, I've missed the comment. The metadata are set to context, so we can extract byruntime.ServerMetadataFromContext(ctx) from anywhere including forwarder functions.
There is no need to pass the metadata by arguments, but it's not strong opinion.

@kazegusuri kazegusuri force-pushed the metadata branch 2 times, most recently from 495fa00 to c33445c Compare January 31, 2016 08:15
@kazegusuri
Copy link
Contributor Author

Addressed comments, but have problems in streaming rpc.

Receiving header in stream.

stream.Header() is blocking method until header is ready or context has done, so calling stream.Header at first in request_xxx blocks forever if server does not send headers first by grpc.SendHeader.
Another way is to call stream.Header() after sending message, which means add following code after here.

    msg, err := stream.CloseAndRecv()
    md, err := stream.Header()
    if err != nil {
        glog.Errorf("Failed to get stream header: %v", err)
        return nil, metadata, err
    }
    metadata.HeaderMD = md
    metadata.TrailerMD = stream.Trailer()
    return msg, metadata, err

It works well in my small example with streaming, but in integration test it fails sometimes with following error.

integration_test.go:359: {"error":"stream error: code = 1 desc = \"context canceled\"","code":2}

I'm not sure why context was canceled.

Receiving trailer in stream

stream.Trailer() must be called after stream.Recv(), so we cannot get trailer in request_xxx when .Method.GetServerStreaming is true because recv() is called in forward_xxx.
IIUC, Trailer is used for application-level error information. How about passing Trailer to handleForwardResponseStreamError when recv() returns error in ForwardResponseStream and user can define stream error handler like DefaultHTTPError?

@kazegusuri kazegusuri force-pushed the metadata branch 2 times, most recently from 18c9848 to 6e00a64 Compare February 6, 2016 10:36
@kazegusuri
Copy link
Contributor Author

I have fixed metadata handling with some limitations; Dropping support for header metadata in streaming RPC because it's a bit difficult, is it acceptable? And added support to return metadata to client by default.

  1. Header metadata is returned to client by HTTP header with Grpc-metadata- prefix in both normal response and error response, but only on Unary RPC.
  2. Trailer metadata is returned to client by response body with top-level trailer key and json value only on error response, but both Unary RPC and Streaming RPC are supported.

PTAL @yugui

@yugui
Copy link
Member

yugui commented Feb 8, 2016

@kazegusuri

I have fixed metadata handling with some limitations; Dropping support for header metadata in streaming RPC because it's a bit difficult, is it acceptable?

Yes. Let me examine it by myself later. But it is acceptable for now.

And added support to return metadata to client by default.
1.Header metadata is returned to client by HTTP header with Grpc-metadata- prefix in both normal response and error response, but only on Unary RPC.

OK.

2.Trailer metadata is returned to client by response body with top-level trailer key and json value only on error response, but both Unary RPC and Streaming RPC are supported.

It looks weird to put it in response body because it was metadata, and the application-level error does not belong to a specific chunk of body.
I understand that it is hard to implement for streaming RPC, but is it possible to forward trailer metadata as HTTP 1.1 trailers?

@kazegusuri
Copy link
Contributor Author

It looks weird to put it in response body because it was metadata, and the application-level error does not belong to a specific chunk of body.
I understand that it is hard to implement for streaming RPC, but is it possible to forward trailer metadata as HTTP 1.1 trailers?

I never knew HTTP 1.1 trailer. I'll try it.
I think it's better to use HTTP 1.1 trailer for streaming RPC. Do you think to use it for Unary RPC?

@achew22
Copy link
Collaborator

achew22 commented Feb 8, 2016

@yugui I can't find any evidence that any browsers actually support HTTP 1.1. How do you get access to trailers in an XHR object?

@yugui
Copy link
Member

yugui commented Feb 8, 2016

@achew22 Good point.
getResponseHeader looks to be expected to contain trailers, although Safari doesn't implement it.
https://github.com/w3c/web-platform-tests/blob/master/XMLHttpRequest/getresponseheader-chunked-trailer.htm

@kazegusuri Although the most straightforward mapping of gRPC trailer metadata is HTTP trailers, there are several things to consider.

  1. As @achew22 pointed, it might not be compatible to browser implementations.

  2. We can list up trailer fields before sending response body for unary RPC. But we cannot for streaming RPC. So we cannot send Trailer header in such a case -- it violates a recommendation in RFC 2616.

    https://tools.ietf.org/html/rfc2616#section-14.40

    An HTTP/1.1 message SHOULD include a Trailer header field in a
    message using chunked transfer-coding with a non-empty trailer

@kazegusuri
Copy link
Contributor Author

My first thought is the use case of gRPC trailer is specific for error details, so I attached the metadata to error message for both unary and streaming rpc.
But I understand it's metadata and it's good design to map gRPC trailer to HTTP 1.1 trailer.

I understand that it is hard to implement for streaming RPC, but is it possible to forward trailer metadata as HTTP 1.1 trailers?

Possible for unary RPC and streaming RPC with client streaming, but impossible for with server streaming.
Go 1.5 http server does not send trailers which are not included in Trailer header. When gRPC trailer is sent to gateway while receiving streaming, HTTP header was already sent to client and we cannot always know trailer before sending header.

We can list up trailer fields before sending response body for unary RPC. But we cannot for streaming RPC. So we cannot send Trailer header in such a case -- it violates a recommendation in RFC 2616.

As described above, yes we cannot use HTTP 1.1 trailer for streaming RPC.
So I used HTTP 1.1 trialer only for unary RPC, and for streaming RPC grpc trailer is returned as independent chunk like error message. In both case grpc trailer is always returned to client if set regardless of the response is error or not. Does this make sense? @yugui

As @achew22 pointed, it might not be compatible to browser implementations.

I cannot know how impact to use HTTP 1.1 trailer.
Anyway HTTP 1.1 trailer cannot be used for streaming RPC and response body is used for that case in current impl. If this can be a problem, it's a reasonable option to use response body for unary RPC, too.

@yugui
Copy link
Member

yugui commented Feb 16, 2016

@kazegusuri

As described above, yes we cannot use HTTP 1.1 trailer for streaming RPC.
So I used HTTP 1.1 trialer only for unary RPC, and for streaming RPC grpc trailer is returned as independent chunk like error message. In both case grpc trailer is always returned to client if set regardless of the response is error or not. Does this make sense? @yugui

Yes, it makes sense.
Strictly speaking, we can still send HTTP 1.1 trailers in server streaming RPCs without the corresponding Trailer header if we hijack the HTTP connection. But I don't think it is worth to try because it makes the implementation much complicated and it violates the recommendation in RFC 2616.

Summarizing, I expect the following spec. Is this correct?

  1. Header metadata are mapped to HTTP headers which have Grpc-Metadata- prefix. It is supported in all of unary, client-streaming, server-streaming and both-streaming RPCs.
  2. Trailer metadata are mapped to HTTP trailers which have Grpc-Trailer- prefix. But it is supported only in unary RPC and client-streaming RPC.
    • We can reconsider trailers in server-streaming RPCs when we have a concrete use case.

@kazegusuri
Copy link
Contributor Author

Header metadata are mapped to HTTP headers which have Grpc-Metadata- prefix. It is supported in all of unary, client-streaming, server-streaming and both-streaming RPCs.

Not supported for server-streaming and both-streaming RPCs (at least this PR) because header timing issue.

Trailer metadata are mapped to HTTP trailers which have Grpc-Trailer- prefix. But it is supported only in unary RPC and client-streaming RPC.

Yes. But for server-streaming and both-streaming, Trailer metadata are returned as an extra chunk of message body.

@yugui
Copy link
Member

yugui commented Feb 16, 2016

Not supported for server-streaming and both-streaming RPCs (at least this PR) because header timing issue.

You need to call stream.CloseSend. Then you can safely call Header() before CloseAndRecv.

Yes. But for server-streaming and both-streaming, Trailer metadata are returned as an extra chunk of message body.

I am not confident if it is the right solution. So it looks better to conservatively avoid supporting trailer metadata in server-streaming and both-streaming. Is it OK for you?

@kazegusuri
Copy link
Contributor Author

You need to call stream.CloseSend. Then you can safely call Header() before CloseAndRecv.

Thank you for comment. I will check it out.

I am not confident if it is the right solution. So it looks better to conservatively avoid supporting trailer metadata in server-streaming and both-streaming. Is it OK for you?

Yes.

@kazegusuri kazegusuri force-pushed the metadata branch 2 times, most recently from 67015be to 8680a68 Compare February 17, 2016 15:52
@kazegusuri
Copy link
Contributor Author

Header metadata are mapped to HTTP headers which have Grpc-Metadata- prefix. It is supported in all of unary, client-streaming, server-streaming and both-streaming RPCs.
Trailer metadata are mapped to HTTP trailers which have Grpc-Trailer- prefix. But it is supported only in unary RPC and client-streaming RPC.

I hope all comments addressed and met the spec.
PTAL @yugui

type serverMetadataKey struct{}

// NewServerMetadataContext creates a new context with ServerMetadata
func NewServerMetadataContext(ctx context.Context, md *ServerMetadata) context.Context {
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, you no longer need to pass a *ServerMetadata. You can pass just ServerMetadata.

@yugui
Copy link
Member

yugui commented Feb 18, 2016

Thank you very much. Now the design looks good to me.
Let me add some more coding-level comments.

type errorBody struct {
Error string `json:"error"`
Code int `json:"code"`
type ErrorBody struct {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it is a good idea to expose this type just for testing.
This struct is just a convenient way for this runtime package to construct JSON object on error. But the struct itself is not a part of API exposed for users or generated codes by this package.

@kazegusuri
Copy link
Contributor Author

Addressed comments.

@yugui
Copy link
Member

yugui commented Feb 18, 2016

LGTM.

Thank you for your excellent contribution.

yugui added a commit that referenced this pull request Feb 18, 2016
make available header and trailer metadata
@yugui yugui merged commit b94f713 into grpc-ecosystem:master Feb 18, 2016
@kazegusuri kazegusuri deleted the metadata branch February 18, 2016 12:28
@kazegusuri
Copy link
Contributor Author

Thanks! I'm grateful for your help.

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

Successfully merging this pull request may close these issues.

3 participants