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

Add sample code for setting and reading error details #506

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions examples/error_metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Setting and Reading Error Details

gRPC servers return a single error value in response to client requests.
The error value contains an error code and a description. In some cases more
details regarding the error are required in order to take action. The recommended
way to specify error details is by attaching metadata in the HTTP/2 trailers
on the server and reading that metadata from the client.

The examples in this directory demonstrate how to set and read error details using
metadata attached in HTTP/2 trailers.

## Background

For this sample, we've already generated the server and client stubs from [helloworld.proto](helloworld/helloworld.proto).

## Install

```
$ go get -u google.golang.org/grpc/examples/error_metadata/greeter_client
$ go get -u google.golang.org/grpc/examples/error_metadata/greeter_server
```

## Try It!

Run the server

```
$ greeter_server &
```

Run the client

```
$ greeter_client
```

```
Greeting: Hello world
```

Run the client again

```
$ greeter_client
```

```
Could not greet: rpc error: code = 8 desc = "Request limit exceeded."
Error details:
count: 2
message: Limit one greeting per person.
```
85 changes: 85 additions & 0 deletions examples/error_metadata/greeter_client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

package main

import (
"fmt"
"log"
"os"

pb "google.golang.org/grpc/examples/error_metadata/helloworld"

"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

const (
address = "localhost:50051"
defaultName = "world"
)

func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)

// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}

// Set up md to hold the metadata returned from the server.
md := metadata.Pairs()

// Contact the server and capture the trailer metadata in md using a call option.
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}, grpc.Trailer(&md))
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

one thing I was trying to figure out is that:

is this error a rpc error from server side or this is a client-side/network error?

Copy link
Author

Choose a reason for hiding this comment

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

I think the best way to check that would be to test the error code:

grpc.Code(err) != codes.Unknown

From the docs:

Code returns the error code for err if it was produced by the rpc system. Otherwise, it returns codes.Unknown.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is what I am doing. However, in the doc it also says https://github.com/grpc/grpc-go/blob/master/codes/codes.go#L53.

Choose a reason for hiding this comment

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

Network errors should be Unavailable IIRC.

Copy link
Contributor

Choose a reason for hiding this comment

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

checking error code is not right way to differentiate client and server side errors. The common practice is that the server application need pack a bit more info into the error description (e.g., serialized error protobuf).

fmt.Printf("Could not greet: %v\n", err)

// Print the error details returned in response trailers.
fmt.Println("Error details:")
for k, v := range md {
fmt.Printf(" %s: %s\n", k, v[0])
}
os.Exit(1)
}

fmt.Printf("Greeting: %s\n", r.Message)
}
87 changes: 87 additions & 0 deletions examples/error_metadata/greeter_server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

package main

import (
"log"
"net"
"strconv"

pb "google.golang.org/grpc/examples/error_metadata/helloworld"

"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)

const (
port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
count map[string]int
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
// Track the number of times the user has been greeted.
s.count[in.Name] += 1
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the goal of this example is to demonstrate how to set and read error details. I am wondering if involving metadata handling makes it over complicated.

BTW, you need mutex to guard the access of s.count. It is racy if you have multiple concurrent clients.

Choose a reason for hiding this comment

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

Re: metadata, see grpc/grpc#4543, whose Go version this PR is trying to solve.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. Then I need think through how this can be achieved in a nice way.


// Return an error with details using the response trailer.
if s.count[in.Name] > 1 {
m := make(map[string]string)
m["message"] = "Limit one greeting per person."
m["count"] = strconv.Itoa(s.count[in.Name])
md := metadata.New(m)
grpc.SetTrailer(ctx, md)

return nil, grpc.Errorf(codes.ResourceExhausted, "Request limit exceeded.")
}

return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()

cm := make(map[string]int)
pb.RegisterGreeterServer(s, &server{cm})
s.Serve(lis)
}
134 changes: 134 additions & 0 deletions examples/error_metadata/helloworld/helloworld.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading