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

A behaviour is used because it provides a way to generate the interface and types without being where the actual implementation is also done. This way if a change happens to the proto you can regenerate the interface without any issues with the implementation of the service, simply then update the implemntation callbacks to match the changed interface.

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

#### Interceptors

##### Unary Interceptor

A unary interceptor can be any function that accepts a context, decoded request body, server info map and the method function:

```erlang
some_unary_interceptor(Ctx, Request, ServerInfo, Fun) ->
    %% do some interception stuff
    Fun(Ctx, Request).
```

The interceptor is configured in the `grpc_opts` set in the environment or passed to the supervisor `start_child` function. An example from the test suite sets `grpc_opts` in the application environment:

```erlang
#{service_protos => [route_guide_pb],
  unary_interceptor => fun(Ctx, Req, _, Method) ->
                         Method(Ctx, #{latitude => 30,
                                       longitude => 90})
                       end}
```

##### Streaming Interceptor

##### Middleware

There is a provided interceptor `grpcbox_chain_interceptor` which accepts a list of interceptors to apply in order, with the final interceptor calling the method handler. An example from the test suite adds a trailer in each interceptor to show the chain working:

```erlang
#{service_protos => [route_guide_pb],
  unary_interceptor =>
    grpcbox_chain_interceptor:unary([fun ?MODULE:one/4,
                                     fun ?MODULE:two/4,
                                     fun ?MODULE:three/4])})
```

#### Metadata

Metadata is sent in headers and trailers.

CT Tests
---

To run the Common Test suite:

```
$ rebar3 ct
```

Interop Tests
---

The `interop` rebar3 profile builds with an implementation of the `test.proto` for grpc interop testing:

```
$ rebar3 as interop shell

> grpcbox_sup:start_child().
```

With the shell running the tests can then be run from a script:

```
$ interop/run_tests.sh
```

The script by default uses the Go test client that can be installed with the following:

```
$ go get -u github.com/grpc/grpc-go/interop
$ go build -o $GOPATH/bin/go-grpc-interop-client github.com/grpc/grpc-go/interop/client
```