Advanced example covering metadata, status code, status details
Elsewhere @anentropic writes that it's not obvious how to code for non-OK status codes. In particular I'm not sure we have anything describing how application-specific use of application-specified non-OK status codes should work.
Should non-OK status codes be covered in an "advanced" example beyond route guide?
For example: client calls a 'get detail' endpoint requesting an id which doesn't exist. What is the idiomatic grpc way to handle this?
Context: I have never used protobufs before. I am converting an existing REST API to grpc.
What I feel would be useful is some design guidance and discussion of 'best practice' in building a grpc service. At the moment the 'rules of the game' are not clear, they could be described up front.
For example:
- could not find how to do nullable field in protobuf3. This doesn't match the data model in the db
- grpc method can only return a single type of response. in REST API it's quite common to return either success data or error messages, or empty success 202/204 or error messages. the client knows what to deserialize based on status code.
Both of these seem quite strict constraints. I can work out my own solutions but there must be established patterns for dealing with them. It'd be great to be guided straight to a good pattern and avoid pitfalls others have already encountered.
(update: after more googling today it seems maybe I can solve both by creative use of oneof fields)
I've looked at the Node.js source to see how status messages are constructed. I'm going to do the same later today for Java and Python. I've so far only used gRPC on those 3 languages (Java on Android and Netty), so I can contribute with either a blog post or by submitting a PR to this repo. Hopefully other people can do the same for languages that they've used.
@nathanielmanistaatgoogle @hsaliak any updates on this?
Or, at least, any advice on how to implement this?
Google's own usage of grpc does not seem to have a clear pattern on how to cover errors, for example:
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
//...
}
It simply returns Shelf, and is absolutely unclear what to return when there is an error.
Hey @jimbojetlag, I've added an example of how to send error messages with metadata. The example is in Java/Kotlin, and it includes both string and binary metadata. I hope it helps. You can find it here: https://github.com/nevi-me/grpc-demos/commit/71a3cc9df17550675986c7dc6da6ca759f340468?diff=unified
It's quite late here in SA, but I'm going to have some downtime over the weekend, I'll try complete the issue that I opened over the weekend, and commit my code when I'm back to civilisation early next week. I hope this helps in the interim.
Do note that since the Java server implements unary calls with the same signature as a stream, with the requirement that you immediately call onCompleted() after sending your data, you can also raise an error with onError(<StatusRuntimeException|Throwable>).
The interesting thing which I haven't figured out though is how to send trailers with a successful call. I'll try it over the weekend. I've so far only done it on a Node.js server 😄
@nathanielmanistaatgoogle with some guidance, I'm happy to add some details on a few languages.
I'm still yet to figure out how to send trailers with streaming requests and responses (I presume before/with onCompleted), but I'll read through the Nodejs and Java docs. I tried implementing a streaming service on Python at work this week, but struggled a bit.
@nevi-me I cannot find anything about non-OK proto message in your repo. I'm only interested in the original question in this thread. How to deal with non-OK cases at the protobuf level?
Again, in the context of:
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
//...
}
How do you define a message when there is an error in the implementation of CreateShelf?
Oh, my apologies.
Look at the binary metadata that I pass with the error. That metadata is a protobuf message, so you'd send the Shelf message as part of the error.
My understanding is that the moment you receive non-OK on the client side, an error is thrown. When you catch that error, you can check for the presence of metadata.
What I've done on the example is what you're looking for, it's unfortunate that I didn't directly answer your original question explicitly, but you can use my code to achieve said desired results.
@nevi-me I'm not sure if this makes a difference, but unlike your example, the Shelf example does not return a stream.
Also, I'm still unsure about how this works. CreateShelf rcp return the Shelf type. How can you send a different type involving the errors state as the response?
Here is a live working example to demonstrate the question:
Google genomis API has this endpoint:
rpc CreatePipeline(CreatePipelineRequest) returns (Pipeline) {
option (google.api.http) = { post: "/v1alpha2/pipelines" body: "pipeline" };
}
Now try this endpoint in your browser:
https://cloud.google.com/genomics/reference/rest/v1alpha2/pipelines/create
Without signing in, this is the response that I get:
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
Here is the question: The RPC returns Pipeline message defined here, the response however, looks nothing like the defined Pipeline message. How does this happen?
Here is the question: The RPC returns Pipeline message defined here, the response however, looks nothing like the defined Pipeline message. How does this happen?
That's because that the RPC returned an onError, which is populated with the detail. Please at least clone the repo and try out the code that I shared, because that answers your question.
If there is an error, you respond with onError instead of onNext. If you're using Nodejs for example, your unary responses are in the signature callback(err, message), so if you populate the error message, you'd have something like:
return callback({
code: 401,
message: "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
status: grpc.status.UNAUTHENTICATED
})
If the above still doesn't make sense, please share a snippet of your implementation, so I can show you what I mean using your code. I only understand Nodejs, Java and Python implementations for now.
@nevi-me thanks for explanation. I am interested in Go implementation, here is an example:
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
This rcp can only return pb.HelloReply type. Of course you can return an error too, but that error will not be passed to the client, it's only for the server side check. The only structure that can be passed to the client is of pb.HelloReply type.
However, the Google genomics api example in the above can actually returns 2 different structures depending on the error/success state.
This is no longer a question; this is something we should do in our set of examples.