gRPC stands for Remote Procedure Call (RPC) protocol, that leverages Protocol Buffers (protobufs) as its message format. the client application can directly call method available on a remote server using method stubs. Protobufs are alternative to formats like JSON or XML, which is smaller, simpler and more efficient way of serializing data. advantages over restful api:
- reduces the size of payloads being sent
- use HTTP/2: concurrent requests, streaming instead of request-response, smaller sensitivity to latency
- gRPC contracts are stricter and clearly defined it fits especially good for IoT, mobile devices or other constrained/low-power environments. disadvantages are: Not all clients (browsers) support the use of HTTP/2
an boilerplate for gRPC is https://github.com/MartinHeinz/python-project-blueprint
components
protocol
an example protocol is like this:
// example.proto
syntax = "proto3";
package example;
message User {
int32 id = 1;
string name = 2;
}
message UserInfo {
int32 age = 1;
string address = 2;
string phoneNumber = 3;
}
methods
service UserService {
rpc GetUserInfo (User) returns (UserInfo) {}
}
Installation and Setting Up
#! /bin/bash
# Download and Unzip compiler
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
unzip protoc-3.11.4-linux-x86_64.zip -d protoc3
# Move the binary to directory which is PATH
sudo mv protoc3/bin/* /usr/local/bin/
sudo mv protoc3/include/* /usr/local/include/
# Change owner
sudo chown $USER /usr/local/bin/protoc
sudo chown -R $USER /usr/local/include/google
# Test if it works
protoc --version
# libprotoc 3.11.4
#activate python environment
source .../venv/bin/activate
pip install grpcio grpcio-tools
usage
server
to start the server you need to run: python -m blueprint
build server
// put these in the protocol file echo.proto
syntax = "proto3";
package echo;
// The request message containing the user's message.
message EchoRequest {
string message = 1;
}
// The response message containing the original message.
message EchoReply {
string message = 1;
}
// The echo service definition.
service Echo {
// Echo back reply.
rpc Reply (EchoRequest) returns (EchoReply) {}
}
to use these protocol definition we need server and client interfaces:
python3 -m grpc_tools.protoc \
#specify output generated *_pb2.py and *_grpc_pb2.py files respectively.
--python_out=./blueprint/generated \
--grpc_python_out=./blueprint/generated \
#specify protoscol file path
./blueprint/proto/*.proto
sed -i -E 's/^import.*_pb2/from . \0/' ./blueprint/generated/*.py
utilize generated service
# grpc.py
from .generated import echo_pb2_grpc, echo_pb2
class Echoer(echo_pb2_grpc.EchoServicer):
def Reply(self, request, context):
return echo_pb2.EchoReply(message=f'You said: {request.message}')
application server
# app.py
from concurrent import futures
import grpc
from .generated import echo_pb2_grpc
from .grpc import Echoer
class Server:
@staticmethod
def run():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
echo_pb2_grpc.add_EchoServicer_to_server(Echoer(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
# __main__.py
from .app import Server
if __name__ == '__main__':
Server.run()
client
to run client: $ python -m blueprint.echo_client
from __future__ import print_function
import logging
import grpc
from .generated import echo_pb2
from .generated import echo_pb2_grpc
def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = echo_pb2_grpc.EchoStub(channel)
response = stub.Reply(echo_pb2.EchoRequest(message='Hello World!'))
print("Echo client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
pytest
# conftest.py
import pytest
@pytest.fixture(scope='module')
def grpc_add_to_server():
from blueprint.generated.echo_pb2_grpc import add_EchoServicer_to_server
return add_EchoServicer_to_server
@pytest.fixture(scope='module')
def grpc_servicer():
from blueprint.grpc import Echoer
return Echoer()
@pytest.fixture(scope='module')
def grpc_stub(grpc_channel):
from blueprint.generated.echo_pb2_grpc import EchoStub
return EchoStub(grpc_channel)
# test_grpc.py
def test_reply(grpc_stub):
value = 'test-data'
request = blueprint.echo_pb2.EchoRequest(message=value)
response = grpc_stub.Reply(request)
assert response.message == f'You said: {value}'