mirror of https://github.com/grpc/grpc.git
parent
1805e2e431
commit
30cc99d42c
5 changed files with 407 additions and 0 deletions
@ -0,0 +1,96 @@ |
||||
# gRPC Python Error Handling Example |
||||
|
||||
The goal of this example is sending error status from server that is more complicated than a code and detail string. |
||||
|
||||
The definition for an RPC method in proto files contains request message and response message. There are many error states that can be shared across RPC methods (e.g. stack trace, insufficient quota). Using a different path to handle error will make the code more maintainable. |
||||
|
||||
Ideally, the final status of an RPC should be described in the trailing headers of HTTP2, and gRPC Python provides helper functions in `grpcio-status` package to assist the packing and unpacking of error status. |
||||
|
||||
### Definition of Status Proto |
||||
|
||||
```proto |
||||
// The `Status` type defines a logical error model that is suitable for different |
||||
// programming environments, including REST APIs and RPC APIs. It is used by |
||||
// [gRPC](https://github.com/grpc). The error model is designed to be: |
||||
// |
||||
// - Simple to use and understand for most users |
||||
// - Flexible enough to meet unexpected needs |
||||
// |
||||
// # Overview |
||||
// |
||||
// The `Status` message contains three pieces of data: error code, error message, |
||||
// and error details. The error code should be an enum value of |
||||
// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The |
||||
// error message should be a developer-facing English message that helps |
||||
// developers *understand* and *resolve* the error. If a localized user-facing |
||||
// error message is needed, put the localized message in the error details or |
||||
// localize it in the client. The optional error details may contain arbitrary |
||||
// information about the error. There is a predefined set of error detail types |
||||
// in the package `google.rpc` that can be used for common error conditions. |
||||
// |
||||
// # Language mapping |
||||
// |
||||
// The `Status` message is the logical representation of the error model, but it |
||||
// is not necessarily the actual wire format. When the `Status` message is |
||||
// exposed in different client libraries and different wire protocols, it can be |
||||
// mapped differently. For example, it will likely be mapped to some exceptions |
||||
// in Java, but more likely mapped to some error codes in C. |
||||
// |
||||
// # Other uses |
||||
// |
||||
// The error model and the `Status` message can be used in a variety of |
||||
// environments, either with or without APIs, to provide a |
||||
// consistent developer experience across different environments. |
||||
// |
||||
// Example uses of this error model include: |
||||
// |
||||
// - Partial errors. If a service needs to return partial errors to the client, |
||||
// it may embed the `Status` in the normal response to indicate the partial |
||||
// errors. |
||||
// |
||||
// - Workflow errors. A typical workflow has multiple steps. Each step may |
||||
// have a `Status` message for error reporting. |
||||
// |
||||
// - Batch operations. If a client uses batch request and batch response, the |
||||
// `Status` message should be used directly inside batch response, one for |
||||
// each error sub-response. |
||||
// |
||||
// - Asynchronous operations. If an API call embeds asynchronous operation |
||||
// results in its response, the status of those operations should be |
||||
// represented directly using the `Status` message. |
||||
// |
||||
// - Logging. If some API errors are stored in logs, the message `Status` could |
||||
// be used directly after any stripping needed for security/privacy reasons. |
||||
message Status { |
||||
// The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. |
||||
int32 code = 1; |
||||
|
||||
// A developer-facing error message, which should be in English. Any |
||||
// user-facing error message should be localized and sent in the |
||||
// [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. |
||||
string message = 2; |
||||
|
||||
// A list of messages that carry the error details. There is a common set of |
||||
// message types for APIs to use. |
||||
repeated google.protobuf.Any details = 3; |
||||
} |
||||
``` |
||||
|
||||
### Usage of Well-Known-Proto `Any` |
||||
|
||||
Please check [ProtoBuf Document: Any](https://developers.google.com/protocol-buffers/docs/reference/python-generated#any) |
||||
|
||||
```Python |
||||
any_message.Pack(message) |
||||
any_message.Unpack(message) |
||||
assert any_message.Is(message.DESCRIPTOR) |
||||
``` |
||||
|
||||
### Common Protos |
||||
|
||||
These protos are defined by Google Cloud API. Most error cases are covered in `error_dettails.proto`, but you may provide any custom error detail proto you want. |
||||
|
||||
Please refer to: |
||||
* [code.proto](https://github.com/googleapis/api-common-protos/blob/master/google/rpc/code.proto). |
||||
* [status.proto](https://github.com/googleapis/api-common-protos/blob/master/google/rpc/status.proto). |
||||
* [error_details.proto](https://github.com/googleapis/api-common-protos/blob/master/google/rpc/error_details.proto). |
@ -0,0 +1,50 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
"""This example handles rich error status in client-side.""" |
||||
|
||||
from __future__ import print_function |
||||
import logging |
||||
|
||||
import grpc |
||||
from grpc_status import rpc_status |
||||
from google.rpc import error_details_pb2 |
||||
|
||||
import helloworld_pb2 |
||||
import helloworld_pb2_grpc |
||||
|
||||
|
||||
def run(): |
||||
# NOTE(gRPC Python Team): .close() is possible on a channel and should be |
||||
# used in circumstances in which the with statement does not fit the needs |
||||
# of the code. |
||||
with grpc.insecure_channel('localhost:50051') as channel: |
||||
stub = helloworld_pb2_grpc.GreeterStub(channel) |
||||
try: |
||||
response = stub.SayHello(helloworld_pb2.HelloRequest(name='Alice')) |
||||
print('Call success:', response.message) |
||||
except grpc.RpcError as rpc_error: |
||||
print('Call failure:', rpc_error) |
||||
status = rpc_status.from_call(rpc_error) |
||||
for detail in status.details: |
||||
if detail.Is(error_details_pb2.QuotaFailure.DESCRIPTOR): |
||||
info = error_details_pb2.QuotaFailure() |
||||
detail.Unpack(info) |
||||
print('Quota failure:', info) |
||||
else: |
||||
print('Unexpected failure:', detail) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
run() |
@ -0,0 +1,81 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
"""This example sends out rich error status from server-side.""" |
||||
|
||||
from concurrent import futures |
||||
import time |
||||
import logging |
||||
import threading |
||||
|
||||
import grpc |
||||
from grpc_status import rpc_status |
||||
|
||||
from google.protobuf import any_pb2 |
||||
from google.rpc import code_pb2, status_pb2, error_details_pb2 |
||||
|
||||
import helloworld_pb2 |
||||
import helloworld_pb2_grpc |
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24 |
||||
|
||||
|
||||
def create_greet_limit_exceed_error_status(name): |
||||
detail = any_pb2.Any() |
||||
detail.Pack( |
||||
error_details_pb2.QuotaFailure( |
||||
violations=[ |
||||
error_details_pb2.QuotaFailure.Violation( |
||||
subject="name:%s" % name, |
||||
description="Limit one greeting per person", |
||||
) |
||||
],)) |
||||
return status_pb2.Status( |
||||
code=code_pb2.RESOURCE_EXHAUSTED, |
||||
message='Request limit exceeded.', |
||||
details=[detail], |
||||
) |
||||
|
||||
|
||||
class LimitedGreeter(helloworld_pb2_grpc.GreeterServicer): |
||||
|
||||
def __init__(self): |
||||
self._lock = threading.RLock() |
||||
self._greeted = set() |
||||
|
||||
def SayHello(self, request, context): |
||||
with self._lock: |
||||
if request.name in self._greeted: |
||||
rich_status = create_greet_limit_exceed_error_status( |
||||
request.name) |
||||
context.abort_with_status(rpc_status.to_status(rich_status)) |
||||
else: |
||||
self._greeted.add(request.name) |
||||
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name) |
||||
|
||||
|
||||
def serve(): |
||||
server = grpc.server(futures.ThreadPoolExecutor()) |
||||
helloworld_pb2_grpc.add_GreeterServicer_to_server(LimitedGreeter(), server) |
||||
server.add_insecure_port('[::]:50051') |
||||
server.start() |
||||
try: |
||||
while True: |
||||
time.sleep(_ONE_DAY_IN_SECONDS) |
||||
except KeyboardInterrupt: |
||||
server.stop(None) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
serve() |
@ -0,0 +1,134 @@ |
||||
# Generated by the protocol buffer compiler. DO NOT EDIT! |
||||
# source: helloworld.proto |
||||
|
||||
import sys |
||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) |
||||
from google.protobuf import descriptor as _descriptor |
||||
from google.protobuf import message as _message |
||||
from google.protobuf import reflection as _reflection |
||||
from google.protobuf import symbol_database as _symbol_database |
||||
from google.protobuf import descriptor_pb2 |
||||
# @@protoc_insertion_point(imports) |
||||
|
||||
_sym_db = _symbol_database.Default() |
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor( |
||||
name='helloworld.proto', |
||||
package='helloworld', |
||||
syntax='proto3', |
||||
serialized_pb=_b('\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2I\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3') |
||||
) |
||||
|
||||
|
||||
|
||||
|
||||
_HELLOREQUEST = _descriptor.Descriptor( |
||||
name='HelloRequest', |
||||
full_name='helloworld.HelloRequest', |
||||
filename=None, |
||||
file=DESCRIPTOR, |
||||
containing_type=None, |
||||
fields=[ |
||||
_descriptor.FieldDescriptor( |
||||
name='name', full_name='helloworld.HelloRequest.name', index=0, |
||||
number=1, type=9, cpp_type=9, label=1, |
||||
has_default_value=False, default_value=_b("").decode('utf-8'), |
||||
message_type=None, enum_type=None, containing_type=None, |
||||
is_extension=False, extension_scope=None, |
||||
options=None), |
||||
], |
||||
extensions=[ |
||||
], |
||||
nested_types=[], |
||||
enum_types=[ |
||||
], |
||||
options=None, |
||||
is_extendable=False, |
||||
syntax='proto3', |
||||
extension_ranges=[], |
||||
oneofs=[ |
||||
], |
||||
serialized_start=32, |
||||
serialized_end=60, |
||||
) |
||||
|
||||
|
||||
_HELLOREPLY = _descriptor.Descriptor( |
||||
name='HelloReply', |
||||
full_name='helloworld.HelloReply', |
||||
filename=None, |
||||
file=DESCRIPTOR, |
||||
containing_type=None, |
||||
fields=[ |
||||
_descriptor.FieldDescriptor( |
||||
name='message', full_name='helloworld.HelloReply.message', index=0, |
||||
number=1, type=9, cpp_type=9, label=1, |
||||
has_default_value=False, default_value=_b("").decode('utf-8'), |
||||
message_type=None, enum_type=None, containing_type=None, |
||||
is_extension=False, extension_scope=None, |
||||
options=None), |
||||
], |
||||
extensions=[ |
||||
], |
||||
nested_types=[], |
||||
enum_types=[ |
||||
], |
||||
options=None, |
||||
is_extendable=False, |
||||
syntax='proto3', |
||||
extension_ranges=[], |
||||
oneofs=[ |
||||
], |
||||
serialized_start=62, |
||||
serialized_end=91, |
||||
) |
||||
|
||||
DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST |
||||
DESCRIPTOR.message_types_by_name['HelloReply'] = _HELLOREPLY |
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR) |
||||
|
||||
HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), dict( |
||||
DESCRIPTOR = _HELLOREQUEST, |
||||
__module__ = 'helloworld_pb2' |
||||
# @@protoc_insertion_point(class_scope:helloworld.HelloRequest) |
||||
)) |
||||
_sym_db.RegisterMessage(HelloRequest) |
||||
|
||||
HelloReply = _reflection.GeneratedProtocolMessageType('HelloReply', (_message.Message,), dict( |
||||
DESCRIPTOR = _HELLOREPLY, |
||||
__module__ = 'helloworld_pb2' |
||||
# @@protoc_insertion_point(class_scope:helloworld.HelloReply) |
||||
)) |
||||
_sym_db.RegisterMessage(HelloReply) |
||||
|
||||
|
||||
DESCRIPTOR.has_options = True |
||||
DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW')) |
||||
|
||||
_GREETER = _descriptor.ServiceDescriptor( |
||||
name='Greeter', |
||||
full_name='helloworld.Greeter', |
||||
file=DESCRIPTOR, |
||||
index=0, |
||||
options=None, |
||||
serialized_start=93, |
||||
serialized_end=166, |
||||
methods=[ |
||||
_descriptor.MethodDescriptor( |
||||
name='SayHello', |
||||
full_name='helloworld.Greeter.SayHello', |
||||
index=0, |
||||
containing_service=None, |
||||
input_type=_HELLOREQUEST, |
||||
output_type=_HELLOREPLY, |
||||
options=None, |
||||
), |
||||
]) |
||||
_sym_db.RegisterServiceDescriptor(_GREETER) |
||||
|
||||
DESCRIPTOR.services_by_name['Greeter'] = _GREETER |
||||
|
||||
# @@protoc_insertion_point(module_scope) |
@ -0,0 +1,46 @@ |
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! |
||||
import grpc |
||||
|
||||
import helloworld_pb2 as helloworld__pb2 |
||||
|
||||
|
||||
class GreeterStub(object): |
||||
"""The greeting service definition. |
||||
""" |
||||
|
||||
def __init__(self, channel): |
||||
"""Constructor. |
||||
|
||||
Args: |
||||
channel: A grpc.Channel. |
||||
""" |
||||
self.SayHello = channel.unary_unary( |
||||
'/helloworld.Greeter/SayHello', |
||||
request_serializer=helloworld__pb2.HelloRequest.SerializeToString, |
||||
response_deserializer=helloworld__pb2.HelloReply.FromString, |
||||
) |
||||
|
||||
|
||||
class GreeterServicer(object): |
||||
"""The greeting service definition. |
||||
""" |
||||
|
||||
def SayHello(self, request, context): |
||||
"""Sends a greeting |
||||
""" |
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED) |
||||
context.set_details('Method not implemented!') |
||||
raise NotImplementedError('Method not implemented!') |
||||
|
||||
|
||||
def add_GreeterServicer_to_server(servicer, server): |
||||
rpc_method_handlers = { |
||||
'SayHello': grpc.unary_unary_rpc_method_handler( |
||||
servicer.SayHello, |
||||
request_deserializer=helloworld__pb2.HelloRequest.FromString, |
||||
response_serializer=helloworld__pb2.HelloReply.SerializeToString, |
||||
), |
||||
} |
||||
generic_handler = grpc.method_handlers_generic_handler( |
||||
'helloworld.Greeter', rpc_method_handlers) |
||||
server.add_generic_rpc_handlers((generic_handler,)) |
Loading…
Reference in new issue