grpcbox
=====
Library for creating [grpc](https://grpc.io) servers in Erlang, based on the [chatterbox](https://github.com/joedevivo/chatterbox) http2 server.
Very much still alpha quality.
Implementing a Service
----
The easiest way to get started is using the plugin, [grpcbox_plugin](https://github.com/tsloughter/grpcbox_plugin):
```erlang
{deps, [grpcbox]}.
{grpc, [{protos, "priv/protos"},
{gpb_opts, [{module_name_suffix, "_pb"}]}]}.
{plugins, [grpcbox_plugin]}.
```
Currently `grpcbox` and the plugin are a bit picky and the `gpb` options will always include `[use_packages, maps, {i, "."}, {o, "src"}]`.
Assuming the `priv/protos` directory of your application has the `route_guide.proto` found in this repo, `priv/protos/route_guide.proto`, the output from running the plugin will be:
```shell
$ rebar3 grpc gen
===> Writing src/route_guide_pb.erl
===> Writing src/grpcbox_route_guide_bhvr.erl
```
#### Unary RPC
Unary RPCs receive a single request and return a single response. The RPC `GetFeature` takes a single `Point` and returns the `Feature` at that point:
```protobuf
rpc GetFeature(Point) returns (Feature) {}
```
The callback generated by the `grpcbox_plugin` will look like:
```erlang
-callback get_feature(ctx:ctx(), route_guide_pb:'grpcbox.Point'()) ->
{ok, route_guide_pb:'grpcbox.Feature'()} | {error, term()}.
```
And the implementation is as simple as an Erlang function that takes the arguments `Ctx`, the context of this current request, and a `Point` map, returning a `Feature` map:
```erlang
get_feature(Ctx, Point) ->
Feature = #{name => find_point(Point, data()),
location => Point},
{ok, Feature, Ctx}.
```
#### Streaming Output
Instead of returning a single feature the server can stream a response of multiple features by defining the RPC to have a `stream Feature` return:
```protobuf
rpc ListFeatures(Rectangle) returns (stream Feature) {}
```
In this case the callback still receives a map argument but also a `grpcbox_stream` argument:
```erlang
-callback list_features(route_guide_pb:'grpcbox.Rectangle'(), grpcbox_stream:t()) ->
ok | {error, term()}.
```
The `GrpcStream` variable is passed to `grpcbox_stream:send/2` for returning an individual feature over the stream to the client. The stream is ended by the server when the function completes.
```erlang
list_features(_Message, GrpcStream) ->
grpcbox_stream:send(#{name => <<"Tour Eiffel">>,
location => #{latitude => 3,
longitude => 5}}, GrpcStream),
grpcbox_stream:send(#{name => <<"Louvre">>,
location => #{latitude => 4,
longitude => 5}}, GrpcStream),
ok.
```
#### Streaming Input
The client can also stream a sequence of messages:
```protobuf
rpc RecordRoute(stream Point) returns (RouteSummary) {}
```
In this case the callback receives a `reference()` instead of a direct value from the client:
```erlang
-callback record_route(reference(), grpcbox_stream:t()) ->
{ok, route_guide_pb:'grpcbox.RouteSummary'()} | {error, term()}.
```
The process the callback is running in will receive the individual messages on the stream as tuples `{reference(), route_guide_pb:'grpcbox.Point'()}`. The end of the stream is sent as the message `{reference(), eos}` at which point the function can return the response:
```erlang
record_route(Ref, GrpcStream) ->
record_route(Ref, #{t_start => erlang:system_time(1),
acc => []}, GrpcStream).
record_route(Ref, Data=#{t_start := T0, acc := Points}, GrpcStream) ->
receive
{Ref, eos} ->
{ok, #{elapsed_time => erlang:system_time(1) - T0,
point_count => length(Points),
feature_count => count_features(Points),
distance => distance(Points)}, GrpcStream};
{Ref, Point} ->
record_route(Ref, Data#{acc => [Point | Points]}, GrpcStream)
end.
```
#### Streaming In and Out
A bidrectional streaming RPC is defined when both input and output are streams:
```protobuf
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
```
```erlang
-callback route_chat(reference(), grpcbox_stream:t()) ->
ok | {error, term()}.
```
The sequence of input messages will again be sent to the callback's process as Erlang messages and any output messages are sent to the client with `grpcbox_stream`:
```erlang
route_chat(Ref, GrpcStream) ->
route_chat(Ref, [], GrpcStream).
route_chat(Ref, Data, GrpcStream) ->
receive
{Ref, eos} ->
ok;
{Ref, #{location := Location} = P} ->
Messages = proplists:get_all_values(Location, Data),
[grpcbox_stream:send(Message, GrpcStream) || Message <- Messages],
route_chat(Ref, [{Location, P} | Data], GrpcStream)
end.
```
#### Metadata
Metadata is sent in headers and trailers.
Test
---
To run the Common Test suite:
```
$ rebar3 ct
```
To run another client implementation to test, in particular the route guide example that comes with the Go client, `google.golang.org/grpc/examples/route_guide/client/client.go`:
```
$ rebar3 as test shell
```
And run the client from your `$GOPATH`:
```
$ bin/client -server_addr 127.0.0.1:8080
```