README.md

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
```