gRPC-Example#
By default, gRPC uses Protocol Buffers (Protobuf) for serialising data, although it also works with other data formats such as JSON.
Define the data structure#
The first step when working with protocol buffers is to define the structure for
the data you want to serialise in a .proto
file. Protocol buffer data is
structured as messages, where each message is a small logical record of
information containing a series of name-value pairs called fields. Here’s a
simple example accounts.proto
:
// SPDX-FileCopyrightText: 2021 Veit Schiele
//
// SPDX-License-Identifier: BSD-3-Clause
syntax = "proto3";
message Account {
Warning
You shouldn’t simply use uint32
for user or group IDs, as these would be
far too easy to guess. You can use an RFC 4122-compliant implementation
for this purpose. You can find a corresponding protobuf configuration in
rfc4122.proto
.
After you have defined your data structure, you use the protocol buffer compiler
protoc
to generate descriptors in your preferred languages. These provide
simple accessors for each field, as well as methods to serialise the whole
structure. For example, if your language is Python, running the compiler on the
example above will generate declarators you can then use in your application to
populate, serialise, and retrieve protocol buffer messages.
Define the gRPC service#
gRPC services are also defined in the .proto
files, with RPC method
parameters and return types specified as protocol buffer messages:
uint32 account_id = 1;
string account_name = 2;
}
message CreateAccountRequest {
string account_name = 1;
}
message CreateAccountResult {
Account account = 1;
}
message GetAccountsRequest {
repeated Account account = 1;
}
message GetAccountsResult {
Account account = 1;
}
service Accounts {
rpc CreateAccount (CreateAccountRequest) returns (CreateAccountResult);
rpc GetAccounts (GetAccountsRequest) returns (stream GetAccountsResult);
}
Generate the gRPC Code#
$ pipenv install grpcio grpcio-tools
$ pipenv run python -m grpc_tools.protoc --python_out=. --grpc_python_out=. accounts.proto
This generates two files:
accounts_pb2.py
contains classes for the messages defined in
accounts.proto
.accounts_pb2_grpc.py
contains the defined classes
AccountsStub
for calling RPCs,AccountsServicer
for the API definition of the service and a functionadd_AccountsServicer_to_server
for the server.
Create server#
For this we write the file accounts_server.py
:
# SPDX-FileCopyrightText: 2021 Veit Schiele
#
# SPDX-License-Identifier: BSD-3-Clause
import logging
from concurrent import futures
import accounts_pb2 as accounts_messages
import accounts_pb2_grpc as accounts_service
import grpc
class AccountsService(accounts_service.AccountsServicer):
def CreateAccount(self, request, context):
metadata = dict(context.invocation_metadata())
print(metadata)
account = accounts_messages.Account(
account_name=request.account_name, account_id=1
)
return accounts_messages.CreateAccountResult(account=account)
def GetAccounts(self, request, context):
for account in request.account:
account = accounts_messages.Account(
account_name=account.account_name,
account_id=account.account_id,
)
yield accounts_messages.GetAccountsResult(account=account)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
accounts_service.add_AccountsServicer_to_server(AccountsService(), server)
server.add_insecure_port("[::]:8081")
server.start()
server.wait_for_termination()
if __name__ == "__main__":
logging.basicConfig()
serve()
Create client#
For this we create accounts_client.py
:
# SPDX-FileCopyrightText: 2021 Veit Schiele
#
# SPDX-License-Identifier: BSD-3-Clause
import logging
import accounts_pb2 as accounts_messages
import accounts_pb2_grpc as accounts_service
import grpc
def run():
channel = grpc.insecure_channel("localhost:8081")
stub = accounts_service.AccountsStub(channel)
response = stub.CreateAccount(
accounts_messages.CreateAccountRequest(account_name="tom"),
)
print("Account created:", response.account.account_name)
if __name__ == "__main__":
logging.basicConfig()
run()
Run client and server#
Starting the server:
$ pipenv run python accounts_server.py
Starting the client from another terminal:
$ pipenv run python accounts_client.py Account created: tom