You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
9.9 KiB
317 lines
9.9 KiB
.. _module-pw_rpc_nanopb:
|
|
|
|
------
|
|
nanopb
|
|
------
|
|
``pw_rpc`` can generate services which encode/decode RPC requests and responses
|
|
as nanopb message structs.
|
|
|
|
Usage
|
|
=====
|
|
To enable nanopb code generation, the build argument
|
|
``dir_pw_third_party_nanopb`` must be set to point to a local nanopb
|
|
installation.
|
|
|
|
Define a ``pw_proto_library`` containing the .proto file defining your service
|
|
(and optionally other related protos), then depend on the ``nanopb_rpc``
|
|
version of that library in the code implementing the service.
|
|
|
|
.. code::
|
|
|
|
# chat/BUILD.gn
|
|
|
|
import("$dir_pw_build/target_types.gni")
|
|
import("$dir_pw_protobuf_compiler/proto.gni")
|
|
|
|
pw_proto_library("chat_protos") {
|
|
sources = [ "chat_protos/chat_service.proto" ]
|
|
}
|
|
|
|
# Library that implements the ChatService.
|
|
pw_source_set("chat_service") {
|
|
sources = [
|
|
"chat_service.cc",
|
|
"chat_service.h",
|
|
]
|
|
public_deps = [ ":chat_protos.nanopb_rpc" ]
|
|
}
|
|
|
|
A C++ header file is generated for each input .proto file, with the ``.proto``
|
|
extension replaced by ``.rpc.pb.h``. For example, given the input file
|
|
``chat_protos/chat_service.proto``, the generated header file will be placed
|
|
at the include path ``"chat_protos/chat_service.rpc.pb.h"``.
|
|
|
|
Generated code API
|
|
==================
|
|
Take the following RPC service as an example.
|
|
|
|
.. code:: protobuf
|
|
|
|
// chat/chat_protos/chat_service.proto
|
|
|
|
syntax = "proto3";
|
|
|
|
service ChatService {
|
|
// Returns information about a chatroom.
|
|
rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {}
|
|
|
|
// Lists all of the users in a chatroom. The response is streamed as there
|
|
// may be a large amount of users.
|
|
rpc ListUsersInRoom(ListUsersRequest) returns (stream ListUsersResponse) {}
|
|
|
|
// Uploads a file, in chunks, to a chatroom.
|
|
rpc UploadFile(stream UploadFileRequest) returns (UploadFileResponse) {}
|
|
|
|
// Sends messages to a chatroom while receiving messages from other users.
|
|
rpc Chat(stream ChatMessage) returns (stream ChatMessage) {}
|
|
}
|
|
|
|
Server-side
|
|
-----------
|
|
A C++ class is generated for each service in the .proto file. The class is
|
|
located within a special ``generated`` sub-namespace of the file's package.
|
|
|
|
The generated class is a base class which must be derived to implement the
|
|
service's methods. The base class is templated on the derived class.
|
|
|
|
.. code:: c++
|
|
|
|
#include "chat_protos/chat_service.rpc.pb.h"
|
|
|
|
class ChatService final : public generated::ChatService<ChatService> {
|
|
public:
|
|
// Implementations of the service's RPC methods; see below.
|
|
};
|
|
|
|
Unary RPC
|
|
^^^^^^^^^
|
|
A unary RPC is implemented as a function which takes in the RPC's request struct
|
|
and populates a response struct to send back, with a status indicating whether
|
|
the request succeeded.
|
|
|
|
.. code:: c++
|
|
|
|
pw::Status GetRoomInformation(pw::rpc::ServerContext& ctx,
|
|
const RoomInfoRequest& request,
|
|
RoomInfoResponse& response);
|
|
|
|
Server streaming RPC
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
A server streaming RPC receives the client's request message alongside a
|
|
``ServerWriter``, used to stream back responses.
|
|
|
|
.. code:: c++
|
|
|
|
void ListUsersInRoom(pw::rpc::ServerContext& ctx,
|
|
const ListUsersRequest& request,
|
|
pw::rpc::ServerWriter<ListUsersResponse>& writer);
|
|
|
|
The ``ServerWriter`` object is movable, and remains active until it is manually
|
|
closed or goes out of scope. The writer has a simple API to return responses:
|
|
|
|
.. cpp:function:: Status ServerWriter::Write(const T& response)
|
|
|
|
Writes a single response message to the stream. The returned status indicates
|
|
whether the write was successful.
|
|
|
|
.. cpp:function:: void ServerWriter::Finish(Status status = OkStatus())
|
|
|
|
Closes the stream and sends back the RPC's overall status to the client.
|
|
|
|
Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail.
|
|
|
|
.. attention::
|
|
|
|
Make sure to use ``std::move`` when passing the ``ServerWriter`` around to
|
|
avoid accidentally closing it and ending the RPC.
|
|
|
|
Client streaming RPC
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
.. attention::
|
|
|
|
``pw_rpc`` does not yet support client streaming RPCs.
|
|
|
|
Bidirectional streaming RPC
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. attention::
|
|
|
|
``pw_rpc`` does not yet support bidirectional streaming RPCs.
|
|
|
|
Client-side
|
|
-----------
|
|
A corresponding client class is generated for every service defined in the proto
|
|
file. Like the service class, it is placed under the ``generated`` namespace.
|
|
The class is named after the service, with a ``Client`` suffix. For example, the
|
|
``ChatService`` would create a ``generated::ChatServiceClient``.
|
|
|
|
The client class contains static methods to call each of the service's methods.
|
|
It is not meant to be instantiated. The signatures for the methods all follow
|
|
the same format, taking a channel through which to communicate, the initial
|
|
request struct, and a response handler.
|
|
|
|
.. code-block:: c++
|
|
|
|
static NanopbClientCall<UnaryResponseHandler<RoomInfoResponse>>
|
|
GetRoomInformation(Channel& channel,
|
|
const RoomInfoRequest& request,
|
|
UnaryResponseHandler<RoomInfoResponse> handler);
|
|
|
|
The ``NanopbClientCall`` object returned by the RPC invocation stores the active
|
|
RPC's context. For more information on ``ClientCall`` objects, refer to the
|
|
:ref:`core RPC documentation <module-pw_rpc-making-calls>`.
|
|
|
|
Response handlers
|
|
^^^^^^^^^^^^^^^^^
|
|
RPC responses are sent back to the caller through a response handler object.
|
|
These are classes with virtual callback functions implemented by the RPC caller
|
|
to handle RPC events.
|
|
|
|
There are two types of response handlers: unary and server-streaming, which are
|
|
used depending whether the method's responses are a stream or not.
|
|
|
|
Unary / client streaming RPC
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
A ``UnaryResponseHandler`` is used by methods where the server returns a single
|
|
response. It contains a callback for the response, which is only called once.
|
|
|
|
.. code-block:: c++
|
|
|
|
template <typename Response>
|
|
class UnaryResponseHandler {
|
|
public:
|
|
virtual ~UnaryResponseHandler() = default;
|
|
|
|
// Called when the response is received from the server with the method's
|
|
// status and the deserialized response struct.
|
|
virtual void ReceivedResponse(Status status, const Response& response) = 0;
|
|
|
|
// Called when an error occurs internally in the RPC client or server.
|
|
virtual void RpcError(Status) {}
|
|
};
|
|
|
|
.. cpp:class:: template <typename Response> UnaryResponseHandler
|
|
|
|
A handler for RPC methods which return a single response (i.e. unary and
|
|
client streaming).
|
|
|
|
.. cpp:function:: virtual void UnaryResponseHandler::ReceivedResponse(Status status, const Response& response)
|
|
|
|
Callback invoked when the response is recieved from the server. Guaranteed to
|
|
only be called once.
|
|
|
|
.. cpp:function:: virtual void UnaryResponseHandler::RpcError(Status status)
|
|
|
|
Callback invoked if an internal error occurs in the RPC system. Optional;
|
|
defaults to a no-op.
|
|
|
|
**Example implementation**
|
|
|
|
.. code-block:: c++
|
|
|
|
class RoomInfoHandler : public UnaryResponseHandler<RoomInfoResponse> {
|
|
public:
|
|
void ReceivedResponse(Status status,
|
|
const RoomInfoResponse& response) override {
|
|
if (status.ok()) {
|
|
response_ = response;
|
|
}
|
|
}
|
|
|
|
constexpr RoomInfoResponse& response() { return response_; }
|
|
|
|
private:
|
|
RoomInfoResponse response_;
|
|
};
|
|
|
|
Server streaming / bidirectional streaming RPC
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
For methods which return a response stream, a ``ServerStreamingResponseHandler``
|
|
is used.
|
|
|
|
.. code:: c++
|
|
|
|
class ServerStreamingResponseHandler {
|
|
public:
|
|
virtual ~ServerStreamingResponseHandler() = default;
|
|
|
|
// Called on every response received from the server with the deserialized
|
|
// response struct.
|
|
virtual void ReceivedResponse(const Response& response) = 0;
|
|
|
|
// Called when the server ends the stream with the overall RPC status.
|
|
virtual void Complete(Status status) = 0;
|
|
|
|
// Called when an error occurs internally in the RPC client or server.
|
|
virtual void RpcError(Status) {}
|
|
};
|
|
|
|
.. cpp:class:: template <typename Response> ServerStreamingResponseHandler
|
|
|
|
A handler for RPC methods which return zero or more responses (i.e. server
|
|
and bidirectional streaming).
|
|
|
|
.. cpp:function:: virtual void ServerStreamingResponseHandler::ReceivedResponse(const Response& response)
|
|
|
|
Callback invoked whenever a response is received from the server.
|
|
|
|
.. cpp:function:: virtual void ServerStreamingResponseHandler::Complete(Status status)
|
|
|
|
Callback invoked when the server ends the stream, with the overall status for
|
|
the RPC.
|
|
|
|
.. cpp:function:: virtual void ServerStreamingResponseHandler::RpcError(Status status)
|
|
|
|
Callback invoked if an internal error occurs in the RPC system. Optional;
|
|
defaults to a no-op.
|
|
|
|
**Example implementation**
|
|
|
|
.. code-block:: c++
|
|
|
|
class ChatHandler : public UnaryResponseHandler<ChatMessage> {
|
|
public:
|
|
void ReceivedResponse(const ChatMessage& response) override {
|
|
gui_.RenderChatMessage(response);
|
|
}
|
|
|
|
void Complete(Status status) override {
|
|
client_.Exit(status);
|
|
}
|
|
|
|
private:
|
|
ChatGui& gui_;
|
|
ChatClient& client_;
|
|
};
|
|
|
|
Example usage
|
|
~~~~~~~~~~~~~
|
|
The following example demonstrates how to call an RPC method using a nanopb
|
|
service client and receive the response.
|
|
|
|
.. code-block:: c++
|
|
|
|
#include "chat_protos/chat_service.rpc.pb.h"
|
|
|
|
namespace {
|
|
MyChannelOutput output;
|
|
pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<0>(&output)};
|
|
pw::rpc::Client client(channels);
|
|
}
|
|
|
|
void InvokeSomeRpcs() {
|
|
RoomInfoHandler handler;
|
|
|
|
// The RPC will remain active as long as `call` is alive.
|
|
auto call = ChatServiceClient::GetRoomInformation(channels[0],
|
|
{.room = "pigweed"},
|
|
handler);
|
|
|
|
// For simplicity, block here. An actual implementation would likely
|
|
// std::move the call somewhere to keep it active while doing other work.
|
|
while (call.active()) {
|
|
Wait();
|
|
}
|
|
|
|
DoStuff(handler.response());
|
|
}
|