diff --git a/examples/helloworld/.gitignore b/examples/helloworld/.gitignore deleted file mode 100644 index 06dbcb6f..00000000 --- a/examples/helloworld/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build - -# If you run "mix test --cover", coverage assets end up here. -/cover - -# The directory Mix downloads your dependencies sources to. -/deps - -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -/priv/grpc_c.so* -/src/grpc_c -/tmp - -/log \ No newline at end of file diff --git a/examples/helloworld/README.md b/examples/helloworld/README.md deleted file mode 100644 index e3c2b345..00000000 --- a/examples/helloworld/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Helloworld in grpc-elixir - -## Usage - -1. Install deps and compile -```shell -$ mix do deps.get, compile -``` - -2. Run the server -```shell -$ mix run --no-halt -``` - -3. Run the client script -```shell -$ mix run priv/client.exs -``` - -## HTTP Transcoding - -``` shell -# Say hello -curl -H 'Content-type: application/json' http://localhost:50051/v1/greeter/test - -# Say hello from -curl -XPOST -H 'Content-type: application/json' -d '{"name": "test", "from": "anon"}' http://localhost:50051/v1/greeter -``` - -## Regenerate Elixir code from proto - -1. Modify the proto `priv/protos/helloworld.proto` -2. Install `protoc` [here](https://developers.google.com/protocol-buffers/docs/downloads) -3. Install `protoc-gen-elixir` -``` -mix escript.install hex protobuf -``` -4. Generate the code: - -```shell -$ (cd ../../; mix build_protobuf_escript && mix escript.build) -$ protoc -I priv/protos --elixir_out=:./lib/ --grpc_elixir_out=./lib --plugin="../../deps/protobuf/protoc-gen-elixir" --plugin="../../protoc-gen-grpc_elixir" priv/protos/helloworld.proto -``` - -Refer to [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) for more information. - -## How to start server when starting your application? - -Pass `start_server: true` as an option for the `GRPC.Server.Supervisor` in your supervision tree. - -## Benchmark - -Using [ghz](https://ghz.sh/) - -``` -$ MIX_ENV=prod iex -S mix -# Now cowboy doesn't work well with concurrency in a connection, like --concurrency 6 --connections 1 -$ ghz --insecure --proto priv/protos/helloworld.proto --call helloworld.Greeter.SayHello -d '{"name":"Joe"}' -z 10s --concurrency 6 --connections 6 127.0.0.1:50051 -# The result is for branch improve-perf -Summary: - Count: 124239 - Total: 10.00 s - Slowest: 18.85 ms - Fastest: 0.18 ms - Average: 0.44 ms - Requests/sec: 12423.71 - -# Go -Summary: - Count: 258727 - Total: 10.00 s - Slowest: 5.39 ms - Fastest: 0.09 ms - Average: 0.19 ms - Requests/sec: 25861.68 -``` diff --git a/examples/helloworld/config/config.exs b/examples/helloworld/config/config.exs deleted file mode 100644 index 9def7c2c..00000000 --- a/examples/helloworld/config/config.exs +++ /dev/null @@ -1,3 +0,0 @@ -import Config - -import_config "#{Mix.env}.exs" diff --git a/examples/helloworld/config/dev.exs b/examples/helloworld/config/dev.exs deleted file mode 100644 index becde769..00000000 --- a/examples/helloworld/config/dev.exs +++ /dev/null @@ -1 +0,0 @@ -import Config diff --git a/examples/helloworld/config/prod.exs b/examples/helloworld/config/prod.exs deleted file mode 100644 index 2c946e80..00000000 --- a/examples/helloworld/config/prod.exs +++ /dev/null @@ -1,4 +0,0 @@ -import Config - -config :logger, - level: :warning diff --git a/examples/helloworld/config/test.exs b/examples/helloworld/config/test.exs deleted file mode 100644 index becde769..00000000 --- a/examples/helloworld/config/test.exs +++ /dev/null @@ -1 +0,0 @@ -import Config diff --git a/examples/helloworld/lib/endpoint.ex b/examples/helloworld/lib/endpoint.ex deleted file mode 100644 index c8bc64f6..00000000 --- a/examples/helloworld/lib/endpoint.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule Helloworld.Endpoint do - use GRPC.Endpoint - - intercept GRPC.Server.Interceptors.Logger - run Helloworld.Greeter.Server -end diff --git a/examples/helloworld/lib/helloworld.pb.ex b/examples/helloworld/lib/helloworld.pb.ex deleted file mode 100644 index 9a10f57b..00000000 --- a/examples/helloworld/lib/helloworld.pb.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Helloworld.HelloRequest do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :name, 1, type: :string -end - -defmodule Helloworld.HelloRequestFrom do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :name, 1, type: :string - field :from, 2, type: :string -end - -defmodule Helloworld.HelloReply do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :message, 1, type: :string - field :today, 2, type: Google.Protobuf.Timestamp -end - -defmodule Helloworld.GetMessageRequest do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :name, 1, type: :string -end - -defmodule Helloworld.Message do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :text, 1, type: :string -end diff --git a/examples/helloworld/lib/helloworld.svc.ex b/examples/helloworld/lib/helloworld.svc.ex deleted file mode 100644 index dcf882ab..00000000 --- a/examples/helloworld/lib/helloworld.svc.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Helloworld.Greeter.Service do - @moduledoc false - - use GRPC.Service, name: "helloworld.Greeter", protoc_gen_elixir_version: "0.11.0" - - rpc(:SayHello, Helloworld.HelloRequest, Helloworld.HelloReply, %{ - http: %{ - type: Google.Api.PbExtension, - value: %Google.Api.HttpRule{ - __unknown_fields__: [], - additional_bindings: [], - body: "", - pattern: {:get, "/v1/greeter/{name}"}, - response_body: "", - selector: "" - } - } - }) - - rpc(:SayHelloFrom, Helloworld.HelloRequestFrom, Helloworld.HelloReply, %{ - http: %{ - type: Google.Api.PbExtension, - value: %Google.Api.HttpRule{ - __unknown_fields__: [], - additional_bindings: [], - body: "*", - pattern: {:post, "/v1/greeter"}, - response_body: "", - selector: "" - } - } - }) -end - -defmodule Helloworld.Greeter.Stub do - @moduledoc false - - use GRPC.Stub, service: Helloworld.Greeter.Service -end - -defmodule Helloworld.Messaging.Service do - @moduledoc false - - use GRPC.Service, name: "helloworld.Messaging", protoc_gen_elixir_version: "0.11.0" - - rpc(:GetMessage, Helloworld.GetMessageRequest, Helloworld.Message, %{ - http: %{ - type: Google.Api.PbExtension, - value: %Google.Api.HttpRule{ - __unknown_fields__: [], - additional_bindings: [], - body: "", - pattern: {:get, "/v1/{name=messages/*}"}, - response_body: "", - selector: "" - } - } - }) -end - -defmodule Helloworld.Messaging.Stub do - @moduledoc false - - use GRPC.Stub, service: Helloworld.Messaging.Service -end diff --git a/examples/helloworld/lib/helloworld_app.ex b/examples/helloworld/lib/helloworld_app.ex deleted file mode 100644 index d84d62a5..00000000 --- a/examples/helloworld/lib/helloworld_app.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule HelloworldApp do - use Application - - def start(_type, _args) do - children = [ - {GRPC.Server.Supervisor, endpoint: Helloworld.Endpoint, port: 50051, start_server: true} - ] - - opts = [strategy: :one_for_one, name: HelloworldApp] - Supervisor.start_link(children, opts) - end -end diff --git a/examples/helloworld/lib/server.ex b/examples/helloworld/lib/server.ex deleted file mode 100644 index 8786949f..00000000 --- a/examples/helloworld/lib/server.ex +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Helloworld.Greeter.Server do - use GRPC.Server, - service: Helloworld.Greeter.Service, - http_transcode: true - - @spec say_hello(Helloworld.HelloRequest.t(), GRPC.Server.Stream.t()) :: - Helloworld.HelloReply.t() - def say_hello(request, _stream) do - %Helloworld.HelloReply{ - message: "Hello #{request.name}", - today: today() - } - end - - @spec say_hello_from(Helloworld.HelloFromRequest.t(), GRPC.Server.Stream.t()) :: - Helloworld.HelloReply.t() - def say_hello_from(request, _stream) do - %Helloworld.HelloReply{ - message: "Hello #{request.name}. From #{request.from}", - today: today() - } - end - - defp today do - nanos_epoch = System.system_time() |> System.convert_time_unit(:native, :nanosecond) - seconds = div(nanos_epoch, 1_000_000_000) - nanos = nanos_epoch - seconds * 1_000_000_000 - - %Google.Protobuf.Timestamp{seconds: seconds, nanos: nanos} - end -end diff --git a/examples/helloworld/mix.exs b/examples/helloworld/mix.exs deleted file mode 100644 index 78e2443c..00000000 --- a/examples/helloworld/mix.exs +++ /dev/null @@ -1,27 +0,0 @@ -defmodule Helloworld.Mixfile do - use Mix.Project - - def project do - [ - app: :helloworld, - version: "0.1.0", - elixir: "~> 1.4", - build_embedded: Mix.env() == :prod, - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - def application do - [mod: {HelloworldApp, []}, applications: [:logger, :grpc]] - end - - defp deps do - [ - {:grpc, path: "../../"}, - {:jason, "~> 1.3.0"}, - {:protobuf, "~> 0.14"}, - {:google_protos, "~> 0.3.0"}, - ] - end -end diff --git a/examples/helloworld/mix.lock b/examples/helloworld/mix.lock deleted file mode 100644 index f6e1a184..00000000 --- a/examples/helloworld/mix.lock +++ /dev/null @@ -1,15 +0,0 @@ -%{ - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, - "gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"}, - "google_protos": {:hex, :google_protos, "0.3.0", "15faf44dce678ac028c289668ff56548806e313e4959a3aaf4f6e1ebe8db83f4", [:mix], [{:protobuf, "~> 0.10", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1f6b7fb20371f72f418b98e5e48dae3e022a9a6de1858d4b254ac5a5d0b4035f"}, - "googleapis": {:hex, :googleapis, "0.1.0", "13770f3f75f5b863fb9acf41633c7bc71bad788f3f553b66481a096d083ee20e", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1989a7244fd17d3eb5f3de311a022b656c3736b39740db46506157c4604bd212"}, - "gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, -} diff --git a/examples/helloworld/priv/client.exs b/examples/helloworld/priv/client.exs deleted file mode 100644 index 0209556f..00000000 --- a/examples/helloworld/priv/client.exs +++ /dev/null @@ -1,9 +0,0 @@ -{:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Client.Interceptors.Logger]) - -{:ok, reply} = - channel - |> Helloworld.Greeter.Stub.say_hello(Helloworld.HelloRequest.new(name: "grpc-elixir")) - -# pass tuple `timeout: :infinity` as a second arg to stay in IEx debugging - -IO.inspect(reply) diff --git a/examples/helloworld/priv/protos/google/api/annotations.proto b/examples/helloworld/priv/protos/google/api/annotations.proto deleted file mode 100644 index efdab3db..00000000 --- a/examples/helloworld/priv/protos/google/api/annotations.proto +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2015 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -import "google/api/http.proto"; -import "google/protobuf/descriptor.proto"; - -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "AnnotationsProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -extend google.protobuf.MethodOptions { - // See `HttpRule`. - HttpRule http = 72295728; -} diff --git a/examples/helloworld/priv/protos/google/api/http.proto b/examples/helloworld/priv/protos/google/api/http.proto deleted file mode 100644 index 113fa936..00000000 --- a/examples/helloworld/priv/protos/google/api/http.proto +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2015 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "HttpProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -// Defines the HTTP configuration for an API service. It contains a list of -// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method -// to one or more HTTP REST API methods. -message Http { - // A list of HTTP configuration rules that apply to individual API methods. - // - // **NOTE:** All service configuration rules follow "last one wins" order. - repeated HttpRule rules = 1; - - // When set to true, URL path parameters will be fully URI-decoded except in - // cases of single segment matches in reserved expansion, where "%2F" will be - // left encoded. - // - // The default behavior is to not decode RFC 6570 reserved characters in multi - // segment matches. - bool fully_decode_reserved_expansion = 2; -} - -// # gRPC Transcoding -// -// gRPC Transcoding is a feature for mapping between a gRPC method and one or -// more HTTP REST endpoints. It allows developers to build a single API service -// that supports both gRPC APIs and REST APIs. Many systems, including [Google -// APIs](https://github.com/googleapis/googleapis), -// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC -// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), -// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature -// and use it for large scale production services. -// -// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies -// how different portions of the gRPC request message are mapped to the URL -// path, URL query parameters, and HTTP request body. It also controls how the -// gRPC response message is mapped to the HTTP response body. `HttpRule` is -// typically specified as an `google.api.http` annotation on the gRPC method. -// -// Each mapping specifies a URL path template and an HTTP method. The path -// template may refer to one or more fields in the gRPC request message, as long -// as each field is a non-repeated field with a primitive (non-message) type. -// The path template controls how fields of the request message are mapped to -// the URL path. -// -// Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/{name=messages/*}" -// }; -// } -// } -// message GetMessageRequest { -// string name = 1; // Mapped to URL path. -// } -// message Message { -// string text = 1; // The resource content. -// } -// -// This enables an HTTP REST to gRPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` -// -// Any fields in the request message which are not bound by the path template -// automatically become HTTP query parameters if there is no HTTP request body. -// For example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get:"/v1/messages/{message_id}" -// }; -// } -// } -// message GetMessageRequest { -// message SubMessage { -// string subfield = 1; -// } -// string message_id = 1; // Mapped to URL path. -// int64 revision = 2; // Mapped to URL query parameter `revision`. -// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. -// } -// -// This enables a HTTP JSON to RPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | -// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: -// "foo"))` -// -// Note that fields which are mapped to URL query parameters must have a -// primitive type or a repeated primitive type or a non-repeated message type. -// In the case of a repeated type, the parameter can be repeated in the URL -// as `...?param=A¶m=B`. In the case of a message type, each field of the -// message is mapped to a separate parameter, such as -// `...?foo.a=A&foo.b=B&foo.c=C`. -// -// For HTTP methods that allow a request body, the `body` field -// specifies the mapping. Consider a REST update method on the -// message resource collection: -// -// service Messaging { -// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "message" -// }; -// } -// } -// message UpdateMessageRequest { -// string message_id = 1; // mapped to the URL -// Message message = 2; // mapped to the body -// } -// -// The following HTTP JSON to RPC mapping is enabled, where the -// representation of the JSON in the request body is determined by -// protos JSON encoding: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" message { text: "Hi!" })` -// -// The special name `*` can be used in the body mapping to define that -// every field not bound by the path template should be mapped to the -// request body. This enables the following alternative definition of -// the update method: -// -// service Messaging { -// rpc UpdateMessage(Message) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "*" -// }; -// } -// } -// message Message { -// string message_id = 1; -// string text = 2; -// } -// -// -// The following HTTP JSON to RPC mapping is enabled: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" text: "Hi!")` -// -// Note that when using `*` in the body mapping, it is not possible to -// have HTTP parameters, as all fields not bound by the path end in -// the body. This makes this option more rarely used in practice when -// defining REST APIs. The common usage of `*` is in custom methods -// which don't use the URL at all for transferring data. -// -// It is possible to define multiple HTTP methods for one RPC by using -// the `additional_bindings` option. Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/messages/{message_id}" -// additional_bindings { -// get: "/v1/users/{user_id}/messages/{message_id}" -// } -// }; -// } -// } -// message GetMessageRequest { -// string message_id = 1; -// string user_id = 2; -// } -// -// This enables the following two alternative HTTP JSON to RPC mappings: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` -// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: -// "123456")` -// -// ## Rules for HTTP mapping -// -// 1. Leaf request fields (recursive expansion nested messages in the request -// message) are classified into three categories: -// - Fields referred by the path template. They are passed via the URL path. -// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP -// request body. -// - All other fields are passed via the URL query parameters, and the -// parameter name is the field path in the request message. A repeated -// field can be represented as multiple query parameters under the same -// name. -// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields -// are passed via URL path and HTTP request body. -// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all -// fields are passed via URL path and URL query parameters. -// -// ### Path template syntax -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -// -// The syntax `*` matches a single URL path segment. The syntax `**` matches -// zero or more URL path segments, which must be the last part of the URL path -// except the `Verb`. -// -// The syntax `Variable` matches part of the URL path as specified by its -// template. A variable template must not contain other variables. If a variable -// matches a single path segment, its template may be omitted, e.g. `{var}` -// is equivalent to `{var=*}`. -// -// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` -// contains any reserved character, such characters should be percent-encoded -// before the matching. -// -// If a variable contains exactly one path segment, such as `"{var}"` or -// `"{var=*}"`, when such a variable is expanded into a URL path on the client -// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The -// server side does the reverse decoding. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{var}`. -// -// If a variable contains multiple path segments, such as `"{var=foo/*}"` -// or `"{var=**}"`, when such a variable is expanded into a URL path on the -// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. -// The server side does the reverse decoding, except "%2F" and "%2f" are left -// unchanged. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{+var}`. -// -// ## Using gRPC API Service Configuration -// -// gRPC API Service Configuration (service config) is a configuration language -// for configuring a gRPC service to become a user-facing product. The -// service config is simply the YAML representation of the `google.api.Service` -// proto message. -// -// As an alternative to annotating your proto file, you can configure gRPC -// transcoding in your service config YAML files. You do this by specifying a -// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same -// effect as the proto annotation. This can be particularly useful if you -// have a proto that is reused in multiple services. Note that any transcoding -// specified in the service config will override any matching transcoding -// configuration in the proto. -// -// Example: -// -// http: -// rules: -// # Selects a gRPC method and applies HttpRule to it. -// - selector: example.v1.Messaging.GetMessage -// get: /v1/messages/{message_id}/{sub.subfield} -// -// ## Special notes -// -// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the -// proto to JSON conversion must follow the [proto3 -// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). -// -// While the single segment variable follows the semantics of -// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String -// Expansion, the multi segment variable **does not** follow RFC 6570 Section -// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion -// does not expand special characters like `?` and `#`, which would lead -// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding -// for multi segment variables. -// -// The path variables **must not** refer to any repeated or mapped field, -// because client libraries are not capable of handling such variable expansion. -// -// The path variables **must not** capture the leading "/" character. The reason -// is that the most common use case "{var}" does not capture the leading "/" -// character. For consistency, all path variables must share the same behavior. -// -// Repeated message fields must not be mapped to URL query parameters, because -// no client library can support such complicated mapping. -// -// If an API needs to use a JSON array for request or response body, it can map -// the request or response body to a repeated field. However, some gRPC -// Transcoding implementations may not support this feature. -message HttpRule { - // Selects a method to which this rule applies. - // - // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - string selector = 1; - - // Determines the URL pattern is matched by this rules. This pattern can be - // used with any of the {get|put|post|delete|patch} methods. A custom method - // can be defined using the 'custom' field. - oneof pattern { - // Maps to HTTP GET. Used for listing and getting information about - // resources. - string get = 2; - - // Maps to HTTP PUT. Used for replacing a resource. - string put = 3; - - // Maps to HTTP POST. Used for creating a resource or performing an action. - string post = 4; - - // Maps to HTTP DELETE. Used for deleting a resource. - string delete = 5; - - // Maps to HTTP PATCH. Used for updating a resource. - string patch = 6; - - // The custom pattern is used for specifying an HTTP method that is not - // included in the `pattern` field, such as HEAD, or "*" to leave the - // HTTP method unspecified for this rule. The wild-card rule is useful - // for services that provide content to Web (HTML) clients. - CustomHttpPattern custom = 8; - } - - // The name of the request field whose value is mapped to the HTTP request - // body, or `*` for mapping all request fields not captured by the path - // pattern to the HTTP body, or omitted for not having any HTTP request body. - // - // NOTE: the referred field must be present at the top-level of the request - // message type. - string body = 7; - - // Optional. The name of the response field whose value is mapped to the HTTP - // response body. When omitted, the entire response message will be used - // as the HTTP response body. - // - // NOTE: The referred field must be present at the top-level of the response - // message type. - string response_body = 12; - - // Additional HTTP bindings for the selector. Nested bindings must - // not contain an `additional_bindings` field themselves (that is, - // the nesting may only be one level deep). - repeated HttpRule additional_bindings = 11; -} - -// A custom pattern is used for defining custom HTTP verb. -message CustomHttpPattern { - // The name of this custom HTTP verb. - string kind = 1; - - // The path matched by this custom verb. - string path = 2; -} diff --git a/examples/helloworld/priv/protos/helloworld.proto b/examples/helloworld/priv/protos/helloworld.proto deleted file mode 100644 index 632519a0..00000000 --- a/examples/helloworld/priv/protos/helloworld.proto +++ /dev/null @@ -1,62 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; -option objc_class_prefix = "HLW"; - -import "google/api/annotations.proto"; -import "google/protobuf/timestamp.proto"; - -package helloworld; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) { - option (google.api.http) = { - get: "/v1/greeter/{name}" - }; - } - - rpc SayHelloFrom (HelloRequestFrom) returns (HelloReply) { - option (google.api.http) = { - post: "/v1/greeter" - body: "*" - }; - } -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// HelloRequestFrom! -message HelloRequestFrom { - // Name! - string name = 1; - // From! - string from = 2; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; - google.protobuf.Timestamp today = 2; -} - -service Messaging { - rpc GetMessage(GetMessageRequest) returns (Message) { - option (google.api.http) = { - get: "/v1/{name=messages/*}" - }; - } -} - -message GetMessageRequest { - string name = 1; // Mapped to URL path. -} -message Message { - string text = 1; // The resource content. -} diff --git a/examples/helloworld/test/hello_world_test.exs b/examples/helloworld/test/hello_world_test.exs deleted file mode 100644 index 55c51ed5..00000000 --- a/examples/helloworld/test/hello_world_test.exs +++ /dev/null @@ -1,16 +0,0 @@ -defmodule HelloworldTest do - @moduledoc false - - use ExUnit.Case - - setup_all do - {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Client.Interceptors.Logger]) - [channel: channel] - end - - test "helloworld should be successful", %{channel: channel} do - req = Helloworld.HelloRequest.new(name: "grpc-elixir") - assert {:ok, %{message: msg, today: _}} = Helloworld.Greeter.Stub.say_hello(channel, req) - assert msg == "Hello grpc-elixir" - end -end diff --git a/examples/helloworld/test/test_helper.exs b/examples/helloworld/test/test_helper.exs deleted file mode 100644 index 869559e7..00000000 --- a/examples/helloworld/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() diff --git a/examples/helloworld_streams/.formatter.exs b/examples/helloworld_streams/.formatter.exs deleted file mode 100644 index d2cda26e..00000000 --- a/examples/helloworld_streams/.formatter.exs +++ /dev/null @@ -1,4 +0,0 @@ -# Used by "mix format" -[ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] -] diff --git a/examples/helloworld_streams/.gitignore b/examples/helloworld_streams/.gitignore deleted file mode 100644 index 965abf9c..00000000 --- a/examples/helloworld_streams/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build/ - -# If you run "mix test --cover", coverage assets end up here. -/cover/ - -# The directory Mix downloads your dependencies sources to. -/deps/ - -# Where third-party dependencies like ExDoc output generated docs. -/doc/ - -# Ignore .fetch files in case you like to edit your project deps locally. -/.fetch - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -# Ignore package tarball (built via "mix hex.build"). -helloworld_streams-*.tar - -# Temporary files, for example, from tests. -/tmp/ diff --git a/examples/helloworld_streams/README.md b/examples/helloworld_streams/README.md deleted file mode 100644 index 79b5c634..00000000 --- a/examples/helloworld_streams/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# HelloworldStreams - -**TODO: Add description** - -## Installation - -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `helloworld_streams` to your list of dependencies in `mix.exs`: - -```elixir -def deps do - [ - {:helloworld_streams, "~> 0.1.0"} - ] -end -``` - -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at . - diff --git a/examples/helloworld_streams/lib/helloworld_streams.ex b/examples/helloworld_streams/lib/helloworld_streams.ex deleted file mode 100644 index 8c1285a1..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule HelloworldStreams do - @moduledoc """ - Documentation for `HelloworldStreams`. - """ -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/application.ex b/examples/helloworld_streams/lib/helloworld_streams/application.ex deleted file mode 100644 index 44cfaa16..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/application.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule HelloworldStreams.Application do - @moduledoc false - use Application - - @impl true - def start(_type, _args) do - children = [ - HelloworldStreams.Utils.Transformer, - GrpcReflection, - { - GRPC.Server.Supervisor, - endpoint: HelloworldStreams.Endpoint, port: 50053, start_server: true - } - ] - - opts = [strategy: :one_for_one, name: HelloworldStreams.Supervisor] - Supervisor.start_link(children, opts) - end -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/endpoint.ex b/examples/helloworld_streams/lib/helloworld_streams/endpoint.ex deleted file mode 100644 index c02a0c9b..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/endpoint.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule HelloworldStreams.Endpoint do - @moduledoc false - use GRPC.Endpoint - - intercept(GRPC.Server.Interceptors.Logger) - run(HelloworldStreams.Utils.Reflection) - run(HelloworldStreams.Server) -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/server.ex b/examples/helloworld_streams/lib/helloworld_streams/server.ex deleted file mode 100644 index a9976dba..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/server.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule HelloworldStreams.Server do - @moduledoc """ - gRPC service for streaming data. - """ - use GRPC.Server, service: Stream.EchoServer.Service - - alias HelloworldStreams.Utils.Transformer - alias GRPC.Stream, as: GRPCStream - - alias Stream.HelloRequest - alias Stream.HelloReply - - @spec say_unary_hello(HelloRequest.t(), GRPC.Server.Stream.t()) :: any() - def say_unary_hello(request, _materializer) do - GRPCStream.unary(request) - |> GRPCStream.ask(Transformer) - |> GRPCStream.map(fn - %HelloReply{} = reply -> - %HelloReply{message: "[Reply] #{reply.message}"} - - {:error, reason} -> - GRPC.RPCError.exception(message: "[Error] #{inspect(reason)}") - end) - |> GRPCStream.run() - end - - @spec say_server_hello(HelloRequest.t(), GRPC.Server.Stream.t()) :: any() - def say_server_hello(request, materializer) do - create_output_stream(request) - |> GRPCStream.from() - |> GRPCStream.run_with(materializer) - end - - defp create_output_stream(msg) do - Stream.repeatedly(fn -> - index = :rand.uniform(10) - %HelloReply{message: "[#{index}] I'm the Server for #{msg.name}"} - end) - |> Stream.take(10) - |> Enum.to_list() - end - - @spec say_bid_stream_hello(Enumerable.t(), GRPC.Server.Stream.t()) :: any() - def say_bid_stream_hello(request, materializer) do - # simulate a infinite stream of data - # this is a simple example, in a real world application - # you would probably use a GenStage or similar - # to handle the stream of data - output_stream = - Stream.repeatedly(fn -> - index = :rand.uniform(10) - %HelloReply{message: "[#{index}] I'm the Server ;)"} - end) - - GRPCStream.from(request, join_with: output_stream) - |> GRPCStream.map(fn - %HelloRequest{} = hello -> - %HelloReply{message: "Welcome #{hello.name}"} - - output_item -> - output_item - end) - |> GRPCStream.run_with(materializer) - end -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/stream.pb.ex b/examples/helloworld_streams/lib/helloworld_streams/stream.pb.ex deleted file mode 100644 index b060b543..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/stream.pb.ex +++ /dev/null @@ -1,156 +0,0 @@ -defmodule Stream.HelloRequest do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - def descriptor do - # credo:disable-for-next-line - %Google.Protobuf.DescriptorProto{ - name: "HelloRequest", - field: [ - %Google.Protobuf.FieldDescriptorProto{ - name: "name", - extendee: nil, - number: 1, - label: :LABEL_OPTIONAL, - type: :TYPE_STRING, - type_name: nil, - default_value: nil, - options: nil, - oneof_index: nil, - json_name: "name", - proto3_optional: nil, - __unknown_fields__: [] - } - ], - nested_type: [], - enum_type: [], - extension_range: [], - extension: [], - options: nil, - oneof_decl: [], - reserved_range: [], - reserved_name: [], - __unknown_fields__: [] - } - end - - field(:name, 1, type: :string) -end - -defmodule Stream.HelloReply do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - def descriptor do - # credo:disable-for-next-line - %Google.Protobuf.DescriptorProto{ - name: "HelloReply", - field: [ - %Google.Protobuf.FieldDescriptorProto{ - name: "message", - extendee: nil, - number: 1, - label: :LABEL_OPTIONAL, - type: :TYPE_STRING, - type_name: nil, - default_value: nil, - options: nil, - oneof_index: nil, - json_name: "message", - proto3_optional: nil, - __unknown_fields__: [] - } - ], - nested_type: [], - enum_type: [], - extension_range: [], - extension: [], - options: nil, - oneof_decl: [], - reserved_range: [], - reserved_name: [], - __unknown_fields__: [] - } - end - - field(:message, 1, type: :string) -end - -defmodule Stream.EchoServer.Service do - @moduledoc false - - use GRPC.Service, name: "stream.EchoServer", protoc_gen_elixir_version: "0.14.0" - - def descriptor do - # credo:disable-for-next-line - %Google.Protobuf.ServiceDescriptorProto{ - name: "EchoServer", - method: [ - %Google.Protobuf.MethodDescriptorProto{ - name: "SayUnaryHello", - input_type: ".stream.HelloRequest", - output_type: ".stream.HelloReply", - options: %Google.Protobuf.MethodOptions{ - deprecated: false, - idempotency_level: :IDEMPOTENCY_UNKNOWN, - features: nil, - uninterpreted_option: [], - __pb_extensions__: %{}, - __unknown_fields__: [] - }, - client_streaming: false, - server_streaming: false, - __unknown_fields__: [] - }, - %Google.Protobuf.MethodDescriptorProto{ - name: "SayServerHello", - input_type: ".stream.HelloRequest", - output_type: ".stream.HelloReply", - options: %Google.Protobuf.MethodOptions{ - deprecated: false, - idempotency_level: :IDEMPOTENCY_UNKNOWN, - features: nil, - uninterpreted_option: [], - __pb_extensions__: %{}, - __unknown_fields__: [] - }, - client_streaming: false, - server_streaming: true, - __unknown_fields__: [] - }, - %Google.Protobuf.MethodDescriptorProto{ - name: "SayBidStreamHello", - input_type: ".stream.HelloRequest", - output_type: ".stream.HelloReply", - options: %Google.Protobuf.MethodOptions{ - deprecated: false, - idempotency_level: :IDEMPOTENCY_UNKNOWN, - features: nil, - uninterpreted_option: [], - __pb_extensions__: %{}, - __unknown_fields__: [] - }, - client_streaming: true, - server_streaming: true, - __unknown_fields__: [] - } - ], - options: nil, - __unknown_fields__: [] - } - end - - rpc(:SayUnaryHello, Stream.HelloRequest, Stream.HelloReply) - - rpc(:SayServerHello, Stream.HelloRequest, stream(Stream.HelloReply)) - - rpc(:SayBidStreamHello, stream(Stream.HelloRequest), stream(Stream.HelloReply)) -end - -defmodule Stream.EchoServer.Stub do - @moduledoc false - - use GRPC.Stub, service: Stream.EchoServer.Service -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/utils/reflection.ex b/examples/helloworld_streams/lib/helloworld_streams/utils/reflection.ex deleted file mode 100644 index a5e25e59..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/utils/reflection.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule HelloworldStreams.Utils.Reflection do - @moduledoc """ - gRPC reflection server. - """ - use GrpcReflection.Server, - version: :v1, - services: [Stream.EchoServer.Service] -end diff --git a/examples/helloworld_streams/lib/helloworld_streams/utils/transformer.ex b/examples/helloworld_streams/lib/helloworld_streams/utils/transformer.ex deleted file mode 100644 index ed9de052..00000000 --- a/examples/helloworld_streams/lib/helloworld_streams/utils/transformer.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule HelloworldStreams.Utils.Transformer do - @moduledoc """ - `Transformer` GenServer for example purposes. - """ - use GenServer - - alias Stream.HelloRequest - alias Stream.HelloReply - - def start_link(_) do - GenServer.start_link(__MODULE__, nil, name: __MODULE__) - end - - def init(_), do: {:ok, %{}} - - def handle_info({:request, %HelloRequest{} = value, from}, state) do - Process.send(from, {:response, %HelloReply{message: "Hello #{value.name}"}}, []) - {:noreply, state} - end -end diff --git a/examples/helloworld_streams/mix.exs b/examples/helloworld_streams/mix.exs deleted file mode 100644 index 8107d0bb..00000000 --- a/examples/helloworld_streams/mix.exs +++ /dev/null @@ -1,28 +0,0 @@ -defmodule HelloworldStreams.MixProject do - use Mix.Project - - def project do - [ - app: :helloworld_streams, - version: "0.1.0", - elixir: "~> 1.15", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - def application do - [ - extra_applications: [:logger], - mod: {HelloworldStreams.Application, []} - ] - end - - defp deps do - [ - {:grpc, path: "../../", override: true}, - {:protobuf, "~> 0.14"}, - {:grpc_reflection, "~> 0.1"} - ] - end -end diff --git a/examples/helloworld_streams/mix.lock b/examples/helloworld_streams/mix.lock deleted file mode 100644 index ee24db4b..00000000 --- a/examples/helloworld_streams/mix.lock +++ /dev/null @@ -1,14 +0,0 @@ -%{ - "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, - "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, - "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, - "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, - "google_protos": {:hex, :google_protos, "0.3.0", "15faf44dce678ac028c289668ff56548806e313e4959a3aaf4f6e1ebe8db83f4", [:mix], [{:protobuf, "~> 0.10", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1f6b7fb20371f72f418b98e5e48dae3e022a9a6de1858d4b254ac5a5d0b4035f"}, - "grpc_reflection": {:hex, :grpc_reflection, "0.1.5", "d00cdf8ef2638edb9578248eedc742e1b34eda9100e61be764c552c10f4b46cb", [:mix], [{:grpc, "~> 0.9", [hex: :grpc, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "848334d16029aee33728603be6171fc8bfcdfa3508cd6885ec1729e2e6ac60a5"}, - "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, - "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, -} diff --git a/examples/helloworld_streams/priv/protos/stream.proto b/examples/helloworld_streams/priv/protos/stream.proto deleted file mode 100644 index e0ad2ac0..00000000 --- a/examples/helloworld_streams/priv/protos/stream.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; - -package stream; - -message HelloRequest { - string name = 1; -} - -message HelloReply { - string message = 1; -} - -service EchoServer { - rpc SayUnaryHello (HelloRequest) returns (HelloReply) {} - rpc SayServerHello (HelloRequest) returns (stream HelloReply) {} - rpc SayBidStreamHello (stream HelloRequest) returns (stream HelloReply) {} -} diff --git a/examples/helloworld_streams/test/helloworld_streams_test.exs b/examples/helloworld_streams/test/helloworld_streams_test.exs deleted file mode 100644 index 838751bb..00000000 --- a/examples/helloworld_streams/test/helloworld_streams_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule HelloworldStreamsTest do - use ExUnit.Case - doctest HelloworldStreams -end diff --git a/examples/helloworld_streams/test/test_helper.exs b/examples/helloworld_streams/test/test_helper.exs deleted file mode 100644 index 869559e7..00000000 --- a/examples/helloworld_streams/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() diff --git a/examples/helloworld_transcoding/.gitignore b/examples/helloworld_transcoding/.gitignore deleted file mode 100644 index 06dbcb6f..00000000 --- a/examples/helloworld_transcoding/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build - -# If you run "mix test --cover", coverage assets end up here. -/cover - -# The directory Mix downloads your dependencies sources to. -/deps - -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -/priv/grpc_c.so* -/src/grpc_c -/tmp - -/log \ No newline at end of file diff --git a/examples/helloworld_transcoding/README.md b/examples/helloworld_transcoding/README.md deleted file mode 100644 index 66624ce0..00000000 --- a/examples/helloworld_transcoding/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Helloworld with HTTP/json transcoding in grpc-elixir - -## Usage - -1. Install deps and compile -```shell -$ mix do deps.get, compile -``` - -2. Run the server -```shell -$ mix run --no-halt -``` - -3. Run the client script -```shell -$ mix run priv/client.exs -``` - -## HTTP Transcoding - -``` shell -# Say hello -$ curl -H 'accept: application/json' http://localhost:50051/v1/greeter/test - -# Say hello from -$ curl -XPOST -H 'Content-type: application/json' -d '{"name": "test", "from": "anon"}' http://localhost:50051/v1/greeter -``` - -## Regenerate Elixir code from proto - -1. Modify the proto `priv/protos/helloworld.proto` - -2. Install `protoc` [here](https://developers.google.com/protocol-buffers/docs/downloads) - -``` -mix deps.get -``` - -4. Generate `google.api.http` extensions: - -``` shell -$ mix protobuf.generate --include-path=priv/protos --output-path=./lib priv/protos/google/api/annotations.proto priv/protos/google/api/http.proto -``` - -4. Generate the code: - -```shell -$ mix protobuf.generate --include-path=priv/protos --plugins=ProtobufGenerate.Plugins.GRPCWithOptions --output-path=./lib priv/protos/helloworld.proto -``` - -Refer to [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) for more information. - -## How to start server when starting your application? - -Pass `start_server: true` as an option for the `GRPC.Server.Supervisor` in your supervision tree. - -## Benchmark - -Using [ghz](https://ghz.sh/) - -``` -$ MIX_ENV=prod iex -S mix -# Now cowboy doesn't work well with concurrency in a connection, like --concurrency 6 --connections 1 -$ ghz --insecure --proto priv/protos/helloworld.proto --call helloworld.Greeter.SayHello -d '{"name":"Joe"}' -z 10s --concurrency 6 --connections 6 127.0.0.1:50051 -# The result is for branch improve-perf -Summary: - Count: 124239 - Total: 10.00 s - Slowest: 18.85 ms - Fastest: 0.18 ms - Average: 0.44 ms - Requests/sec: 12423.71 - -# Go -Summary: - Count: 258727 - Total: 10.00 s - Slowest: 5.39 ms - Fastest: 0.09 ms - Average: 0.19 ms - Requests/sec: 25861.68 -``` diff --git a/examples/helloworld_transcoding/config/config.exs b/examples/helloworld_transcoding/config/config.exs deleted file mode 100644 index 9def7c2c..00000000 --- a/examples/helloworld_transcoding/config/config.exs +++ /dev/null @@ -1,3 +0,0 @@ -import Config - -import_config "#{Mix.env}.exs" diff --git a/examples/helloworld_transcoding/config/dev.exs b/examples/helloworld_transcoding/config/dev.exs deleted file mode 100644 index becde769..00000000 --- a/examples/helloworld_transcoding/config/dev.exs +++ /dev/null @@ -1 +0,0 @@ -import Config diff --git a/examples/helloworld_transcoding/config/prod.exs b/examples/helloworld_transcoding/config/prod.exs deleted file mode 100644 index 2dd33c31..00000000 --- a/examples/helloworld_transcoding/config/prod.exs +++ /dev/null @@ -1,4 +0,0 @@ -import Config - -config :logger, - level: :warn diff --git a/examples/helloworld_transcoding/config/test.exs b/examples/helloworld_transcoding/config/test.exs deleted file mode 100644 index becde769..00000000 --- a/examples/helloworld_transcoding/config/test.exs +++ /dev/null @@ -1 +0,0 @@ -import Config diff --git a/examples/helloworld_transcoding/lib/endpoint.ex b/examples/helloworld_transcoding/lib/endpoint.ex deleted file mode 100644 index c8bc64f6..00000000 --- a/examples/helloworld_transcoding/lib/endpoint.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule Helloworld.Endpoint do - use GRPC.Endpoint - - intercept GRPC.Server.Interceptors.Logger - run Helloworld.Greeter.Server -end diff --git a/examples/helloworld_transcoding/lib/helloworld.pb.ex b/examples/helloworld_transcoding/lib/helloworld.pb.ex deleted file mode 100644 index ef45cc5c..00000000 --- a/examples/helloworld_transcoding/lib/helloworld.pb.ex +++ /dev/null @@ -1,55 +0,0 @@ -defmodule Helloworld.HelloRequest do - @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :name, 1, type: :string -end - -defmodule Helloworld.HelloRequestFrom do - @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :name, 1, type: :string - field :from, 2, type: :string -end - -defmodule Helloworld.HelloReply do - @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 - - field :message, 1, type: :string - field :today, 2, type: Google.Protobuf.Timestamp -end - -defmodule Helloworld.Greeter.Service do - @moduledoc false - use GRPC.Service, name: "helloworld.Greeter", protoc_gen_elixir_version: "0.14.1" - - rpc(:SayHello, Helloworld.HelloRequest, Helloworld.HelloReply, %{ - http: %{ - type: Google.Api.PbExtension, - value: %Google.Api.HttpRule{ - selector: "", - body: "", - additional_bindings: [], - response_body: "", - pattern: {:get, "/v1/greeter/{name}"}, - __unknown_fields__: [] - } - } - }) - - rpc(:SayHelloFrom, Helloworld.HelloRequestFrom, Helloworld.HelloReply, %{ - http: %{ - type: Google.Api.PbExtension, - value: %Google.Api.HttpRule{ - selector: "", - body: "*", - additional_bindings: [], - response_body: "", - pattern: {:post, "/v1/greeter"}, - __unknown_fields__: [] - } - } - }) -end diff --git a/examples/helloworld_transcoding/lib/helloworld_app.ex b/examples/helloworld_transcoding/lib/helloworld_app.ex deleted file mode 100644 index d84d62a5..00000000 --- a/examples/helloworld_transcoding/lib/helloworld_app.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule HelloworldApp do - use Application - - def start(_type, _args) do - children = [ - {GRPC.Server.Supervisor, endpoint: Helloworld.Endpoint, port: 50051, start_server: true} - ] - - opts = [strategy: :one_for_one, name: HelloworldApp] - Supervisor.start_link(children, opts) - end -end diff --git a/examples/helloworld_transcoding/lib/server.ex b/examples/helloworld_transcoding/lib/server.ex deleted file mode 100644 index 8786949f..00000000 --- a/examples/helloworld_transcoding/lib/server.ex +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Helloworld.Greeter.Server do - use GRPC.Server, - service: Helloworld.Greeter.Service, - http_transcode: true - - @spec say_hello(Helloworld.HelloRequest.t(), GRPC.Server.Stream.t()) :: - Helloworld.HelloReply.t() - def say_hello(request, _stream) do - %Helloworld.HelloReply{ - message: "Hello #{request.name}", - today: today() - } - end - - @spec say_hello_from(Helloworld.HelloFromRequest.t(), GRPC.Server.Stream.t()) :: - Helloworld.HelloReply.t() - def say_hello_from(request, _stream) do - %Helloworld.HelloReply{ - message: "Hello #{request.name}. From #{request.from}", - today: today() - } - end - - defp today do - nanos_epoch = System.system_time() |> System.convert_time_unit(:native, :nanosecond) - seconds = div(nanos_epoch, 1_000_000_000) - nanos = nanos_epoch - seconds * 1_000_000_000 - - %Google.Protobuf.Timestamp{seconds: seconds, nanos: nanos} - end -end diff --git a/examples/helloworld_transcoding/mix.exs b/examples/helloworld_transcoding/mix.exs deleted file mode 100644 index c67c06c5..00000000 --- a/examples/helloworld_transcoding/mix.exs +++ /dev/null @@ -1,28 +0,0 @@ -defmodule Helloworld.Mixfile do - use Mix.Project - - def project do - [ - app: :helloworld, - version: "0.1.0", - elixir: "~> 1.4", - build_embedded: Mix.env() == :prod, - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - def application do - [mod: {HelloworldApp, []}, applications: [:logger, :grpc]] - end - - defp deps do - [ - {:grpc, path: "../../"}, - {:protobuf, "~> 0.14"}, - {:jason, "~> 1.3.0"}, - {:google_protos, "~> 0.3.0"}, - {:protobuf_generate, "~> 0.1", only: [:dev, :test]} - ] - end -end diff --git a/examples/helloworld_transcoding/mix.lock b/examples/helloworld_transcoding/mix.lock deleted file mode 100644 index 439d9710..00000000 --- a/examples/helloworld_transcoding/mix.lock +++ /dev/null @@ -1,13 +0,0 @@ -%{ - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "google_protos": {:hex, :google_protos, "0.3.0", "15faf44dce678ac028c289668ff56548806e313e4959a3aaf4f6e1ebe8db83f4", [:mix], [{:protobuf, "~> 0.10", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1f6b7fb20371f72f418b98e5e48dae3e022a9a6de1858d4b254ac5a5d0b4035f"}, - "gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, - "protobuf_generate": {:hex, :protobuf_generate, "0.1.1", "f6098b85161dcfd48a4f6f1abee4ee5e057981dfc50aafb1aa4bd5b0529aa89b", [:mix], [{:protobuf, "~> 0.11", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "93a38c8e2aba2a17e293e9ef1359122741f717103984aa6d1ebdca0efb17ab9d"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, -} diff --git a/examples/helloworld_transcoding/priv/client.exs b/examples/helloworld_transcoding/priv/client.exs deleted file mode 100644 index dc6bea5d..00000000 --- a/examples/helloworld_transcoding/priv/client.exs +++ /dev/null @@ -1,9 +0,0 @@ -{:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Logger.Client]) - -{:ok, reply} = - channel - |> Helloworld.Greeter.Stub.say_hello(Helloworld.HelloRequest.new(name: "grpc-elixir")) - -# pass tuple `timeout: :infinity` as a second arg to stay in IEx debugging - -IO.inspect(reply) diff --git a/examples/helloworld_transcoding/priv/protos/google/api/annotations.proto b/examples/helloworld_transcoding/priv/protos/google/api/annotations.proto deleted file mode 100644 index efdab3db..00000000 --- a/examples/helloworld_transcoding/priv/protos/google/api/annotations.proto +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2015 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -import "google/api/http.proto"; -import "google/protobuf/descriptor.proto"; - -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "AnnotationsProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -extend google.protobuf.MethodOptions { - // See `HttpRule`. - HttpRule http = 72295728; -} diff --git a/examples/helloworld_transcoding/priv/protos/google/api/http.proto b/examples/helloworld_transcoding/priv/protos/google/api/http.proto deleted file mode 100644 index 113fa936..00000000 --- a/examples/helloworld_transcoding/priv/protos/google/api/http.proto +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2015 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "HttpProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -// Defines the HTTP configuration for an API service. It contains a list of -// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method -// to one or more HTTP REST API methods. -message Http { - // A list of HTTP configuration rules that apply to individual API methods. - // - // **NOTE:** All service configuration rules follow "last one wins" order. - repeated HttpRule rules = 1; - - // When set to true, URL path parameters will be fully URI-decoded except in - // cases of single segment matches in reserved expansion, where "%2F" will be - // left encoded. - // - // The default behavior is to not decode RFC 6570 reserved characters in multi - // segment matches. - bool fully_decode_reserved_expansion = 2; -} - -// # gRPC Transcoding -// -// gRPC Transcoding is a feature for mapping between a gRPC method and one or -// more HTTP REST endpoints. It allows developers to build a single API service -// that supports both gRPC APIs and REST APIs. Many systems, including [Google -// APIs](https://github.com/googleapis/googleapis), -// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC -// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), -// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature -// and use it for large scale production services. -// -// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies -// how different portions of the gRPC request message are mapped to the URL -// path, URL query parameters, and HTTP request body. It also controls how the -// gRPC response message is mapped to the HTTP response body. `HttpRule` is -// typically specified as an `google.api.http` annotation on the gRPC method. -// -// Each mapping specifies a URL path template and an HTTP method. The path -// template may refer to one or more fields in the gRPC request message, as long -// as each field is a non-repeated field with a primitive (non-message) type. -// The path template controls how fields of the request message are mapped to -// the URL path. -// -// Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/{name=messages/*}" -// }; -// } -// } -// message GetMessageRequest { -// string name = 1; // Mapped to URL path. -// } -// message Message { -// string text = 1; // The resource content. -// } -// -// This enables an HTTP REST to gRPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` -// -// Any fields in the request message which are not bound by the path template -// automatically become HTTP query parameters if there is no HTTP request body. -// For example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get:"/v1/messages/{message_id}" -// }; -// } -// } -// message GetMessageRequest { -// message SubMessage { -// string subfield = 1; -// } -// string message_id = 1; // Mapped to URL path. -// int64 revision = 2; // Mapped to URL query parameter `revision`. -// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. -// } -// -// This enables a HTTP JSON to RPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | -// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: -// "foo"))` -// -// Note that fields which are mapped to URL query parameters must have a -// primitive type or a repeated primitive type or a non-repeated message type. -// In the case of a repeated type, the parameter can be repeated in the URL -// as `...?param=A¶m=B`. In the case of a message type, each field of the -// message is mapped to a separate parameter, such as -// `...?foo.a=A&foo.b=B&foo.c=C`. -// -// For HTTP methods that allow a request body, the `body` field -// specifies the mapping. Consider a REST update method on the -// message resource collection: -// -// service Messaging { -// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "message" -// }; -// } -// } -// message UpdateMessageRequest { -// string message_id = 1; // mapped to the URL -// Message message = 2; // mapped to the body -// } -// -// The following HTTP JSON to RPC mapping is enabled, where the -// representation of the JSON in the request body is determined by -// protos JSON encoding: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" message { text: "Hi!" })` -// -// The special name `*` can be used in the body mapping to define that -// every field not bound by the path template should be mapped to the -// request body. This enables the following alternative definition of -// the update method: -// -// service Messaging { -// rpc UpdateMessage(Message) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "*" -// }; -// } -// } -// message Message { -// string message_id = 1; -// string text = 2; -// } -// -// -// The following HTTP JSON to RPC mapping is enabled: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" text: "Hi!")` -// -// Note that when using `*` in the body mapping, it is not possible to -// have HTTP parameters, as all fields not bound by the path end in -// the body. This makes this option more rarely used in practice when -// defining REST APIs. The common usage of `*` is in custom methods -// which don't use the URL at all for transferring data. -// -// It is possible to define multiple HTTP methods for one RPC by using -// the `additional_bindings` option. Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/messages/{message_id}" -// additional_bindings { -// get: "/v1/users/{user_id}/messages/{message_id}" -// } -// }; -// } -// } -// message GetMessageRequest { -// string message_id = 1; -// string user_id = 2; -// } -// -// This enables the following two alternative HTTP JSON to RPC mappings: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` -// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: -// "123456")` -// -// ## Rules for HTTP mapping -// -// 1. Leaf request fields (recursive expansion nested messages in the request -// message) are classified into three categories: -// - Fields referred by the path template. They are passed via the URL path. -// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP -// request body. -// - All other fields are passed via the URL query parameters, and the -// parameter name is the field path in the request message. A repeated -// field can be represented as multiple query parameters under the same -// name. -// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields -// are passed via URL path and HTTP request body. -// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all -// fields are passed via URL path and URL query parameters. -// -// ### Path template syntax -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -// -// The syntax `*` matches a single URL path segment. The syntax `**` matches -// zero or more URL path segments, which must be the last part of the URL path -// except the `Verb`. -// -// The syntax `Variable` matches part of the URL path as specified by its -// template. A variable template must not contain other variables. If a variable -// matches a single path segment, its template may be omitted, e.g. `{var}` -// is equivalent to `{var=*}`. -// -// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` -// contains any reserved character, such characters should be percent-encoded -// before the matching. -// -// If a variable contains exactly one path segment, such as `"{var}"` or -// `"{var=*}"`, when such a variable is expanded into a URL path on the client -// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The -// server side does the reverse decoding. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{var}`. -// -// If a variable contains multiple path segments, such as `"{var=foo/*}"` -// or `"{var=**}"`, when such a variable is expanded into a URL path on the -// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. -// The server side does the reverse decoding, except "%2F" and "%2f" are left -// unchanged. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{+var}`. -// -// ## Using gRPC API Service Configuration -// -// gRPC API Service Configuration (service config) is a configuration language -// for configuring a gRPC service to become a user-facing product. The -// service config is simply the YAML representation of the `google.api.Service` -// proto message. -// -// As an alternative to annotating your proto file, you can configure gRPC -// transcoding in your service config YAML files. You do this by specifying a -// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same -// effect as the proto annotation. This can be particularly useful if you -// have a proto that is reused in multiple services. Note that any transcoding -// specified in the service config will override any matching transcoding -// configuration in the proto. -// -// Example: -// -// http: -// rules: -// # Selects a gRPC method and applies HttpRule to it. -// - selector: example.v1.Messaging.GetMessage -// get: /v1/messages/{message_id}/{sub.subfield} -// -// ## Special notes -// -// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the -// proto to JSON conversion must follow the [proto3 -// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). -// -// While the single segment variable follows the semantics of -// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String -// Expansion, the multi segment variable **does not** follow RFC 6570 Section -// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion -// does not expand special characters like `?` and `#`, which would lead -// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding -// for multi segment variables. -// -// The path variables **must not** refer to any repeated or mapped field, -// because client libraries are not capable of handling such variable expansion. -// -// The path variables **must not** capture the leading "/" character. The reason -// is that the most common use case "{var}" does not capture the leading "/" -// character. For consistency, all path variables must share the same behavior. -// -// Repeated message fields must not be mapped to URL query parameters, because -// no client library can support such complicated mapping. -// -// If an API needs to use a JSON array for request or response body, it can map -// the request or response body to a repeated field. However, some gRPC -// Transcoding implementations may not support this feature. -message HttpRule { - // Selects a method to which this rule applies. - // - // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - string selector = 1; - - // Determines the URL pattern is matched by this rules. This pattern can be - // used with any of the {get|put|post|delete|patch} methods. A custom method - // can be defined using the 'custom' field. - oneof pattern { - // Maps to HTTP GET. Used for listing and getting information about - // resources. - string get = 2; - - // Maps to HTTP PUT. Used for replacing a resource. - string put = 3; - - // Maps to HTTP POST. Used for creating a resource or performing an action. - string post = 4; - - // Maps to HTTP DELETE. Used for deleting a resource. - string delete = 5; - - // Maps to HTTP PATCH. Used for updating a resource. - string patch = 6; - - // The custom pattern is used for specifying an HTTP method that is not - // included in the `pattern` field, such as HEAD, or "*" to leave the - // HTTP method unspecified for this rule. The wild-card rule is useful - // for services that provide content to Web (HTML) clients. - CustomHttpPattern custom = 8; - } - - // The name of the request field whose value is mapped to the HTTP request - // body, or `*` for mapping all request fields not captured by the path - // pattern to the HTTP body, or omitted for not having any HTTP request body. - // - // NOTE: the referred field must be present at the top-level of the request - // message type. - string body = 7; - - // Optional. The name of the response field whose value is mapped to the HTTP - // response body. When omitted, the entire response message will be used - // as the HTTP response body. - // - // NOTE: The referred field must be present at the top-level of the response - // message type. - string response_body = 12; - - // Additional HTTP bindings for the selector. Nested bindings must - // not contain an `additional_bindings` field themselves (that is, - // the nesting may only be one level deep). - repeated HttpRule additional_bindings = 11; -} - -// A custom pattern is used for defining custom HTTP verb. -message CustomHttpPattern { - // The name of this custom HTTP verb. - string kind = 1; - - // The path matched by this custom verb. - string path = 2; -} diff --git a/examples/helloworld_transcoding/priv/protos/helloworld.proto b/examples/helloworld_transcoding/priv/protos/helloworld.proto deleted file mode 100644 index 55c41005..00000000 --- a/examples/helloworld_transcoding/priv/protos/helloworld.proto +++ /dev/null @@ -1,47 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; -option objc_class_prefix = "HLW"; - -import "google/api/annotations.proto"; -import "google/protobuf/timestamp.proto"; - -package helloworld; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) { - option (google.api.http) = { - get: "/v1/greeter/{name}" - }; - } - - rpc SayHelloFrom (HelloRequestFrom) returns (HelloReply) { - option (google.api.http) = { - post: "/v1/greeter" - body: "*" - }; - } -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// HelloRequestFrom! -message HelloRequestFrom { - // Name! - string name = 1; - // From! - string from = 2; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; - google.protobuf.Timestamp today = 2; -} diff --git a/examples/helloworld_transcoding/test/hello_world_test.exs b/examples/helloworld_transcoding/test/hello_world_test.exs deleted file mode 100644 index 962d07ac..00000000 --- a/examples/helloworld_transcoding/test/hello_world_test.exs +++ /dev/null @@ -1,16 +0,0 @@ -defmodule HelloworldTest do - @moduledoc false - - use ExUnit.Case - - setup_all do - {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Logger.Client]) - [channel: channel] - end - - test "helloworld should be successful", %{channel: channel} do - req = Helloworld.HelloRequest.new(name: "grpc-elixir") - assert {:ok, %{message: msg, today: _}} = Helloworld.Greeter.Stub.say_hello(channel, req) - assert msg == "Hello grpc-elixir" - end -end diff --git a/examples/helloworld_transcoding/test/test_helper.exs b/examples/helloworld_transcoding/test/test_helper.exs deleted file mode 100644 index 869559e7..00000000 --- a/examples/helloworld_transcoding/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() diff --git a/examples/route_guide/.gitignore b/examples/route_guide/.gitignore deleted file mode 100644 index 68f57605..00000000 --- a/examples/route_guide/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build - -# If you run "mix test --cover", coverage assets end up here. -/cover - -# The directory Mix downloads your dependencies sources to. -/deps - -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -/log diff --git a/examples/route_guide/README.md b/examples/route_guide/README.md deleted file mode 100644 index 5e9fa0df..00000000 --- a/examples/route_guide/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# RouteGuide in grpc-elixir - -## Usage - -1. Install deps and compile -``` -$ mix do deps.get, compile -``` - -2. Run the server -``` -$ mix run --no-halt -``` - -2. Run the client -``` -$ mix run priv/client.exs -``` - -## Regenerate Elixir code from proto - -1. Modify the proto `priv/route_guide.proto` -2. Install `protoc` [here](https://developers.google.com/protocol-buffers/docs/downloads) -3. Install `protoc-gen-elixir` -``` -mix escript.install hex protobuf -``` -3. Generate the code: -```shell -$ protoc -I priv --elixir_out=plugins=grpc:./lib/ priv/route_guide.proto -``` - -Refer to [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) for more information. - -## Authentication - -``` -$ TLS=true mix run --no-halt -$ TLS=true mix run priv/client.exs -``` - -## FAQ - -* How to change log level? Check out `config/config.exs`, default to warn -* Use local grpc-elixir? Uncomment `{:grpc, path: "../../"}` in `mix.exs` -* Why is output format of `Feature` & `Point` different from normal map? Check out `lib/inspect.ex` diff --git a/examples/route_guide/lib/app.ex b/examples/route_guide/lib/app.ex deleted file mode 100644 index 8cebbf31..00000000 --- a/examples/route_guide/lib/app.ex +++ /dev/null @@ -1,27 +0,0 @@ -defmodule Routeguide.App do - use Application - - @cert_path Path.expand("./tls/server1.pem", :code.priv_dir(:route_guide)) - @key_path Path.expand("./tls/server1.key", :code.priv_dir(:route_guide)) - - def start(_type, _args) do - children = [ - RouteGuide.Data, - {GRPC.Server.Supervisor, start_args()} - ] - - opts = [strategy: :one_for_one, name: Routeguide] - Supervisor.start_link(children, opts) - end - - defp start_args do - opts = [endpoint: Routeguide.Endpoint, port: 10000, start_server: true] - - if System.get_env("TLS") do - cred = GRPC.Credential.new(ssl: [certfile: @cert_path, keyfile: @key_path]) - Keyword.put(opts, :cred, cred) - else - opts - end - end -end diff --git a/examples/route_guide/lib/client.ex b/examples/route_guide/lib/client.ex deleted file mode 100644 index 5d33f4ba..00000000 --- a/examples/route_guide/lib/client.ex +++ /dev/null @@ -1,104 +0,0 @@ -defmodule RouteGuide.Client do - def main(channel) do - print_feature(channel, Routeguide.Point.new(latitude: 409_146_138, longitude: -746_188_906)) - print_feature(channel, Routeguide.Point.new(latitude: 0, longitude: 0)) - - # Looking for features between 40, -75 and 42, -73. - print_features( - channel, - Routeguide.Rectangle.new( - lo: Routeguide.Point.new(latitude: 400_000_000, longitude: -750_000_000), - hi: Routeguide.Point.new(latitude: 420_000_000, longitude: -730_000_000) - ) - ) - - run_record_route(channel) - - run_route_chat(channel) - end - - def print_feature(channel, point) do - IO.puts("Getting feature for point (#{point.latitude}, #{point.longitude})") - {:ok, reply} = channel |> Routeguide.RouteGuide.Stub.get_feature(point) - IO.inspect(reply) - end - - def print_features(channel, rect) do - IO.puts("Looking for features within #{inspect(rect)}") - {:ok, stream} = channel |> Routeguide.RouteGuide.Stub.list_features(rect) - - Enum.each(stream, fn {:ok, feature} -> - IO.inspect(feature) - end) - end - - def run_record_route(channel) do - ts = :os.timestamp() - seed = :rand.seed(:exs64, ts) - {count, seed} = :rand.uniform_s(seed) - count = trunc(count * 100 + 2) - - {points, _seed} = - Enum.reduce(1..count, {[], seed}, fn _, {acc, seed} -> - {point, seed} = random_point(seed) - {[point | acc], seed} - end) - - IO.puts("Traversing #{length(points)} points.") - stream = channel |> Routeguide.RouteGuide.Stub.record_route() - - Enum.reduce(points, points, fn _, [point | tail] -> - opts = if length(tail) == 0, do: [end_stream: true], else: [] - GRPC.Stub.send_request(stream, point, opts) - tail - end) - - res = GRPC.Stub.recv(stream) - IO.puts("Route summary: #{inspect(res)}") - end - - def run_route_chat(channel) do - data = [ - %{lat: 0, long: 1, msg: "First message"}, - %{lat: 0, long: 2, msg: "Second message"}, - %{lat: 0, long: 3, msg: "Third message"}, - %{lat: 0, long: 1, msg: "Fourth message"}, - %{lat: 0, long: 2, msg: "Fifth message"}, - %{lat: 0, long: 3, msg: "Sixth message"} - ] - - stream = channel |> Routeguide.RouteGuide.Stub.route_chat() - - notes = - Enum.map(data, fn %{lat: lat, long: long, msg: msg} -> - point = Routeguide.Point.new(latitude: lat, longitude: long) - Routeguide.RouteNote.new(location: point, message: msg) - end) - - task = - Task.async(fn -> - Enum.reduce(notes, notes, fn _, [note | tail] -> - opts = if length(tail) == 0, do: [end_stream: true], else: [] - GRPC.Stub.send_request(stream, note, opts) - tail - end) - end) - - {:ok, result_enum} = GRPC.Stub.recv(stream) - Task.await(task) - - Enum.each(result_enum, fn {:ok, note} -> - IO.puts( - "Got message #{note.message} at point(#{note.location.latitude}, #{note.location.longitude})" - ) - end) - end - - defp random_point(seed) do - {lat, seed} = :rand.uniform_s(seed) - {long, seed} = :rand.uniform_s(seed) - lat = trunc((trunc(lat * 180) - 90) * 1.0e7) - long = trunc((trunc(long * 360) - 180) * 1.0e7) - {Routeguide.Point.new(latitude: lat, longitude: long), seed} - end -end diff --git a/examples/route_guide/lib/data.ex b/examples/route_guide/lib/data.ex deleted file mode 100644 index 7d1fe528..00000000 --- a/examples/route_guide/lib/data.ex +++ /dev/null @@ -1,34 +0,0 @@ -defmodule RouteGuide.Data do - use Agent - - @json_path Path.expand("../priv/route_guide_db.json", __DIR__) - - def start_link(_) do - features = load_features() - Agent.start_link(fn -> %{features: features, notes: %{}} end, name: __MODULE__) - end - - def fetch_features do - Agent.get(__MODULE__, &Map.get(&1, :features)) - end - - def fetch_notes do - Agent.get(__MODULE__, &Map.get(&1, :notes)) - end - - def update_notes(notes) do - Agent.update(__MODULE__, &Map.put(&1, :notes, notes)) - end - - defp load_features(path \\ @json_path) do - data = File.read!(path) - items = Jason.decode!(data) - - for %{"location" => location, "name" => name} <- items do - point = - Routeguide.Point.new(latitude: location["latitude"], longitude: location["longitude"]) - - Routeguide.Feature.new(name: name, location: point) - end - end -end diff --git a/examples/route_guide/lib/endpoint.ex b/examples/route_guide/lib/endpoint.ex deleted file mode 100644 index 5a25c582..00000000 --- a/examples/route_guide/lib/endpoint.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule Routeguide.Endpoint do - use GRPC.Endpoint - - intercept GRPC.Server.Interceptors.Logger - run Routeguide.RouteGuide.Server -end diff --git a/examples/route_guide/lib/inspect.ex b/examples/route_guide/lib/inspect.ex deleted file mode 100644 index fb5cabac..00000000 --- a/examples/route_guide/lib/inspect.ex +++ /dev/null @@ -1,36 +0,0 @@ -defimpl Inspect, for: Routeguide.Point do - def inspect(%{__struct__: struct} = point, opts) do - lat_str = Inspect.Integer.inspect(point.latitude || 0, opts) - lng_str = Inspect.Integer.inspect(point.longitude || 0, opts) - middle = "latitude: " <> lat_str <> ", longitude: " <> lng_str - - if Map.get(opts, :compact, true) do - "<" <> middle <> ">" - else - name = Inspect.Atom.inspect(struct, opts) - "%#{name}{" <> middle <> "}" - end - end -end - -defimpl Inspect, for: Routeguide.Feature do - def inspect(%{__struct__: struct} = feature, opts) do - name = Inspect.Atom.inspect(struct, opts) - - name_str = - if feature.name do - Inspect.BitString.inspect(feature.name, opts) - else - Inspect.Atom.inspect(nil, opts) - end - - loc_str = Inspect.Routeguide.Point.inspect(feature.location, opts) - middle = "name: " <> name_str <> ", location: " <> loc_str - - if Map.get(opts, :compact, true) do - "<" <> middle <> ">" - else - "%#{name}{" <> middle <> "}" - end - end -end diff --git a/examples/route_guide/lib/route_guide.pb.ex b/examples/route_guide/lib/route_guide.pb.ex deleted file mode 100644 index 17cdb490..00000000 --- a/examples/route_guide/lib/route_guide.pb.ex +++ /dev/null @@ -1,66 +0,0 @@ -defmodule Routeguide.Point do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - field :latitude, 1, type: :int32 - field :longitude, 2, type: :int32 -end - -defmodule Routeguide.Rectangle do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - field :lo, 1, type: Routeguide.Point - field :hi, 2, type: Routeguide.Point -end - -defmodule Routeguide.Feature do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - field :name, 1, type: :string - field :location, 2, type: Routeguide.Point -end - -defmodule Routeguide.RouteNote do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - field :location, 1, type: Routeguide.Point - field :message, 2, type: :string -end - -defmodule Routeguide.RouteSummary do - @moduledoc false - - use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 - - field :point_count, 1, type: :int32, json_name: "pointCount" - field :feature_count, 2, type: :int32, json_name: "featureCount" - field :distance, 3, type: :int32 - field :elapsed_time, 4, type: :int32, json_name: "elapsedTime" -end - -defmodule Routeguide.RouteGuide.Service do - @moduledoc false - - use GRPC.Service, name: "routeguide.RouteGuide", protoc_gen_elixir_version: "0.14.0" - - rpc :GetFeature, Routeguide.Point, Routeguide.Feature - - rpc :ListFeatures, Routeguide.Rectangle, stream(Routeguide.Feature) - - rpc :RecordRoute, stream(Routeguide.Point), Routeguide.RouteSummary - - rpc :RouteChat, stream(Routeguide.RouteNote), stream(Routeguide.RouteNote) -end - -defmodule Routeguide.RouteGuide.Stub do - @moduledoc false - - use GRPC.Stub, service: Routeguide.RouteGuide.Service -end diff --git a/examples/route_guide/lib/server.ex b/examples/route_guide/lib/server.ex deleted file mode 100644 index 3e90b8bc..00000000 --- a/examples/route_guide/lib/server.ex +++ /dev/null @@ -1,112 +0,0 @@ -defmodule Routeguide.RouteGuide.Server do - use GRPC.Server, service: Routeguide.RouteGuide.Service - alias GRPC.Server - alias RouteGuide.Data - - @spec get_feature(Routeguide.Point, GRPC.Server.Stream.t()) :: Routeguide.Feature.t() - def get_feature(point, _stream) do - features = Data.fetch_features() - default_feature = Routeguide.Feature.new(location: point) - - Enum.find(features, default_feature, fn feature -> - feature.location == point - end) - end - - @spec list_features(Routeguide.Rectangle.t(), GRPC.Server.Stream.t()) :: any() - def list_features(rect, stream) do - features = Data.fetch_features() - - features - |> Enum.filter(fn %{location: loc} -> in_range?(loc, rect) end) - |> Enum.each(fn feature -> Server.send_reply(stream, feature) end) - end - - @spec record_route(Enumerable.t(), GRPC.Server.Stream.t()) :: Routeguide.RouteSummary.t() - def record_route(req_enum, _stream) do - features = Data.fetch_features() - start_time = now_ts() - - {_, distance, point_count, feature_count} = - Enum.reduce(req_enum, {nil, 0, 0, 0}, fn point, - {last, distance, point_count, feature_count} -> - point_count = point_count + 1 - found_feature = Enum.find(features, fn f -> f.location == point end) - feature_count = if found_feature, do: feature_count + 1, else: feature_count - distance = if last, do: distance + calc_distance(last, point), else: distance - {point, distance, point_count, feature_count} - end) - - Routeguide.RouteSummary.new( - point_count: point_count, - feature_count: feature_count, - distance: distance, - elapsed_time: now_ts() - start_time - ) - end - - @spec record_route(Enumerable.t(), GRPC.Server.Stream.t()) :: any() - def route_chat(req_enum, stream) do - notes = - Enum.reduce(req_enum, Data.fetch_notes(), fn note, notes -> - key = serialize_location(note.location) - new_notes = Map.update(notes, key, [note], &(&1 ++ [note])) - - Enum.each(new_notes[key], fn note -> - IO.inspect(note) - Server.send_reply(stream, note) - end) - - new_notes - end) - - Data.update_notes(notes) - end - - defp in_range?(%{longitude: long, latitude: lat}, %{lo: low, hi: high}) do - left = min(low.longitude, high.longitude) - right = max(low.longitude, high.longitude) - bottom = min(low.latitude, high.latitude) - top = max(low.latitude, high.latitude) - - long >= left && long <= right && lat >= bottom && lat <= top - end - - defp now_ts do - DateTime.utc_now() |> DateTime.to_unix() - end - - # calcDistance calculates the distance between two points using the "haversine" formula. - # This code was taken from http://www.movable-type.co.uk/scripts/latlong.html. - defp calc_distance(p1, p2) do - cord_factor = 1.0e7 - r = 6_371_000.0 - lat1 = (p1.latitude || 0) / cord_factor - lat2 = (p2.latitude || 0) / cord_factor - lng1 = (p1.longitude || 0) / cord_factor - lng2 = (p2.longitude || 0) / cord_factor - phi1 = to_radians(lat1) - phi2 = to_radians(lat2) - delta_phi = to_radians(lat2 - lat1) - delta_lambda = to_radians(lng2 - lng1) - - a = - sqr(:math.sin(delta_phi / 2)) + - :math.cos(phi1) * :math.cos(phi2) * sqr(:math.sin(delta_lambda / 2)) - - c = 2 * :math.atan2(:math.sqrt(a), :math.sqrt(1 - a)) - round(r * c) - end - - defp to_radians(num) do - num * :math.pi() / 180 - end - - defp sqr(num) do - num * num - end - - def serialize_location(p) do - "#{p.latitude} #{p.longitude}" - end -end diff --git a/examples/route_guide/mix.exs b/examples/route_guide/mix.exs deleted file mode 100644 index 504e8267..00000000 --- a/examples/route_guide/mix.exs +++ /dev/null @@ -1,38 +0,0 @@ -defmodule RouteGuide.Mixfile do - use Mix.Project - - def project do - [ - app: :route_guide, - version: "0.1.0", - elixir: "~> 1.11", - build_embedded: Mix.env() == :prod, - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - # Configuration for the OTP application - # - # Type "mix help compile.app" for more information - def application do - [mod: {Routeguide.App, []}, applications: [:logger, :grpc, :protobuf, :jason]] - end - - # Dependencies can be Hex packages: - # - # {:mydep, "~> 0.3.0"} - # - # Or git/path repositories: - # - # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} - # - # Type "mix help deps" for more examples and options - defp deps do - [ - {:grpc, path: "../../"}, - {:protobuf, "~> 0.14"}, - {:jason, "~> 1.2"} - ] - end -end diff --git a/examples/route_guide/mix.lock b/examples/route_guide/mix.lock deleted file mode 100644 index f251ba2f..00000000 --- a/examples/route_guide/mix.lock +++ /dev/null @@ -1,13 +0,0 @@ -%{ - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, - "gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"}, - "gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, -} diff --git a/examples/route_guide/priv/client.exs b/examples/route_guide/priv/client.exs deleted file mode 100644 index 5545ce9f..00000000 --- a/examples/route_guide/priv/client.exs +++ /dev/null @@ -1,13 +0,0 @@ -opts = [interceptors: [GRPC.Client.Interceptors.Logger]] - -opts = - if System.get_env("TLS") do - ca_path = Path.expand("./tls/ca.pem", :code.priv_dir(:route_guide)) - cred = GRPC.Credential.new(ssl: [cacertfile: ca_path]) - [{:cred, cred} | opts] - else - opts - end - -{:ok, channel} = GRPC.Stub.connect("localhost:10000", opts) -RouteGuide.Client.main(channel) diff --git a/examples/route_guide/priv/protos/route_guide.proto b/examples/route_guide/priv/protos/route_guide.proto deleted file mode 100644 index 3cfe1604..00000000 --- a/examples/route_guide/priv/protos/route_guide.proto +++ /dev/null @@ -1,41 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.routeguide"; -option java_outer_classname = "RouteGuideProto"; - -package routeguide; - -service RouteGuide { - rpc GetFeature(Point) returns (Feature) {} - rpc ListFeatures(Rectangle) returns (stream Feature) {} - rpc RecordRoute(stream Point) returns (RouteSummary) {} - rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} -} - -message Point { - int32 latitude = 1; - int32 longitude = 2; -} - -message Rectangle { - Point lo = 1; - Point hi = 2; -} - -message Feature { - string name = 1; - Point location = 2; -} - -message RouteNote { - Point location = 1; - string message = 2; -} - -message RouteSummary { - int32 point_count = 1; - int32 feature_count = 2; - int32 distance = 3; - int32 elapsed_time = 4; -} diff --git a/examples/route_guide/priv/route_guide_db.json b/examples/route_guide/priv/route_guide_db.json deleted file mode 100644 index 9d6a980a..00000000 --- a/examples/route_guide/priv/route_guide_db.json +++ /dev/null @@ -1,601 +0,0 @@ -[{ - "location": { - "latitude": 407838351, - "longitude": -746143763 - }, - "name": "Patriots Path, Mendham, NJ 07945, USA" -}, { - "location": { - "latitude": 408122808, - "longitude": -743999179 - }, - "name": "101 New Jersey 10, Whippany, NJ 07981, USA" -}, { - "location": { - "latitude": 413628156, - "longitude": -749015468 - }, - "name": "U.S. 6, Shohola, PA 18458, USA" -}, { - "location": { - "latitude": 419999544, - "longitude": -740371136 - }, - "name": "5 Conners Road, Kingston, NY 12401, USA" -}, { - "location": { - "latitude": 414008389, - "longitude": -743951297 - }, - "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" -}, { - "location": { - "latitude": 419611318, - "longitude": -746524769 - }, - "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" -}, { - "location": { - "latitude": 406109563, - "longitude": -742186778 - }, - "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" -}, { - "location": { - "latitude": 416802456, - "longitude": -742370183 - }, - "name": "352 South Mountain Road, Wallkill, NY 12589, USA" -}, { - "location": { - "latitude": 412950425, - "longitude": -741077389 - }, - "name": "Bailey Turn Road, Harriman, NY 10926, USA" -}, { - "location": { - "latitude": 412144655, - "longitude": -743949739 - }, - "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" -}, { - "location": { - "latitude": 415736605, - "longitude": -742847522 - }, - "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" -}, { - "location": { - "latitude": 413843930, - "longitude": -740501726 - }, - "name": "162 Merrill Road, Highland Mills, NY 10930, USA" -}, { - "location": { - "latitude": 410873075, - "longitude": -744459023 - }, - "name": "Clinton Road, West Milford, NJ 07480, USA" -}, { - "location": { - "latitude": 412346009, - "longitude": -744026814 - }, - "name": "16 Old Brook Lane, Warwick, NY 10990, USA" -}, { - "location": { - "latitude": 402948455, - "longitude": -747903913 - }, - "name": "3 Drake Lane, Pennington, NJ 08534, USA" -}, { - "location": { - "latitude": 406337092, - "longitude": -740122226 - }, - "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" -}, { - "location": { - "latitude": 406421967, - "longitude": -747727624 - }, - "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" -}, { - "location": { - "latitude": 416318082, - "longitude": -749677716 - }, - "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" -}, { - "location": { - "latitude": 415301720, - "longitude": -748416257 - }, - "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" -}, { - "location": { - "latitude": 402647019, - "longitude": -747071791 - }, - "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" -}, { - "location": { - "latitude": 412567807, - "longitude": -741058078 - }, - "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" -}, { - "location": { - "latitude": 416855156, - "longitude": -744420597 - }, - "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" -}, { - "location": { - "latitude": 404663628, - "longitude": -744820157 - }, - "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" -}, { - "location": { - "latitude": 407113723, - "longitude": -749746483 - }, - "name": "" -}, { - "location": { - "latitude": 402133926, - "longitude": -743613249 - }, - "name": "" -}, { - "location": { - "latitude": 400273442, - "longitude": -741220915 - }, - "name": "" -}, { - "location": { - "latitude": 411236786, - "longitude": -744070769 - }, - "name": "" -}, { - "location": { - "latitude": 411633782, - "longitude": -746784970 - }, - "name": "211-225 Plains Road, Augusta, NJ 07822, USA" -}, { - "location": { - "latitude": 415830701, - "longitude": -742952812 - }, - "name": "" -}, { - "location": { - "latitude": 413447164, - "longitude": -748712898 - }, - "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" -}, { - "location": { - "latitude": 405047245, - "longitude": -749800722 - }, - "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" -}, { - "location": { - "latitude": 418858923, - "longitude": -746156790 - }, - "name": "" -}, { - "location": { - "latitude": 417951888, - "longitude": -748484944 - }, - "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" -}, { - "location": { - "latitude": 407033786, - "longitude": -743977337 - }, - "name": "26 East 3rd Street, New Providence, NJ 07974, USA" -}, { - "location": { - "latitude": 417548014, - "longitude": -740075041 - }, - "name": "" -}, { - "location": { - "latitude": 410395868, - "longitude": -744972325 - }, - "name": "" -}, { - "location": { - "latitude": 404615353, - "longitude": -745129803 - }, - "name": "" -}, { - "location": { - "latitude": 406589790, - "longitude": -743560121 - }, - "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" -}, { - "location": { - "latitude": 414653148, - "longitude": -740477477 - }, - "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" -}, { - "location": { - "latitude": 405957808, - "longitude": -743255336 - }, - "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" -}, { - "location": { - "latitude": 411733589, - "longitude": -741648093 - }, - "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" -}, { - "location": { - "latitude": 412676291, - "longitude": -742606606 - }, - "name": "1270 Lakes Road, Monroe, NY 10950, USA" -}, { - "location": { - "latitude": 409224445, - "longitude": -748286738 - }, - "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" -}, { - "location": { - "latitude": 406523420, - "longitude": -742135517 - }, - "name": "652 Garden Street, Elizabeth, NJ 07202, USA" -}, { - "location": { - "latitude": 401827388, - "longitude": -740294537 - }, - "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" -}, { - "location": { - "latitude": 410564152, - "longitude": -743685054 - }, - "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" -}, { - "location": { - "latitude": 408472324, - "longitude": -740726046 - }, - "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" -}, { - "location": { - "latitude": 412452168, - "longitude": -740214052 - }, - "name": "5 White Oak Lane, Stony Point, NY 10980, USA" -}, { - "location": { - "latitude": 409146138, - "longitude": -746188906 - }, - "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" -}, { - "location": { - "latitude": 404701380, - "longitude": -744781745 - }, - "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" -}, { - "location": { - "latitude": 409642566, - "longitude": -746017679 - }, - "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" -}, { - "location": { - "latitude": 408031728, - "longitude": -748645385 - }, - "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" -}, { - "location": { - "latitude": 413700272, - "longitude": -742135189 - }, - "name": "367 Prospect Road, Chester, NY 10918, USA" -}, { - "location": { - "latitude": 404310607, - "longitude": -740282632 - }, - "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" -}, { - "location": { - "latitude": 409319800, - "longitude": -746201391 - }, - "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" -}, { - "location": { - "latitude": 406685311, - "longitude": -742108603 - }, - "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" -}, { - "location": { - "latitude": 419018117, - "longitude": -749142781 - }, - "name": "43 Dreher Road, Roscoe, NY 12776, USA" -}, { - "location": { - "latitude": 412856162, - "longitude": -745148837 - }, - "name": "Swan Street, Pine Island, NY 10969, USA" -}, { - "location": { - "latitude": 416560744, - "longitude": -746721964 - }, - "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" -}, { - "location": { - "latitude": 405314270, - "longitude": -749836354 - }, - "name": "" -}, { - "location": { - "latitude": 414219548, - "longitude": -743327440 - }, - "name": "" -}, { - "location": { - "latitude": 415534177, - "longitude": -742900616 - }, - "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" -}, { - "location": { - "latitude": 406898530, - "longitude": -749127080 - }, - "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" -}, { - "location": { - "latitude": 407586880, - "longitude": -741670168 - }, - "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" -}, { - "location": { - "latitude": 400106455, - "longitude": -742870190 - }, - "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" -}, { - "location": { - "latitude": 400066188, - "longitude": -746793294 - }, - "name": "" -}, { - "location": { - "latitude": 418803880, - "longitude": -744102673 - }, - "name": "40 Mountain Road, Napanoch, NY 12458, USA" -}, { - "location": { - "latitude": 414204288, - "longitude": -747895140 - }, - "name": "" -}, { - "location": { - "latitude": 414777405, - "longitude": -740615601 - }, - "name": "" -}, { - "location": { - "latitude": 415464475, - "longitude": -747175374 - }, - "name": "48 North Road, Forestburgh, NY 12777, USA" -}, { - "location": { - "latitude": 404062378, - "longitude": -746376177 - }, - "name": "" -}, { - "location": { - "latitude": 405688272, - "longitude": -749285130 - }, - "name": "" -}, { - "location": { - "latitude": 400342070, - "longitude": -748788996 - }, - "name": "" -}, { - "location": { - "latitude": 401809022, - "longitude": -744157964 - }, - "name": "" -}, { - "location": { - "latitude": 404226644, - "longitude": -740517141 - }, - "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" -}, { - "location": { - "latitude": 410322033, - "longitude": -747871659 - }, - "name": "" -}, { - "location": { - "latitude": 407100674, - "longitude": -747742727 - }, - "name": "" -}, { - "location": { - "latitude": 418811433, - "longitude": -741718005 - }, - "name": "213 Bush Road, Stone Ridge, NY 12484, USA" -}, { - "location": { - "latitude": 415034302, - "longitude": -743850945 - }, - "name": "" -}, { - "location": { - "latitude": 411349992, - "longitude": -743694161 - }, - "name": "" -}, { - "location": { - "latitude": 404839914, - "longitude": -744759616 - }, - "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" -}, { - "location": { - "latitude": 414638017, - "longitude": -745957854 - }, - "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" -}, { - "location": { - "latitude": 412127800, - "longitude": -740173578 - }, - "name": "" -}, { - "location": { - "latitude": 401263460, - "longitude": -747964303 - }, - "name": "" -}, { - "location": { - "latitude": 412843391, - "longitude": -749086026 - }, - "name": "" -}, { - "location": { - "latitude": 418512773, - "longitude": -743067823 - }, - "name": "" -}, { - "location": { - "latitude": 404318328, - "longitude": -740835638 - }, - "name": "42-102 Main Street, Belford, NJ 07718, USA" -}, { - "location": { - "latitude": 419020746, - "longitude": -741172328 - }, - "name": "" -}, { - "location": { - "latitude": 404080723, - "longitude": -746119569 - }, - "name": "" -}, { - "location": { - "latitude": 401012643, - "longitude": -744035134 - }, - "name": "" -}, { - "location": { - "latitude": 404306372, - "longitude": -741079661 - }, - "name": "" -}, { - "location": { - "latitude": 403966326, - "longitude": -748519297 - }, - "name": "" -}, { - "location": { - "latitude": 405002031, - "longitude": -748407866 - }, - "name": "" -}, { - "location": { - "latitude": 409532885, - "longitude": -742200683 - }, - "name": "" -}, { - "location": { - "latitude": 416851321, - "longitude": -742674555 - }, - "name": "" -}, { - "location": { - "latitude": 406411633, - "longitude": -741722051 - }, - "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" -}, { - "location": { - "latitude": 413069058, - "longitude": -744597778 - }, - "name": "261 Van Sickle Road, Goshen, NY 10924, USA" -}, { - "location": { - "latitude": 418465462, - "longitude": -746859398 - }, - "name": "" -}, { - "location": { - "latitude": 411733222, - "longitude": -744228360 - }, - "name": "" -}, { - "location": { - "latitude": 410248224, - "longitude": -747127767 - }, - "name": "3 Hasta Way, Newton, NJ 07860, USA" -}] diff --git a/examples/route_guide/priv/tls/ca.pem b/examples/route_guide/priv/tls/ca.pem deleted file mode 100644 index 6c8511a7..00000000 --- a/examples/route_guide/priv/tls/ca.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla -Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 -YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT -BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 -+L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu -g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd -Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau -sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m -oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG -Dfcog5wrJytaQ6UA0wE= ------END CERTIFICATE----- diff --git a/examples/route_guide/priv/tls/server1.key b/examples/route_guide/priv/tls/server1.key deleted file mode 100644 index 143a5b87..00000000 --- a/examples/route_guide/priv/tls/server1.key +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD -M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf -3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY -AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm -V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY -tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p -dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q -K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR -81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff -DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd -aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 -ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 -XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe -F98XJ7tIFfJq ------END PRIVATE KEY----- diff --git a/examples/route_guide/priv/tls/server1.pem b/examples/route_guide/priv/tls/server1.pem deleted file mode 100644 index f3d43fcc..00000000 --- a/examples/route_guide/priv/tls/server1.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET -MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx -MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV -BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 -ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco -LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg -zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd -9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw -CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy -em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G -CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 -hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh -y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 ------END CERTIFICATE----- diff --git a/guides/advanced/cors.md b/guides/advanced/cors.md new file mode 100644 index 00000000..62baaeac --- /dev/null +++ b/guides/advanced/cors.md @@ -0,0 +1,16 @@ +# CORS + +When accessing gRPC from a browser via HTTP transcoding or gRPC-Web, CORS headers may be required for the browser to allow access to the gRPC endpoint. Adding CORS headers can be done by using `GRPC.Server.Interceptors.CORS` as an interceptor in your `GRPC.Endpoint` module, configuring it as described in the module documentation: + +Example: + +```elixir +# Define your endpoint +defmodule Helloworld.Endpoint do + use GRPC.Endpoint + + intercept GRPC.Server.Interceptors.Logger + intercept GRPC.Server.Interceptors.CORS, allow_origin: "mydomain.io" + run Helloworld.Greeter.Server +end +``` \ No newline at end of file diff --git a/guides/advanced/load_balancing.md b/guides/advanced/load_balancing.md new file mode 100644 index 00000000..272470f6 --- /dev/null +++ b/guides/advanced/load_balancing.md @@ -0,0 +1,44 @@ +# Load Balancing + +Load balancing is a core capability of modern distributed gRPC systems. Instead of connecting directly to a single static address, the Elixir gRPC client can dynamically resolve multiple backend endpoints using pluggable target schemes (DNS, Unix sockets, xDS, and more). This allows clients to automatically distribute traffic across services and benefit from infrastructure-level routing — whether running on Kubernetes, service meshes like Istio, or traditional on-prem deployments. + +The implementation in this library follows the official gRPC Client Load Balancing specification, ensuring compatibility with ecosystem tooling such as Envoy, xDS control planes (see note below), and DNS-based service discovery. + +This guide explains how to define target URIs and how the built-in resolver discovers and continuously refreshes backend servers. Once configured, your load-balancing strategy becomes part of the connection string, no additional code required. + +## Target Schemes and Resolvers + +The `connect/2` function supports URI-like targets that are resolved via the internal **gRPC** [Resolver](lib/grpc/client/resolver.ex). +You can connect using `DNS`, `Unix Domain sockets`, and `IPv4/IPv6` for now. + +### Supported formats: + +| Scheme | Example | Description | +|:----------|:----------------------------|:---------------------------------------------| +| `dns://` | `"dns://example.com:50051"` | Resolves via DNS `A/AAAA` records | +| `ipv4:` | `"ipv4:10.0.0.5:50051"` | Connects directly to an IPv4 address | +| `unix:` | `"unix:/tmp/service.sock"` | Connects via a Unix domain socket | +| none | `"127.0.0.1:50051"` | Implicit DNS (default port `50051`) | + +--- + +## Examples: + +### DNS + +```elixir +iex> {:ok, _pid} = GRPC.Client.Supervisor.start_link() +iex> {:ok, channel} = GRPC.Stub.connect("dns://orders.prod.svc.cluster.local:50051") +iex> request = Orders.GetOrderRequest.new(id: "123") +iex> {:ok, reply} = channel |> Orders.OrderService.Stub.get_order(request) +``` + +### Unix Domain Sockets + +```elixir +iex> {:ok, channel} = GRPC.Stub.connect("unix:/tmp/my.sock") +``` + +>__Note__: When using `DNS` target, the connection layer periodically refreshes endpoints. + +--- \ No newline at end of file diff --git a/guides/pooling.md b/guides/advanced/pooling.md similarity index 94% rename from guides/pooling.md rename to guides/advanced/pooling.md index 33a6e031..93f5c9c0 100644 --- a/guides/pooling.md +++ b/guides/advanced/pooling.md @@ -1,4 +1,4 @@ -# Managing HTTP/2 Connections Efficiently +# Pooling When managing large numbers of gRPC HTTP/2 connections, you may benefit from pooling of some sort. diff --git a/livebooks/telemetry.livemd b/guides/advanced/telemetry.livemd similarity index 98% rename from livebooks/telemetry.livemd rename to guides/advanced/telemetry.livemd index deaf0c04..3d63f632 100644 --- a/livebooks/telemetry.livemd +++ b/guides/advanced/telemetry.livemd @@ -1,17 +1,17 @@ # Telemetry ```elixir -my_app_root = Path.join(__DIR__, "..") +app_root = Path.join(__DIR__, "..") Mix.install( [ - {:grpc, path: my_app_root, env: :dev}, + {:grpc, path: app_root, env: :dev}, {:telemetry_metrics, "~> 0.7"}, {:telemetry_metrics_prometheus, "~> 1.1"}, {:req, "~> 0.3"} ], - config_path: Path.join(my_app_root, "config/config.exs"), - lockfile: Path.join(my_app_root, "mix.lock") + config_path: Path.join(app_root, "config/config.exs"), + lockfile: Path.join(app_root, "mix.lock") ) ``` diff --git a/guides/advanced/transcoding.livemd b/guides/advanced/transcoding.livemd new file mode 100644 index 00000000..c28d810f --- /dev/null +++ b/guides/advanced/transcoding.livemd @@ -0,0 +1,214 @@ +# Transcoding + +The goal of transcoding is to allow HTTP/JSON calls to be automatically converted into gRPC protobuf calls +without external gateways. +--- + +## Setup + +```elixir +app_root = Path.join(__DIR__, "..") + +Mix.install( + [ + {:grpc, path: app_root, env: :dev} + ], + config_path: Path.join(app_root, "config/config.exs"), + lockfile: Path.join(app_root, "mix.lock") +) +``` + +## Protobuf Service and Messages + +```elixir +defmodule Helloworld.HelloRequest do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 + + field :name, 1, type: :string +end + +defmodule Helloworld.HelloRequestFrom do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 + + field :name, 1, type: :string + field :from, 2, type: :string +end + +defmodule Helloworld.HelloReply do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3 + + field :message, 1, type: :string + field :today, 2, type: Google.Protobuf.Timestamp +end + +defmodule Helloworld.Greeter.Service do + @moduledoc false + use GRPC.Service, name: "helloworld.Greeter", protoc_gen_elixir_version: "0.14.1" + + rpc(:SayHello, Helloworld.HelloRequest, Helloworld.HelloReply, %{ + http: %{ + type: Google.Api.PbExtension, + value: %Google.Api.HttpRule{ + selector: "", + body: "", + additional_bindings: [], + response_body: "", + pattern: {:get, "/v1/greeter/{name}"}, + __unknown_fields__: [] + } + } + }) + + rpc(:SayHelloFrom, Helloworld.HelloRequestFrom, Helloworld.HelloReply, %{ + http: %{ + type: Google.Api.PbExtension, + value: %Google.Api.HttpRule{ + selector: "", + body: "*", + additional_bindings: [], + response_body: "", + pattern: {:post, "/v1/greeter"}, + __unknown_fields__: [] + } + } + }) +end +``` + +In a real-world application, this would be generated from your project's .proto files. You would have to annotate your protobuf in the following way: + +```protobuf +import "google/api/annotations.proto"; +import "google/protobuf/timestamp.proto"; + +package helloworld; + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply) { + option (google.api.http) = { + get: "/v1/greeter/{name}" + }; + } + + rpc SayHelloFrom (HelloRequestFrom) returns (HelloReply) { + option (google.api.http) = { + post: "/v1/greeter" + body: "*" + }; + } +} +``` + +The compilation itself would be something like: + +```sh +mix protobuf.generate --include-path=priv/proto --include-path=deps/googleapis --generate-descriptors=true --output-path=./lib --plugins=ProtobufGenerate.Plugins.GRPCWithOptions google/api/annotations.proto google/api/http.proto helloworld.proto +``` + +--- + +## Enable transcoding on the Elixir side. + +```elixir +defmodule Helloworld.Greeter.Server do + use GRPC.Server, + service: Helloworld.Greeter.Service, + http_transcode: true + + alias GRPC.Stream, as: GRPCStream + alias Helloworld.HelloRequest + alias Helloworld.HelloReply + + def say_hello(request, stream) do + GRPCStream.unary(request, materializer: stream) + |> GRPCStream.map(fn + %HelloRequest{} = reply -> + %HelloReply{ + message: "Hello #{request.name}", + today: today() + } + + {:error, reason} -> + {:error, GRPC.RPCError.exception(message: "[Error] #{inspect(reason)}")} + end) + |> GRPCStream.run() + end + + def say_hello_from(request, _stream) do + GRPCStream.unary(request, materializer: stream) + |> GRPCStream.map(fn + %HelloFromRequest{} = reply -> + %HelloReply{ + message: "Hello #{request.name}. From #{request.from}", + today: today() + } + + _ -> + GRPC.RPCError.exception(message: "[Error] something bad happened") + end) + |> GRPCStream.run() + end + + defp today() do + nanos_epoch = System.system_time() |> System.convert_time_unit(:native, :nanosecond) + seconds = div(nanos_epoch, 1_000_000_000) + nanos = nanos_epoch - seconds * 1_000_000_000 + + %Google.Protobuf.Timestamp{seconds: seconds, nanos: nanos} + end +end +``` + +--- + +## Endpoint + Supervisor + +```elixir +defmodule TranscodeEndpoint do + use GRPC.Endpoint + intercept(GRPC.Server.Interceptors.Logger) + run(Helloworld.Greeter.Server) +end + +{:ok, _pid} = + GRPC.Server.Supervisor.start_link( + endpoint: TranscodeEndpoint, + port: 50054, + start_server: true + ) + +IO.puts("Transcoded gRPC Server running on :50054") +``` + +This automatically activates HTTP endpoints based on the annotations. + +--- + +## Testing with `curl` + +```shell +# Say hello +$ curl -H 'accept: application/json' http://localhost:50054/v1/greeter/test +``` + +```shell +# Say hello from +$ curl -XPOST -H 'Content-type: application/json' -d '{"name": "test", "from": "anon"}' http://localhost:50054/v1/greeter +``` + +--- + +## Important notes + +| Feature | Status | +|-----------------------|-------------------------------------------| +| CORS | Not enabled by default. See CORS section. | +| Server Streaming | Supported. | +| Query params → fields | Supported. See note below. | + +>__Note__: gRPC Transcode HttpRule https://docs.cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule + +--- \ No newline at end of file diff --git a/guides/cheatsheets/streams.cheatmd b/guides/cheatsheets/streams.cheatmd new file mode 100644 index 00000000..943c3f6f --- /dev/null +++ b/guides/cheatsheets/streams.cheatmd @@ -0,0 +1,125 @@ +# Stream Operators + +`GRPC.Stream` provides a functional and composable API for building +fault-tolerant pipelines for unary and streaming gRPC calls in Elixir. + +This cheatsheet summarizes the main operators available today. + +## Creating Streams + +### `unary/1` +Wraps a single request into a stream (unary RPC type). + +```elixir +iex> GRPC.Stream.unary(request) +``` + +### `from/1` +Creates a stream from an stream RPC. + +```elixir +iex> GRPC.Stream.from(request) +``` + +## Transforming Streams + +### `map/2` +Transforms each element in the stream. + +```elixir +iex> stream |> GRPC.Stream.map(&process/1) +``` + +### `flat_map/2` +Transforms each element in the stream. + +```elixir +iex> GRPC.Stream.from([1, 2]) +iex> |> GRPC.Stream.flat_map(&[&1, &1]) +iex> |> GRPC.Stream.to_flow() +iex> |> Enum.to_list() +``` + +### `ask/3` +Performs an external call using a Materializer. + +```elixir +iex> pid = +iex> spawn(fn -> +iex> receive do +iex> {:request, :hello, test_pid} -> +iex> send(test_pid, {:response, :world}) +iex> end +iex> end) +iex> +iex> GRPC.Stream.from([:hello]) +iex> |> GRPC.Stream.ask(pid) +iex> |> GRPC.Stream.to_flow() +iex> |> Enum.to_list() +``` + +## Filtering, grouping, and reduce Streams + +### `filter/2` +Filters the stream using the given predicate function. + +```elixir +iex> GRPC.Stream.from([1, 2, 3, 4]) +iex> |> GRPC.Stream.filter(&(rem(&1, 2) == 0)) +iex> |> GRPC.Stream.to_flow() +iex> |> Enum.to_list() +``` + +## Effects + +### `effect/2` +Applies a side-effect function to each element of the stream without altering its values. + +```elixir +iex> parent = self() +iex> stream = +...> GRPC.Stream.from(request) +...> |> GRPC.Stream.effect(fn x -> send(parent, {:seen, x*2}) end) +...> |> GRPC.Stream.to_flow() +...> |> Enum.to_list() +iex> flush() +iex> stream +[1, 2, 3] +``` + +## Error Handling + +### `map_error/2` +Intercepts and transforms errors & exceptions. + +```elixir +iex> GRPC.Stream.from([1, 2]) +iex> |> GRPC.Stream.map(fn +iex> 2 -> raise "boom" +iex> x -> x +iex> end) +iex> |> GRPC.Stream.map_error(fn +iex> {:error, %RuntimeError{message: "boom"}} -> +iex> GRPC.RPCError.exception(message: "Validation or runtime error") +iex> +iex> msg -> +iex> msg +iex> end) +``` + +## Running Streams + +### `run/1` +Executes the pipeline for unary RPC. + +```elixir +iex> stream |> GRPC.Stream.run() +``` + +### `run_with/2` +Executes the pipeline for stream RPC's. + +```elixir +iex> stream |> GRPC.Stream.run_with(materializer) +``` + diff --git a/guides/getting_started/client.md b/guides/getting_started/client.md new file mode 100644 index 00000000..1d748640 --- /dev/null +++ b/guides/getting_started/client.md @@ -0,0 +1,86 @@ +# Client + +This section demonstrates how to establish client connections and perform RPC calls using the Elixir gRPC client. + +--- + +## Basic Connection and RPC + +Typically, you start this client supervisor as part of your application's supervision tree: + +```elixir +children = [ + GRPC.Client.Supervisor +] + +opts = [strategy: :one_for_one, name: MyApp.Supervisor] +Supervisor.start_link(children, opts) +``` + +You can also start it manually in scripts or test environments: +```elixir +{:ok, _pid} = GRPC.Client.Supervisor.start_link() +``` + +Then connect with gRPC server: + +```elixir +iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051") +iex> request = Helloworld.HelloRequest.new(name: "grpc-elixir") +iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request) +``` + +--- + +## Using Interceptors + +Client interceptors allow you to add logic to the request/response lifecycle, such as logging, tracing, or authentication. + +```elixir +iex> {:ok, channel} = +...> GRPC.Stub.connect("localhost:50051", +...> interceptors: [GRPC.Client.Interceptors.Logger] +...> ) +iex> request = Helloworld.HelloRequest.new(name: "Alice") +iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request) +``` + +--- + +## Compression and Metadata + +You can specify message compression and attach default headers to all requests. + +```elixir +iex> {:ok, channel} = +...> GRPC.Stub.connect("localhost:50051", +...> compressor: GRPC.Compressor.Gzip, +...> headers: [{"authorization", "Bearer my-token"}] +...> ) +``` + +--- + +## Client Adapters + +By default, `GRPC.Stub.connect/2` uses the **Gun** adapter. +You can switch to **Mint** (pure Elixir HTTP/2) or other adapters as needed. + +### Using Mint Adapter + +```elixir +iex> GRPC.Stub.connect("localhost:50051", +...> adapter: GRPC.Client.Adapters.Mint +...> ) +``` + +You can configure adapter options globally via your application’s config: + +```elixir +# File: config/config.exs +config :grpc, GRPC.Client.Adapters.Mint, + timeout: 10_000, + transport_opts: [cacertfile: "/etc/ssl/certs/ca-certificates.crt"] +``` + +The accepted options are the same as [`Mint.HTTP.connect/4`](https://hexdocs.pm/mint/Mint.HTTP.html#connect/4-options). \ No newline at end of file diff --git a/guides/getting_started/codegen.md b/guides/getting_started/codegen.md new file mode 100644 index 00000000..72c1dd1d --- /dev/null +++ b/guides/getting_started/codegen.md @@ -0,0 +1,54 @@ +# Codegen + +Use `protoc` with [protobuf elixir plugin](https://github.com/elixir-protobuf/protobuf) or using [protobuf_generate](https://hexdocs.pm/protobuf_generate/readme.html) hex package to generate the necessary files. + +--- + +## Write your protobuf file + +```protobuf +syntax = "proto3"; + +package helloworld; + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greeting +message HelloReply { + string message = 1; +} + +// The greeting service definition. +service GreetingServer { + rpc SayUnaryHello (HelloRequest) returns (HelloReply) {} + rpc SayServerHello (HelloRequest) returns (stream HelloReply) {} + rpc SayBidStreamHello (stream HelloRequest) returns (stream HelloReply) {} +} +``` + +## Compile protos (protoc + elixir plugin) + +The most basic way to compile protobuf files is by using the elixir plugin for the protoc compiler: + +```bash +protoc --elixir_out=plugins=grpc:./lib -I./priv/protos helloworld.proto +``` + +See more detailed explanation [here](https://hexdocs.pm/protobuf/readme.html#generate-elixir-code). + +But you can also benefit from more advanced options if you use the protobuf_generator. This is especially useful for use with HTTP Transcoding: + +```shell +mix protobuf.generate \ + --include-path=priv/proto \ + --include-path=deps/googleapis \ + --generate-descriptors=true \ + --output-path=./lib \ + --plugins=ProtobufGenerate.Plugins.GRPCWithOptions \ + google/api/annotations.proto google/api/http.proto helloworld.proto +``` + +See more detailed explanation [here](https://hexdocs.pm/protobuf_generate/readme.html). \ No newline at end of file diff --git a/guides/getting_started/error_handling.md b/guides/getting_started/error_handling.md new file mode 100644 index 00000000..63d3e79b --- /dev/null +++ b/guides/getting_started/error_handling.md @@ -0,0 +1,71 @@ +# Error Handling + +Effective error management is essential for maintaining reliability in gRPC streaming pipelines. In Elixir gRPC, all stream operators participate in a unified error propagation model, ensuring that failures — whether returned as {:error, reason} tuples or raised unexpectedly — are captured and translated consistently throughout the dataflow. + +Developers can intercept, transform, and recover from errors using dedicated operators such as `map_error/2`, enabling graceful degradation, domain-specific responses, and seamless conversion into `GRPC.RPCError` formats that propagate correctly to clients. + +This document explains how streaming error handling works, how exceptions interact with the pipeline, and how to design resilient services that continue processing even when individual elements fail. + +--- + +## Recovery from errors + +The `map_error/2` operator intercepts and transforms errors or exceptions emitted by previous stages in a stream pipeline. + +It provides a unified mechanism for handling: + +* Expected errors, such as validation or domain failures (`{:error, reason}`) +* Unexpected runtime errors, including raised or thrown exceptions inside other operators. + +```elixir +iex> GRPC.Stream.from([1, 2]) +...> |> GRPC.Stream.map(fn +...> 2 -> raise "boom" +...> x -> x +...> end) +...> |> GRPC.Stream.map_error(fn +...> {:error, {:exception, _reason}} -> +...> {:error, GRPC.RPCError.exception(message: "Booomm")} +...> end) +``` + +In this example: + +* The function inside `map/2` raises an exception for the value `2`. +* `map_error/2` captures and transforms that error into a structured `GRPC.RPCError` response. +* The stream continues processing without being interrupted. + +This makes `map_error/2` suitable for input validation, runtime fault recovery, and user-facing error translation within gRPC pipelines. + +--- + +## Unified Error Matching and Propagation + +All stream operators share a unified error propagation model that guarantees consistent handling of exceptions and failures across the pipeline. + +This ensures that user-defined functions within the stream — whether pure transformations, side effects, or external calls — always produce a predictable and recoverable result, maintaining the integrity of the dataflow even in the presence of unexpected errors. + +```elixir +def say_unary_hello(request, _materializer) do + GRPCStream.unary(request) + |> GRPCStream.ask(Transformer) + |> GRPCStream.map(fn + %HelloReply{} = reply -> + %HelloReply{message: "[Reply] #{reply.message}"} + + {:error, reason} -> + {:error, GRPC.RPCError.exception(message: "error calling external process: #{inspect(reason)}")} + + error -> + Logger.error("Unknown error") + error + end) + |> GRPCStream.run() +end +``` + +By normalizing all possible outcomes, `GRPC.Stream` ensures fault-tolerant, exception-safe pipelines where operators can freely raise, throw, or return tuples without breaking the flow execution. + +This unified model allows developers to build composable and reliable streaming pipelines that gracefully recover from both domain and runtime errors. + +>_Note_: In the example above, we could use `map_error/2` instead of `map/2` to handle error cases explicitly. However, since the function also performs a transformation on successful values, `map/2` remains appropriate and useful in this context. \ No newline at end of file diff --git a/guides/getting_started/quickstart.livemd b/guides/getting_started/quickstart.livemd new file mode 100644 index 00000000..7e3a55d8 --- /dev/null +++ b/guides/getting_started/quickstart.livemd @@ -0,0 +1,140 @@ +# Quickstart + +`GRPC` is a fully featured Elixir implementation of the gRPC protocol (grpc.io), +enabling efficient communication between services through a unified and +stream-oriented API. It supports all RPC types, friendly error handling, TLS, +interceptors, reflection, and optional HTTP transcoding. + +Suitable for both server and client development in pure Elixir, enabling +scalable, efficient and type-safe distributed systems. + +## Main features: + + * Unary, Server Streaming, Client Streaming, Bi-directional Streaming RPCs; + * Streaming-first API for every call; + * Interceptors (auth, logging, rate limiting, tracing); + * Error handling with predictable propagation; + * TLS authentication and message compression; + * Connection load balancing strategies (Round Robin, Pick First); + * gRPC Reflection; + * HTTP Transcoding for REST ↔ gRPC compatibility; + +--- + +## Setup + +```elixir +app_root = Path.join(__DIR__, "..") + +Mix.install( + [ + {:grpc, path: app_root, env: :dev}, + {:protobuf, "~> 0.14"}, # optional for importing well-known Google gRPC types + {:grpc_reflection, "~> 0.2"}, # optional for enabling gRPC reflection + {:protobuf_generate, "~> 0.1", only: :dev} # optional for Protobuf code generation with plugins + ], + config_path: Path.join(app_root, "config/config.exs"), + lockfile: Path.join(app_root, "mix.lock") +) +``` + +--- + +## Protobuf Service and Messages + +```elixir +defmodule Helloworld.HelloRequest do + use Protobuf, syntax: :proto3 + field :name, 1, type: :string +end + +defmodule Helloworld.HelloReply do + use Protobuf, syntax: :proto3 + field :message, 1, type: :string +end + +defmodule Helloworld.Greeter.Service do + use GRPC.Service, name: "helloworld.Greeter" + rpc :SayHello, Helloworld.HelloRequest, Helloworld.HelloReply +end + +defmodule Helloworld.Greeter.Stub do + use GRPC.Stub, service: Helloworld.Greeter.Service +end +``` + +--- + +## Logging Interceptor + +We create a basic interceptor to log incoming RPC calls. + +```elixir +defmodule LoggingInterceptor do + @behaviour GRPC.Server.Interceptor + require Logger + + def init(options), do: options + + def call(%GRPC.Server.Stream{} = stream, req, next, _opts) do + Logger.info("RPC: #{stream.service_name}/#{stream.method_name} received request") + next.(stream, req) + end +end +``` + +--- + +## gRPC Server Implementation + +```elixir +defmodule HelloServer do + use GRPC.Server, service: Helloworld.Greeter.Service + + def say_hello(%{name: name}, _stream) do + Helloworld.HelloReply.new(message: "Hello, #{name}!") + end +end +``` + +--- + +## Endpoint with Interceptor + +```elixir +defmodule HelloEndpoint do + use GRPC.Endpoint + + intercept(LoggingInterceptor) + run(HelloServer) +end +``` + +--- + +## Starting the Server + +Here we start the GRPC server under supervision at port `50051`. + +```elixir +{:ok, _pid} = + GRPC.Server.Supervisor.start_link(endpoint: HelloEndpoint, port: 50051) + +IO.puts("gRPC Server running on port 50051") +``` + +--- + +## Create a Client and Test the RPC + +```elixir +{:ok, _} = GRPC.Client.Supervisor.start_link() +{:ok, channel} = GRPC.Stub.connect("localhost:50051") + +request = Helloworld.HelloRequest.new(name: "Hello gRPC Livebook") +{:ok, reply} = Helloworld.Greeter.Stub.say_hello(channel, request) + +IO.inspect(reply, label: "Received reply") +``` + +--- diff --git a/guides/getting_started/stream.livemd b/guides/getting_started/stream.livemd new file mode 100644 index 00000000..770f4459 --- /dev/null +++ b/guides/getting_started/stream.livemd @@ -0,0 +1,231 @@ +# Streaming + +gRPC streaming in Elixir introduces a fully composable way to process data as it flows +between client and server. Instead of treating each request as an isolated transaction, +streaming allows messages to be transformed incrementally, combined with other streams +and enriched through side-effects while still in transit. + +The objective here is to demonstrate how `GRPC.Stream` enables functional composition +applied over live data, allowing pipelines to evolve, react and continue processing +even under constant input. + +By exploring unary, server streaming and bidirectional streaming examples, this +document highlights how state, concurrency and data transformation can interact +seamlessly in an event-driven communication model. + +--- + +## Setup + +```elixir +app_root = Path.join(__DIR__, "..") + +Mix.install( + [ + {:grpc, path: app_root, env: :dev} + ], + config_path: Path.join(app_root, "config/config.exs"), + lockfile: Path.join(app_root, "mix.lock") +) +``` + +--- + +## Proto Messages & Service + +For simplicity, all proto definitions are inline. + +```elixir +defmodule Stream.HelloRequest do + use Protobuf, syntax: :proto3 + field :name, 1, type: :string +end + +defmodule Stream.HelloReply do + use Protobuf, syntax: :proto3 + field :message, 1, type: :string +end + +defmodule Stream.EchoServer.Service do + use GRPC.Service, name: "stream.EchoServer" + + rpc :SayUnaryHello, Stream.HelloRequest, Stream.HelloReply + rpc :SayServerHello, Stream.HelloRequest, stream(Stream.HelloReply) + rpc :SayBidStreamHello, stream(Stream.HelloRequest), stream(Stream.HelloReply) +end + +defmodule Stream.EchoServer.Stub do + use GRPC.Stub, service: Stream.EchoServer.Service +end +``` + +--- + +## Transformer (Helper Process) + +Used for unary example composition. + +```elixir +defmodule Transformer do + use GenServer + + alias Stream.HelloRequest + alias Stream.HelloReply + + def start_link(_) do + GenServer.start_link(__MODULE__, nil, name: __MODULE__) + end + + def init(_), do: {:ok, %{}} + + def handle_info({:request, %HelloRequest{} = value, from}, state) do + Process.send(from, {:response, %HelloReply{message: "Hello #{value.name}"}}, []) + {:noreply, state} + end +end + +{:ok, _} = Transformer.start_link(nil) +``` + +--- + +## Server Implementation + +```elixir +defmodule EchoStreamServer do + use GRPC.Server, service: Stream.EchoServer.Service + + alias GRPC.Stream, as: GRPCStream + alias Stream.HelloRequest + alias Stream.HelloReply + + # Unary example + def say_unary_hello(%HelloRequest{name: name}, _stream) do + GRPCStream.unary(request) + |> GRPCStream.ask(Transformer) + |> GRPCStream.map(fn + %HelloReply{} = reply -> + %HelloReply{message: "[Reply] #{reply.message}"} + + {:error, reason} -> + {:error, GRPC.RPCError.exception(message: "[Error] #{inspect(reason)}")} + end) + |> GRPCStream.run() + end + + # Server‑Side streaming + def say_server_hello(%HelloRequest{name: name} = _req, stream) do + Stream.repeatedly(fn -> + %HelloReply{message: "Hello from server → #{name}"} + end) + |> Stream.take(5) + |> GRPCStream.from() + |> GRPCStream.run_with(stream) + end + + # Bidirectional streaming + def say_bid_stream_hello(request_stream, stream) do + GRPCStream.from(request_stream, join_with: output_join_stream()) + |> GRPCStream.map(fn + %HelloRequest{name: name} -> + %HelloReply{message: "Welcome #{name}!"} + msg -> + msg + end) + |> GRPCStream.run_with(stream) + end + + defp output_join_stream() do + Stream.repeatedly(fn -> + %Stream.HelloReply{message: "↔ Server heartbeat"} + end) + end +end +``` + +--- + +## Endpoint + Supervisor + +```elixir +defmodule StreamingEndpoint do + use GRPC.Endpoint + intercept(GRPC.Server.Interceptors.Logger) + run(EchoStreamServer) +end + +{:ok, _pid} = + GRPC.Server.Supervisor.start_link( + endpoint: StreamingEndpoint, + port: 50054, + start_server: true + ) + +IO.puts("Streaming gRPC Server running on :50054") +``` + +--- + +## Client Tests + +### Unary + +```elixir +{:ok, _} = GRPC.Client.Supervisor.start_link() +{:ok, channel} = GRPC.Stub.connect("localhost:50054") + +{:ok, reply} = + Stream.EchoServer.Stub.say_unary_hello( + channel, + Stream.HelloRequest{name: "Unary Test"} + ) + +IO.inspect(reply, label: "Unary reply") +``` + +--- + +### Server Streaming + +```elixir +{:ok, stream} = + Stream.EchoServer.Stub.say_server_hello( + channel, + Stream.HelloRequest{name: "Server Stream"} + ) + +Enum.each(stream, fn msg -> + IO.inspect(msg, label: "◀ Server message") +end) +``` + +--- + +### Bidirectional Streaming + +```elixir +{:ok, bidi_stream} = + Stream.EchoServer.Stub.say_bid_stream_hello(channel) + +# Send 3 input messages +Enum.each(~w(Alice Bob Carol)a, fn name -> + GRPC.Stub.send_request( + bidi_stream, + Stream.HelloRequest{name: name} + ) + Process.sleep(150) +end) + +# Close input stream +GRPC.Stub.end_stream(bidi_stream) + +# Receive responses +{:ok, ex_stream} = GRPC.Stub.recv(bidi_stream) +Enum.each(ex_stream, fn msg -> + IO.inspect(msg, label: "↔ Stream reply") +end) +``` + +The Stream API supports composable stream transformations via `ask`, `map`, `run` and others functions, enabling clean and declarative stream pipelines. For a complete list of available operators see [here](lib/grpc/stream.ex). + +--- \ No newline at end of file diff --git a/interop/dump-50051 copy.pcap b/interop/dump-50051 copy.pcap deleted file mode 100644 index 11aefbae..00000000 Binary files a/interop/dump-50051 copy.pcap and /dev/null differ diff --git a/interop/dump-50051.pcap b/interop/dump-50051.pcap deleted file mode 100644 index 11aefbae..00000000 Binary files a/interop/dump-50051.pcap and /dev/null differ diff --git a/lib/grpc.ex b/lib/grpc.ex index b33f6c14..956881f0 100644 --- a/lib/grpc.ex +++ b/lib/grpc.ex @@ -1,4 +1,115 @@ defmodule GRPC do + @moduledoc """ + GRPC is a fully featured Elixir implementation of the gRPC protocol (grpc.io), + enabling efficient communication between services through a unified and + stream-oriented API. It supports all RPC types, friendly error handling, TLS, + interceptors, reflection, and optional HTTP transcoding. + + Suitable for both server and client development in pure Elixir, enabling + scalable, efficient and type-safe distributed systems. + + ## Main features: + + * Unary, Server Streaming, Client Streaming, Bi-directional Streaming RPCs; + * Streaming-first API for every call; + * Interceptors; + * Error handling with predictable propagation; + * TLS authentication and message compression; + * Connection load balancing strategies (Round Robin, Pick First); + * gRPC Reflection; + * HTTP Transcoding for REST ↔ gRPC compatibility; + + ## Installation: + + def deps do + [ + {:grpc, "~> 0.11"}, + {:protobuf, "~> 0.14"}, + {:grpc_reflection, "~> 0.2"} + ] + end + + ## Protobuf code generation: + + protoc --elixir_out=plugins=grpc:./lib -I./priv/protos helloworld.proto + + ## Basic Server Example + + defmodule MyApp.Greeter.Server do + use GRPC.Server, service: MyApp.Greeter.Service + alias MyApp.{HelloRequest, HelloReply} + + def say_hello(request, stream) do + request + |> GRPC.Stream.unary(materializer: stream) + |> GRPC.Stream.map(fn %HelloRequest{name: name} -> + %HelloReply{message: "Hello"} + end) + |> GRPC.Stream.run() + end + end + + defmodule MyApp.Endpoint do + use GRPC.Endpoint + run MyApp.Greeter.Server + end + + children = [ + {GRPC.Server.Supervisor, endpoint: MyApp.Endpoint, port: 50051} + ] + + ## Server-side streaming: + + def say_hi_stream(request, stream) do + Stream.repeatedly(fn -> + %HelloReply{message: "Hi!"} + end) + |> Stream.take(5) + |> GRPC.Stream.from() + |> GRPC.Stream.run_with(stream) + end + + ## Bidirectional streaming: + + def chat(request_enum, stream) do + GRPC.Stream.from(request_enum) + |> GRPC.Stream.map(fn req -> + %HelloReply{message: "I'm the Server ;)"} + end) + |> GRPC.Stream.run_with(stream) + end + + See `GRPC.Stream` for more Server examples. + + ## Basic Client Example + + {:ok, _} = GRPC.Client.Supervisor.start_link() + {:ok, channel} = GRPC.Stub.connect("localhost:50051") + + req = MyApp.HelloRequest.new(name: "Elixir") + {:ok, reply} = MyApp.Greeter.Stub.say_hello(channel, req) + + See `GRPC.Stub` for more Client examples. + + ## HTTP Transcoding (optional) + + Enable REST-to-gRPC mapping: + + use GRPC.Server, + service: MyApp.Greeter.Service, + http_transcode: true + + Useful when interacting with gRPC from browsers or REST clients. + + ## CORS Support (optional) + + defmodule MyApp.Endpoint do + use GRPC.Endpoint + intercept GRPC.Server.Interceptors.CORS, allow_origin: "*" + run MyApp.Greeter.Server + end + + """ @version GRPC.Mixfile.project()[:version] @doc """ diff --git a/lib/grpc/client/resolver.ex b/lib/grpc/client/resolver.ex index 1a3b7d33..ec93a2de 100644 --- a/lib/grpc/client/resolver.ex +++ b/lib/grpc/client/resolver.ex @@ -3,7 +3,7 @@ defmodule GRPC.Client.Resolver do Behaviour for gRPC client resolvers. A gRPC resolver is responsible for translating a target string into - a list of connection endpoints (addresses) and an optional `ServiceConfig`. + a list of connection endpoints (addresses) and an optional `GRPC.Client.ServiceConfig`. gRPC supports multiple naming schemes, allowing clients to connect to servers via DNS, fixed IPs, Unix domain sockets, or through diff --git a/lib/grpc/client/resolver/xds.ex b/lib/grpc/client/resolver/xds.ex index b881b91e..8b334586 100644 --- a/lib/grpc/client/resolver/xds.ex +++ b/lib/grpc/client/resolver/xds.ex @@ -1,4 +1,5 @@ defmodule GRPC.Client.Resolver.XDS do + @moduledoc false @behaviour GRPC.Client.Resolver @impl GRPC.Client.Resolver diff --git a/lib/grpc/stream.ex b/lib/grpc/stream.ex index faeeedcb..84b115ba 100644 --- a/lib/grpc/stream.ex +++ b/lib/grpc/stream.ex @@ -82,6 +82,7 @@ defmodule GRPC.Stream do flow = GRPC.Stream.from(request, max_demand: 50) """ + @doc type: :creation @spec from(any(), Keyword.t()) :: t() def from(input, opts \\ []) @@ -109,12 +110,13 @@ defmodule GRPC.Stream do And any other options supported by `Flow`. ## Returns - - A `GRPCStream` that emits the single gRPC message under demand. + - A `GRPC.Stream` that emits the single gRPC message under demand. ## Example - flow = GRPCStream.single(request, max_demand: 5) + flow = GRPC.Stream.unary(request, max_demand: 5) """ + @doc type: :creation @spec unary(any(), Keyword.t()) :: t() def unary(input, opts \\ []) when is_struct(input), do: build_grpc_stream([input], Keyword.merge(opts, unary: true)) @@ -128,6 +130,7 @@ defmodule GRPC.Stream do A `Flow` pipeline. """ + @doc type: :transforms @spec to_flow(t()) :: Flow.t() def to_flow(%__MODULE__{flow: flow}) when is_nil(flow), do: Flow.from_enumerable([]) @@ -142,7 +145,22 @@ defmodule GRPC.Stream do The `stream` argument must be initialized as a `:unary` stream with a `:materializer` set. + + ## Example + + def say_unary_hello(request, mat) do + GRPC.Stream.unary(request, materializer: mat) + |> GRPC.Stream.map(fn + %HelloReply{} = reply -> + %HelloReply{message: "[Reply] message"} + + {:error, _reason} -> + GRPC.RPCError.exception(message: "[Error] Something bad happened") + end) + |> GRPC.Stream.run() + end """ + @doc type: :materialization @spec run(stream :: t()) :: :noreply def run(%__MODULE__{flow: flow, options: opts}) do opts = Keyword.take(opts, [:unary, :materializer]) @@ -181,8 +199,24 @@ defmodule GRPC.Stream do ## Example - GRPC.Stream.run_with(request, mat) + def say_bid_stream_hello(request, materializer) do + output_stream = + Stream.repeatedly(fn -> + %HelloReply{message: "I'm the Server ;)"} + end) + + GRPC.Stream.from(request, join_with: output_stream) + |> GRPC.Stream.map(fn + %HelloRequest{} = _hello -> + %HelloReply{message: "Welcome Sr!"} + + {:error, _reason} -> + GRPC.RPCError.exception(message: "[Error] Something bad happened") + end) + |> GRPC.Stream.run_with(materializer) + end """ + @doc type: :materialization @spec run_with(t(), Stream.t(), Keyword.t()) :: :ok def run_with( %__MODULE__{flow: flow, options: flow_opts} = _stream, @@ -255,6 +289,7 @@ defmodule GRPC.Stream do - The use of `effect/2` ensures that the original item is returned unchanged, enabling seamless continuation of the pipeline. """ + @doc type: :actions @spec effect(t(), (term -> any)) :: t() defdelegate effect(stream, effect_fun), to: Operators @@ -273,6 +308,7 @@ defmodule GRPC.Stream do - `timeout`: Timeout in milliseconds (defaults to `5000`). """ + @doc type: :actions @spec ask(t(), pid | atom, non_neg_integer) :: t() | {:error, :timeout | :process_not_alive} defdelegate ask(stream, target, timeout \\ 5000), to: Operators @@ -284,6 +320,7 @@ defmodule GRPC.Stream do This version propagates errors via raised exceptions, which can crash the Flow worker and halt the pipeline. Prefer `ask/3` for production usage unless failure should abort the stream. """ + @doc type: :actions @spec ask!(t(), pid | atom, non_neg_integer) :: t() defdelegate ask!(stream, target, timeout \\ 5000), to: Operators @@ -293,6 +330,7 @@ defmodule GRPC.Stream do The filter function is applied concurrently to the stream entries, so it shouldn't rely on execution order. """ @spec filter(t(), (term -> term)) :: t + @doc type: :transforms defdelegate filter(stream, filter), to: Operators @doc """ @@ -300,12 +338,14 @@ defmodule GRPC.Stream do Useful for emitting multiple messages for each input. """ + @doc type: :transforms @spec flat_map(t, (term -> Enumerable.t())) :: t() defdelegate flat_map(stream, flat_mapper), to: Operators @doc """ Applies a function to each stream item. """ + @doc type: :transforms @spec map(t(), (term -> term)) :: t() defdelegate map(stream, mapper), to: Operators @@ -365,12 +405,14 @@ defmodule GRPC.Stream do - Use this operator to implement robust error recovery, input validation, or to normalize exceptions from downstream Flow stages into well-defined gRPC errors. """ + @doc type: :transforms defdelegate map_error(stream, func), to: Operators @doc """ Applies a transformation function to each stream item, passing the context as an additional argument. This is useful for operations that require access to the stream's headers. """ + @doc type: :transforms @spec map_with_context(t(), (map(), term -> term)) :: t() defdelegate map_with_context(stream, mapper), to: Operators @@ -386,6 +428,7 @@ defmodule GRPC.Stream do See https://hexdocs.pm/flow/Flow.html#module-partitioning for more details. """ + @doc type: :transforms @spec partition(t(), keyword()) :: t() defdelegate partition(stream, options \\ []), to: Operators @@ -401,6 +444,7 @@ defmodule GRPC.Stream do See https://hexdocs.pm/flow/Flow.html#reduce/3 for more details. """ + @doc type: :transforms @spec reduce(t, (-> acc), (term(), acc -> acc)) :: t when acc: term() defdelegate reduce(stream, acc_fun, reducer_fun), to: Operators @@ -408,6 +452,7 @@ defmodule GRPC.Stream do Emits only distinct items from the stream. See `uniq_by/2` for more information. """ + @doc type: :transforms @spec uniq(t) :: t defdelegate uniq(stream), to: Operators @@ -418,6 +463,7 @@ defmodule GRPC.Stream do This function requires care when used for unbounded flows. For more information see https://hexdocs.pm/flow/Flow.html#uniq_by/2 """ + @doc type: :transforms @spec uniq_by(t, (term -> term)) :: t defdelegate uniq_by(stream, fun), to: Operators diff --git a/lib/grpc/stub.ex b/lib/grpc/stub.ex index 5f7796ce..2ceabc60 100644 --- a/lib/grpc/stub.ex +++ b/lib/grpc/stub.ex @@ -106,7 +106,7 @@ defmodule GRPC.Stub do @doc """ Establishes a connection with a gRPC server and returns a `GRPC.Channel` required - for sending requests. Supports advanced connection resolution via the gRPC `Resolver` + for sending requests. Supports advanced connection resolution via the gRPC `GRPC.Client.Resolver` and various target schemes (`dns`, `unix`, `xds`, `host:port`, etc). This function is part of the connection orchestration layer, which manages diff --git a/mix.exs b/mix.exs index 6a8d4fe5..1e476d5a 100644 --- a/mix.exs +++ b/mix.exs @@ -1,6 +1,7 @@ defmodule GRPC.Mixfile do use Mix.Project + @source_url "https://github.com/elixir-grpc/grpc" @version "0.11.2" def project do @@ -10,17 +11,13 @@ defmodule GRPC.Mixfile do elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), build_embedded: Mix.env() == :prod, - start_permanent: Mix.env() == :prod, deps: deps(), + docs: docs(), + name: "gRPC", + start_permanent: Mix.env() == :prod, package: package(), aliases: aliases(), - description: "The Elixir implementation of gRPC", - docs: [ - extras: ["README.md"], - main: "readme", - source_ref: "v#{@version}", - source_url: "https://github.com/elixir-grpc/grpc" - ], + description: "The Elixir implementation of gRPC Protocol", xref: [exclude: [IEx]] ] end @@ -43,12 +40,14 @@ defmodule GRPC.Mixfile do {:cowlib, "~> 2.12"}, {:castore, "~> 0.1 or ~> 1.0", optional: true}, {:protobuf, "~> 0.14"}, - {:protobuf_generate, "~> 0.1.1", only: [:dev, :test]}, {:mint, "~> 1.5"}, - {:ex_doc, "~> 0.29", only: :dev}, + {:telemetry, "~> 1.0"}, + {:protobuf_generate, "~> 0.1.1", only: [:dev, :test]}, {:ex_parameterized, "~> 1.3.7", only: :test}, {:mox, "~> 1.2", only: :test}, - {:telemetry, "~> 1.0"} + {:ex_doc, "~> 0.39", only: [:dev, :docs]}, + {:makeup, "~> 1.2.1", only: [:dev, :docs]}, + {:makeup_syntect, "~> 0.1", only: [:dev, :docs]} ] end @@ -61,6 +60,109 @@ defmodule GRPC.Mixfile do } end + defp docs() do + [ + main: "GRPC", + source_ref: "v#{@version}", + source_url_pattern: "#{@source_url}/blob/v#{@version}/grpc/%{path}#L%{line}", + extras: [ + "guides/getting_started/quickstart.livemd", + "guides/getting_started/stream.livemd", + "guides/getting_started/error_handling.md", + "guides/getting_started/client.md", + "guides/getting_started/codegen.md", + "guides/cheatsheets/streams.cheatmd", + "guides/advanced/transcoding.livemd", + "guides/advanced/load_balancing.md", + "guides/advanced/cors.md", + "guides/advanced/telemetry.livemd", + "guides/advanced/pooling.md" + ], + skip_undefined_reference_warnings_on: ["CHANGELOG.md"], + groups_for_docs: [ + "Functions: Creation": &(&1[:type] == :creation), + "Functions: Materializers": &(&1[:type] == :materialization), + "Functions: Transformers": &(&1[:type] == :transforms), + "Functions: Actions": &(&1[:type] == :actions) + ], + groups_for_modules: [ + Server: [ + GRPC.Server, + GRPC.Service, + GRPC.Endpoint, + GRPC.Server.Supervisor, + GRPC.Server.Adapter, + GRPC.Server.Adapters.Cowboy, + GRPC.Server.Adapters.Cowboy.Handler, + GRPC.Server.Adapters.Cowboy.Router, + GRPC.Status, + GRPC.Server.Stream, + GRPC.Server.Interceptors.Logger, + GRPC.Server.Interceptors.CORS, + GRPC.Server.Router + ], + Stream: [ + GRPC.Stream, + GRPC.Stream.Operators + ], + Client: [ + GRPC.Stub, + GRPC.Channel, + GRPC.Credential, + GRPC.Client.Supervisor, + GRPC.Client.Adapter, + GRPC.Client.Adapters.Gun, + GRPC.Client.Adapters.Mint, + GRPC.Client.Adapters.Mint.StreamResponseProcess, + GRPC.Client.Adapters.Mint.ConnectionProcess, + GRPC.Client.Adapters.Mint.ConnectionProcess.State, + GRPC.Client.Interceptor, + GRPC.Client.Interceptors.Logger, + GRPC.Client.Connection, + GRPC.Client.LoadBalancing, + GRPC.Client.LoadBalancing.RoundRobin, + GRPC.Client.LoadBalancing.PickFirst, + GRPC.Client.Resolver, + GRPC.Client.Resolver.DNS, + GRPC.Client.Resolver.DNS.Adapter, + GRPC.Client.Resolver.Unix, + GRPC.Client.Resolver.IPv4, + GRPC.Client.Resolver.IPv6, + GRPC.Client.ServiceConfig, + GRPC.Client.Stream + ], + Telemetry: [ + GRPC.Telemetry + ], + Transport: [ + GRPC.Transport.HTTP2, + GRPC.Transport.Utils + ], + Codecs: [ + GRPC.Codec, + GRPC.Codec.Erlpack, + GRPC.Codec.JSON, + GRPC.Codec.Proto, + GRPC.Codec.WebText + ], + Compressors: [ + GRPC.Compressor, + GRPC.Compressor.Gzip + ], + "Error Handling": [ + GRPC.RPCError, + GRPC.Server.Adapters.ReportException, + GRPC.Logger + ] + ], + groups_for_extras: [ + "Getting Started": ~r"^guides/getting_started/", + Advanced: ~r"^guides/advanced/", + Cheatsheets: ~r"^guides/cheatsheets/" + ] + ] + end + defp aliases do [ gen_test_protos: &gen_test_protos/1 diff --git a/mix.lock b/mix.lock index d5663839..00abe334 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,8 @@ "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, "cowboy": {:hex, :cowboy, "2.11.0", "356bf784599cf6f2cdc6ad12fdcfb8413c2d35dab58404cf000e1feaed3f5645", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"}, "ex_parameterized": {:hex, :ex_parameterized, "1.3.7", "801f85fc4651cb51f11b9835864c6ed8c5e5d79b1253506b5bb5421e8ab2f050", [:mix], [], "hexpm", "1fb0dc4aa9e8c12ae23806d03bcd64a5a0fc9cd3f4c5602ba72561c9b54a625c"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, @@ -11,16 +11,18 @@ "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "makeup_syntect": {:hex, :makeup_syntect, "0.1.3", "ae2c3437f479ea50d08d794acaf02a2f3a8c338dd1f757f6b237c42eb27fcde1", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}, {:rustler, "~> 0.36.1", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8.2", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "a27bd3bd8f7b87465d110295a33ed1022202bea78701bd2bbeadfb45d690cdbf"}, "meck": {:hex, :meck, "1.0.0", "24676cb6ee6951530093a93edcd410cfe4cb59fe89444b875d35c9d3909a15d0", [:rebar3], [], "hexpm", "680a9bcfe52764350beb9fb0335fb75fee8e7329821416cee0a19fec35433882"}, "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, "protobuf_generate": {:hex, :protobuf_generate, "0.1.3", "57841bc60e2135e190748119d83f78669ee7820c0ad6555ada3cd3cd7df93143", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "dae4139b00ba77a279251a0ceb5593b1bae745e333b4ce1ab7e81e8e4906016b"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.3", "4e741024b0b097fe783add06e53ae9a6f23ddc78df1010f215df0c02915ef5a8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "c23f5f33cb6608542de4d04faf0f0291458c352a4648e4d28d17ee1098cddcc4"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, }