Encoding

GRPC supports sending messages using any encoding format, and grpclib supports this feature as well.

By default, gRPC interprets application/grpc content type as application/grpc+proto content type. So by default gRPC uses Protocol Buffers as encoding format.

But why content type has such name with a proto subtype? This is because messages in gRPC are sent as length-delimited stream of binary blobs. This format can’t be changed, so content type should always be in the form of application/grpc+{subtype}, where {subtype} can be anything you want, e.g. proto, fbs, json, thrift, bson, msgpack.

Codec

In order to use custom serialization format, you should implement CodecBase abstract base class:

from grpclib.encoding.base import CodecBase

class JSONCodec(CodecBase):
    __content_subtype__ = 'json'

    def encode(self, message, message_type):
        return json.dumps(message, ensure_ascii=False).encode('utf-8')

    def decode(self, data: bytes, message_type):
        return json.loads(data.decode('utf-8'))

If your format doesn’t have interface definition language (like protocol buffers language) and code-generation tools (like protoc compiler), you will have to manage your server-side and client-side code yourself. JSON format doesn’t have such tools, so let’s try define our server-side and client side code.

Naming Conventions

Even if you don’t use Protocol Buffers for messages encoding, this language also defines coding style for services definition. These rules are related to service names and method names, which are used by gRPC to build :path pseudo header:

:path = /dotted.package.CamelCaseServiceName/CamelCaseMethodName

Protocol Buffers Style Guide says:

You should use CamelCase (with an initial capital) for both the service name and any RPC method names.

Server example

from grpclib.const import Cardinality, Handler
from grpclib.server import Server

class PingServiceHandler:

    async def Ping(self, stream):
        request = await stream.recv_message()
        ...
        await stream.send_message({'value': 'pong'})

    def __mapping__(self):
        return {
            '/ping.PingService/Ping': Handler(
                self.UnaryUnary,
                Cardinality.UNARY_UNARY,
                None,
                None,
            ),
        }

server = Server([PingServiceHandler()], codec=JSONCodec())

Client example

from grpclib.client import Channel, UnaryUnaryMethod

class PingServiceStub:

    def __init__(self, channel):
        self.Ping = UnaryUnaryMethod(
            channel,
            '/ping.PingService/Ping',
            None,
            None,
        )

channel = Channel(codec=JSONCodec())
ping_stub = PingServiceStub(channel)
...
await ping_stub.Ping({'value': 'ping'})