質問
Python で grpc を作成し、インターネット上のチュートリアルに従うと、エラーが発生します。
gRPC Python で反復子リクエストを使用してデータ ストリーミングを行うと、「反復リクエストで例外が発生しました!」というメッセージが表示されます。
理由
StackOverflowから理由が見つかりました。
rpcをstream-stream ではなく unary-stream にする予定の 場合:
残念ながらストリームを であると宣言したにもかかわらず
stream-stream
、それを のように使用していたときにも同じエラーが発生しましたunary-stream
。メッセージを修正してコードを再生成する必要がありました。
問題を解決する前に、エラーの原因を簡単に説明します。
gRPC のサービス API には次の 4 種類があります。
-
UnaryAPI:普通一元方法
-
ServerStreaming: サーバー側のプッシュ ストリーム
-
ClientStreaming: クライアントプッシュストリーム
-
BidirectionalStreaming: 双方向プッシュ ストリーム
対応する 4 つのプロトは次のように定義されます。
syntax = "proto3";
option go_package = "github.com/lixd/grpc-go-example/features/proto/echo";
package echo;
// Echo 服务,包含了4种类型API
service Echo {
// UnaryAPI
rpc UnaryEcho(EchoRequest) returns (EchoResponse) {
}
// SServerStreaming
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {
}
// ClientStreamingE
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {
}
// BidirectionalStreaming
rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {
}
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
-
単項エコー
-
入力リクエストは単項です
-
出力応答も単項です
-
-
サーバーストリーミングエコー
-
入力リクエストは単項です
-
出力応答はストリームです
-
-
クライアントストリーミングエコー
-
入力リクエストはストリームです
-
出力応答は単項です
-
-
双方向ストリーミングエコー
-
入力リクエストはストリームです
-
出力応答もストリームです
-
対応する生成されたファイルxxx_pb2_grpc.py
の定義は次のとおりです。
次のコードは、公式サンプルのRoute_guide_pb2_grpc.pyからのもので、上で定義した関数名とは異なりますが、4 つの gRPC メソッドの違いがわかります。
-
self.GetFeature = channel.unary_unary
-
self.ListFeatures = channel.unary_stream
-
self.RecordRoute = channel.stream_unary
-
self.RouteChat = channel.stream_stream
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import route_guide_pb2 as route__guide__pb2
class RouteGuideStub(object):
"""Interface exported by the server.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.GetFeature = channel.unary_unary(
'/routeguide.RouteGuide/GetFeature',
request_serializer=route__guide__pb2.Point.SerializeToString,
response_deserializer=route__guide__pb2.Feature.FromString,
)
self.ListFeatures = channel.unary_stream(
'/routeguide.RouteGuide/ListFeatures',
request_serializer=route__guide__pb2.Rectangle.SerializeToString,
response_deserializer=route__guide__pb2.Feature.FromString,
)
self.RecordRoute = channel.stream_unary(
'/routeguide.RouteGuide/RecordRoute',
request_serializer=route__guide__pb2.Point.SerializeToString,
response_deserializer=route__guide__pb2.RouteSummary.FromString,
)
self.RouteChat = channel.stream_stream(
'/routeguide.RouteGuide/RouteChat',
request_serializer=route__guide__pb2.RouteNote.SerializeToString,
response_deserializer=route__guide__pb2.RouteNote.FromString,
)
オンライン単項チュートリアルに従って記述します (最初のタイプ)。実際には、ストリーミングにはクライアントを使用する必要があります (3 番目のタイプ)
最初の定義 (単項から単項へ) によれば、書き込み方法は次のようになります。
with grpc.insecure_channel('localhost:50051') as channel:
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
feature = stub.GetFeature(route_guide_pb2.Point(latitude=40, longitude=-74))
3 番目の定義 (ストリームから単項へ) によれば、書き込み方法は次のようになります。
points= [
route_guide_pb2.Point(latitude=0, longitude=0),
route_guide_pb2.Point(latitude=0, longitude=1),
route_guide_pb2.Point(latitude=1, longitude=0),
route_guide_pb2.Point(latitude=1, longitude=1),
]
def generate_requests(points):
for point in points:
yield point
with grpc.insecure_channel('localhost:50051') as channel:
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
feature = stub.RecordRoute(generate_requests())
ジェネレーターを宣言しgenerate_route
、ジェネレーターを使用して毎回ストリームからリクエストを取得して送信する必要があります。
公式サンプル
より詳細で完全なコード。スキップできますが、コード構造全体をよりよく理解できます。
リクエストに相当する、Feature、Point、Rectangle、RouteNote、Routesummary、およびその他のクラスの定義が含まれます。
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor
class Feature(_message.Message):
__slots__ = ["location", "name"]
LOCATION_FIELD_NUMBER: _ClassVar[int]
NAME_FIELD_NUMBER: _ClassVar[int]
location: Point
name: str
def __init__(self, name: _Optional[str] = ..., location: _Optional[_Union[Point, _Mapping]] = ...) -> None: ...
class Point(_message.Message):
__slots__ = ["latitude", "longitude"]
LATITUDE_FIELD_NUMBER: _ClassVar[int]
LONGITUDE_FIELD_NUMBER: _ClassVar[int]
latitude: int
longitude: int
def __init__(self, latitude: _Optional[int] = ..., longitude: _Optional[int] = ...) -> None: ...
class Rectangle(_message.Message):
__slots__ = ["hi", "lo"]
HI_FIELD_NUMBER: _ClassVar[int]
LO_FIELD_NUMBER: _ClassVar[int]
hi: Point
lo: Point
def __init__(self, lo: _Optional[_Union[Point, _Mapping]] = ..., hi: _Optional[_Union[Point, _Mapping]] = ...) -> None: ...
class RouteNote(_message.Message):
__slots__ = ["location", "message"]
LOCATION_FIELD_NUMBER: _ClassVar[int]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
location: Point
message: str
def __init__(self, location: _Optional[_Union[Point, _Mapping]] = ..., message: _Optional[str] = ...) -> None: ...
class RouteSummary(_message.Message):
__slots__ = ["distance", "elapsed_time", "feature_count", "point_count"]
DISTANCE_FIELD_NUMBER: _ClassVar[int]
ELAPSED_TIME_FIELD_NUMBER: _ClassVar[int]
FEATURE_COUNT_FIELD_NUMBER: _ClassVar[int]
POINT_COUNT_FIELD_NUMBER: _ClassVar[int]
distance: int
elapsed_time: int
feature_count: int
point_count: int
def __init__(self, point_count: _Optional[int] = ..., feature_count: _Optional[int] = ..., distance: _Optional[int] = ..., elapsed_time: _Optional[int] = ...) -> None: ...
GRPCの定義
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import route_guide_pb2 as route__guide__pb2
class RouteGuideStub(object):
"""Interface exported by the server.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.GetFeature = channel.unary_unary(
'/routeguide.RouteGuide/GetFeature',
request_serializer=route__guide__pb2.Point.SerializeToString,
response_deserializer=route__guide__pb2.Feature.FromString,
)
self.ListFeatures = channel.unary_stream(
'/routeguide.RouteGuide/ListFeatures',
request_serializer=route__guide__pb2.Rectangle.SerializeToString,
response_deserializer=route__guide__pb2.Feature.FromString,
)
self.RecordRoute = channel.stream_unary(
'/routeguide.RouteGuide/RecordRoute',
request_serializer=route__guide__pb2.Point.SerializeToString,
response_deserializer=route__guide__pb2.RouteSummary.FromString,
)
self.RouteChat = channel.stream_stream(
'/routeguide.RouteGuide/RouteChat',
request_serializer=route__guide__pb2.RouteNote.SerializeToString,
response_deserializer=route__guide__pb2.RouteNote.FromString,
)
# 以下省略部分
# Copyright 2015 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.
"""The Python implementation of the gRPC route guide client."""
from __future__ import print_function
import logging
import random
import grpc
import route_guide_pb2
import route_guide_pb2_grpc
import route_guide_resources
# ------------------ unary_unary (below) ------------------
def guide_get_one_feature(stub, point):
feature = stub.GetFeature(point)
if not feature.location:
print("Server returned incomplete feature")
return
if feature.name:
print("Feature called %s at %s" % (feature.name, feature.location))
else:
print("Found no feature at %s" % feature.location)
def guide_get_feature(stub):
guide_get_one_feature(
stub, route_guide_pb2.Point(latitude=409146138, longitude=-746188906))
guide_get_one_feature(stub, route_guide_pb2.Point(latitude=0, longitude=0))
# ------------------ unary_stream (below) ------------------
def guide_list_features(stub):
rectangle = route_guide_pb2.Rectangle(
lo=route_guide_pb2.Point(latitude=400000000, longitude=-750000000),
hi=route_guide_pb2.Point(latitude=420000000, longitude=-730000000))
print("Looking for features between 40, -75 and 42, -73")
features = stub.ListFeatures(rectangle)
for feature in features:
print("Feature called %s at %s" % (feature.name, feature.location))
# ------------------ stream_unary (below) ------------------
def generate_route(feature_list):
for _ in range(0, 10):
random_feature = feature_list[random.randint(0, len(feature_list) - 1)]
print("Visiting point %s" % random_feature.location)
yield random_feature.location
def guide_record_route(stub):
feature_list = route_guide_resources.read_route_guide_database()
route_iterator = generate_route(feature_list)
route_summary = stub.RecordRoute(route_iterator)
print("Finished trip with %s points " % route_summary.point_count)
print("Passed %s features " % route_summary.feature_count)
print("Travelled %s meters " % route_summary.distance)
print("It took %s seconds " % route_summary.elapsed_time)
# ------------------ stream_stream (below) ------------------
def make_route_note(message, latitude, longitude):
return route_guide_pb2.RouteNote(
message=message,
location=route_guide_pb2.Point(latitude=latitude, longitude=longitude))
def generate_messages():
messages = [
make_route_note("First message", 0, 0),
make_route_note("Second message", 0, 1),
make_route_note("Third message", 1, 0),
make_route_note("Fourth message", 0, 0),
make_route_note("Fifth message", 1, 0),
]
for msg in messages:
print("Sending %s at %s" % (msg.message, msg.location))
yield msg
def guide_route_chat(stub):
responses = stub.RouteChat(generate_messages())
for response in responses:
print("Received message %s at %s" %
(response.message, response.location))
# ------------------ run ------------------
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 = route_guide_pb2_grpc.RouteGuideStub(channel)
print("-------------- GetFeature --------------")
guide_get_feature(stub)
print("-------------- ListFeatures --------------")
guide_list_features(stub)
print("-------------- RecordRoute --------------")
guide_record_route(stub)
print("-------------- RouteChat --------------")
guide_route_chat(stub)
if __name__ == '__main__':
logging.basicConfig()
run()
解決
yield
要約すると、解決策は一度に1 データずつジェネレーターを作成することです。
def generate_requests(points):
for point in points:
yield point
with grpc.insecure_channel('localhost:50051') as channel:
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
feature = stub.RecordRoute(generate_requests())