diff --git a/.ci/build-plt-cache.sh b/.ci/build-plt-cache.sh
deleted file mode 100755
index 53842593..00000000
--- a/.ci/build-plt-cache.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-# This script builds/restores PLT cache files to/from a cache at ~/.pltcache
-set -ex
-
-# "Mix will default to the :dev environment"
-MIX_ENV="${MIX_ENV:-dev}"
-
-mix compile
-
-# Create the PLT cache directory, if it doesn't already exist
-mkdir -p "$HOME"/.pltcache
-# Copy the PLT files into the _build directory, if they exist
-cp "$HOME"/.pltcache/*-"$MIX_ENV".plt _build/"$MIX_ENV"/ || true
-
-# Build the PLT cache (uses the existing one if present)
-mix dialyzer --plt
-
-# Copy the PLT files into the cache so they can be used next time
-cp _build/"$MIX_ENV"/*-"$MIX_ENV".plt "$HOME"/.pltcache/
diff --git a/.formatter.exs b/.formatter.exs
index 26b1f145..6ee67280 100644
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -1,6 +1,9 @@
-
[
- inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "examples/*/{config,lib,priv}/*.ex"],
+ inputs: [
+ "{mix,.formatter}.exs",
+ "{config,lib,test}/**/*.{ex,exs}",
+ "examples/*/{config,lib,priv}/*.ex"
+ ],
import_deps: [:protobuf],
locals_without_parens: [rpc: 3, intercept: 1, intercept: 2, run: 1, run: 2],
export: [
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 968b7166..0760d9db 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,7 @@ name: CI
on:
pull_request:
branches:
- - '**'
+ - "**"
push:
branches:
- master
@@ -12,15 +12,20 @@ jobs:
check_format:
runs-on: ubuntu-latest
name: Check format
- container:
- image: elixir:1.9-slim
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: 24
+ elixir-version: 1.13.1
+ - name: Retrieve dependencies cache
+ uses: actions/cache@v3
+ id: mix-cache # id to use in retrieve action
+ with:
+ path: deps
+ key: v1-${{ matrix.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
- name: Install Dependencies
- run: |
- mix local.rebar --force
- mix local.hex --force
- mix deps.get
+ run: mix deps.get 1>/dev/null
- name: Check format
run: mix format --check-formatted
@@ -29,57 +34,137 @@ jobs:
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
- otp: [20.x, 21.x, 22.x]
- elixir: [1.7.x, 1.8.x, 1.9.x, 1.10.x]
+ otp: [22.x, 23.x, 24.x, 25.x]
+ elixir: [1.11.x, 1.12.x, 1.13.x]
exclude:
- - otp: 20.x
- elixir: 1.10.x
+ - otp: 25.x
+ elixir: 1.11.x
+ - otp: 25.x
+ elixir: 1.12.x
needs: check_format
steps:
- - uses: actions/checkout@v1
- - uses: actions/setup-elixir@v1.2.0
- with:
- otp-version: ${{matrix.otp}}
- elixir-version: ${{matrix.elixir}}
- - name: Install Dependencies
- run: |
- mix local.rebar --force
- mix local.hex --force
- mix deps.get
- - name: Run Tests
- run: mix test
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: ${{matrix.otp}}
+ elixir-version: ${{matrix.elixir}}
+ - name: Retrieve dependencies cache
+ uses: actions/cache@v3
+ id: mix-cache # id to use in retrieve action
+ with:
+ path: deps
+ key: v1-${{ matrix.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
+ - name: Install Dependencies
+ run: mix deps.get 1>/dev/null
+ - name: Run Tests
+ run: mix test
interop-tests:
runs-on: ubuntu-latest
name: Interop tests
- container:
- image: elixir:1.9-slim
needs: check_format
+ if: ${{ github.ref != 'refs/heads/master' }}
+ steps:
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: 25.x
+ elixir-version: 1.13.x
+ - name: Retrieve dependencies cache
+ uses: actions/cache@v3
+ id: mix-cache # id to use in retrieve action
+ with:
+ path: deps
+ key: v1-${{ matrix.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
+ - name: Install Dependencies
+ run: mix deps.get 1>/dev/null
+ working-directory: ./interop
+ - name: Run interop tests
+ run: mix run script/run.exs --rounds 64
+ working-directory: ./interop
+
+ interop-tests-all:
+ runs-on: ubuntu-latest
+ name: Interop tests OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
+ needs: check_format
+ if: ${{ github.ref == 'refs/heads/master' }}
+ strategy:
+ matrix:
+ otp: [22.x, 23.x, 24.x, 25.x]
+ elixir: [1.11.x, 1.12.x, 1.13.x]
+ exclude:
+ - otp: 25.x
+ elixir: 1.11.x
+ - otp: 25.x
+ elixir: 1.12.x
steps:
- - uses: actions/checkout@v1
- - name: Install Dependencies
- run: |
- mix local.rebar --force
- mix local.hex --force
- mix deps.get
- working-directory: ./interop
- - name: Run interop tests
- run: mix run script/run.exs
- working-directory: ./interop
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: 25.x
+ elixir-version: 1.13.x
+ - name: Retrieve dependencies cache
+ uses: actions/cache@v3
+ id: mix-cache # id to use in retrieve action
+ with:
+ path: deps
+ key: v1-${{ matrix.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
+ - name: Install Dependencies
+ run: mix deps.get 1>/dev/null
+ working-directory: ./interop
+ - name: Run interop tests
+ run: mix run script/run.exs --rounds 64
+ working-directory: ./interop
+
+ dialyzer:
+ name: Dialyzer
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ otp: [24.x, 25.x]
+ elixir: [1.13.x]
+ env:
+ MIX_ENV: test
+ steps:
+ - uses: actions/checkout@v3
+ - id: set_vars
+ run: |
+ mix_hash="${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}"
+ echo "::set-output name=mix_hash::$mix_hash"
+ - id: cache-plt
+ uses: actions/cache@v3
+ with:
+ path: |
+ _build/test/plts/dialyzer.plt
+ _build/test/plts/dialyzer.plt.hash
+ key: plt-cache-${{ matrix.otp }}-${{ matrix.elixir }}-${{ steps.set_vars.outputs.mix_hash }}
+ restore-keys: |
+ plt-cache-${{ matrix.otp }}-${{ matrix.elixir }}-
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: ${{matrix.otp}}
+ elixir-version: ${{matrix.elixir}}
+ - run: mix deps.get 1>/dev/null
+ - run: mix dialyzer --format short
check_release:
runs-on: ubuntu-latest
name: Check release
needs: check_format
- container:
- image: elixir:1.9-slim
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: 24
+ elixir-version: 1.13.1
+ - name: Retrieve dependencies cache
+ uses: actions/cache@v3
+ id: mix-cache # id to use in retrieve action
+ with:
+ path: deps
+ key: v1-${{ matrix.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
- name: Install Dependencies
- run: |
- mix local.rebar --force
- mix local.hex --force
- mix deps.get
+ run: mix deps.get 1>/dev/null
- name: Build hex
run: mix hex.build
- name: Generate docs
diff --git a/.github/workflows/cron_ci.yml b/.github/workflows/cron_ci.yml
deleted file mode 100644
index beebc9a4..00000000
--- a/.github/workflows/cron_ci.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Cron CI
-
-on:
- schedule:
- - cron: '0 0 * * 0'
-
-jobs:
- interop-tests:
- runs-on: ubuntu-latest
- name: Interop tests
- container:
- image: elixir:1.9
- steps:
- - uses: actions/checkout@v1
- - name: Install Dependencies
- run: |
- mix local.rebar --force
- mix local.hex --force
- mix deps.get
- working-directory: ./interop
- - name: Run Cron CI
- run: make ci-cron
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 00000000..d905c6c8
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+elixir 1.13.3-otp-25
+erlang 25.0.3
diff --git a/Makefile b/Makefile
index efc78c46..33479e25 100644
--- a/Makefile
+++ b/Makefile
@@ -18,10 +18,4 @@ test-all:
mix test
cd interop && mix run script/run.exs
-# This is heavy
-ci-cron:
- cd interop && mix deps.get && mix run script/run.exs --rounds 1000 --concurrency 30 && cd -
- mix deps.get && bash .ci/build-plt-cache.sh && mix dialyzer
-
-
.PHONY: test release test-prepare test-all ci-cron
diff --git a/README.md b/README.md
index 8f1754b6..f01146fb 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,27 @@
# gRPC Elixir
[](https://hex.pm/packages/grpc)
-[](https://travis-ci.org/elixir-grpc/grpc)
+[](https://app.travis-ci.com/elixir-grpc/grpc)
[](https://github.com/elixir-grpc/grpc/actions)
-[](http://inch-ci.org/github/elixir-grpc/grpc)
+[](https://github.com/elixir-grpc/grpc/blob/master/LICENSE.md)
+[](https://github.com/elixir-grpc/grpc/commits/master)
+[](https://hex.pm/packages/elixir-grpc/grpc)
An Elixir implementation of [gRPC](http://www.grpc.io/).
-**WARNING: Be careful to use it in production! Test and benchmark in advance.**
+## Table of contents
-**NOTICE: Erlang/OTP needs >= 20.3.2**
+- [Notice](#notice)
+- [Installation](#installation)
+- [Usage](#usage)
+- [Features](#features)
+- [Benchmark](#benchmark)
+- [Contributing](#contributing)
-**NOTICE: grpc_gun**
-
-Now `{:gun, "~> 2.0.0", hex: :grpc_gun}` is used in mix.exs because grpc depnds on Gun 2.0,
-but its stable version is not released. So I published a [2.0 version on hex](https://hex.pm/packages/grpc_gun)
-with a different name. So if you have other dependencies who depends on Gun, you need to use
-override: `{:gun, "~> 2.0.0", hex: :grpc_gun, override: true}`. Let's wait for this issue
-https://github.com/ninenines/gun/issues/229.
+## Notice
+> __Note__
+> The [Gun](https://github.com/ninenines/gun) library doesn't have a full 2.0 release yet, so we depend on `:grcp_gun 2.0.1` for now.
+This is the same as `:gun 2.0.0-rc.2`, but [Hex](https://hex.pm/) doesn't let us depend on RC versions for releases.
## Installation
@@ -26,9 +30,11 @@ The package can be installed as:
```elixir
def deps do
[
- {:grpc, github: "elixir-grpc/grpc"},
- # 2.9.0 fixes some important bugs, so it's better to use ~> 2.9.0
- {:cowlib, "~> 2.9.0", override: true}
+ {:grpc, "~> 0.5.0"},
+ # We don't force protobuf as a dependency for more
+ # flexibility on which protobuf library is used,
+ # but you probably want to use it as well
+ {:protobuf, "~> 0.10"}
]
end
```
@@ -36,7 +42,9 @@ The package can be installed as:
## Usage
1. Generate Elixir code from proto file as [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) shows(especially the `gRPC Support` section).
+
2. Implement the server side code like below and remember to return the expected message types.
+
```elixir
defmodule Helloworld.Greeter.Server do
use GRPC.Server, service: Helloworld.Greeter.Service
@@ -67,7 +75,7 @@ defmodule HelloworldApp do
def start(_type, _args) do
children = [
# ...
- supervisor(GRPC.Server.Supervisor, [{Helloworld.Endpoint, 50051}])
+ {GRPC.Server.Supervisor, endpoint: Helloworld.Endpoint, port: 50051}
]
opts = [strategy: :one_for_one, name: YourApp]
@@ -76,25 +84,8 @@ defmodule HelloworldApp do
end
```
-Then start it when starting your application:
-
-```elixir
-# config.exs
-config :grpc, start_server: true
-
-# test.exs
-config :grpc, start_server: false
-
-$ iex -S mix
-```
-
-or run grpc.server using a mix task
-
-```
-$ mix grpc.server
-```
-
4. Call rpc:
+
```elixir
iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051")
iex> request = Helloworld.HelloRequest.new(name: "grpc-elixir")
@@ -107,24 +98,18 @@ iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.L
Check [examples](examples) and [interop](interop)(Interoperability Test) for some examples.
-## TODO
-
-- [x] Unary RPC
-- [x] Server streaming RPC
-- [x] Client streaming RPC
-- [x] Bidirectional streaming RPC
-- [x] Helloworld and RouteGuide examples
-- [x] Doc and more tests
-- [x] Authentication with TLS
-- [x] Improve code generation from protos ([protobuf-elixir](https://github.com/tony612/protobuf-elixir) [#8](https://github.com/elixir-grpc/grpc/issues/8))
-- [x] Timeout for unary calls
-- [x] Errors handling
-- [x] Benchmarking
-- [x] Logging
-- [x] Interceptors(See `GRPC.Endpoint`)
-- [x] [Connection Backoff](https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md)
-- [x] Data compression
-- [x] Support other encoding(other than protobuf)
+## Features
+
+- Various kinds of RPC:
+ - [Unary](https://grpc.io/docs/what-is-grpc/core-concepts/#unary-rpc)
+ - [Server-streaming](https://grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc)
+ - [Client-streaming](https://grpc.io/docs/what-is-grpc/core-concepts/#client-streaming-rpc)
+ - [Bidirectional-streaming](https://grpc.io/docs/what-is-grpc/core-concepts/#bidirectional-streaming-rpc)
+- [TLS Authentication](https://grpc.io/docs/guides/auth/#supported-auth-mechanisms)
+- [Error handling](https://grpc.io/docs/guides/error/)
+- Interceptors(See [`GRPC.Endpoint`](https://github.com/elixir-grpc/grpc/blob/master/lib/grpc/endpoint.ex))
+- [Connection Backoff](https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md)
+- Data compression
## Benchmark
@@ -132,15 +117,9 @@ Check [examples](examples) and [interop](interop)(Interoperability Test) for som
2. [Benchmark](benchmark) followed by official spec
-## Sponsors
-
-This project is being sponsored by [Tubi](https://tubitv.com/). Thank you!
-
-
-
## Contributing
-You contributions are welcome!
+Your contributions are welcome!
Please open issues if you have questions, problems and ideas. You can create pull
requests directly if you want to fix little bugs, add small features and so on.
diff --git a/benchmark/config/config.exs b/benchmark/config/config.exs
index db5eb576..1c012859 100644
--- a/benchmark/config/config.exs
+++ b/benchmark/config/config.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
config :logger, level: :info
diff --git a/benchmark/config/dev.exs b/benchmark/config/dev.exs
index d2d855e6..becde769 100644
--- a/benchmark/config/dev.exs
+++ b/benchmark/config/dev.exs
@@ -1 +1 @@
-use Mix.Config
+import Config
diff --git a/benchmark/config/prod.exs b/benchmark/config/prod.exs
index 28c45b13..3da7d10f 100644
--- a/benchmark/config/prod.exs
+++ b/benchmark/config/prod.exs
@@ -1,3 +1,3 @@
-use Mix.Config
+import Config
config :logger, level: :warn
diff --git a/benchmark/lib/grpc/core/stats.pb.ex b/benchmark/lib/grpc/core/stats.pb.ex
index fcb7f196..0b106138 100644
--- a/benchmark/lib/grpc/core/stats.pb.ex
+++ b/benchmark/lib/grpc/core/stats.pb.ex
@@ -3,8 +3,8 @@ defmodule Grpc.Core.Bucket do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- start: float,
- count: non_neg_integer
+ start: float(),
+ count: non_neg_integer()
}
defstruct [:start, :count]
@@ -29,7 +29,7 @@ defmodule Grpc.Core.Metric do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- value: {atom, any},
+ value: {atom(), any()},
name: String.t()
}
defstruct [:value, :name]
diff --git a/benchmark/lib/grpc/testing/control.pb.ex b/benchmark/lib/grpc/testing/control.pb.ex
index 6b73ab91..58121ce9 100644
--- a/benchmark/lib/grpc/testing/control.pb.ex
+++ b/benchmark/lib/grpc/testing/control.pb.ex
@@ -3,7 +3,7 @@ defmodule Grpc.Testing.PoissonParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- offered_load: float
+ offered_load: float()
}
defstruct [:offered_load]
@@ -23,7 +23,7 @@ defmodule Grpc.Testing.LoadParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- load: {atom, any}
+ load: {atom(), any()}
}
defstruct [:load]
@@ -37,7 +37,7 @@ defmodule Grpc.Testing.SecurityParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- use_test_ca: boolean,
+ use_test_ca: boolean(),
server_host_override: String.t(),
cred_type: String.t()
}
@@ -53,7 +53,7 @@ defmodule Grpc.Testing.ChannelArg do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- value: {atom, any},
+ value: {atom(), any()},
name: String.t()
}
defstruct [:value, :name]
@@ -70,22 +70,22 @@ defmodule Grpc.Testing.ClientConfig do
@type t :: %__MODULE__{
server_targets: [String.t()],
- client_type: atom | integer,
+ client_type: atom() | integer(),
security_params: Grpc.Testing.SecurityParams.t() | nil,
- outstanding_rpcs_per_channel: integer,
- client_channels: integer,
- async_client_threads: integer,
- rpc_type: atom | integer,
+ outstanding_rpcs_per_channel: integer(),
+ client_channels: integer(),
+ async_client_threads: integer(),
+ rpc_type: atom() | integer(),
load_params: Grpc.Testing.LoadParams.t() | nil,
payload_config: Grpc.Testing.PayloadConfig.t() | nil,
histogram_params: Grpc.Testing.HistogramParams.t() | nil,
- core_list: [integer],
- core_limit: integer,
+ core_list: [integer()],
+ core_limit: integer(),
other_client_api: String.t(),
channel_args: [Grpc.Testing.ChannelArg.t()],
- threads_per_cq: integer,
- messages_per_stream: integer,
- use_coalesce_api: boolean
+ threads_per_cq: integer(),
+ messages_per_stream: integer(),
+ use_coalesce_api: boolean()
}
defstruct [
:server_targets,
@@ -143,7 +143,7 @@ defmodule Grpc.Testing.Mark do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- reset: boolean
+ reset: boolean()
}
defstruct [:reset]
@@ -155,7 +155,7 @@ defmodule Grpc.Testing.ClientArgs do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- argtype: {atom, any}
+ argtype: {atom(), any()}
}
defstruct [:argtype]
@@ -169,16 +169,16 @@ defmodule Grpc.Testing.ServerConfig do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- server_type: atom | integer,
+ server_type: atom() | integer(),
security_params: Grpc.Testing.SecurityParams.t() | nil,
- port: integer,
- async_server_threads: integer,
- core_limit: integer,
+ port: integer(),
+ async_server_threads: integer(),
+ core_limit: integer(),
payload_config: Grpc.Testing.PayloadConfig.t() | nil,
- core_list: [integer],
+ core_list: [integer()],
other_server_api: String.t(),
- threads_per_cq: integer,
- resource_quota_size: integer,
+ threads_per_cq: integer(),
+ resource_quota_size: integer(),
channel_args: [Grpc.Testing.ChannelArg.t()]
}
defstruct [
@@ -213,7 +213,7 @@ defmodule Grpc.Testing.ServerArgs do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- argtype: {atom, any}
+ argtype: {atom(), any()}
}
defstruct [:argtype]
@@ -228,8 +228,8 @@ defmodule Grpc.Testing.ServerStatus do
@type t :: %__MODULE__{
stats: Grpc.Testing.ServerStats.t() | nil,
- port: integer,
- cores: integer
+ port: integer(),
+ cores: integer()
}
defstruct [:stats, :port, :cores]
@@ -251,7 +251,7 @@ defmodule Grpc.Testing.CoreResponse do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- cores: integer
+ cores: integer()
}
defstruct [:cores]
@@ -273,12 +273,12 @@ defmodule Grpc.Testing.Scenario do
@type t :: %__MODULE__{
name: String.t(),
client_config: Grpc.Testing.ClientConfig.t() | nil,
- num_clients: integer,
+ num_clients: integer(),
server_config: Grpc.Testing.ServerConfig.t() | nil,
- num_servers: integer,
- warmup_seconds: integer,
- benchmark_seconds: integer,
- spawn_local_worker_count: integer
+ num_servers: integer(),
+ warmup_seconds: integer(),
+ benchmark_seconds: integer(),
+ spawn_local_worker_count: integer()
}
defstruct [
:name,
@@ -318,11 +318,11 @@ defmodule Grpc.Testing.ScenarioResultSummary do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- qps: float,
- qps_per_server_core: float,
- server_system_time: float,
- server_user_time: float,
- client_system_time: float,
+ qps: float(),
+ qps_per_server_core: float(),
+ server_system_time: float(),
+ server_user_time: float(),
+ client_system_time: float(),
client_user_time: float,
latency_50: float,
latency_90: float,
diff --git a/benchmark/lib/grpc/testing/messages.pb.ex b/benchmark/lib/grpc/testing/messages.pb.ex
index 8c5021b3..fab6e82e 100644
--- a/benchmark/lib/grpc/testing/messages.pb.ex
+++ b/benchmark/lib/grpc/testing/messages.pb.ex
@@ -3,7 +3,7 @@ defmodule Grpc.Testing.BoolValue do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- value: boolean
+ value: boolean()
}
defstruct [:value]
@@ -15,8 +15,8 @@ defmodule Grpc.Testing.Payload do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- type: atom | integer,
- body: binary
+ type: atom() | integer(),
+ body: binary()
}
defstruct [:type, :body]
@@ -29,7 +29,7 @@ defmodule Grpc.Testing.EchoStatus do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- code: integer,
+ code: integer(),
message: String.t()
}
defstruct [:code, :message]
@@ -43,11 +43,11 @@ defmodule Grpc.Testing.SimpleRequest do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- response_type: atom | integer,
- response_size: integer,
+ response_type: atom() | integer(),
+ response_size: integer(),
payload: Grpc.Testing.Payload.t() | nil,
- fill_username: boolean,
- fill_oauth_scope: boolean,
+ fill_username: boolean(),
+ fill_oauth_scope: boolean(),
response_compressed: Grpc.Testing.BoolValue.t() | nil,
response_status: Grpc.Testing.EchoStatus.t() | nil,
expect_compressed: Grpc.Testing.BoolValue.t() | nil
@@ -108,7 +108,7 @@ defmodule Grpc.Testing.StreamingInputCallResponse do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- aggregated_payload_size: integer
+ aggregated_payload_size: integer()
}
defstruct [:aggregated_payload_size]
@@ -120,8 +120,8 @@ defmodule Grpc.Testing.ResponseParameters do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- size: integer,
- interval_us: integer,
+ size: integer(),
+ interval_us: integer(),
compressed: Grpc.Testing.BoolValue.t() | nil
}
defstruct [:size, :interval_us, :compressed]
@@ -136,7 +136,7 @@ defmodule Grpc.Testing.StreamingOutputCallRequest do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- response_type: atom | integer,
+ response_type: atom() | integer(),
response_parameters: [Grpc.Testing.ResponseParameters.t()],
payload: Grpc.Testing.Payload.t() | nil,
response_status: Grpc.Testing.EchoStatus.t() | nil
@@ -166,7 +166,7 @@ defmodule Grpc.Testing.ReconnectParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- max_reconnect_backoff_ms: integer
+ max_reconnect_backoff_ms: integer()
}
defstruct [:max_reconnect_backoff_ms]
@@ -178,8 +178,8 @@ defmodule Grpc.Testing.ReconnectInfo do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- passed: boolean,
- backoff_ms: [integer]
+ passed: boolean(),
+ backoff_ms: [integer()]
}
defstruct [:passed, :backoff_ms]
diff --git a/benchmark/lib/grpc/testing/payloads.pb.ex b/benchmark/lib/grpc/testing/payloads.pb.ex
index 18a1a63e..713a88e0 100644
--- a/benchmark/lib/grpc/testing/payloads.pb.ex
+++ b/benchmark/lib/grpc/testing/payloads.pb.ex
@@ -3,8 +3,8 @@ defmodule Grpc.Testing.ByteBufferParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- req_size: integer,
- resp_size: integer
+ req_size: integer(),
+ resp_size: integer()
}
defstruct [:req_size, :resp_size]
@@ -17,8 +17,8 @@ defmodule Grpc.Testing.SimpleProtoParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- req_size: integer,
- resp_size: integer
+ req_size: integer(),
+ resp_size: integer()
}
defstruct [:req_size, :resp_size]
@@ -39,7 +39,7 @@ defmodule Grpc.Testing.PayloadConfig do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- payload: {atom, any}
+ payload: {atom(), any()}
}
defstruct [:payload]
diff --git a/benchmark/lib/grpc/testing/stats.pb.ex b/benchmark/lib/grpc/testing/stats.pb.ex
index 23418c6e..a8414038 100644
--- a/benchmark/lib/grpc/testing/stats.pb.ex
+++ b/benchmark/lib/grpc/testing/stats.pb.ex
@@ -3,12 +3,12 @@ defmodule Grpc.Testing.ServerStats do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- time_elapsed: float,
- time_user: float,
- time_system: float,
- total_cpu_time: non_neg_integer,
- idle_cpu_time: non_neg_integer,
- cq_poll_count: non_neg_integer,
+ time_elapsed: float(),
+ time_user: float(),
+ time_system: float(),
+ total_cpu_time: non_neg_integer(),
+ idle_cpu_time: non_neg_integer(),
+ cq_poll_count: non_neg_integer(),
core_stats: Grpc.Core.Stats.t() | nil
}
defstruct [
@@ -35,8 +35,8 @@ defmodule Grpc.Testing.HistogramParams do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- resolution: float,
- max_possible: float
+ resolution: float(),
+ max_possible: float()
}
defstruct [:resolution, :max_possible]
@@ -49,12 +49,12 @@ defmodule Grpc.Testing.HistogramData do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- bucket: [non_neg_integer],
- min_seen: float,
- max_seen: float,
- sum: float,
- sum_of_squares: float,
- count: float
+ bucket: [non_neg_integer()],
+ min_seen: float(),
+ max_seen: float(),
+ sum: float(),
+ sum_of_squares: float(),
+ count: float()
}
defstruct [:bucket, :min_seen, :max_seen, :sum, :sum_of_squares, :count]
@@ -71,8 +71,8 @@ defmodule Grpc.Testing.RequestResultCount do
use Protobuf, syntax: :proto3
@type t :: %__MODULE__{
- status_code: integer,
- count: integer
+ status_code: integer(),
+ count: integer()
}
defstruct [:status_code, :count]
@@ -86,11 +86,11 @@ defmodule Grpc.Testing.ClientStats do
@type t :: %__MODULE__{
latencies: Grpc.Testing.HistogramData.t() | nil,
- time_elapsed: float,
- time_user: float,
- time_system: float,
+ time_elapsed: float(),
+ time_user: float(),
+ time_system: float(),
request_results: [Grpc.Testing.RequestResultCount.t()],
- cq_poll_count: non_neg_integer,
+ cq_poll_count: non_neg_integer(),
core_stats: Grpc.Core.Stats.t() | nil
}
defstruct [
diff --git a/benchmark/mix.exs b/benchmark/mix.exs
index 9ed822e3..ba3e4223 100644
--- a/benchmark/mix.exs
+++ b/benchmark/mix.exs
@@ -22,7 +22,7 @@ defmodule Benchmark.MixProject do
defp deps do
[
{:grpc, path: ".."},
- {:protobuf, github: "tony612/protobuf-elixir", override: true}
+ {:protobuf, "~> 0.10"}
]
end
end
diff --git a/benchmark/mix.lock b/benchmark/mix.lock
index 32da1efe..23a514a7 100644
--- a/benchmark/mix.lock
+++ b/benchmark/mix.lock
@@ -1,7 +1,7 @@
%{
- "cowboy": {:git, "https://github.com/elixir-grpc/cowboy.git", "db1b09fb06038415e5c643282554c0b9f8e6a976", [tag: "grpc-2.6.3"]},
- "cowlib": {:git, "https://github.com/elixir-grpc/cowlib.git", "1cc32e27d917bfe615da6957006fd9f8d6e604bd", [tag: "grpc-2.7.3"]},
- "gun": {:git, "https://github.com/elixir-grpc/gun.git", "975177bd5c179c800c8685cf64376689642ac972", [tag: "grpc-1.3.2"]},
- "protobuf": {:git, "https://github.com/tony612/protobuf-elixir.git", "384f97ca03aa4f874e527e9f28f5ebbae2f142f1", []},
- "ranch": {:git, "https://github.com/ninenines/ranch", "3190aef88aea04d6dce8545fe9b4574288903f44", [ref: "1.7.1"]},
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
+ "gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"},
+ "protobuf": {:hex, :protobuf, "0.10.0", "4e8e3cf64c5be203b329f88bb8b916cb8d00fb3a12b2ac1f545463ae963c869f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4ae21a386142357aa3d31ccf5f7d290f03f3fa6f209755f6e87fc2c58c147893"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
}
diff --git a/config/config.exs b/config/config.exs
index 01274c30..773875cb 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,3 +1,7 @@
-use Mix.Config
+import Config
-config :grpc, http2_client_adapter: GRPC.Adapter.Gun
+config_file = Path.expand("#{config_env()}.exs", __DIR__)
+
+if File.exists?(config_file) do
+ import_config config_file
+end
diff --git a/config/test.exs b/config/test.exs
new file mode 100644
index 00000000..477c9079
--- /dev/null
+++ b/config/test.exs
@@ -0,0 +1,3 @@
+import Config
+
+config :logger, level: :info
diff --git a/examples/helloworld/.gitignore b/examples/helloworld/.gitignore
index 62deea56..06dbcb6f 100644
--- a/examples/helloworld/.gitignore
+++ b/examples/helloworld/.gitignore
@@ -20,4 +20,4 @@ erl_crash.dump
/src/grpc_c
/tmp
-/log
+/log
\ No newline at end of file
diff --git a/examples/helloworld/README.md b/examples/helloworld/README.md
index 8eaa2c26..924681fd 100644
--- a/examples/helloworld/README.md
+++ b/examples/helloworld/README.md
@@ -9,7 +9,7 @@ $ mix do deps.get, compile
2. Run the server
```shell
-$ mix grpc.server
+$ mix run --no-halt
```
3. Run the client script
@@ -34,11 +34,7 @@ Refer to [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) for
## How to start server when starting your application?
-Change the config to:
-
-```elixir
-config :grpc, start_server: true
-```
+Pass `start_server: true` as an option for the `GRPC.Server.Supervisor` in your supervision tree.
## Benchmark
diff --git a/examples/helloworld/config/config.exs b/examples/helloworld/config/config.exs
index c2f683e9..9def7c2c 100644
--- a/examples/helloworld/config/config.exs
+++ b/examples/helloworld/config/config.exs
@@ -1,6 +1,3 @@
-use Mix.Config
-
-# Start server in OTP
-# config :grpc, start_server: true
+import Config
import_config "#{Mix.env}.exs"
diff --git a/examples/helloworld/config/dev.exs b/examples/helloworld/config/dev.exs
index d2d855e6..becde769 100644
--- a/examples/helloworld/config/dev.exs
+++ b/examples/helloworld/config/dev.exs
@@ -1 +1 @@
-use Mix.Config
+import Config
diff --git a/examples/helloworld/config/prod.exs b/examples/helloworld/config/prod.exs
index 9d6d504a..2dd33c31 100644
--- a/examples/helloworld/config/prod.exs
+++ b/examples/helloworld/config/prod.exs
@@ -1,6 +1,4 @@
-use Mix.Config
-
-config :grpc, start_server: true
+import Config
config :logger,
level: :warn
diff --git a/examples/helloworld/config/test.exs b/examples/helloworld/config/test.exs
new file mode 100644
index 00000000..becde769
--- /dev/null
+++ b/examples/helloworld/config/test.exs
@@ -0,0 +1 @@
+import Config
diff --git a/examples/helloworld/lib/helloworld.pb.ex b/examples/helloworld/lib/helloworld.pb.ex
index 7c65811d..a8ff6dfa 100644
--- a/examples/helloworld/lib/helloworld.pb.ex
+++ b/examples/helloworld/lib/helloworld.pb.ex
@@ -1,31 +1,26 @@
defmodule Helloworld.HelloRequest do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- name: String.t()
- }
- defstruct [:name]
-
- field :name, 1, optional: true, type: :string
+ field :name, 1, type: :string
end
defmodule Helloworld.HelloReply do
- use Protobuf
-
- @type t :: %__MODULE__{
- message: String.t()
- }
- defstruct [:message]
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :message, 1, optional: true, type: :string
+ field :message, 1, type: :string
+ field :today, 2, type: Google.Protobuf.Timestamp
end
defmodule Helloworld.Greeter.Service do
- use GRPC.Service, name: "helloworld.Greeter"
+ @moduledoc false
+ use GRPC.Service, name: "helloworld.Greeter", protoc_gen_elixir_version: "0.10.0"
rpc :SayHello, Helloworld.HelloRequest, Helloworld.HelloReply
end
defmodule Helloworld.Greeter.Stub do
+ @moduledoc false
use GRPC.Stub, service: Helloworld.Greeter.Service
end
diff --git a/examples/helloworld/lib/helloworld_app.ex b/examples/helloworld/lib/helloworld_app.ex
index 93500609..d84d62a5 100644
--- a/examples/helloworld/lib/helloworld_app.ex
+++ b/examples/helloworld/lib/helloworld_app.ex
@@ -2,10 +2,8 @@ defmodule HelloworldApp do
use Application
def start(_type, _args) do
- import Supervisor.Spec
-
children = [
- supervisor(GRPC.Server.Supervisor, [{Helloworld.Endpoint, 50051}])
+ {GRPC.Server.Supervisor, endpoint: Helloworld.Endpoint, port: 50051, start_server: true}
]
opts = [strategy: :one_for_one, name: HelloworldApp]
diff --git a/examples/helloworld/lib/server.ex b/examples/helloworld/lib/server.ex
index a82535d6..b85241f8 100644
--- a/examples/helloworld/lib/server.ex
+++ b/examples/helloworld/lib/server.ex
@@ -4,6 +4,13 @@ defmodule Helloworld.Greeter.Server do
@spec say_hello(Helloworld.HelloRequest.t(), GRPC.Server.Stream.t()) ::
Helloworld.HelloReply.t()
def say_hello(request, _stream) do
- Helloworld.HelloReply.new(message: "Hello #{request.name}")
+ 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
+
+ Helloworld.HelloReply.new(
+ message: "Hello #{request.name}",
+ today: %Google.Protobuf.Timestamp{seconds: seconds, nanos: nanos}
+ )
end
end
diff --git a/examples/helloworld/mix.exs b/examples/helloworld/mix.exs
index ff2c94c8..9bf4cc3e 100644
--- a/examples/helloworld/mix.exs
+++ b/examples/helloworld/mix.exs
@@ -2,25 +2,26 @@ 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()]
+ [
+ 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]]
+ [mod: {HelloworldApp, []}, applications: [:logger, :grpc]]
end
defp deps do
[
{:grpc, path: "../../"},
- {:protobuf, github: "tony612/protobuf-elixir", override: true},
- {:cowlib, "~> 2.8.0", hex: :grpc_cowlib, override: true},
- {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
+ {:protobuf, "~> 0.10"},
+ {:google_protos, "~> 0.3.0"},
+ {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}
]
end
end
diff --git a/examples/helloworld/mix.lock b/examples/helloworld/mix.lock
index 010aed56..f96a70d1 100644
--- a/examples/helloworld/mix.lock
+++ b/examples/helloworld/mix.lock
@@ -1,8 +1,10 @@
%{
- "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
- "cowlib": {:hex, :grpc_cowlib, "2.8.1", "ddaf77f3b89bd8e6c76df67b28a4b069688eef91c0c497a246cf9bfcdf87f7d3", [:rebar3], [], "hexpm"},
- "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
- "gun": {:hex, :grpc_gun, "2.0.0", "f99678a2ab975e74372a756c86ec30a8384d3ac8a8b86c7ed6243ef4e61d2729", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm"},
- "protobuf": {:git, "https://github.com/tony612/protobuf-elixir.git", "384f97ca03aa4f874e527e9f28f5ebbae2f142f1", []},
- "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
+ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
+ "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+ "google_protos": {:hex, :google_protos, "0.3.0", "15faf44dce678ac028c289668ff56548806e313e4959a3aaf4f6e1ebe8db83f4", [:mix], [{:protobuf, "~> 0.10", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1f6b7fb20371f72f418b98e5e48dae3e022a9a6de1858d4b254ac5a5d0b4035f"},
+ "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"},
+ "protobuf": {:hex, :protobuf, "0.10.0", "4e8e3cf64c5be203b329f88bb8b916cb8d00fb3a12b2ac1f545463ae963c869f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4ae21a386142357aa3d31ccf5f7d290f03f3fa6f209755f6e87fc2c58c147893"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
}
diff --git a/examples/helloworld/priv/client.exs b/examples/helloworld/priv/client.exs
index cfb8560e..dc6bea5d 100644
--- a/examples/helloworld/priv/client.exs
+++ b/examples/helloworld/priv/client.exs
@@ -1,6 +1,9 @@
{: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"))
+ 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/helloworld.proto b/examples/helloworld/priv/protos/helloworld.proto
index 688974b2..12849981 100644
--- a/examples/helloworld/priv/protos/helloworld.proto
+++ b/examples/helloworld/priv/protos/helloworld.proto
@@ -5,6 +5,8 @@ option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
+import "google/protobuf/timestamp.proto";
+
package helloworld;
// The greeting service definition.
@@ -21,4 +23,5 @@ message HelloRequest {
// The response message containing the greetings
message HelloReply {
string message = 1;
+ google.protobuf.Timestamp today = 2;
}
diff --git a/examples/helloworld/test/hello_world_test.exs b/examples/helloworld/test/hello_world_test.exs
new file mode 100644
index 00000000..962d07ac
--- /dev/null
+++ b/examples/helloworld/test/hello_world_test.exs
@@ -0,0 +1,16 @@
+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/test/test_helper.exs b/examples/helloworld/test/test_helper.exs
new file mode 100644
index 00000000..869559e7
--- /dev/null
+++ b/examples/helloworld/test/test_helper.exs
@@ -0,0 +1 @@
+ExUnit.start()
diff --git a/examples/route_guide/README.md b/examples/route_guide/README.md
index 7ab2d1a7..5e9fa0df 100644
--- a/examples/route_guide/README.md
+++ b/examples/route_guide/README.md
@@ -9,7 +9,7 @@ $ mix do deps.get, compile
2. Run the server
```
-$ mix grpc.server
+$ mix run --no-halt
```
2. Run the client
@@ -35,7 +35,7 @@ Refer to [protobuf-elixir](https://github.com/tony612/protobuf-elixir#usage) for
## Authentication
```
-$ TLS=true mix grpc.server
+$ TLS=true mix run --no-halt
$ TLS=true mix run priv/client.exs
```
@@ -44,10 +44,3 @@ $ TLS=true mix run priv/client.exs
* 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`
-* How to start server when starting your application?
-
- Change the config to:
-
- ```elixir
- config :grpc, start_server: true
- ```
diff --git a/examples/route_guide/config/config.exs b/examples/route_guide/config/config.exs
deleted file mode 100644
index f346b5d2..00000000
--- a/examples/route_guide/config/config.exs
+++ /dev/null
@@ -1,3 +0,0 @@
-use Mix.Config
-
-# config :grpc, start_server: true
diff --git a/examples/route_guide/lib/app.ex b/examples/route_guide/lib/app.ex
index 5410d767..8cebbf31 100644
--- a/examples/route_guide/lib/app.ex
+++ b/examples/route_guide/lib/app.ex
@@ -5,11 +5,9 @@ defmodule Routeguide.App do
@key_path Path.expand("./tls/server1.key", :code.priv_dir(:route_guide))
def start(_type, _args) do
- import Supervisor.Spec
-
children = [
- supervisor(RouteGuide.Data, []),
- supervisor(GRPC.Server.Supervisor, [start_args()])
+ RouteGuide.Data,
+ {GRPC.Server.Supervisor, start_args()}
]
opts = [strategy: :one_for_one, name: Routeguide]
@@ -17,12 +15,13 @@ defmodule Routeguide.App do
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])
- IO.inspect(cred)
- {Routeguide.Endpoint, 10000, cred: cred}
+ Keyword.put(opts, :cred, cred)
else
- {Routeguide.Endpoint, 10000}
+ opts
end
end
end
diff --git a/examples/route_guide/lib/client.ex b/examples/route_guide/lib/client.ex
index 150a751b..5d33f4ba 100644
--- a/examples/route_guide/lib/client.ex
+++ b/examples/route_guide/lib/client.ex
@@ -89,9 +89,7 @@ defmodule RouteGuide.Client do
Enum.each(result_enum, fn {:ok, note} ->
IO.puts(
- "Got message #{note.message} at point(#{note.location.latitude}, #{
- note.location.longitude
- })"
+ "Got message #{note.message} at point(#{note.location.latitude}, #{note.location.longitude})"
)
end)
end
diff --git a/examples/route_guide/lib/data.ex b/examples/route_guide/lib/data.ex
index 11cf0343..7d1fe528 100644
--- a/examples/route_guide/lib/data.ex
+++ b/examples/route_guide/lib/data.ex
@@ -1,7 +1,9 @@
defmodule RouteGuide.Data do
+ use Agent
+
@json_path Path.expand("../priv/route_guide_db.json", __DIR__)
- def start_link do
+ def start_link(_) do
features = load_features()
Agent.start_link(fn -> %{features: features, notes: %{}} end, name: __MODULE__)
end
@@ -20,13 +22,13 @@ defmodule RouteGuide.Data do
defp load_features(path \\ @json_path) do
data = File.read!(path)
- items = Poison.Parser.parse!(data)
+ items = Jason.decode!(data)
- Enum.map(items, fn %{"location" => location, "name" => name} ->
+ 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
end
diff --git a/examples/route_guide/lib/route_guide.pb.ex b/examples/route_guide/lib/route_guide.pb.ex
index f4ce6742..66a64c91 100644
--- a/examples/route_guide/lib/route_guide.pb.ex
+++ b/examples/route_guide/lib/route_guide.pb.ex
@@ -1,81 +1,59 @@
defmodule Routeguide.Point do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- latitude: integer,
- longitude: integer
- }
- defstruct [:latitude, :longitude]
-
- field :latitude, 1, optional: true, type: :int32
- field :longitude, 2, optional: true, type: :int32
+ field :latitude, 1, type: :int32
+ field :longitude, 2, type: :int32
end
defmodule Routeguide.Rectangle do
- use Protobuf
-
- @type t :: %__MODULE__{
- lo: Routeguide.Point.t(),
- hi: Routeguide.Point.t()
- }
- defstruct [:lo, :hi]
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :lo, 1, optional: true, type: Routeguide.Point
- field :hi, 2, optional: true, type: Routeguide.Point
+ field :lo, 1, type: Routeguide.Point
+ field :hi, 2, type: Routeguide.Point
end
defmodule Routeguide.Feature do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- name: String.t(),
- location: Routeguide.Point.t()
- }
- defstruct [:name, :location]
-
- field :name, 1, optional: true, type: :string
- field :location, 2, optional: true, type: Routeguide.Point
+ field :name, 1, type: :string
+ field :location, 2, type: Routeguide.Point
end
defmodule Routeguide.RouteNote do
- use Protobuf
-
- @type t :: %__MODULE__{
- location: Routeguide.Point.t(),
- message: String.t()
- }
- defstruct [:location, :message]
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :location, 1, optional: true, type: Routeguide.Point
- field :message, 2, optional: true, type: :string
+ field :location, 1, type: Routeguide.Point
+ field :message, 2, type: :string
end
defmodule Routeguide.RouteSummary do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- point_count: integer,
- feature_count: integer,
- distance: integer,
- elapsed_time: integer
- }
- defstruct [:point_count, :feature_count, :distance, :elapsed_time]
-
- field :point_count, 1, optional: true, type: :int32
- field :feature_count, 2, optional: true, type: :int32
- field :distance, 3, optional: true, type: :int32
- field :elapsed_time, 4, optional: true, type: :int32
+ 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
- use GRPC.Service, name: "routeguide.RouteGuide"
+ @moduledoc false
+ use GRPC.Service, name: "routeguide.RouteGuide", protoc_gen_elixir_version: "0.10.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
index 3cf7e70f..3e90b8bc 100644
--- a/examples/route_guide/lib/server.ex
+++ b/examples/route_guide/lib/server.ex
@@ -13,7 +13,7 @@ defmodule Routeguide.RouteGuide.Server do
end)
end
- @spec list_features(Routeguide.Rectangle.t(), GRPC.Server.Stream.t()) :: any
+ @spec list_features(Routeguide.Rectangle.t(), GRPC.Server.Stream.t()) :: any()
def list_features(rect, stream) do
features = Data.fetch_features()
@@ -45,7 +45,7 @@ defmodule Routeguide.RouteGuide.Server do
)
end
- @spec record_route(Enumerable.t(), GRPC.Server.Stream.t()) :: any
+ @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 ->
diff --git a/examples/route_guide/mix.exs b/examples/route_guide/mix.exs
index 812a09d8..045792a6 100644
--- a/examples/route_guide/mix.exs
+++ b/examples/route_guide/mix.exs
@@ -4,7 +4,7 @@ defmodule RouteGuide.Mixfile do
def project do
[app: :route_guide,
version: "0.1.0",
- elixir: "~> 1.3",
+ elixir: "~> 1.11",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
@@ -15,7 +15,7 @@ defmodule RouteGuide.Mixfile do
# Type "mix help compile.app" for more information
def application do
[mod: {Routeguide.App, []},
- applications: [:logger, :grpc, :poison]]
+ applications: [:logger, :grpc, :protobuf, :jason]]
end
# Dependencies can be Hex packages:
@@ -30,9 +30,9 @@ defmodule RouteGuide.Mixfile do
defp deps do
[
{:grpc, path: "../../"},
- {:poison, "~> 3.0"},
- {:cowlib, "~> 2.8.0", hex: :grpc_cowlib, override: true},
- {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
+ {:protobuf, "~> 0.10"},
+ {:jason, "~> 1.2"},
+ {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
]
end
end
diff --git a/examples/route_guide/mix.lock b/examples/route_guide/mix.lock
index 5205d247..3d14fba0 100644
--- a/examples/route_guide/mix.lock
+++ b/examples/route_guide/mix.lock
@@ -1,9 +1,10 @@
%{
- "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
- "cowlib": {:hex, :grpc_cowlib, "2.8.1", "ddaf77f3b89bd8e6c76df67b28a4b069688eef91c0c497a246cf9bfcdf87f7d3", [:rebar3], [], "hexpm"},
- "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], []},
- "gun": {:hex, :grpc_gun, "2.0.0", "f99678a2ab975e74372a756c86ec30a8384d3ac8a8b86c7ed6243ef4e61d2729", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm"},
- "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
- "protobuf": {:hex, :protobuf, "0.5.0", "ec9857903f8c49cf01ad5f1340956e19ebe0ef05e225b15b442f33b13031ce08", [:mix], []},
- "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
+ "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
+ "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+ "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"},
+ "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
+ "protobuf": {:hex, :protobuf, "0.10.0", "4e8e3cf64c5be203b329f88bb8b916cb8d00fb3a12b2ac1f545463ae963c869f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4ae21a386142357aa3d31ccf5f7d290f03f3fa6f209755f6e87fc2c58c147893"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
}
diff --git a/examples/route_guide/priv/client.exs b/examples/route_guide/priv/client.exs
index d3c3178e..79a33f0f 100644
--- a/examples/route_guide/priv/client.exs
+++ b/examples/route_guide/priv/client.exs
@@ -1,9 +1,10 @@
opts = [interceptors: [GRPC.Logger.Client]]
+
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]
+ [{:cred, cred} | opts]
else
opts
end
diff --git a/examples/route_guide/priv/route_guide.proto b/examples/route_guide/priv/protos/route_guide.proto
similarity index 100%
rename from examples/route_guide/priv/route_guide.proto
rename to examples/route_guide/priv/protos/route_guide.proto
diff --git a/interop/config/config.exs b/interop/config/config.exs
index 3fe942a9..5d2660ce 100644
--- a/interop/config/config.exs
+++ b/interop/config/config.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
config :prometheus, GRPCPrometheus.ServerInterceptor,
latency: :histogram
@@ -6,7 +6,4 @@ config :prometheus, GRPCPrometheus.ServerInterceptor,
config :prometheus, GRPCPrometheus.ClientInterceptor,
latency: :histogram
-# config :grpc, start_server: true
-
-# config :logger, level: :debug
config :logger, level: :warn
diff --git a/interop/lib/grpc_testing/empty.pb.ex b/interop/lib/grpc_testing/empty.pb.ex
index 0208e266..77ee57fd 100644
--- a/interop/lib/grpc_testing/empty.pb.ex
+++ b/interop/lib/grpc_testing/empty.pb.ex
@@ -1,7 +1,4 @@
defmodule Grpc.Testing.Empty do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{}
- defstruct []
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
end
diff --git a/interop/lib/grpc_testing/messages.pb.ex b/interop/lib/grpc_testing/messages.pb.ex
index aec33378..6a9d03e8 100644
--- a/interop/lib/grpc_testing/messages.pb.ex
+++ b/interop/lib/grpc_testing/messages.pb.ex
@@ -1,202 +1,283 @@
defmodule Grpc.Testing.PayloadType do
@moduledoc false
- use Protobuf, enum: true, syntax: :proto3
-
- @type t :: integer | :COMPRESSABLE
+ use Protobuf, enum: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :COMPRESSABLE, 0
end
+defmodule Grpc.Testing.GrpclbRouteType do
+ @moduledoc false
+ use Protobuf, enum: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
-defmodule Grpc.Testing.BoolValue do
+ field :GRPCLB_ROUTE_TYPE_UNKNOWN, 0
+ field :GRPCLB_ROUTE_TYPE_FALLBACK, 1
+ field :GRPCLB_ROUTE_TYPE_BACKEND, 2
+end
+defmodule Grpc.Testing.ClientConfigureRequest.RpcType do
@moduledoc false
- use Protobuf, syntax: :proto3
+ use Protobuf, enum: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- value: boolean
- }
- defstruct [:value]
+ field :EMPTY_CALL, 0
+ field :UNARY_CALL, 1
+end
+defmodule Grpc.Testing.BoolValue do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :value, 1, type: :bool
end
-
defmodule Grpc.Testing.Payload do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- type: Grpc.Testing.PayloadType.t(),
- body: binary
- }
- defstruct [:type, :body]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :type, 1, type: Grpc.Testing.PayloadType, enum: true
field :body, 2, type: :bytes
end
-
defmodule Grpc.Testing.EchoStatus do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- code: integer,
- message: String.t()
- }
- defstruct [:code, :message]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :code, 1, type: :int32
field :message, 2, type: :string
end
-
defmodule Grpc.Testing.SimpleRequest do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- response_type: Grpc.Testing.PayloadType.t(),
- response_size: integer,
- payload: Grpc.Testing.Payload.t() | nil,
- fill_username: boolean,
- fill_oauth_scope: boolean,
- response_compressed: Grpc.Testing.BoolValue.t() | nil,
- response_status: Grpc.Testing.EchoStatus.t() | nil,
- expect_compressed: Grpc.Testing.BoolValue.t() | nil,
- fill_server_id: boolean
- }
- defstruct [
- :response_type,
- :response_size,
- :payload,
- :fill_username,
- :fill_oauth_scope,
- :response_compressed,
- :response_status,
- :expect_compressed,
- :fill_server_id
- ]
-
- field :response_type, 1, type: Grpc.Testing.PayloadType, enum: true
- field :response_size, 2, type: :int32
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :response_type, 1, type: Grpc.Testing.PayloadType, json_name: "responseType", enum: true
+ field :response_size, 2, type: :int32, json_name: "responseSize"
field :payload, 3, type: Grpc.Testing.Payload
- field :fill_username, 4, type: :bool
- field :fill_oauth_scope, 5, type: :bool
- field :response_compressed, 6, type: Grpc.Testing.BoolValue
- field :response_status, 7, type: Grpc.Testing.EchoStatus
- field :expect_compressed, 8, type: Grpc.Testing.BoolValue
- field :fill_server_id, 9, type: :bool
+ field :fill_username, 4, type: :bool, json_name: "fillUsername"
+ field :fill_oauth_scope, 5, type: :bool, json_name: "fillOauthScope"
+ field :response_compressed, 6, type: Grpc.Testing.BoolValue, json_name: "responseCompressed"
+ field :response_status, 7, type: Grpc.Testing.EchoStatus, json_name: "responseStatus"
+ field :expect_compressed, 8, type: Grpc.Testing.BoolValue, json_name: "expectCompressed"
+ field :fill_server_id, 9, type: :bool, json_name: "fillServerId"
+ field :fill_grpclb_route_type, 10, type: :bool, json_name: "fillGrpclbRouteType"
end
-
defmodule Grpc.Testing.SimpleResponse do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- payload: Grpc.Testing.Payload.t() | nil,
- username: String.t(),
- oauth_scope: String.t(),
- server_id: String.t()
- }
- defstruct [:payload, :username, :oauth_scope, :server_id]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :payload, 1, type: Grpc.Testing.Payload
field :username, 2, type: :string
- field :oauth_scope, 3, type: :string
- field :server_id, 4, type: :string
-end
+ field :oauth_scope, 3, type: :string, json_name: "oauthScope"
+ field :server_id, 4, type: :string, json_name: "serverId"
+
+ field :grpclb_route_type, 5,
+ type: Grpc.Testing.GrpclbRouteType,
+ json_name: "grpclbRouteType",
+ enum: true
+ field :hostname, 6, type: :string
+end
defmodule Grpc.Testing.StreamingInputCallRequest do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- payload: Grpc.Testing.Payload.t() | nil,
- expect_compressed: Grpc.Testing.BoolValue.t() | nil
- }
- defstruct [:payload, :expect_compressed]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :payload, 1, type: Grpc.Testing.Payload
- field :expect_compressed, 2, type: Grpc.Testing.BoolValue
+ field :expect_compressed, 2, type: Grpc.Testing.BoolValue, json_name: "expectCompressed"
end
-
defmodule Grpc.Testing.StreamingInputCallResponse do
@moduledoc false
- use Protobuf, syntax: :proto3
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- aggregated_payload_size: integer
- }
- defstruct [:aggregated_payload_size]
-
- field :aggregated_payload_size, 1, type: :int32
+ field :aggregated_payload_size, 1, type: :int32, json_name: "aggregatedPayloadSize"
end
-
defmodule Grpc.Testing.ResponseParameters do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- size: integer,
- interval_us: integer,
- compressed: Grpc.Testing.BoolValue.t() | nil
- }
- defstruct [:size, :interval_us, :compressed]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :size, 1, type: :int32
- field :interval_us, 2, type: :int32
+ field :interval_us, 2, type: :int32, json_name: "intervalUs"
field :compressed, 3, type: Grpc.Testing.BoolValue
end
-
defmodule Grpc.Testing.StreamingOutputCallRequest do
@moduledoc false
- use Protobuf, syntax: :proto3
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :response_type, 1, type: Grpc.Testing.PayloadType, json_name: "responseType", enum: true
- @type t :: %__MODULE__{
- response_type: Grpc.Testing.PayloadType.t(),
- response_parameters: [Grpc.Testing.ResponseParameters.t()],
- payload: Grpc.Testing.Payload.t() | nil,
- response_status: Grpc.Testing.EchoStatus.t() | nil
- }
- defstruct [:response_type, :response_parameters, :payload, :response_status]
+ field :response_parameters, 2,
+ repeated: true,
+ type: Grpc.Testing.ResponseParameters,
+ json_name: "responseParameters"
- field :response_type, 1, type: Grpc.Testing.PayloadType, enum: true
- field :response_parameters, 2, repeated: true, type: Grpc.Testing.ResponseParameters
field :payload, 3, type: Grpc.Testing.Payload
- field :response_status, 7, type: Grpc.Testing.EchoStatus
+ field :response_status, 7, type: Grpc.Testing.EchoStatus, json_name: "responseStatus"
end
-
defmodule Grpc.Testing.StreamingOutputCallResponse do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- payload: Grpc.Testing.Payload.t() | nil
- }
- defstruct [:payload]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :payload, 1, type: Grpc.Testing.Payload
end
-
defmodule Grpc.Testing.ReconnectParams do
@moduledoc false
- use Protobuf, syntax: :proto3
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- max_reconnect_backoff_ms: integer
- }
- defstruct [:max_reconnect_backoff_ms]
+ field :max_reconnect_backoff_ms, 1, type: :int32, json_name: "maxReconnectBackoffMs"
+end
+defmodule Grpc.Testing.ReconnectInfo do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :max_reconnect_backoff_ms, 1, type: :int32
+ field :passed, 1, type: :bool
+ field :backoff_ms, 2, repeated: true, type: :int32, json_name: "backoffMs"
end
+defmodule Grpc.Testing.LoadBalancerStatsRequest do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
-defmodule Grpc.Testing.ReconnectInfo do
+ field :num_rpcs, 1, type: :int32, json_name: "numRpcs"
+ field :timeout_sec, 2, type: :int32, json_name: "timeoutSec"
+end
+defmodule Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeer.RpcsByPeerEntry do
@moduledoc false
- use Protobuf, syntax: :proto3
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- passed: boolean,
- backoff_ms: [integer]
- }
- defstruct [:passed, :backoff_ms]
+ field :key, 1, type: :string
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeer do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :passed, 1, type: :bool
- field :backoff_ms, 2, repeated: true, type: :int32
+ field :rpcs_by_peer, 1,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeer.RpcsByPeerEntry,
+ json_name: "rpcsByPeer",
+ map: true
+end
+defmodule Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeerEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerStatsResponse.RpcsByMethodEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeer
+end
+defmodule Grpc.Testing.LoadBalancerStatsResponse do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :rpcs_by_peer, 1,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerStatsResponse.RpcsByPeerEntry,
+ json_name: "rpcsByPeer",
+ map: true
+
+ field :num_failures, 2, type: :int32, json_name: "numFailures"
+
+ field :rpcs_by_method, 3,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerStatsResponse.RpcsByMethodEntry,
+ json_name: "rpcsByMethod",
+ map: true
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsRequest do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsStartedByMethodEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsSucceededByMethodEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsFailedByMethodEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.MethodStats.ResultEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :int32
+ field :value, 2, type: :int32
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.MethodStats do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :rpcs_started, 1, type: :int32, json_name: "rpcsStarted"
+
+ field :result, 2,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.MethodStats.ResultEntry,
+ map: true
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse.StatsPerMethodEntry do
+ @moduledoc false
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.MethodStats
+end
+defmodule Grpc.Testing.LoadBalancerAccumulatedStatsResponse do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :num_rpcs_started_by_method, 1,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsStartedByMethodEntry,
+ json_name: "numRpcsStartedByMethod",
+ map: true,
+ deprecated: true
+
+ field :num_rpcs_succeeded_by_method, 2,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsSucceededByMethodEntry,
+ json_name: "numRpcsSucceededByMethod",
+ map: true,
+ deprecated: true
+
+ field :num_rpcs_failed_by_method, 3,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.NumRpcsFailedByMethodEntry,
+ json_name: "numRpcsFailedByMethod",
+ map: true,
+ deprecated: true
+
+ field :stats_per_method, 4,
+ repeated: true,
+ type: Grpc.Testing.LoadBalancerAccumulatedStatsResponse.StatsPerMethodEntry,
+ json_name: "statsPerMethod",
+ map: true
+end
+defmodule Grpc.Testing.ClientConfigureRequest.Metadata do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :type, 1, type: Grpc.Testing.ClientConfigureRequest.RpcType, enum: true
+ field :key, 2, type: :string
+ field :value, 3, type: :string
+end
+defmodule Grpc.Testing.ClientConfigureRequest do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
+
+ field :types, 1, repeated: true, type: Grpc.Testing.ClientConfigureRequest.RpcType, enum: true
+ field :metadata, 2, repeated: true, type: Grpc.Testing.ClientConfigureRequest.Metadata
+ field :timeout_sec, 3, type: :int32, json_name: "timeoutSec"
+end
+defmodule Grpc.Testing.ClientConfigureResponse do
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
end
diff --git a/interop/lib/grpc_testing/test.pb.ex b/interop/lib/grpc_testing/test.pb.ex
index 963a45c5..a1a231cd 100644
--- a/interop/lib/grpc_testing/test.pb.ex
+++ b/interop/lib/grpc_testing/test.pb.ex
@@ -1,9 +1,11 @@
defmodule Grpc.Testing.TestService.Service do
@moduledoc false
- use GRPC.Service, name: "grpc.testing.TestService"
+ use GRPC.Service, name: "grpc.testing.TestService", protoc_gen_elixir_version: "0.10.0"
rpc :EmptyCall, Grpc.Testing.Empty, Grpc.Testing.Empty
+
rpc :UnaryCall, Grpc.Testing.SimpleRequest, Grpc.Testing.SimpleResponse
+
rpc :CacheableUnaryCall, Grpc.Testing.SimpleRequest, Grpc.Testing.SimpleResponse
rpc :StreamingOutputCall,
@@ -29,10 +31,9 @@ defmodule Grpc.Testing.TestService.Stub do
@moduledoc false
use GRPC.Stub, service: Grpc.Testing.TestService.Service
end
-
defmodule Grpc.Testing.UnimplementedService.Service do
@moduledoc false
- use GRPC.Service, name: "grpc.testing.UnimplementedService"
+ use GRPC.Service, name: "grpc.testing.UnimplementedService", protoc_gen_elixir_version: "0.10.0"
rpc :UnimplementedCall, Grpc.Testing.Empty, Grpc.Testing.Empty
end
@@ -41,12 +42,12 @@ defmodule Grpc.Testing.UnimplementedService.Stub do
@moduledoc false
use GRPC.Stub, service: Grpc.Testing.UnimplementedService.Service
end
-
defmodule Grpc.Testing.ReconnectService.Service do
@moduledoc false
- use GRPC.Service, name: "grpc.testing.ReconnectService"
+ use GRPC.Service, name: "grpc.testing.ReconnectService", protoc_gen_elixir_version: "0.10.0"
rpc :Start, Grpc.Testing.ReconnectParams, Grpc.Testing.Empty
+
rpc :Stop, Grpc.Testing.Empty, Grpc.Testing.ReconnectInfo
end
@@ -54,3 +55,50 @@ defmodule Grpc.Testing.ReconnectService.Stub do
@moduledoc false
use GRPC.Stub, service: Grpc.Testing.ReconnectService.Service
end
+defmodule Grpc.Testing.LoadBalancerStatsService.Service do
+ @moduledoc false
+ use GRPC.Service,
+ name: "grpc.testing.LoadBalancerStatsService",
+ protoc_gen_elixir_version: "0.10.0"
+
+ rpc :GetClientStats,
+ Grpc.Testing.LoadBalancerStatsRequest,
+ Grpc.Testing.LoadBalancerStatsResponse
+
+ rpc :GetClientAccumulatedStats,
+ Grpc.Testing.LoadBalancerAccumulatedStatsRequest,
+ Grpc.Testing.LoadBalancerAccumulatedStatsResponse
+end
+
+defmodule Grpc.Testing.LoadBalancerStatsService.Stub do
+ @moduledoc false
+ use GRPC.Stub, service: Grpc.Testing.LoadBalancerStatsService.Service
+end
+defmodule Grpc.Testing.XdsUpdateHealthService.Service do
+ @moduledoc false
+ use GRPC.Service,
+ name: "grpc.testing.XdsUpdateHealthService",
+ protoc_gen_elixir_version: "0.10.0"
+
+ rpc :SetServing, Grpc.Testing.Empty, Grpc.Testing.Empty
+
+ rpc :SetNotServing, Grpc.Testing.Empty, Grpc.Testing.Empty
+end
+
+defmodule Grpc.Testing.XdsUpdateHealthService.Stub do
+ @moduledoc false
+ use GRPC.Stub, service: Grpc.Testing.XdsUpdateHealthService.Service
+end
+defmodule Grpc.Testing.XdsUpdateClientConfigureService.Service do
+ @moduledoc false
+ use GRPC.Service,
+ name: "grpc.testing.XdsUpdateClientConfigureService",
+ protoc_gen_elixir_version: "0.10.0"
+
+ rpc :Configure, Grpc.Testing.ClientConfigureRequest, Grpc.Testing.ClientConfigureResponse
+end
+
+defmodule Grpc.Testing.XdsUpdateClientConfigureService.Stub do
+ @moduledoc false
+ use GRPC.Stub, service: Grpc.Testing.XdsUpdateClientConfigureService.Service
+end
diff --git a/interop/lib/interop/app.ex b/interop/lib/interop/app.ex
index 22857fde..1d3d2d81 100644
--- a/interop/lib/interop/app.ex
+++ b/interop/lib/interop/app.ex
@@ -2,15 +2,10 @@ defmodule Interop.App do
use Application
def start(_type, _args) do
- import Supervisor.Spec
-
- children = [
- supervisor(GRPC.Server.Supervisor, [{Interop.Endpoint, 10000}])
- ]
+ children = [{GRPC.Server.Supervisor, endpoint: Interop.Endpoint, port: 10000}]
GRPCPrometheus.ServerInterceptor.setup()
GRPCPrometheus.ClientInterceptor.setup()
- :prometheus_httpd.start()
Interop.ServerInterceptor.Statix.connect()
opts = [strategy: :one_for_one, name: __MODULE__]
diff --git a/interop/lib/interop/client.ex b/interop/lib/interop/client.ex
index dfa9e041..d8661ea9 100644
--- a/interop/lib/interop/client.ex
+++ b/interop/lib/interop/client.ex
@@ -1,13 +1,15 @@
defmodule Interop.Client do
import ExUnit.Assertions, only: [refute: 1]
+ require Logger
+
def connect(host, port, opts \\ []) do
{:ok, ch} = GRPC.Stub.connect(host, port, opts)
ch
end
def empty_unary!(ch) do
- IO.puts("Run empty_unary!")
+ Logger.info("Run empty_unary!")
empty = Grpc.Testing.Empty.new()
{:ok, ^empty} = Grpc.Testing.TestService.Stub.empty_call(ch, empty)
end
@@ -17,21 +19,21 @@ defmodule Interop.Client do
end
def large_unary!(ch) do
- IO.puts("Run large_unary!")
+ Logger.info("Run large_unary!")
req = Grpc.Testing.SimpleRequest.new(response_size: 314_159, payload: payload(271_828))
reply = Grpc.Testing.SimpleResponse.new(payload: payload(314_159))
{:ok, ^reply} = Grpc.Testing.TestService.Stub.unary_call(ch, req)
end
def large_unary2!(ch) do
- IO.puts("Run large_unary2!")
+ Logger.info("Run large_unary2!")
req = Grpc.Testing.SimpleRequest.new(response_size: 1024*1024*8, payload: payload(1024*1024*8))
reply = Grpc.Testing.SimpleResponse.new(payload: payload(1024*1024*8))
{:ok, ^reply} = Grpc.Testing.TestService.Stub.unary_call(ch, req)
end
def client_compressed_unary!(ch) do
- IO.puts("Run client_compressed_unary!")
+ Logger.info("Run client_compressed_unary!")
# "Client calls UnaryCall with the feature probe, an uncompressed message" is not supported
req = Grpc.Testing.SimpleRequest.new(expect_compressed: %{value: true}, response_size: 314_159, payload: payload(271_828))
@@ -44,7 +46,7 @@ defmodule Interop.Client do
end
def server_compressed_unary!(ch) do
- IO.puts("Run server_compressed_unary!")
+ Logger.info("Run server_compressed_unary!")
req = Grpc.Testing.SimpleRequest.new(response_compressed: %{value: true}, response_size: 314_159, payload: payload(271_828))
reply = Grpc.Testing.SimpleResponse.new(payload: payload(314_159))
@@ -57,7 +59,7 @@ defmodule Interop.Client do
end
def client_streaming!(ch) do
- IO.puts("Run client_streaming!")
+ Logger.info("Run client_streaming!")
stream =
ch
@@ -79,7 +81,7 @@ defmodule Interop.Client do
end
def client_compressed_streaming!(ch) do
- IO.puts("Run client_compressed_streaming!")
+ Logger.info("Run client_compressed_streaming!")
# INVALID_ARGUMENT testing is not supported
@@ -97,7 +99,7 @@ defmodule Interop.Client do
end
def server_streaming!(ch) do
- IO.puts("Run server_streaming!")
+ Logger.info("Run server_streaming!")
params = Enum.map([31415, 9, 2653, 58979], &res_param(&1))
req = Grpc.Testing.StreamingOutputCallRequest.new(response_parameters: params)
{:ok, res_enum} = ch |> Grpc.Testing.TestService.Stub.streaming_output_call(req)
@@ -110,7 +112,7 @@ defmodule Interop.Client do
end
def server_compressed_streaming!(ch) do
- IO.puts("Run server_compressed_streaming!")
+ Logger.info("Run server_compressed_streaming!")
req = Grpc.Testing.StreamingOutputCallRequest.new(response_parameters: [
%{compressed: %{value: true},
size: 31415},
@@ -127,7 +129,7 @@ defmodule Interop.Client do
end
def ping_pong!(ch) do
- IO.puts("Run ping_pong!")
+ Logger.info("Run ping_pong!")
stream = Grpc.Testing.TestService.Stub.full_duplex_call(ch)
req = fn size1, size2 ->
@@ -156,7 +158,7 @@ defmodule Interop.Client do
end
def empty_stream!(ch) do
- IO.puts("Run empty_stream!")
+ Logger.info("Run empty_stream!")
{:ok, res_enum} =
ch
@@ -168,7 +170,7 @@ defmodule Interop.Client do
end
def custom_metadata!(ch) do
- IO.puts("Run custom_metadata!")
+ Logger.info("Run custom_metadata!")
# UnaryCall
req = Grpc.Testing.SimpleRequest.new(response_size: 314_159, payload: payload(271_828))
reply = Grpc.Testing.SimpleResponse.new(payload: payload(314_159))
@@ -205,7 +207,7 @@ defmodule Interop.Client do
end
def status_code_and_message!(ch) do
- IO.puts("Run status_code_and_message!")
+ Logger.info("Run status_code_and_message!")
code = 2
msg = "test status message"
@@ -227,7 +229,7 @@ defmodule Interop.Client do
end
def unimplemented_service!(ch) do
- IO.puts("Run unimplemented_service!")
+ Logger.info("Run unimplemented_service!")
req = Grpc.Testing.Empty.new()
{:error, %GRPC.RPCError{status: 12}} =
@@ -235,7 +237,7 @@ defmodule Interop.Client do
end
def cancel_after_begin!(ch) do
- IO.puts("Run cancel_after_begin!")
+ Logger.info("Run cancel_after_begin!")
stream = Grpc.Testing.TestService.Stub.streaming_input_call(ch)
stream = GRPC.Stub.cancel(stream)
error = GRPC.RPCError.exception(1, "The operation was cancelled")
@@ -243,7 +245,7 @@ defmodule Interop.Client do
end
def cancel_after_first_response!(ch) do
- IO.puts("Run cancel_after_first_response!")
+ Logger.info("Run cancel_after_first_response!")
req =
Grpc.Testing.StreamingOutputCallRequest.new(
@@ -264,7 +266,7 @@ defmodule Interop.Client do
end
def timeout_on_sleeping_server!(ch) do
- IO.puts("Run timeout_on_sleeping_server!")
+ Logger.info("Run timeout_on_sleeping_server!")
req =
Grpc.Testing.StreamingOutputCallRequest.new(
diff --git a/interop/mix.exs b/interop/mix.exs
index dd3dff6a..2ce8d69c 100644
--- a/interop/mix.exs
+++ b/interop/mix.exs
@@ -23,13 +23,12 @@ defmodule Interop.MixProject do
defp deps do
[
{:grpc, path: "..", override: true},
- {:cowlib, "~> 2.9.0", override: true},
+ {:protobuf, "~> 0.10"},
{:grpc_prometheus, ">= 0.1.0"},
{:grpc_statsd, "~> 0.1.0"},
{:statix, ">= 1.2.1"},
{:extrace, "~> 0.2"},
- {:prometheus, "~> 4.0", override: true},
- {:prometheus_httpd, "~> 2.0"}
+ {:prometheus, "~> 4.0", override: true}
]
end
end
diff --git a/interop/mix.lock b/interop/mix.lock
index b04bcec6..11f40443 100644
--- a/interop/mix.lock
+++ b/interop/mix.lock
@@ -1,17 +1,17 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
- "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
- "cowlib": {:hex, :cowlib, "2.9.0", "8365736a2ada74d5e8640c9b03efff15aceffcf2c7cba2e5ffd0c549f54bf0da", [:rebar3], [], "hexpm", "e8a93bbdf5c4f3d63fbb0cae422de2b58227955bebad5fed978f7a83b0ca4c89"},
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"extrace": {:hex, :extrace, "0.2.1", "e234f1f64df8c989771b7b5d047a3412f10512c0e3d414fc7eb0e8fc633779f8", [:mix], [{:recon, "~> 2.5", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "1b2d9fc4bacc208d5aaa97f61e7c47b66ff4dfc155e9b95647e68ca316ab3981"},
"grpc": {:git, "https://github.com/elixir-grpc/grpc.git", "21422839798e49bf6d29327fab0a7add51becedd", []},
"grpc_prometheus": {:hex, :grpc_prometheus, "0.1.0", "a2f45ca83018c4ae59e4c293b7455634ac09e38c36cba7cc1fb8affdf462a6d5", [:mix], [{:grpc, ">= 0.0.0", [hex: :grpc, repo: "hexpm", optional: true]}, {:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8b9ab3098657e7daec0b3edc78e1d02418bc0871618d8ca89b51b74a8086bb71"},
"grpc_statsd": {:hex, :grpc_statsd, "0.1.0", "a95ae388188486043f92a3c5091c143f5a646d6af80c9da5ee616546c4d8f5ff", [:mix], [{:grpc, ">= 0.0.0", [hex: :grpc, repo: "hexpm", optional: true]}, {:statix, ">= 0.0.0", [hex: :statix, repo: "hexpm", optional: true]}], "hexpm", "de0c05db313c7b3ffeff345855d173fd82fec3de16591a126b673f7f698d9e74"},
- "gun": {:hex, :grpc_gun, "2.0.0", "f99678a2ab975e74372a756c86ec30a8384d3ac8a8b86c7ed6243ef4e61d2729", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "03dbbca1a9c604a0267a40ea1d69986225091acb822de0b2dbea21d5815e410b"},
+ "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"},
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm", "b479a33d4aa4ba7909186e29bb6c1240254e0047a8e2a9f88463f50c0089370e"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"},
"prometheus_httpd": {:hex, :prometheus_httpd, "2.1.11", "f616ed9b85b536b195d94104063025a91f904a4cfc20255363f49a197d96c896", [:rebar3], [{:accept, "~> 0.3", [hex: :accept, repo: "hexpm", optional: false]}, {:prometheus, "~> 4.2", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "0bbe831452cfdf9588538eb2f570b26f30c348adae5e95a7d87f35a5910bcf92"},
- "protobuf": {:hex, :protobuf, "0.7.1", "7d1b9f7d9ecb32eccd96b0c58572de4d1c09e9e3bc414e4cb15c2dce7013f195", [:mix], [], "hexpm", "6eff7a5287963719521c82e5d5b4583fd1d7cdd89ad129f0ea7d503a50a4d13f"},
- "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
+ "protobuf": {:hex, :protobuf, "0.10.0", "4e8e3cf64c5be203b329f88bb8b916cb8d00fb3a12b2ac1f545463ae963c869f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4ae21a386142357aa3d31ccf5f7d290f03f3fa6f209755f6e87fc2c58c147893"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
"statix": {:hex, :statix, "1.2.1", "4f23c8cc2477ea0de89fed5e34f08c54b0d28b838f7b8f26613155f2221bb31e", [:mix], [], "hexpm", "7f988988fddcce19ae376bb8e47aa5ea5dabf8d4ba78d34d1ae61eb537daf72e"},
}
diff --git a/interop/script/run.exs b/interop/script/run.exs
index 0728d6e7..81cb5d53 100644
--- a/interop/script/run.exs
+++ b/interop/script/run.exs
@@ -1,18 +1,26 @@
{options, _, _} = OptionParser.parse(System.argv(), strict: [rounds: :integer, concurrency: :integer, port: :integer])
-rounds = Keyword.get(options, :rounds, 100)
-concurrency = Keyword.get(options, :concurrency, 10)
-port = Keyword.get(options, :port, 0)
+rounds = Keyword.get(options, :rounds) || 20
+max_concurrency = System.schedulers_online()
+concurrency = Keyword.get(options, :concurrency) || max_concurrency
+port = Keyword.get(options, :port) || 0
+level = Keyword.get(options, :log_level) || "warn"
+level = String.to_existing_atom(level)
-IO.puts "Rounds: #{rounds}; concurrency: #{concurrency}; port: #{port}"
-IO.puts ""
+require Logger
+
+Logger.configure(level: level)
+
+Logger.info("Rounds: #{rounds}; concurrency: #{concurrency}; port: #{port}")
alias Interop.Client
+
{:ok, _pid, port} = GRPC.Server.start_endpoint(Interop.Endpoint, port)
-stream = Task.async_stream(1..concurrency, fn cli ->
+1..concurrency
+|> Task.async_stream(fn _cli ->
ch = Client.connect("127.0.0.1", port, interceptors: [GRPCPrometheus.ClientInterceptor, GRPC.Logger.Client])
- run = fn(i) ->
- IO.puts("Client##{cli}, Round #{i}")
+
+ for _ <- 1..rounds do
Client.empty_unary!(ch)
Client.cacheable_unary!(ch)
Client.large_unary!(ch)
@@ -32,14 +40,9 @@ stream = Task.async_stream(1..concurrency, fn cli ->
Client.cancel_after_first_response!(ch)
Client.timeout_on_sleeping_server!(ch)
end
- Enum.each(1..rounds, run)
:ok
end, max_concurrency: concurrency, ordered: false, timeout: :infinity)
-
-Enum.map(stream, fn result ->
- result
-end)
-|> IO.inspect
+|> Enum.to_list()
# defmodule Helper do
# def flush() do
@@ -54,5 +57,5 @@ end)
# end
# Helper.flush()
-IO.puts("Succeed!")
+Logger.info("Succeed!")
:ok = GRPC.Server.stop_endpoint(Interop.Endpoint)
diff --git a/lib/grpc/channel.ex b/lib/grpc/channel.ex
index 09194cc1..d7818b07 100644
--- a/lib/grpc/channel.ex
+++ b/lib/grpc/channel.ex
@@ -11,23 +11,23 @@ defmodule GRPC.Channel do
* `:port` - server's port to connect
* `:scheme` - scheme of connection, like `http`
* `:cred` - credentials used for authentication
- * `:adapter` - a client adapter module, like `GRPC.Adapter.Gun`
+ * `:adapter` - a client adapter module, like `GRPC.Client.Adapters.Gun`
* `:codec` - a default codec for this channel
* `:adapter_payload` - payload the adapter uses
"""
@type t :: %__MODULE__{
host: String.t(),
- port: non_neg_integer,
+ port: non_neg_integer(),
scheme: String.t(),
cred: GRPC.Credential.t(),
- adapter: atom,
- adapter_payload: any,
- codec: module,
+ adapter: atom(),
+ adapter_payload: any(),
+ codec: module(),
interceptors: [],
- compressor: module,
- accepted_compressors: [module],
- headers: list
+ compressor: module(),
+ accepted_compressors: [module()],
+ headers: list()
}
defstruct host: nil,
port: nil,
diff --git a/lib/grpc/client/adapter.ex b/lib/grpc/client/adapter.ex
new file mode 100644
index 00000000..b1f684a6
--- /dev/null
+++ b/lib/grpc/client/adapter.ex
@@ -0,0 +1,25 @@
+defmodule GRPC.Client.Adapter do
+ @moduledoc """
+ HTTP client adapter for GRPC.
+ """
+
+ alias GRPC.Client.Stream
+ alias GRPC.Channel
+
+ @typedoc "Determines if the headers have finished being read."
+ @type fin :: :fin | :nofin
+
+ @callback connect(channel :: Channel.t(), opts :: keyword()) ::
+ {:ok, Channel.t()} | {:error, any()}
+
+ @callback disconnect(channel :: Channel.t()) :: {:ok, Channel.t()} | {:error, any()}
+
+ @callback send_request(stream :: Stream.t(), contents :: binary(), opts :: keyword()) ::
+ Stream.t()
+
+ @doc """
+ Check `GRPC.Stub.recv/2` for more context about the return types
+ """
+ @callback receive_data(stream :: Stream.t(), opts :: keyword()) ::
+ GRPC.Stub.receive_data_return() | {:error, any()}
+end
diff --git a/lib/grpc/adapter/gun.ex b/lib/grpc/client/adapters/gun.ex
similarity index 51%
rename from lib/grpc/adapter/gun.ex
rename to lib/grpc/client/adapters/gun.ex
index bd7234aa..b602b704 100644
--- a/lib/grpc/adapter/gun.ex
+++ b/lib/grpc/client/adapters/gun.ex
@@ -1,59 +1,62 @@
-defmodule GRPC.Adapter.Gun do
- @moduledoc false
+defmodule GRPC.Client.Adapters.Gun do
+ @moduledoc """
+ A client adapter using Gun
- # A client adapter using Gun.
- # conn_pid and stream_ref is stored in `GRPC.Server.Stream`.
+ `conn_pid` and `stream_ref` are stored in `GRPC.Server.Stream`.
+ """
+
+ @behaviour GRPC.Client.Adapter
@default_transport_opts [nodelay: true]
- @default_http2_opts %{settings_timeout: :infinity}
@max_retries 100
- @spec connect(GRPC.Channel.t(), any) :: {:ok, GRPC.Channel.t()} | {:error, any}
- def connect(channel, nil), do: connect(channel, %{})
- def connect(%{scheme: "https"} = channel, opts), do: connect_securely(channel, opts)
- def connect(channel, opts), do: connect_insecurely(channel, opts)
+ @impl true
+ def connect(channel, opts) when is_list(opts) do
+ # handle opts as a map due to :gun.open
+ opts = Map.new(opts)
+
+ case channel do
+ %{scheme: "https"} -> connect_securely(channel, opts)
+ _ -> connect_insecurely(channel, opts)
+ end
+ end
defp connect_securely(%{cred: %{ssl: ssl}} = channel, opts) do
- transport_opts = Map.get(opts, :transport_opts, @default_transport_opts ++ ssl)
- open_opts = %{transport: :ssl, protocols: [:http2]}
+ transport_opts = Map.get(opts, :transport_opts) || []
- open_opts =
- if gun_v2?() do
- Map.put(open_opts, :tls_opts, transport_opts)
- else
- Map.put(open_opts, :transport_opts, transport_opts)
- end
+ tls_opts = Keyword.merge(@default_transport_opts ++ ssl, transport_opts)
- open_opts = Map.merge(opts, open_opts)
+ open_opts =
+ opts
+ |> Map.delete(:transport_opts)
+ |> Map.merge(%{transport: :ssl, protocols: [:http2], tls_opts: tls_opts})
do_connect(channel, open_opts)
end
defp connect_insecurely(channel, opts) do
- opts = Map.update(opts, :http2_opts, @default_http2_opts, &Map.merge(&1, @default_http2_opts))
+ opts =
+ Map.update(
+ opts,
+ :http2_opts,
+ %{settings_timeout: :infinity},
+ &Map.put(&1, :settings_timeout, :infinity)
+ )
- transport_opts = Map.get(opts, :transport_opts, @default_transport_opts)
- open_opts = %{transport: :tcp, protocols: [:http2]}
+ transport_opts = Map.get(opts, :transport_opts) || []
- open_opts =
- if gun_v2?() do
- Map.put(open_opts, :tcp_opts, transport_opts)
- else
- Map.put(open_opts, :transport_opts, transport_opts)
- end
+ tcp_opts = Keyword.merge(@default_transport_opts, transport_opts)
- open_opts = Map.merge(opts, open_opts)
+ open_opts =
+ opts
+ |> Map.delete(:transport_opts)
+ |> Map.merge(%{transport: :tcp, protocols: [:http2], tcp_opts: tcp_opts})
do_connect(channel, open_opts)
end
defp do_connect(%{host: host, port: port} = channel, open_opts) do
- open_opts =
- if gun_v2?() do
- Map.merge(%{retry: @max_retries, retry_fun: &__MODULE__.retry_fun/2}, open_opts)
- else
- open_opts
- end
+ open_opts = Map.merge(%{retry: @max_retries, retry_fun: &__MODULE__.retry_fun/2}, open_opts)
{:ok, conn_pid} = open(host, port, open_opts)
@@ -67,10 +70,11 @@ defmodule GRPC.Adapter.Gun do
{:error, reason} ->
:gun.shutdown(conn_pid)
- {:error, "Error when opening connection: #{inspect(reason)}"}
+ {:error, reason}
end
end
+ @impl true
def disconnect(%{adapter_payload: %{conn_pid: gun_pid}} = channel)
when is_pid(gun_pid) do
:ok = :gun.shutdown(gun_pid)
@@ -87,7 +91,7 @@ defmodule GRPC.Adapter.Gun do
defp open(host, port, open_opts),
do: :gun.open(String.to_charlist(host), port, open_opts)
- @spec send_request(GRPC.Client.Stream.t(), binary, map) :: GRPC.Client.Stream.t()
+ @impl true
def send_request(stream, message, opts) do
stream_ref = do_send_request(stream, message, opts)
GRPC.Client.Stream.put_payload(stream, :stream_ref, stream_ref)
@@ -130,12 +134,44 @@ defmodule GRPC.Adapter.Gun do
:gun.cancel(conn_pid, stream_ref)
end
- def recv_headers(%{conn_pid: conn_pid}, %{stream_ref: stream_ref}, opts) do
+ @impl true
+ def receive_data(
+ %{server_stream: true} = stream,
+ opts
+ ) do
+ %{channel: %{adapter_payload: adapter_payload}, payload: payload} = stream
+
+ with {:ok, headers, is_fin} <- recv_headers(adapter_payload, payload, opts) do
+ response = response_stream(is_fin, stream, opts)
+
+ if(opts[:return_headers]) do
+ {:ok, response, %{headers: headers}}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ def receive_data(stream, opts) do
+ %{payload: payload, channel: %{adapter_payload: adapter_payload}} = stream
+
+ with {:ok, headers, _is_fin} <- recv_headers(adapter_payload, payload, opts),
+ {:ok, body, trailers} <- recv_body(adapter_payload, payload, opts),
+ {:ok, response} <- parse_response(stream, headers, body, trailers) do
+ if(opts[:return_headers]) do
+ {:ok, response, %{headers: headers, trailers: trailers}}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ defp recv_headers(%{conn_pid: conn_pid}, %{stream_ref: stream_ref}, opts) do
case await(conn_pid, stream_ref, opts[:timeout]) do
{:response, headers, fin} ->
{:ok, headers, fin}
- error = {:error, _} ->
+ {:error, _} = error ->
error
other ->
@@ -147,7 +183,7 @@ defmodule GRPC.Adapter.Gun do
end
end
- def recv_data_or_trailers(%{conn_pid: conn_pid}, %{stream_ref: stream_ref}, opts) do
+ defp recv_data_or_trailers(%{conn_pid: conn_pid}, %{stream_ref: stream_ref}, opts) do
case await(conn_pid, stream_ref, opts[:timeout]) do
data = {:data, _} ->
data
@@ -155,7 +191,7 @@ defmodule GRPC.Adapter.Gun do
trailers = {:trailers, _} ->
trailers
- error = {:error, _} ->
+ {:error, _} = error ->
error
other ->
@@ -179,7 +215,7 @@ defmodule GRPC.Adapter.Gun do
case :gun.await(conn_pid, stream_ref, timeout) do
{:response, :fin, status, headers} ->
if status == 200 do
- headers = Enum.into(headers, %{})
+ headers = GRPC.Transport.HTTP2.decode_headers(headers)
case headers["grpc-status"] do
nil ->
@@ -209,7 +245,7 @@ defmodule GRPC.Adapter.Gun do
{:response, :nofin, status, headers} ->
if status == 200 do
- headers = Enum.into(headers, %{})
+ headers = GRPC.Transport.HTTP2.decode_headers(headers)
if headers["grpc-status"] && headers["grpc-status"] != "0" do
{:error,
@@ -261,17 +297,6 @@ defmodule GRPC.Adapter.Gun do
end
end
- @char_2 List.first('2')
- def gun_v2?() do
- case :application.get_key(:gun, :vsn) do
- {:ok, [@char_2 | _]} ->
- true
-
- _ ->
- false
- end
- end
-
def retry_fun(retries, _opts) do
curr = @max_retries - retries + 1
@@ -292,4 +317,143 @@ defmodule GRPC.Adapter.Gun do
timeout = round(timeout + jitter * timeout)
%{retries: retries - 1, timeout: timeout}
end
+
+ defp recv_body(conn_payload, stream_payload, opts) do
+ recv_body(conn_payload, stream_payload, "", opts)
+ end
+
+ defp recv_body(conn_payload, stream_payload, acc, opts) do
+ case recv_data_or_trailers(conn_payload, stream_payload, opts) do
+ {:data, data} ->
+ recv_body(conn_payload, stream_payload, <>, opts)
+
+ {:trailers, trailers} ->
+ {:ok, acc, GRPC.Transport.HTTP2.decode_headers(trailers)}
+
+ err ->
+ err
+ end
+ end
+
+ defp response_stream(:fin, _stream, _opts), do: []
+
+ defp response_stream(
+ :nofin,
+ %{
+ channel: %{adapter_payload: ap},
+ response_mod: res_mod,
+ codec: codec,
+ payload: payload
+ },
+ opts
+ ) do
+ state = %{
+ adapter_payload: ap,
+ payload: payload,
+ buffer: <<>>,
+ fin: false,
+ need_more: true,
+ opts: opts,
+ response_mod: res_mod,
+ codec: codec
+ }
+
+ Stream.unfold(state, fn s -> read_stream(s) end)
+ end
+
+ defp read_stream(%{buffer: <<>>, fin: true, fin_resp: nil}), do: nil
+
+ defp read_stream(%{buffer: <<>>, fin: true, fin_resp: fin_resp} = s),
+ do: {fin_resp, Map.put(s, :fin_resp, nil)}
+
+ defp read_stream(
+ %{
+ adapter_payload: ap,
+ payload: payload,
+ buffer: buffer,
+ need_more: true,
+ opts: opts
+ } = stream
+ ) do
+ case recv_data_or_trailers(ap, payload, opts) do
+ {:data, data} ->
+ stream
+ |> Map.put(:need_more, false)
+ |> Map.put(:buffer, buffer <> data)
+ |> read_stream()
+
+ {:trailers, trailers} ->
+ update_stream_with_trailers(stream, trailers, opts[:return_headers])
+
+ error = {:error, _} ->
+ {error, %{buffer: <<>>, fin: true, fin_resp: nil}}
+ end
+ end
+
+ defp read_stream(%{buffer: buffer, need_more: false, response_mod: res_mod, codec: codec} = s) do
+ case GRPC.Message.get_message(buffer) do
+ {{_, message}, rest} ->
+ reply = codec.decode(message, res_mod)
+ new_s = Map.put(s, :buffer, rest)
+ {{:ok, reply}, new_s}
+
+ _ ->
+ read_stream(Map.put(s, :need_more, true))
+ end
+ end
+
+ defp parse_response(
+ %{response_mod: res_mod, codec: codec, accepted_compressors: accepted_compressors},
+ headers,
+ body,
+ trailers
+ ) do
+ with :ok <- parse_trailers(trailers),
+ compressor <- get_compressor(headers, accepted_compressors),
+ body <- get_body(codec, body),
+ {:ok, msg} <- GRPC.Message.from_data(%{compressor: compressor}, body) do
+ {:ok, codec.decode(msg, res_mod)}
+ end
+ end
+
+ defp update_stream_with_trailers(stream, trailers, return_headers?) do
+ trailers = GRPC.Transport.HTTP2.decode_headers(trailers)
+
+ case parse_trailers(trailers) do
+ :ok ->
+ fin_resp = if return_headers?, do: {:trailers, trailers}
+
+ stream
+ |> Map.put(:fin, true)
+ |> Map.put(:fin_resp, fin_resp)
+ |> read_stream()
+
+ error ->
+ {error, %{buffer: <<>>, fin: true, fin_resp: nil}}
+ end
+ end
+
+ defp parse_trailers(trailers) do
+ status = String.to_integer(trailers["grpc-status"])
+
+ if status == GRPC.Status.ok() do
+ :ok
+ else
+ {:error, %GRPC.RPCError{status: status, message: trailers["grpc-message"]}}
+ end
+ end
+
+ defp get_compressor(%{"grpc-encoding" => encoding} = _headers, accepted_compressors) do
+ Enum.find(accepted_compressors, nil, fn c -> c.name() == encoding end)
+ end
+
+ defp get_compressor(_headers, _accepted_compressors), do: nil
+
+ defp get_body(codec, body) do
+ if function_exported?(codec, :unpack_from_channel, 1) do
+ codec.unpack_from_channel(body)
+ else
+ body
+ end
+ end
end
diff --git a/lib/grpc/client/stream.ex b/lib/grpc/client/stream.ex
index c75e6f39..e52842f4 100644
--- a/lib/grpc/client/stream.ex
+++ b/lib/grpc/client/stream.ex
@@ -14,24 +14,24 @@ defmodule GRPC.Client.Stream do
* `:res_stream` - indicates if reply is streaming
"""
- @typep stream_payload :: any
+ @typep stream_payload :: any()
@type t :: %__MODULE__{
channel: GRPC.Channel.t(),
service_name: String.t(),
method_name: String.t(),
- grpc_type: atom,
- rpc: tuple,
+ grpc_type: atom(),
+ rpc: tuple(),
payload: stream_payload,
path: String.t(),
- request_mod: atom,
- response_mod: atom,
- codec: atom,
- server_stream: boolean,
- canceled: boolean,
- compressor: module,
- accepted_compressors: [module],
- headers: map,
- __interface__: map
+ request_mod: atom(),
+ response_mod: atom(),
+ codec: atom(),
+ server_stream: boolean(),
+ canceled: boolean(),
+ compressor: module(),
+ accepted_compressors: [module()],
+ headers: map(),
+ __interface__: map()
}
defstruct channel: nil,
@@ -50,7 +50,10 @@ defmodule GRPC.Client.Stream do
compressor: nil,
accepted_compressors: [],
headers: %{},
- __interface__: %{send_request: &__MODULE__.send_request/3, recv: &GRPC.Stub.do_recv/2}
+ __interface__: %{
+ send_request: &__MODULE__.send_request/3,
+ receive_data: &__MODULE__.receive_data/2
+ }
@doc false
def put_payload(%{payload: payload} = stream, key, val) do
@@ -75,7 +78,7 @@ defmodule GRPC.Client.Stream do
opts
) do
encoded = codec.encode(request)
- send_end_stream = Keyword.get(opts, :end_stream, false)
+ send_end_stream = Keyword.get(opts, :end_stream) || false
# If compressor exists, compress is true by default
compressor =
@@ -90,4 +93,8 @@ defmodule GRPC.Client.Stream do
compressor: compressor
)
end
+
+ def receive_data(%{channel: %{adapter: adapter}} = stream, opts) do
+ adapter.receive_data(stream, opts)
+ end
end
diff --git a/lib/grpc/codec.ex b/lib/grpc/codec.ex
index ce4a2a99..b7a15ab8 100644
--- a/lib/grpc/codec.ex
+++ b/lib/grpc/codec.ex
@@ -7,4 +7,23 @@ defmodule GRPC.Codec do
@callback name() :: String.t()
@callback encode(any) :: binary
@callback decode(any, module :: atom) :: any
+
+ @doc """
+ This function is invoked before the gRPC payload is transformed into a protobuf message whenever it is defined.
+
+ This can be used to apply a transform over the gRPC message before decoding it. For instance grpc-web using the `application/grpc-web-text`
+ content type requires the message to be Base64-encoded, so a server receving messages using grpc-web-text will be required to
+ do a Base64 decode on the payload before decoding the gRPC message.
+ """
+ @callback unpack_from_channel(binary) :: binary
+
+ @doc """
+ This function is invoked whenever it is defined after the protobuf message has been transformed into a gRPC payload.
+
+ This can be used to apply a transform over the gRPC message before sending it.
+ For instance grpc-web using the `application/grpc-web-text` content type requires the message to be Base64-encoded, so a server sending messages using grpc-web-text will be required to
+ do a Base64 encode on the payload before sending the gRPC message.
+ """
+ @callback pack_for_channel(iodata()) :: binary
+ @optional_callbacks unpack_from_channel: 1, pack_for_channel: 1
end
diff --git a/lib/grpc/codec/proto.ex b/lib/grpc/codec/proto.ex
index d3a9748a..d3de8b16 100644
--- a/lib/grpc/codec/proto.ex
+++ b/lib/grpc/codec/proto.ex
@@ -5,11 +5,11 @@ defmodule GRPC.Codec.Proto do
"proto"
end
- def encode(struct) do
- Protobuf.Encoder.encode(struct)
+ def encode(%mod{} = struct) do
+ mod.encode(struct)
end
def decode(binary, module) do
- Protobuf.Decoder.decode(binary, module)
+ module.decode(binary)
end
end
diff --git a/lib/grpc/codec/web_text.ex b/lib/grpc/codec/web_text.ex
new file mode 100644
index 00000000..398e9856
--- /dev/null
+++ b/lib/grpc/codec/web_text.ex
@@ -0,0 +1,29 @@
+defmodule GRPC.Codec.WebText do
+ @behaviour GRPC.Codec
+
+ def name() do
+ "text"
+ end
+
+ def encode(struct) do
+ Protobuf.Encoder.encode(struct)
+ end
+
+ def pack_for_channel(data) when is_list(data) do
+ data
+ |> IO.iodata_to_binary()
+ |> Base.encode64()
+ end
+
+ def pack_for_channel(binary) do
+ Base.encode64(binary)
+ end
+
+ def unpack_from_channel(binary) do
+ Base.decode64!(binary)
+ end
+
+ def decode(binary, module) do
+ Protobuf.Decoder.decode(binary, module)
+ end
+end
diff --git a/lib/grpc/credential.ex b/lib/grpc/credential.ex
index e9eebe0d..1df78ea7 100644
--- a/lib/grpc/credential.ex
+++ b/lib/grpc/credential.ex
@@ -1,8 +1,15 @@
defmodule GRPC.Credential do
@moduledoc """
- Stores credentials for authentication. It can be used to establish secure connections
+ Stores credentials for authentication.
+
+ It can be used to establish secure connections
by passed to `GRPC.Stub.connect/2` as an argument.
+ Some client and server adapter implementations may
+ choose to let request options override some of the
+ configuration here, but this is left as a choice
+ for each adapter.
+
## Examples
iex> cred = GRPC.Credential.new(ssl: [cacertfile: ca_path])
@@ -16,6 +23,6 @@ defmodule GRPC.Credential do
Creates credential.
"""
def new(opts) do
- %__MODULE__{ssl: Keyword.get(opts, :ssl, [])}
+ %__MODULE__{ssl: Keyword.get(opts, :ssl) || []}
end
end
diff --git a/lib/grpc/interceptor.ex b/lib/grpc/interceptor.ex
index 4e4c1555..ac64dc45 100644
--- a/lib/grpc/interceptor.ex
+++ b/lib/grpc/interceptor.ex
@@ -4,9 +4,10 @@ defmodule GRPC.ServerInterceptor do
"""
alias GRPC.Server.Stream
- @type options :: any
- @type rpc_return :: {:ok, Stream.t(), struct} | {:ok, Stream.t()} | {:error, GRPC.RPCError.t()}
- @type next :: (GRPC.Server.rpc_req(), Stream.t() -> rpc_return)
+ @type options :: any()
+ @type rpc_return ::
+ {:ok, Stream.t(), struct()} | {:ok, Stream.t()} | {:error, GRPC.RPCError.t()}
+ @type next :: (GRPC.Server.rpc_req(), Stream.t() -> rpc_return())
@callback init(options) :: options
@callback call(GRPC.Server.rpc_req(), stream :: Stream.t(), next, options) :: rpc_return
@@ -18,8 +19,8 @@ defmodule GRPC.ClientInterceptor do
"""
alias GRPC.Client.Stream
- @type options :: any
- @type req :: struct | nil
+ @type options :: any()
+ @type req :: struct() | nil
@type next :: (Stream.t(), req -> GRPC.Stub.rpc_return())
@callback init(options) :: options
diff --git a/lib/grpc/logger.ex b/lib/grpc/logger.ex
deleted file mode 100644
index 0c1e9b03..00000000
--- a/lib/grpc/logger.ex
+++ /dev/null
@@ -1,95 +0,0 @@
-defmodule GRPC.Logger.Server do
- @moduledoc """
- Print log around server rpc calls, like:
-
- 17:18:45.151 [info] Handled by HelloServer.say_hello
- 17:18:45.151 [info] Response :ok in 11µs
-
- ## Usage
-
- defmodule Your.Endpoint do
- use GRPC.Endpoint
-
- intercept GRPC.Logger.Server, level: :info
- end
- """
- require Logger
- @behaviour GRPC.ServerInterceptor
-
- def init(opts) do
- Keyword.get(opts, :level, :info)
- end
-
- def call(req, stream, next, level) do
- if Logger.compare_levels(level, Logger.level()) != :lt do
- Logger.log(level, fn ->
- ["Handled by ", inspect(stream.server), ".", to_string(elem(stream.rpc, 0))]
- end)
-
- start = System.monotonic_time()
- result = next.(req, stream)
- stop = System.monotonic_time()
-
- status = elem(result, 0)
-
- Logger.log(level, fn ->
- diff = System.convert_time_unit(stop - start, :native, :microsecond)
-
- ["Response ", inspect(status), " in ", formatted_diff(diff)]
- end)
-
- result
- else
- next.(req, stream)
- end
- end
-
- def formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string(), "ms"]
- def formatted_diff(diff), do: [Integer.to_string(diff), "µs"]
-end
-
-defmodule GRPC.Logger.Client do
- require Logger
-
- @moduledoc """
- Print log around client rpc calls, like
-
- 17:13:33.021 [info] Call say_hello of helloworld.Greeter
- 17:13:33.079 [info] Got :ok in 58ms
-
- ## Usage
-
- {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Logger.Client])
- {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [{GRPC.Logger.Client, level: :info}])
- """
-
- def init(opts) do
- Keyword.get(opts, :level, :info)
- end
-
- def call(%{grpc_type: grpc_type} = stream, req, next, level) do
- if Logger.compare_levels(level, Logger.level()) != :lt do
- Logger.log(level, fn ->
- ["Call ", to_string(elem(stream.rpc, 0)), " of ", stream.service_name]
- end)
-
- start = System.monotonic_time()
- result = next.(stream, req)
- stop = System.monotonic_time()
-
- if grpc_type == :unary do
- status = elem(result, 0)
-
- Logger.log(level, fn ->
- diff = System.convert_time_unit(stop - start, :native, :microsecond)
-
- ["Got ", inspect(status), " in ", GRPC.Logger.Server.formatted_diff(diff)]
- end)
- end
-
- result
- else
- next.(stream, req)
- end
- end
-end
diff --git a/lib/grpc/logger/client.ex b/lib/grpc/logger/client.ex
new file mode 100644
index 00000000..a841e342
--- /dev/null
+++ b/lib/grpc/logger/client.ex
@@ -0,0 +1,65 @@
+defmodule GRPC.Logger.Client do
+ @moduledoc """
+ Print log around client rpc calls, like
+
+ 17:13:33.021 [info] Call say_hello of helloworld.Greeter
+ 17:13:33.079 [info] Got :ok in 58ms
+
+ ## Options
+
+ * `:level` - the desired log level. Defaults to `:info`
+ * `:accepted_comparators` - a list with the accepted `Logger.compare_levels(configured_level, Logger.level())` results.
+ Defaults to `[:lt, :eq]`
+
+ ## Usage
+
+ {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Logger.Client])
+ # This will log on `:info` and greater priority
+ {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [{GRPC.Logger.Client, level: :info}])
+ # This will log only on `:info`
+ {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [{GRPC.Logger.Client, level: :info, accepted_comparators: [:eq]}])
+ # This will log on `:info` and lower priority
+ {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [{GRPC.Logger.Client, level: :info, accepted_comparators: [:eq, :gt]}])
+ """
+
+ require Logger
+
+ @behaviour GRPC.ClientInterceptor
+
+ @impl true
+ def init(opts) do
+ level = Keyword.get(opts, :level) || :info
+ accepted_comparators = Keyword.get(opts, :accepted_comparators) || [:lt, :eq]
+ [level: level, accepted_comparators: accepted_comparators]
+ end
+
+ @impl true
+ def call(%{grpc_type: grpc_type} = stream, req, next, opts) do
+ level = Keyword.fetch!(opts, :level)
+ accepted_comparators = Keyword.fetch!(opts, :accepted_comparators)
+
+ if Logger.compare_levels(level, Logger.level()) in accepted_comparators do
+ Logger.log(level, fn ->
+ ["Call ", to_string(elem(stream.rpc, 0)), " of ", stream.service_name]
+ end)
+
+ start = System.monotonic_time()
+ result = next.(stream, req)
+ stop = System.monotonic_time()
+
+ if grpc_type == :unary do
+ status = elem(result, 0)
+
+ Logger.log(level, fn ->
+ diff = System.convert_time_unit(stop - start, :native, :microsecond)
+
+ ["Got ", inspect(status), " in ", GRPC.Logger.Server.formatted_diff(diff)]
+ end)
+ end
+
+ result
+ else
+ next.(stream, req)
+ end
+ end
+end
diff --git a/lib/grpc/logger/server.ex b/lib/grpc/logger/server.ex
new file mode 100644
index 00000000..b9070eaa
--- /dev/null
+++ b/lib/grpc/logger/server.ex
@@ -0,0 +1,75 @@
+defmodule GRPC.Logger.Server do
+ @moduledoc """
+ Print log around server rpc calls, like:
+
+ 17:18:45.151 [info] Handled by HelloServer.say_hello
+ 17:18:45.151 [info] Response :ok in 11µs
+
+ ## Options
+
+ * `:level` - the desired log level. Defaults to `:info`
+ * `:accepted_comparators` - a list with the accepted `Logger.compare_levels(configured_level, Logger.level())` results.
+ Defaults to `[:lt, :eq]`
+
+ ## Usage
+
+ defmodule Your.Endpoint do
+ use GRPC.Endpoint
+
+ intercept GRPC.Logger.Server, level: :info
+ end
+
+ defmodule Your.Endpoint do
+ use GRPC.Endpoint
+
+ # logs on :info and higher priority (warn, error...)
+ intercept GRPC.Logger.Server, level: :info, accepted_comparators: [:lt, :eq]
+ end
+
+ defmodule Your.Endpoint do
+ use GRPC.Endpoint
+
+ # logs only on :error
+ intercept GRPC.Logger.Server, level: :error, accepted_comparators: [:eq]
+ end
+ """
+
+ require Logger
+
+ @behaviour GRPC.ServerInterceptor
+
+ @impl true
+ def init(opts) do
+ level = Keyword.get(opts, :level) || :info
+ accepted_comparators = Keyword.get(opts, :accepted_comparators) || [:lt, :eq]
+ [level: level, accepted_comparators: accepted_comparators]
+ end
+
+ @impl true
+ def call(req, stream, next, opts) do
+ level = Keyword.fetch!(opts, :level)
+ accepted_comparators = opts[:accepted_comparators]
+
+ if Logger.compare_levels(level, Logger.level()) in accepted_comparators do
+ Logger.metadata(request_id: Logger.metadata()[:request_id] || stream.request_id)
+
+ Logger.log(level, "Handled by #{inspect(stream.server)}.#{elem(stream.rpc, 0)}")
+
+ start = System.monotonic_time()
+ result = next.(req, stream)
+ stop = System.monotonic_time()
+
+ status = elem(result, 0)
+ diff = System.convert_time_unit(stop - start, :native, :microsecond)
+
+ Logger.log(level, "Response #{inspect(status)} in #{formatted_diff(diff)}")
+
+ result
+ else
+ next.(req, stream)
+ end
+ end
+
+ def formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string(), "ms"]
+ def formatted_diff(diff), do: [Integer.to_string(diff), "µs"]
+end
diff --git a/lib/grpc/message.ex b/lib/grpc/message.ex
index ba908c7a..cb75a343 100644
--- a/lib/grpc/message.ex
+++ b/lib/grpc/message.ex
@@ -1,37 +1,51 @@
defmodule GRPC.Message do
+ @moduledoc """
+ Transform data between encoded protobuf and HTTP/2 body of gRPC.
+
+ gRPC body format is:
+
+ http://www.grpc.io/docs/guides/wire.html
+ Delimited-Message -> Compressed-Flag Message-Length Message
+ Compressed-Flag -> 0 / 1 # encoded as 1 byte unsigned integer
+ Message-Length -> {length of Message} # encoded as 4 byte unsigned integer
+ Message -> *{binary octet}
+ """
use Bitwise, only_operators: true
@max_message_length 1 <<< (32 - 1)
alias GRPC.RPCError
- @moduledoc false
+ @doc """
+ Transforms Protobuf data into a gRPC body binary.
- # Transform data between encoded protobuf and HTTP/2 body of gRPC.
- #
- # gRPC body format is:
- #
- # # http://www.grpc.io/docs/guides/wire.html
- # Delimited-Message -> Compressed-Flag Message-Length Message
- # Compressed-Flag -> 0 / 1 # encoded as 1 byte unsigned integer
- # Message-Length -> {length of Message} # encoded as 4 byte unsigned integer
- # Message -> *{binary octet}
+ ## Options
- @doc """
- Transform protobuf data to gRPC body
+ * `:compressor` - the optional `GRPC.Compressor` to be used.
+ * `:iolist` - if `true`, encodes the data as an `t:iolist()` instead of a `t:binary()`
+ * `:max_message_length` - the maximum number of bytes for the encoded message.
## Examples
- iex> message = <<1, 2, 3, 4, 5, 6, 7, 8>>
+ iex> message = ["m", [["es", "sa"], "ge"]]
iex> GRPC.Message.to_data(message)
- {:ok, <<0, 0, 0, 0, 8, 1, 2, 3, 4, 5, 6, 7, 8>>, 13}
+ {:ok, <<0, 0, 0, 0, 7, "message">>, 12}
+ iex> GRPC.Message.to_data(message, iolist: true)
+ {:ok, [0, <<0, 0, 0, 7>>, ["m", [["es", "sa"], "ge"]]], 12}
+
+ Error cases:
+
iex> message = <<1, 2, 3, 4, 5, 6, 7, 8, 9>>
iex> GRPC.Message.to_data(message, %{max_message_length: 8})
{:error, "Encoded message is too large (9 bytes)"}
+
"""
- @spec to_data(iodata, map | Keyword.t()) ::
- {:ok, binary, non_neg_integer} | {:error, String.t()}
- def to_data(message, opts \\ %{}) do
+ @spec to_data(iodata, keyword()) ::
+ {:ok, iodata, non_neg_integer} | {:error, String.t()}
+ def to_data(message, opts \\ []) do
compressor = opts[:compressor]
+ iolist = opts[:iolist]
+ codec = opts[:codec]
+ max_length = opts[:max_message_length] || @max_message_length
{compress_flag, message} =
if compressor do
@@ -40,19 +54,26 @@ defmodule GRPC.Message do
{0, message}
end
- length = byte_size(message)
- max_length = opts[:max_message_length] || @max_message_length
+ length = IO.iodata_length(message)
if length > max_length do
{:error, "Encoded message is too large (#{length} bytes)"}
else
- result = <>
+ result = [compress_flag, <>, message]
+
+ result =
+ if function_exported?(codec, :pack_for_channel, 1),
+ do: codec.pack_for_channel(result),
+ else: result
+
+ result = if iolist, do: result, else: IO.iodata_to_binary(result)
+
{:ok, result, length + 5}
end
end
@doc """
- Transform gRPC body to protobuf data
+ Transforms gRPC body into Protobuf data.
## Examples
@@ -66,7 +87,7 @@ defmodule GRPC.Message do
end
@doc """
- Transform gRPC body to protobuf data with compressing
+ Transform gRPC body into Protobuf data with compression.
## Examples
@@ -130,7 +151,7 @@ defmodule GRPC.Message do
end
@doc """
- Get message data from data buffer
+ Get message data from data buffer.
## Examples
diff --git a/lib/grpc/message/protobuf.ex b/lib/grpc/message/protobuf.ex
deleted file mode 100644
index 3a36537f..00000000
--- a/lib/grpc/message/protobuf.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule GRPC.Message.Protobuf do
- @moduledoc false
-
- # Module for encoding or decoding message using Protobuf.
-
- @spec encode(atom, struct) :: binary
- def encode(mod, struct) do
- apply(mod, :encode, [struct])
- end
-
- @spec decode(atom, binary) :: struct
- def decode(mod, message) do
- apply(mod, :decode, [message])
- end
-end
diff --git a/lib/grpc/server.ex b/lib/grpc/server.ex
index 557686a0..0cbed721 100644
--- a/lib/grpc/server.ex
+++ b/lib/grpc/server.ex
@@ -33,18 +33,17 @@ defmodule GRPC.Server do
require Logger
- alias GRPC.Server.Stream
alias GRPC.RPCError
@type rpc_req :: struct | Enumerable.t()
@type rpc_return :: struct | any
- @type rpc :: (GRPC.Server.rpc_req(), Stream.t() -> rpc_return)
+ @type rpc :: (GRPC.Server.rpc_req(), GRPC.Server.Stream.t() -> rpc_return)
defmacro __using__(opts) do
quote bind_quoted: [opts: opts], location: :keep do
service_mod = opts[:service]
service_name = service_mod.__meta__(:name)
- codecs = opts[:codecs] || [GRPC.Codec.Proto]
+ codecs = opts[:codecs] || [GRPC.Codec.Proto, GRPC.Codec.WebText]
compressors = opts[:compressors] || []
Enum.each(service_mod.__rpc_calls__, fn {name, _, _} = rpc ->
@@ -77,22 +76,38 @@ defmodule GRPC.Server do
end
end
- @type servers_map :: %{String.t() => [module]}
- @type servers_list :: module | [module]
-
@doc false
- @spec call(atom, Stream.t(), tuple, atom) :: {:ok, Stream.t(), struct} | {:ok, struct}
+ @spec call(atom(), GRPC.Server.Stream.t(), tuple(), atom()) ::
+ {:ok, GRPC.Server.Stream.t(), struct()} | {:ok, struct()}
def call(
_service_mod,
stream,
{_, {req_mod, req_stream}, {res_mod, res_stream}} = rpc,
func_name
) do
- stream = %{stream | request_mod: req_mod, response_mod: res_mod, rpc: rpc}
+ request_id = generate_request_id()
+
+ stream = %{
+ stream
+ | request_mod: req_mod,
+ request_id: request_id,
+ response_mod: res_mod,
+ rpc: rpc
+ }
handle_request(req_stream, res_stream, stream, func_name)
end
+ defp generate_request_id do
+ binary = <<
+ System.system_time(:nanosecond)::64,
+ :erlang.phash2({node(), self()}, 16_777_216)::24,
+ :erlang.unique_integer()::32
+ >>
+
+ Base.url_encode64(binary, padding: false)
+ end
+
defp handle_request(req_s, res_s, %{server: server} = stream, func_name) do
if function_exported?(server, func_name, 2) do
do_handle_request(req_s, res_s, stream, func_name)
@@ -109,7 +124,14 @@ defmodule GRPC.Server do
) do
{:ok, data} = adapter.read_body(payload)
- case GRPC.Message.from_data(stream, data) do
+ body =
+ if function_exported?(codec, :unpack_from_channel, 1) do
+ codec.unpack_from_channel(data)
+ else
+ data
+ end
+
+ case GRPC.Message.from_data(stream, body) do
{:ok, message} ->
request = codec.decode(message, req_mod)
@@ -214,21 +236,26 @@ defmodule GRPC.Server do
#
# * `:cred` - a credential created by functions of `GRPC.Credential`,
# an insecure server will be created without this option
- # * `:adapter` - use a custom server adapter instead of default `GRPC.Adapter.Cowboy`
+ # * `:adapter` - use a custom server adapter instead of default `GRPC.Server.Adapters.Cowboy`
+ # * `:adapter_opts` - configuration for the specified adapter.
+ # * `:status_handler` - adds a status handler that could be listening on HTTP/1, if necessary.
+ # It should follow the format defined by cowboy_router:compile/3
@doc false
- @spec start(servers_list, non_neg_integer, Keyword.t()) :: {atom, any, non_neg_integer}
+ @spec start(module() | [module()], non_neg_integer(), Keyword.t()) ::
+ {atom(), any(), non_neg_integer()} | {:error, any()}
def start(servers, port, opts \\ []) do
- adapter = Keyword.get(opts, :adapter, GRPC.Adapter.Cowboy)
+ adapter = Keyword.get(opts, :adapter) || GRPC.Server.Adapters.Cowboy
servers = GRPC.Server.servers_to_map(servers)
adapter.start(nil, servers, port, opts)
end
@doc false
- @spec start_endpoint(atom, non_neg_integer, Keyword.t()) :: {atom, any, non_neg_integer}
+ @spec start_endpoint(atom(), non_neg_integer(), Keyword.t()) ::
+ {atom(), any(), non_neg_integer()}
def start_endpoint(endpoint, port, opts \\ []) do
servers = endpoint.__meta__(:servers)
servers = GRPC.Server.servers_to_map(servers)
- adapter = Keyword.get(opts, :adapter, GRPC.Adapter.Cowboy)
+ adapter = Keyword.get(opts, :adapter) || GRPC.Server.Adapters.Cowboy
adapter.start(endpoint, servers, port, opts)
end
@@ -240,19 +267,19 @@ defmodule GRPC.Server do
#
# ## Options
#
- # * `:adapter` - use a custom adapter instead of default `GRPC.Adapter.Cowboy`
+ # * `:adapter` - use a custom adapter instead of default `GRPC.Server.Adapters.Cowboy`
@doc false
- @spec stop(servers_list, Keyword.t()) :: any
+ @spec stop(module() | [module()], Keyword.t()) :: any()
def stop(servers, opts \\ []) do
- adapter = Keyword.get(opts, :adapter, GRPC.Adapter.Cowboy)
+ adapter = Keyword.get(opts, :adapter) || GRPC.Server.Adapters.Cowboy
servers = GRPC.Server.servers_to_map(servers)
adapter.stop(nil, servers)
end
@doc false
- @spec stop_endpoint(atom, Keyword.t()) :: any
+ @spec stop_endpoint(atom(), Keyword.t()) :: any()
def stop_endpoint(endpoint, opts \\ []) do
- adapter = Keyword.get(opts, :adapter, GRPC.Adapter.Cowboy)
+ adapter = Keyword.get(opts, :adapter) || GRPC.Server.Adapters.Cowboy
servers = endpoint.__meta__(:servers)
servers = GRPC.Server.servers_to_map(servers)
adapter.stop(endpoint, servers)
@@ -273,7 +300,7 @@ defmodule GRPC.Server do
iex> GRPC.Server.send_reply(stream, reply)
"""
- @spec send_reply(Stream.t(), struct) :: Stream.t()
+ @spec send_reply(GRPC.Server.Stream.t(), struct()) :: GRPC.Server.Stream.t()
def send_reply(%{__interface__: interface} = stream, reply, opts \\ []) do
interface[:send_reply].(stream, reply, opts)
end
@@ -283,7 +310,7 @@ defmodule GRPC.Server do
You can send headers only once, before that you can set headers using `set_headers/2`.
"""
- @spec send_headers(Stream.t(), map) :: Stream.t()
+ @spec send_headers(GRPC.Server.Stream.t(), map()) :: GRPC.Server.Stream.t()
def send_headers(%{adapter: adapter} = stream, headers) do
adapter.send_headers(stream.payload, headers)
stream
@@ -294,7 +321,7 @@ defmodule GRPC.Server do
You can set headers more than once.
"""
- @spec set_headers(Stream.t(), map) :: Stream.t()
+ @spec set_headers(GRPC.Server.Stream.t(), map()) :: GRPC.Server.Stream.t()
def set_headers(%{adapter: adapter} = stream, headers) do
adapter.set_headers(stream.payload, headers)
stream
@@ -303,7 +330,7 @@ defmodule GRPC.Server do
@doc """
Set custom trailers, which will be sent in the end.
"""
- @spec set_trailers(Stream.t(), map) :: Stream.t()
+ @spec set_trailers(GRPC.Server.Stream.t(), map()) :: GRPC.Server.Stream.t()
def set_trailers(%{adapter: adapter} = stream, trailers) do
adapter.set_resp_trailers(stream.payload, trailers)
stream
@@ -313,7 +340,7 @@ defmodule GRPC.Server do
Set compressor to compress responses. An accepted compressor will be set if clients use one,
even if `set_compressor` is not called. But this can be called to override the chosen.
"""
- @spec set_compressor(Stream.t(), module) :: Stream.t()
+ @spec set_compressor(GRPC.Server.Stream.t(), module()) :: GRPC.Server.Stream.t()
def set_compressor(%{adapter: adapter} = stream, compressor) do
adapter.set_compressor(stream.payload, compressor)
stream
@@ -333,7 +360,7 @@ defmodule GRPC.Server do
end
@doc false
- @spec servers_to_map(servers_list) :: servers_map
+ @spec servers_to_map(module() | [module()]) :: %{String.t() => [module()]}
def servers_to_map(servers) do
Enum.reduce(List.wrap(servers), %{}, fn s, acc ->
Map.put(acc, s.__meta__(:service).__meta__(:name), s)
diff --git a/lib/grpc/server/adapter.ex b/lib/grpc/server/adapter.ex
new file mode 100644
index 00000000..9884ac9f
--- /dev/null
+++ b/lib/grpc/server/adapter.ex
@@ -0,0 +1,27 @@
+defmodule GRPC.Server.Adapter do
+ @moduledoc """
+ HTTP server adapter for GRPC.
+ """
+
+ @type state :: %{
+ pid: pid,
+ handling_timer: reference | nil,
+ resp_trailers: map,
+ compressor: atom | nil,
+ pending_reader: nil
+ }
+
+ @callback start(
+ atom(),
+ %{String.t() => [module()]},
+ port :: non_neg_integer(),
+ opts :: keyword()
+ ) ::
+ {atom(), any(), non_neg_integer()}
+
+ @callback stop(atom(), %{String.t() => [module()]}) :: :ok | {:error, :not_found}
+
+ @callback send_reply(state, content :: binary(), opts :: keyword()) :: any()
+
+ @callback send_headers(state, headers :: map()) :: any()
+end
diff --git a/lib/grpc/adapter/cowboy.ex b/lib/grpc/server/adapters/cowboy.ex
similarity index 68%
rename from lib/grpc/adapter/cowboy.ex
rename to lib/grpc/server/adapters/cowboy.ex
index 31bc0994..51caa7f0 100644
--- a/lib/grpc/adapter/cowboy.ex
+++ b/lib/grpc/server/adapters/cowboy.ex
@@ -1,21 +1,23 @@
-defmodule GRPC.Adapter.Cowboy do
- @moduledoc false
+defmodule GRPC.Server.Adapters.Cowboy do
+ @moduledoc """
+ A server (`b:GRPC.Server.Adapter`) adapter using `:cowboy`.
- # A server(`GRPC.Server`) adapter using Cowboy.
- # Cowboy req will be stored in `:payload` of `GRPC.Server.Stream`.
+ Cowboy requests will be stored in the `:payload` field of the `GRPC.Server.Stream`.
+ """
- # Waiting for this is released on hex https://github.com/ninenines/ranch/pull/227
- @dialyzer {:nowarn_function, running_info: 4}
+ @behaviour GRPC.Server.Adapter
+
+ # ignore a specific warning generated by the case :ranch.child_spec call
+ @dialyzer {:no_match, child_spec: 4}
require Logger
- alias GRPC.Adapter.Cowboy.Handler, as: Handler
+ alias GRPC.Server.Adapters.Cowboy.Handler
@default_num_acceptors 20
@default_max_connections 16384
# Only used in starting a server manually using `GRPC.Server.start(servers)`
- @spec start(atom, GRPC.Server.servers_map(), non_neg_integer, keyword) ::
- {:ok, pid, non_neg_integer}
+ @impl true
def start(endpoint, servers, port, opts) do
start_args = cowboy_start_args(endpoint, servers, port, opts)
start_func = if opts[:cred], do: :start_tls, else: :start_clear
@@ -30,8 +32,8 @@ defmodule GRPC.Adapter.Cowboy do
end
end
- @spec child_spec(atom, GRPC.Server.servers_map(), non_neg_integer, Keyword.t()) ::
- Supervisor.Spec.spec()
+ @spec child_spec(atom(), %{String.t() => [module()]}, non_neg_integer(), Keyword.t()) ::
+ Supervisor.child_spec()
def child_spec(endpoint, servers, port, opts) do
[ref, trans_opts, proto_opts] = cowboy_start_args(endpoint, servers, port, opts)
trans_opts = Map.put(trans_opts, :connection_type, :supervisor)
@@ -43,17 +45,31 @@ defmodule GRPC.Adapter.Cowboy do
{:ranch_tcp, :cowboy_clear}
end
- {ref, mfa, type, timeout, kind, modules} =
- :ranch.child_spec(ref, transport, trans_opts, protocol, proto_opts)
+ # Ideally, we would just update Ranch, but compatibility issues with cowboy hold us back on this
+ # So we just support both child spec versions here instead
+ case :ranch.child_spec(ref, transport, trans_opts, protocol, proto_opts) do
+ {ref, mfa, type, timeout, kind, modules} ->
+ scheme = if opts[:cred], do: :https, else: :http
+ # Wrap real mfa to print starting log
+ wrapped_mfa = {__MODULE__, :start_link, [scheme, endpoint, servers, mfa]}
- scheme = if opts[:cred], do: :https, else: :http
- # Wrap real mfa to print starting log
- mfa = {__MODULE__, :start_link, [scheme, endpoint, servers, mfa]}
- {ref, mfa, type, timeout, kind, modules}
+ %{
+ id: ref,
+ start: wrapped_mfa,
+ restart: type,
+ shutdown: timeout,
+ type: kind,
+ modules: modules
+ }
+
+ child_spec when is_map(child_spec) ->
+ child_spec
+ end
end
# spec: :supervisor.mfargs doesn't work
- @spec start_link(atom, atom, GRPC.Server.servers_map(), any) :: {:ok, pid} | {:error, any}
+ @spec start_link(atom(), atom(), %{String.t() => [module()]}, any()) ::
+ {:ok, pid()} | {:error, any()}
def start_link(scheme, endpoint, servers, {m, f, [ref | _] = a}) do
case apply(m, f, a) do
{:ok, pid} ->
@@ -73,17 +89,17 @@ defmodule GRPC.Adapter.Cowboy do
end
end
- @spec stop(atom, GRPC.Server.servers_map()) :: :ok | {:error, :not_found}
+ @impl true
def stop(endpoint, servers) do
:cowboy.stop_listener(servers_name(endpoint, servers))
end
- @spec read_body(GRPC.Adapter.Cowboy.Handler.state()) :: {:ok, binary}
+ @spec read_body(GRPC.Server.Adapter.state()) :: {:ok, binary()}
def read_body(%{pid: pid}) do
Handler.read_full_body(pid)
end
- @spec reading_stream(GRPC.Adapter.Cowboy.Handler.state()) :: Enumerable.t()
+ @spec reading_stream(GRPC.Server.Adapter.state()) :: Enumerable.t()
def reading_stream(%{pid: pid}) do
Stream.unfold(%{pid: pid, need_more: true, buffer: <<>>}, fn acc -> read_stream(acc) end)
end
@@ -115,11 +131,12 @@ defmodule GRPC.Adapter.Cowboy do
end
end
- @spec send_reply(GRPC.Adapter.Cowboy.Handler.state(), binary, keyword) :: any
+ @impl true
def send_reply(%{pid: pid}, data, opts) do
Handler.stream_body(pid, data, opts, :nofin)
end
+ @impl true
def send_headers(%{pid: pid}, headers) do
Handler.stream_reply(pid, 200, headers)
end
@@ -153,14 +170,25 @@ defmodule GRPC.Adapter.Cowboy do
end
defp cowboy_start_args(endpoint, servers, port, opts) do
- dispatch =
- :cowboy_router.compile([
- {:_, [{:_, GRPC.Adapter.Cowboy.Handler, {endpoint, servers, Enum.into(opts, %{})}}]}
- ])
+ # Custom handler to be able to listen in the same port, more info:
+ # https://github.com/containous/traefik/issues/6211
+ {adapter_opts, opts} = Keyword.pop(opts, :adapter_opts, [])
+ status_handler = Keyword.get(adapter_opts, :status_handler)
+
+ handlers =
+ if status_handler do
+ [
+ status_handler,
+ {:_, GRPC.Server.Adapters.Cowboy.Handler, {endpoint, servers, Enum.into(opts, %{})}}
+ ]
+ else
+ [{:_, GRPC.Server.Adapters.Cowboy.Handler, {endpoint, servers, Enum.into(opts, %{})}}]
+ end
- idle_timeout = Keyword.get(opts, :idle_timeout, :infinity)
- num_acceptors = Keyword.get(opts, :num_acceptors, @default_num_acceptors)
- max_connections = Keyword.get(opts, :max_connections, @default_max_connections)
+ dispatch = :cowboy_router.compile([{:_, handlers}])
+ idle_timeout = Keyword.get(opts, :idle_timeout) || :infinity
+ num_acceptors = Keyword.get(opts, :num_acceptors) || @default_num_acceptors
+ max_connections = Keyword.get(opts, :max_connections) || @default_max_connections
# https://ninenines.eu/docs/en/cowboy/2.7/manual/cowboy_http2/
opts =
@@ -215,8 +243,8 @@ defmodule GRPC.Adapter.Cowboy do
addr_str =
case addr do
- :local ->
- port
+ :undefined ->
+ raise "undefined address for ranch server"
addr ->
"#{:inet.ntoa(addr)}:#{port}"
diff --git a/lib/grpc/adapter/cowboy/handler.ex b/lib/grpc/server/adapters/cowboy/handler.ex
similarity index 93%
rename from lib/grpc/adapter/cowboy/handler.ex
rename to lib/grpc/server/adapters/cowboy/handler.ex
index 495a4f3d..f0f5641b 100644
--- a/lib/grpc/adapter/cowboy/handler.ex
+++ b/lib/grpc/server/adapters/cowboy/handler.ex
@@ -1,4 +1,4 @@
-defmodule GRPC.Adapter.Cowboy.Handler do
+defmodule GRPC.Server.Adapters.Cowboy.Handler do
@moduledoc false
# A cowboy handler accepting all requests and calls corresponding functions
@@ -8,17 +8,13 @@ defmodule GRPC.Adapter.Cowboy.Handler do
alias GRPC.RPCError
require Logger
- @adapter GRPC.Adapter.Cowboy
+ @adapter GRPC.Server.Adapters.Cowboy
@default_trailers HTTP2.server_trailers()
- @type state :: %{
- pid: pid,
- handling_timer: reference | nil,
- resp_trailers: map,
- compressor: atom | nil,
- pending_reader: nil
- }
-
- @spec init(map, {atom, GRPC.Server.servers_map(), map}) :: {:cowboy_loop, map, map}
+
+ @spec init(
+ map(),
+ state :: {endpoint :: atom(), servers :: %{String.t() => [module()]}, opts :: keyword()}
+ ) :: {:cowboy_loop, map(), map()}
def init(req, {endpoint, servers, opts} = state) do
path = :cowboy_req.path(req)
@@ -249,14 +245,12 @@ defmodule GRPC.Adapter.Cowboy.Handler do
if compressor && !Enum.member?(accepted_encodings, compressor.name()) do
msg =
- "A unaccepted encoding #{compressor.name()} is set, valid are: #{
- :cowboy_req.header("grpc-accept-encoding", req)
- }"
+ "A unaccepted encoding #{compressor.name()} is set, valid are: #{:cowboy_req.header("grpc-accept-encoding", req)}"
req = send_error(req, state, msg)
{:stop, req, state}
else
- case GRPC.Message.to_data(data, compressor: compressor) do
+ case GRPC.Message.to_data(data, compressor: compressor, codec: opts[:codec]) do
{:ok, data, _size} ->
req = check_sent_resp(req)
:cowboy_req.stream_body(data, is_fin, req)
@@ -453,10 +447,16 @@ defmodule GRPC.Adapter.Cowboy.Handler do
defp extract_subtype("application/grpc"), do: {:ok, "proto"}
defp extract_subtype("application/grpc+"), do: {:ok, "proto"}
defp extract_subtype("application/grpc;"), do: {:ok, "proto"}
-
defp extract_subtype(<<"application/grpc+", rest::binary>>), do: {:ok, rest}
defp extract_subtype(<<"application/grpc;", rest::binary>>), do: {:ok, rest}
+ defp extract_subtype("application/grpc-web"), do: {:ok, "proto"}
+ defp extract_subtype("application/grpc-web+"), do: {:ok, "proto"}
+ defp extract_subtype("application/grpc-web;"), do: {:ok, "proto"}
+ defp extract_subtype("application/grpc-web-text"), do: {:ok, "text"}
+ defp extract_subtype("application/grpc-web+" <> rest), do: {:ok, rest}
+ defp extract_subtype("application/grpc-web-text+" <> rest), do: {:ok, rest}
+
defp extract_subtype(type) do
Logger.warn("Got unknown content-type #{type}, please create an issue.")
{:ok, "proto"}
@@ -481,8 +481,8 @@ defmodule GRPC.Adapter.Cowboy.Handler do
end
defp async_read_body(req, opts) do
- length = Map.get(opts, :length, 8_000_000)
- period = Map.get(opts, :period, 15000)
+ length = opts[:length] || 8_000_000
+ period = opts[:period] || 15000
ref = make_ref()
:cowboy_req.cast({:read_body, self(), ref, length, period}, req)
diff --git a/lib/grpc/server/stream.ex b/lib/grpc/server/stream.ex
index 9463ecf9..bd349722 100644
--- a/lib/grpc/server/stream.ex
+++ b/lib/grpc/server/stream.ex
@@ -9,7 +9,7 @@ defmodule GRPC.Server.Stream do
## Fields
* `:server` - user defined gRPC server module
- * `:adapter` - a server adapter module, like `GRPC.Adapter.Cowboy`
+ * `:adapter` - a server adapter module, like `GRPC.Server.Adapters.Cowboy`
* `request_mod` - the request module, or nil for untyped protocols
* `response_mod` - the response module, or nil for untyped protocols
* `:codec` - the codec
@@ -18,22 +18,23 @@ defmodule GRPC.Server.Stream do
"""
@type t :: %__MODULE__{
- server: atom,
+ server: atom(),
service_name: String.t(),
method_name: String.t(),
- grpc_type: atom,
- endpoint: atom,
- rpc: tuple,
- request_mod: atom,
- response_mod: atom,
- codec: atom,
- payload: any,
- adapter: atom,
- local: any,
+ grpc_type: atom(),
+ endpoint: atom(),
+ rpc: tuple(),
+ request_mod: atom(),
+ request_id: String.t() | nil,
+ response_mod: atom(),
+ codec: atom(),
+ payload: any(),
+ adapter: atom(),
+ local: any(),
# compressor mainly is used in client decompressing, responses compressing should be set by
# `GRPC.Server.set_compressor`
- compressor: module | nil,
- __interface__: map
+ compressor: module() | nil,
+ __interface__: map()
}
defstruct server: nil,
@@ -43,6 +44,7 @@ defmodule GRPC.Server.Stream do
endpoint: nil,
rpc: nil,
request_mod: nil,
+ request_id: nil,
response_mod: nil,
codec: GRPC.Codec.Proto,
payload: nil,
@@ -54,7 +56,7 @@ defmodule GRPC.Server.Stream do
def send_reply(%{adapter: adapter, codec: codec} = stream, reply, opts) do
# {:ok, data, _size} = reply |> codec.encode() |> GRPC.Message.to_data()
data = codec.encode(reply)
- adapter.send_reply(stream.payload, data, opts)
+ adapter.send_reply(stream.payload, data, Keyword.put(opts, :codec, codec))
stream
end
end
diff --git a/lib/grpc/server/supervisor.ex b/lib/grpc/server/supervisor.ex
index c380a3e0..5d97b1de 100644
--- a/lib/grpc/server/supervisor.ex
+++ b/lib/grpc/server/supervisor.ex
@@ -1,55 +1,77 @@
defmodule GRPC.Server.Supervisor do
- use Supervisor
-
@moduledoc """
A simple supervisor to start your servers.
- You can add it to your OTP tree as below. But to make the servers start, you have to config `grpc`
+ You can add it to your OTP tree as below.
+ To start the server, you can pass `start_server: true` and an option
defmodule Your.App do
use Application
def start(_type, _args) do
- import Supervisor.Spec
-
children = [
- supervisor(GRPC.Server.Supervisor, [{Your.Endpoint, 50051(, opts)}])
- ]
+ {GRPC.Server.Supervisor, endpoint: Your.Endpoint, port: 50051, start_server: true, ...}]
- opts = [strategy: :one_for_one, name: __MODULE__]
- Supervisor.start_link(children, opts)
+ Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
end
end
- # config.exs
- config :grpc, start_server: true
- or
- run `mix grpc.server` on local
-
- View `child_spec/3` for opts.
"""
- @default_adapter GRPC.Adapter.Cowboy
+ use Supervisor
+
+ @default_adapter GRPC.Server.Adapters.Cowboy
require Logger
def start_link(endpoint) do
Supervisor.start_link(__MODULE__, endpoint)
end
- @spec init({module | [module], integer}) ::
- {:ok, {:supervisor.sup_flags(), [:supervisor.child_spec()]}} | :ignore
- def init({endpoint, port}) do
- init({endpoint, port, []})
+ @doc """
+ ## Options
+
+ * `:endpoint` - defines the endpoint module that will be started.
+ * `:port` - the HTTP port for the endpoint.
+ * `:servers` - the list of servers that will be be started.
+
+ Either `:endpoint` or `:servers` must be present, but not both.
+ """
+ @spec init(tuple()) :: no_return
+ @spec init(keyword()) :: {:ok, {:supervisor.sup_flags(), [:supervisor.child_spec()]}} | :ignore
+ def init(opts \\ [])
+
+ def init(opts) when is_tuple(opts) do
+ raise ArgumentError,
+ "passing a tuple as configuration for GRPC.Server.Supervisor is no longer supported. See the documentation for more information on how to configure."
end
- @spec init({module | [module], integer, Keyword.t()}) ::
- {:ok, {:supervisor.sup_flags(), [:supervisor.child_spec()]}} | :ignore
- def init({endpoint, port, opts}) do
- check_deps_version()
+ def init(opts) when is_list(opts) do
+ unless is_nil(Application.get_env(:grpc, :start_server)) do
+ raise "the :start_server config key has been deprecated.\
+ The currently supported way is to configure it\
+ through the :start_server option for the GRPC.Server.Supervisor"
+ end
+
+ endpoint_or_servers =
+ case {opts[:endpoint], opts[:servers]} do
+ {endpoint, servers}
+ when (not is_nil(endpoint) and not is_nil(servers)) or
+ (is_nil(endpoint) and is_nil(servers)) ->
+ raise ArgumentError, "either :endpoint or :servers must be passed, but not both."
+
+ {endpoint, nil} ->
+ endpoint
+
+ {nil, servers} when not is_list(servers) ->
+ raise ArgumentError, "either :servers must be a list of modules"
+
+ {nil, servers} when is_list(servers) ->
+ servers
+ end
children =
- if Application.get_env(:grpc, :start_server, false) do
- [child_spec(endpoint, port, opts)]
+ if opts[:start_server] do
+ [child_spec(endpoint_or_servers, opts[:port], opts)]
else
[]
end
@@ -64,9 +86,13 @@ defmodule GRPC.Server.Supervisor do
* `:cred` - a credential created by functions of `GRPC.Credential`,
an insecure server will be created without this option
+ * `:start_server` - determines if the server will be started.
+ If present, has more precedence then the `config :gprc, :start_server`
+ config value (i.e. `start_server: false` will not start the server in any case).
"""
- @spec child_spec(atom | [atom], integer, Keyword.t()) :: Supervisor.Spec.spec()
- def child_spec(endpoint, port, opts \\ [])
+ @spec child_spec(endpoint_or_servers :: atom() | [atom()], port :: integer, opts :: keyword()) ::
+ Supervisor.Spec.spec()
+ def child_spec(endpoint_or_servers, port, opts \\ [])
def child_spec(endpoint, port, opts) when is_atom(endpoint) do
{endpoint, servers} =
@@ -81,32 +107,14 @@ defmodule GRPC.Server.Supervisor do
{nil, endpoint}
end
- adapter = Keyword.get(opts, :adapter, @default_adapter)
+ adapter = Keyword.get(opts, :adapter) || @default_adapter
servers = GRPC.Server.servers_to_map(servers)
adapter.child_spec(endpoint, servers, port, opts)
end
def child_spec(servers, port, opts) when is_list(servers) do
- adapter = Keyword.get(opts, :adapter, @default_adapter)
+ adapter = Keyword.get(opts, :adapter) || @default_adapter
servers = GRPC.Server.servers_to_map(servers)
adapter.child_spec(nil, servers, port, opts)
end
-
- defp check_deps_version() do
- # cowlib
- case :application.get_key(:cowlib, :vsn) do
- {:ok, vsn} ->
- ver = to_string(vsn)
-
- unless Version.match?(ver, ">= 2.9.0") do
- Logger.warn("cowlib should be >= 2.9.0, it's #{ver} now. See grpc's README for details")
- end
-
- _ ->
- :ok
- end
- rescue
- _ ->
- :ok
- end
end
diff --git a/lib/grpc/status.ex b/lib/grpc/status.ex
index 9c2b7e40..877d5030 100644
--- a/lib/grpc/status.ex
+++ b/lib/grpc/status.ex
@@ -10,11 +10,13 @@ defmodule GRPC.Status do
@doc """
Not an error; returned on success.
"""
+ @spec ok :: t
def ok, do: 0
@doc """
The operation was cancelled (typically by the caller).
"""
+ @spec cancelled() :: t()
def cancelled, do: 1
@doc """
@@ -22,10 +24,11 @@ defmodule GRPC.Status do
An example of where this error may be returned is
if a Status value received from another address space belongs to
- an error-space that is not known in this address space. Also
+ an error-space that is not known in this address space. Also
errors raised by APIs that do not return enough error information
may be converted to this error.
"""
+ @spec unknown :: t
def unknown, do: 2
@doc """
@@ -35,6 +38,7 @@ defmodule GRPC.Status do
INVALID_ARGUMENT indicates arguments that are problematic regardless of
the state of the system (e.g., a malformed file name).
"""
+ @spec invalid_argument :: t
def invalid_argument, do: 3
@doc """
@@ -45,16 +49,19 @@ defmodule GRPC.Status do
successful response from a server could have been delayed long
enough for the deadline to expire.
"""
+ @spec deadline_exceeded :: t
def deadline_exceeded, do: 4
@doc """
Some requested entity (e.g., file or directory) was not found.
"""
+ @spec not_found :: t
def not_found, do: 5
@doc """
Some entity that we attempted to create (e.g., file or directory) already exists.
"""
+ @spec already_exists :: t
def already_exists, do: 6
@doc """
@@ -67,12 +74,14 @@ defmodule GRPC.Status do
used if the caller can not be identified (use UNAUTHENTICATED
instead for those errors).
"""
+ @spec permission_denied :: t
def permission_denied, do: 7
@doc """
Some resource has been exhausted, perhaps a per-user quota, or
perhaps the entire file system is out of space.
"""
+ @spec resource_exhausted :: t
def resource_exhausted, do: 8
@doc """
@@ -82,6 +91,7 @@ defmodule GRPC.Status do
For example, directory to be deleted may be non-empty,
an rmdir operation is applied to a non-directory, etc.
"""
+ @spec failed_precondition :: t
def failed_precondition, do: 9
@doc """
@@ -90,6 +100,7 @@ defmodule GRPC.Status do
Typically due to a concurrency issue like sequencer check failures,
transaction aborts, etc.
"""
+ @spec aborted() :: t()
def aborted, do: 10
@doc """
@@ -97,11 +108,13 @@ defmodule GRPC.Status do
E.g., seeking or reading past end of file.
"""
+ @spec out_of_range :: t
def out_of_range, do: 11
@doc """
Operation is not implemented or not supported/enabled in this service.
"""
+ @spec unimplemented :: t
def unimplemented, do: 12
@doc """
@@ -110,6 +123,7 @@ defmodule GRPC.Status do
Means some invariants expected by underlying system has been broken.
If you see one of these errors, something is very broken.
"""
+ @spec internal :: t
def internal, do: 13
@doc """
@@ -118,18 +132,22 @@ defmodule GRPC.Status do
This is a most likely a transient condition and may be corrected by retrying with
a backoff.
"""
+ @spec unavailable :: t
def unavailable, do: 14
@doc """
Unrecoverable data loss or corruption.
"""
+ @spec data_loss :: t
def data_loss, do: 15
@doc """
The request does not have valid authentication credentials for the operation.
"""
+ @spec unauthenticated :: t
def unauthenticated, do: 16
+ @spec code_name(t()) :: binary()
def code_name(0), do: "OK"
def code_name(1), do: "Canceled"
def code_name(2), do: "Unknown"
diff --git a/lib/grpc/stub.ex b/lib/grpc/stub.ex
index 22a65243..db050c0a 100644
--- a/lib/grpc/stub.ex
+++ b/lib/grpc/stub.ex
@@ -3,7 +3,7 @@ defmodule GRPC.Stub do
A module acting as the interface for gRPC client.
You can do everything in the client side via `GRPC.Stub`, including connecting,
- sending/receiving steaming or non-steaming requests, canceling calls and so on.
+ sending/receiving streaming or non-streaming requests, canceling calls and so on.
A service is needed to define a stub:
@@ -44,13 +44,16 @@ defmodule GRPC.Stub do
# 10 seconds
@default_timeout 10000
- @type rpc_return ::
- {:ok, struct}
- | {:ok, struct, map}
- | GRPC.Client.Stream.t()
+ @type receive_data_return ::
+ {:ok, struct()}
+ | {:ok, struct(), map()}
| {:ok, Enumerable.t()}
- | {:ok, Enumerable.t(), map}
+ | {:ok, Enumerable.t(), map()}
+
+ @type rpc_return ::
+ GRPC.Client.Stream.t()
| {:error, GRPC.RPCError.t()}
+ | receive_data_return
require Logger
@@ -121,11 +124,11 @@ defmodule GRPC.Stub do
* `:interceptors` - client interceptors
* `:codec` - client will use this to encode and decode binary message
* `:compressor` - the client will use this to compress requests and decompress responses. If this is set, accepted_compressors
- will be appended also, so this can be used safely without `:accesspted_compressors`.
+ will be appended also, so this can be used safely without `:accepted_compressors`.
* `:accepted_compressors` - tell servers accepted compressors, this can be used without `:compressor`
* `:headers` - headers to attach to each request
"""
- @spec connect(String.t(), Keyword.t()) :: {:ok, GRPC.Channel.t()} | {:error, any}
+ @spec connect(String.t(), keyword()) :: {:ok, Channel.t()} | {:error, any()}
def connect(addr, opts \\ []) when is_binary(addr) and is_list(opts) do
{host, port} =
case String.split(addr, ":") do
@@ -136,24 +139,25 @@ defmodule GRPC.Stub do
connect(host, port, opts)
end
- @spec connect(String.t(), binary | non_neg_integer, keyword) ::
- {:ok, Channel.t()} | {:error, any}
+ @spec connect(String.t(), binary() | non_neg_integer(), keyword()) ::
+ {:ok, Channel.t()} | {:error, any()}
def connect(host, port, opts) when is_binary(port) do
connect(host, String.to_integer(port), opts)
end
def connect(host, port, opts) when is_integer(port) do
- adapter =
- Keyword.get(
- opts,
- :adapter,
- Application.get_env(:grpc, :http2_client_adapter, GRPC.Adapter.Gun)
- )
+ if Application.get_env(:grpc, :http2_client_adapter) do
+ raise "the :http2_client_adapter config key has been deprecated.\
+ The currently supported way is to configure it\
+ through the :adapter option for GRPC.Stub.connect/3"
+ end
+
+ adapter = Keyword.get(opts, :adapter) || GRPC.Client.Adapters.Gun
cred = Keyword.get(opts, :cred)
scheme = if cred, do: @secure_scheme, else: @insecure_scheme
- interceptors = Keyword.get(opts, :interceptors, []) |> init_interceptors
- codec = Keyword.get(opts, :codec, GRPC.Codec.Proto)
+ interceptors = (Keyword.get(opts, :interceptors) || []) |> init_interceptors
+ codec = Keyword.get(opts, :codec) || GRPC.Codec.Proto
compressor = Keyword.get(opts, :compressor)
accepted_compressors = Keyword.get(opts, :accepted_compressors) || []
headers = Keyword.get(opts, :headers) || []
@@ -165,6 +169,12 @@ defmodule GRPC.Stub do
accepted_compressors
end
+ adapter_opts = opts[:adapter_opts] || []
+
+ unless is_list(adapter_opts) do
+ raise ArgumentError, ":adapter_opts must be a keyword list if present"
+ end
+
%Channel{
host: host,
port: port,
@@ -177,7 +187,7 @@ defmodule GRPC.Stub do
accepted_compressors: accepted_compressors,
headers: headers
}
- |> adapter.connect(opts[:adapter_opts])
+ |> adapter.connect(adapter_opts)
end
def retry_timeout(curr) when curr < 11 do
@@ -210,7 +220,7 @@ defmodule GRPC.Stub do
@doc """
Disconnects the adapter and frees any resources the adapter is consuming
"""
- @spec disconnect(Channel.t()) :: {:ok, Channel.t()} | {:error, any}
+ @spec disconnect(Channel.t()) :: {:ok, Channel.t()} | {:error, any()}
def disconnect(%Channel{adapter: adapter} = channel) do
adapter.disconnect(channel)
end
@@ -234,7 +244,7 @@ defmodule GRPC.Stub do
with the last elem being a map of headers `%{headers: headers, trailers: trailers}`(unary) or
`%{headers: headers}`(server streaming)
"""
- @spec call(atom, tuple, GRPC.Client.Stream.t(), struct | nil, keyword) :: rpc_return
+ @spec call(atom(), tuple(), GRPC.Client.Stream.t(), struct() | nil, keyword()) :: rpc_return
def call(_service_mod, rpc, %{channel: channel} = stream, request, opts) do
{_, {req_mod, req_stream}, {res_mod, response_stream}} = rpc
@@ -247,8 +257,8 @@ defmodule GRPC.Stub do
parse_req_opts([{:timeout, @default_timeout} | opts])
end
- compressor = Map.get(opts, :compressor, channel.compressor)
- accepted_compressors = Map.get(opts, :accepted_compressors, [])
+ compressor = Keyword.get(opts, :compressor, channel.compressor)
+ accepted_compressors = Keyword.get(opts, :accepted_compressors, [])
accepted_compressors =
if compressor do
@@ -259,8 +269,8 @@ defmodule GRPC.Stub do
stream = %{
stream
- | codec: Map.get(opts, :codec, channel.codec),
- compressor: Map.get(opts, :compressor, channel.compressor),
+ | codec: Keyword.get(opts, :codec, channel.codec),
+ compressor: Keyword.get(opts, :compressor, channel.compressor),
accepted_compressors: accepted_compressors
}
@@ -275,7 +285,7 @@ defmodule GRPC.Stub do
) do
last = fn %{codec: codec, compressor: compressor} = s, _ ->
message = codec.encode(request)
- opts = Map.put(opts, :compressor, compressor)
+ opts = Keyword.put(opts, :compressor, compressor)
s
|> channel.adapter.send_request(message, opts)
@@ -322,7 +332,7 @@ defmodule GRPC.Stub do
* `:end_stream` - indicates it's the last one request, then the stream will be in
half_closed state. Default is false.
"""
- @spec send_request(GRPC.Client.Stream.t(), struct, Keyword.t()) :: GRPC.Client.Stream.t()
+ @spec send_request(GRPC.Client.Stream.t(), struct, keyword()) :: GRPC.Client.Stream.t()
def send_request(%{__interface__: interface} = stream, request, opts \\ []) do
interface[:send_request].(stream, request, opts)
end
@@ -379,12 +389,12 @@ defmodule GRPC.Stub do
* `:deadline` - when the request is timeout, will override timeout
* `:return_headers` - when true, headers will be returned.
"""
- @spec recv(GRPC.Client.Stream.t(), keyword | map) ::
- {:ok, struct}
- | {:ok, struct, map}
+ @spec recv(GRPC.Client.Stream.t(), keyword()) ::
+ {:ok, struct()}
+ | {:ok, struct(), map()}
| {:ok, Enumerable.t()}
- | {:ok, Enumerable.t(), map}
- | {:error, any}
+ | {:ok, Enumerable.t(), map()}
+ | {:error, any()}
def recv(stream, opts \\ [])
def recv(%{canceled: true}, _) do
@@ -394,266 +404,56 @@ defmodule GRPC.Stub do
def recv(%{__interface__: interface} = stream, opts) do
opts =
if is_list(opts) do
- parse_recv_opts(opts)
+ parse_recv_opts(Keyword.put_new(opts, :timeout, @default_timeout))
else
opts
end
- interface[:recv].(stream, opts)
- end
-
- @doc false
- def do_recv(%{server_stream: true, channel: channel, payload: payload} = stream, opts) do
- case recv_headers(channel.adapter, channel.adapter_payload, payload, opts) do
- {:ok, headers, is_fin} ->
- res_enum =
- case is_fin do
- :fin -> []
- :nofin -> response_stream(stream, opts)
- end
-
- if opts[:return_headers] do
- {:ok, res_enum, %{headers: headers}}
- else
- {:ok, res_enum}
- end
-
- {:error, reason} ->
- {:error, reason}
- end
- end
-
- def do_recv(
- %{payload: payload, channel: channel} = stream,
- opts
- ) do
- with {:ok, headers, _is_fin} <-
- recv_headers(channel.adapter, channel.adapter_payload, payload, opts),
- {:ok, body, trailers} <-
- recv_body(channel.adapter, channel.adapter_payload, payload, opts) do
- {status, msg} = parse_response(stream, headers, body, trailers)
-
- if opts[:return_headers] do
- {status, msg, %{headers: headers, trailers: trailers}}
- else
- {status, msg}
- end
- else
- error = {:error, _} ->
- error
- end
- end
-
- defp recv_headers(adapter, conn_payload, stream_payload, opts) do
- case adapter.recv_headers(conn_payload, stream_payload, opts) do
- {:ok, headers, is_fin} ->
- {:ok, GRPC.Transport.HTTP2.decode_headers(headers), is_fin}
-
- other ->
- other
- end
- end
-
- defp recv_body(adapter, conn_payload, stream_payload, opts) do
- recv_body(adapter, conn_payload, stream_payload, "", opts)
- end
-
- defp recv_body(adapter, conn_payload, stream_payload, acc, opts) do
- case adapter.recv_data_or_trailers(conn_payload, stream_payload, opts) do
- {:data, data} ->
- recv_body(adapter, conn_payload, stream_payload, <>, opts)
-
- {:trailers, trailers} ->
- {:ok, acc, GRPC.Transport.HTTP2.decode_headers(trailers)}
- end
- end
-
- defp parse_response(
- %{response_mod: res_mod, codec: codec, accepted_compressors: accepted_compressors},
- headers,
- body,
- trailers
- ) do
- case parse_trailers(trailers) do
- :ok ->
- compressor =
- case headers do
- %{"grpc-encoding" => encoding} ->
- Enum.find(accepted_compressors, nil, fn c -> c.name() == encoding end)
-
- _ ->
- nil
- end
-
- case GRPC.Message.from_data(%{compressor: compressor}, body) do
- {:ok, msg} ->
- {:ok, codec.decode(msg, res_mod)}
-
- err ->
- err
- end
-
- error ->
- error
- end
- end
-
- defp parse_trailers(trailers) do
- status = String.to_integer(trailers["grpc-status"])
-
- if status == GRPC.Status.ok() do
- :ok
- else
- {:error, %GRPC.RPCError{status: status, message: trailers["grpc-message"]}}
- end
- end
-
- defp response_stream(
- %{
- channel: %{adapter: adapter, adapter_payload: ap},
- response_mod: res_mod,
- codec: codec,
- payload: payload
- },
- opts
- ) do
- state = %{
- adapter: adapter,
- adapter_payload: ap,
- payload: payload,
- buffer: <<>>,
- fin: false,
- need_more: true,
- opts: opts,
- response_mod: res_mod,
- codec: codec
- }
-
- Stream.unfold(state, fn s -> read_stream(s) end)
- end
-
- defp read_stream(%{buffer: <<>>, fin: true, fin_resp: nil}), do: nil
-
- defp read_stream(%{buffer: <<>>, fin: true, fin_resp: fin_resp} = s),
- do: {fin_resp, Map.put(s, :fin_resp, nil)}
-
- defp read_stream(
- %{
- adapter: adapter,
- adapter_payload: ap,
- payload: payload,
- buffer: buffer,
- need_more: true,
- opts: opts
- } = s
- ) do
- case adapter.recv_data_or_trailers(ap, payload, opts) do
- {:data, data} ->
- buffer = buffer <> data
- new_s = s |> Map.put(:need_more, false) |> Map.put(:buffer, buffer)
- read_stream(new_s)
-
- {:trailers, trailers} ->
- trailers = GRPC.Transport.HTTP2.decode_headers(trailers)
-
- case parse_trailers(trailers) do
- :ok ->
- fin_resp =
- if opts[:return_headers] do
- {:trailers, trailers}
- end
-
- new_s = s |> Map.put(:fin, true) |> Map.put(:fin_resp, fin_resp)
- read_stream(new_s)
-
- error ->
- {error, %{buffer: <<>>, fin: true, fin_resp: nil}}
- end
-
- error = {:error, _} ->
- {error, %{buffer: <<>>, fin: true, fin_resp: nil}}
- end
- end
-
- defp read_stream(%{buffer: buffer, need_more: false, response_mod: res_mod, codec: codec} = s) do
- case GRPC.Message.get_message(buffer) do
- # TODO
- {{_, message}, rest} ->
- reply = codec.decode(message, res_mod)
- new_s = Map.put(s, :buffer, rest)
- {{:ok, reply}, new_s}
-
- _ ->
- read_stream(Map.put(s, :need_more, true))
- end
- end
-
- defp parse_req_opts(list) when is_list(list) do
- parse_req_opts(list, %{})
- end
-
- defp parse_req_opts([{:timeout, timeout} | t], acc) do
- parse_req_opts(t, Map.put(acc, :timeout, timeout))
- end
-
- defp parse_req_opts([{:deadline, deadline} | t], acc) do
- parse_req_opts(t, Map.put(acc, :timeout, GRPC.TimeUtils.to_relative(deadline)))
- end
-
- defp parse_req_opts([{:compressor, compressor} | t], acc) do
- parse_req_opts(t, Map.put(acc, :compressor, compressor))
- end
-
- defp parse_req_opts([{:accepted_compressors, compressors} | t], acc) do
- parse_req_opts(t, Map.put(acc, :accepted_compressors, compressors))
- end
-
- defp parse_req_opts([{:grpc_encoding, grpc_encoding} | t], acc) do
- parse_req_opts(t, Map.put(acc, :grpc_encoding, grpc_encoding))
- end
-
- defp parse_req_opts([{:metadata, metadata} | t], acc) do
- parse_req_opts(t, Map.put(acc, :metadata, metadata))
- end
-
- defp parse_req_opts([{:content_type, content_type} | t], acc) do
- Logger.warn(":content_type has been deprecated, please use :codec")
- parse_req_opts(t, Map.put(acc, :content_type, content_type))
- end
-
- defp parse_req_opts([{:codec, codec} | t], acc) do
- parse_req_opts(t, Map.put(acc, :codec, codec))
- end
-
- defp parse_req_opts([{:return_headers, return_headers} | t], acc) do
- parse_req_opts(t, Map.put(acc, :return_headers, return_headers))
- end
-
- defp parse_req_opts([{key, _} | _], _) do
- raise ArgumentError, "option #{inspect(key)} is not supported"
+ interface[:receive_data].(stream, opts)
+ end
+
+ @valid_req_opts [
+ :timeout,
+ :deadline,
+ :compressor,
+ :accepted_compressors,
+ :grpc_encoding,
+ :metadata,
+ :codec,
+ :return_headers
+ ]
+ defp parse_req_opts(opts) when is_list(opts) do
+ # Map.new is used so we can keep the last value
+ # passed for a given key
+ opts
+ |> Map.new(fn
+ {:deadline, deadline} ->
+ {:timeout, GRPC.TimeUtils.to_relative(deadline)}
+
+ {key, value} when key in @valid_req_opts ->
+ {key, value}
+
+ {key, _} ->
+ raise ArgumentError, "option #{inspect(key)} is not supported"
+ end)
+ |> Map.to_list()
end
- defp parse_req_opts(_, acc), do: acc
-
defp parse_recv_opts(list) when is_list(list) do
- parse_recv_opts(list, %{timeout: @default_timeout})
- end
+ # Map.new is used so we can keep the last value
+ # passed for a given key
- defp parse_recv_opts([{:timeout, timeout} | t], acc) do
- parse_recv_opts(t, Map.put(acc, :timeout, timeout))
- end
+ list
+ |> Map.new(fn
+ {:deadline, deadline} ->
+ {:deadline, GRPC.TimeUtils.to_relative(deadline)}
- defp parse_recv_opts([{:deadline, deadline} | t], acc) do
- parse_recv_opts(t, Map.put(acc, :deadline, GRPC.TimeUtils.to_relative(deadline)))
- end
-
- defp parse_recv_opts([{:return_headers, return_headers} | t], acc) do
- parse_recv_opts(t, Map.put(acc, :return_headers, return_headers))
- end
+ {key, _} when key not in @valid_req_opts ->
+ raise ArgumentError, "option #{inspect(key)} is not supported"
- defp parse_recv_opts([{key, _} | _], _) do
- raise ArgumentError, "option #{inspect(key)} is not supported"
+ kv ->
+ kv
+ end)
+ |> Map.to_list()
end
-
- defp parse_recv_opts(_, acc), do: acc
end
diff --git a/lib/grpc/transport/http2.ex b/lib/grpc/transport/http2.ex
index 189830fc..dd0eea40 100644
--- a/lib/grpc/transport/http2.ex
+++ b/lib/grpc/transport/http2.ex
@@ -8,23 +8,27 @@ defmodule GRPC.Transport.HTTP2 do
require Logger
+ def server_headers(%{codec: GRPC.Codec.WebText = codec}) do
+ %{"content-type" => "application/grpc-web-#{codec.name()}"}
+ end
+
def server_headers(%{codec: codec}) do
- %{"content-type" => "application/grpc+#{codec.name}"}
+ %{"content-type" => "application/grpc+#{codec.name()}"}
end
@spec server_trailers(integer, String.t()) :: map
def server_trailers(status \\ Status.ok(), message \\ "") do
%{
"grpc-status" => Integer.to_string(status),
- "grpc-message" => message
+ "grpc-message" => URI.encode(message)
}
end
@doc """
Now we may not need this because gun already handles the pseudo headers.
"""
- @spec client_headers(GRPC.Client.Stream.t(), map) :: [{String.t(), String.t()}]
- def client_headers(%{channel: channel, path: path} = s, opts \\ %{}) do
+ @spec client_headers(GRPC.Client.Stream.t(), keyword()) :: [{String.t(), String.t()}]
+ def client_headers(%{channel: channel, path: path} = s, opts \\ []) do
[
{":method", "POST"},
{":scheme", channel.scheme},
@@ -33,8 +37,10 @@ defmodule GRPC.Transport.HTTP2 do
] ++ client_headers_without_reserved(s, opts)
end
- @spec client_headers_without_reserved(GRPC.Client.Stream.t(), map) :: [{String.t(), String.t()}]
- def client_headers_without_reserved(%{codec: codec} = stream, opts \\ %{}) do
+ @spec client_headers_without_reserved(GRPC.Client.Stream.t(), keyword()) :: [
+ {String.t(), String.t()}
+ ]
+ def client_headers_without_reserved(%{codec: codec} = stream, opts \\ []) do
[
# It seems only gRPC implemenations only support "application/grpc", so we support :content_type now.
{"content-type", content_type(opts[:content_type], codec)},
@@ -55,15 +61,11 @@ defmodule GRPC.Transport.HTTP2 do
defp content_type(custom, _codec) when is_binary(custom), do: custom
- defp content_type(_, codec) do
- # Some gRPC implementations don't support application/grpc+xyz,
- # to avoid this kind of trouble, use application/grpc by default
- if codec == GRPC.Codec.Proto do
- "application/grpc"
- else
- "application/grpc+#{codec.name}"
- end
- end
+ # Some gRPC implementations don't support application/grpc+xyz,
+ # to avoid this kind of trouble, use application/grpc by default
+ defp content_type(_, GRPC.Codec.Proto), do: "application/grpc"
+ defp content_type(_, codec = GRPC.Codec.WebText), do: "application/grpc-web-#{codec.name()}"
+ defp content_type(_, codec), do: "application/grpc+#{codec.name()}"
def extract_metadata(headers) do
headers
@@ -76,7 +78,7 @@ defmodule GRPC.Transport.HTTP2 do
if is_metadata(k) do
decode_metadata({k, v})
else
- {k, v}
+ decode_reserved({k, v})
end
end)
end
@@ -140,6 +142,12 @@ defmodule GRPC.Transport.HTTP2 do
{key, val}
end
+ defp decode_reserved({"grpc-message" = key, val}) do
+ {key, URI.decode(val)}
+ end
+
+ defp decode_reserved(kv), do: kv
+
defp is_reserved_header(":" <> _), do: true
defp is_reserved_header("grpc-" <> _), do: true
defp is_reserved_header("content-type"), do: true
diff --git a/lib/mix/tasks/grpc.server.ex b/lib/mix/tasks/grpc.server.ex
deleted file mode 100644
index d0e856ed..00000000
--- a/lib/mix/tasks/grpc.server.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule Mix.Tasks.Grpc.Server do
- use Mix.Task
-
- @shortdoc "Starts applications and their servers"
-
- @moduledoc """
- Starts the application by configuring `start_server` to true.
-
- The `--no-halt` flag is automatically added.
- """
- @impl true
- def run(args) do
- Application.put_env(:grpc, :start_server, true, persistent: true)
- Mix.Task.run("run", run_args() ++ args)
- end
-
- defp run_args do
- if iex_running?(), do: [], else: ["--no-halt"]
- end
-
- defp iex_running? do
- Code.ensure_loaded?(IEx) and IEx.started?()
- end
-end
diff --git a/mix.exs b/mix.exs
index 980c755f..af6eed0b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,13 +1,13 @@
defmodule GRPC.Mixfile do
use Mix.Project
- @version "0.5.0-beta.1"
+ @version "0.5.0"
def project do
[
app: :grpc,
version: @version,
- elixir: "~> 1.5",
+ elixir: "~> 1.11",
elixirc_paths: elixirc_paths(Mix.env()),
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
@@ -21,7 +21,10 @@ defmodule GRPC.Mixfile do
source_url: "https://github.com/elixir-grpc/grpc"
],
dialyzer: [
- plt_add_apps: [:mix, :iex]
+ plt_add_deps: :apps_tree,
+ plt_add_apps: [:iex, :mix, :ex_unit],
+ list_unused_filters: true,
+ plt_file: {:no_warn, "_build/#{Mix.env()}/plts/dialyzer.plt"}
],
xref: [exclude: [IEx]]
]
@@ -36,14 +39,14 @@ defmodule GRPC.Mixfile do
defp deps do
[
- {:protobuf, "~> 0.5"},
- {:cowboy, "~> 2.7"},
- {:gun, "~> 2.0.0", hex: :grpc_gun},
- # 2.9.0 fixes some important bugs, so it's better to use ~> 2.9.0
- # {:cowlib, "~> 2.9.0", override: true},
- {:ex_doc, "~> 0.23", only: :dev},
- {:inch_ex, "~> 2.0", only: [:dev, :test]},
- {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
+ {:cowboy, "~> 2.9"},
+ # This is the same as :gun 2.0.0-rc.2,
+ # but we can't depend on an RC for releases
+ {:gun, "~> 2.0.1", hex: :grpc_gun},
+ {:cowlib, "~> 2.11"},
+ {:protobuf, "~> 0.10", only: [:dev, :test]},
+ {:ex_doc, "~> 0.28.0", only: :dev},
+ {:dialyxir, "~> 1.1.0", only: [:dev, :test], runtime: false}
]
end
diff --git a/mix.lock b/mix.lock
index 5e365221..43cfde44 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,19 +1,17 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
- "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
- "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
- "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
- "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
+ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
- "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
- "gun": {:hex, :grpc_gun, "2.0.0", "f99678a2ab975e74372a756c86ec30a8384d3ac8a8b86c7ed6243ef4e61d2729", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "03dbbca1a9c604a0267a40ea1d69986225091acb822de0b2dbea21d5815e410b"},
- "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"},
- "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
- "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
- "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
- "protobuf": {:hex, :protobuf, "0.7.1", "7d1b9f7d9ecb32eccd96b0c58572de4d1c09e9e3bc414e4cb15c2dce7013f195", [:mix], [], "hexpm", "6eff7a5287963719521c82e5d5b4583fd1d7cdd89ad129f0ea7d503a50a4d13f"},
- "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
+ "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
+ "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"},
+ "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
+ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
+ "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
+ "protobuf": {:hex, :protobuf, "0.10.0", "4e8e3cf64c5be203b329f88bb8b916cb8d00fb3a12b2ac1f545463ae963c869f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4ae21a386142357aa3d31ccf5f7d290f03f3fa6f209755f6e87fc2c58c147893"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
}
diff --git a/src/grpc_stream_h.erl b/src/grpc_stream_h.erl
index b231fca3..4e2652e7 100644
--- a/src/grpc_stream_h.erl
+++ b/src/grpc_stream_h.erl
@@ -4,10 +4,6 @@
-module(grpc_stream_h).
-behavior(cowboy_stream).
--ifdef(OTP_RELEASE).
--compile({nowarn_deprecated_function, [{erlang, get_stacktrace, 0}]}).
--endif.
-
-export([init/3]).
-export([data/4]).
-export([info/3]).
@@ -325,21 +321,13 @@ send_request_body(Pid, Ref, fin, BodyLen, Data) ->
%% @todo Better spec.
-spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
request_process(Req, Env, Middlewares) ->
- OTP = erlang:system_info(otp_release),
try
execute(Req, Env, Middlewares)
catch
- exit:Reason ->
- Stacktrace = erlang:get_stacktrace(),
- erlang:raise(exit, {Reason, Stacktrace}, Stacktrace);
- %% OTP 19 does not propagate any exception stacktraces,
- %% we therefore add it for every class of exception.
- _:Reason when OTP =:= "19" ->
- Stacktrace = erlang:get_stacktrace(),
+ exit:Reason:Stacktrace ->
erlang:raise(exit, {Reason, Stacktrace}, Stacktrace);
- %% @todo I don't think this clause is necessary.
- Class:Reason ->
- erlang:raise(Class, Reason, erlang:get_stacktrace())
+ Class:Reason:Stacktrace ->
+ erlang:raise(Class, {Reason, Stacktrace}, Stacktrace)
end.
execute(_, _, []) ->
diff --git a/test/grpc/adapter/gun_test.exs b/test/grpc/adapter/gun_test.exs
new file mode 100644
index 00000000..b3d089e0
--- /dev/null
+++ b/test/grpc/adapter/gun_test.exs
@@ -0,0 +1,87 @@
+defmodule GRPC.Client.Adapters.GunTest do
+ use ExUnit.Case, async: true
+
+ import GRPC.Factory
+
+ alias GRPC.Client.Adapters.Gun
+
+ describe "connect/2" do
+ setup do
+ server_credential = build(:credential)
+ {:ok, _, port} = GRPC.Server.start(FeatureServer, 0, cred: server_credential)
+
+ on_exit(fn ->
+ :ok = GRPC.Server.stop(FeatureServer)
+ end)
+
+ %{
+ port: port,
+ credential:
+ build(:credential,
+ ssl: Keyword.take(server_credential.ssl, [:certfile, :keyfile, :versions])
+ )
+ }
+ end
+
+ test "connects insecurely (default options)", %{port: port, credential: credential} do
+ channel = build(:channel, port: port, host: "localhost", cred: credential)
+
+ assert {:ok, result} = Gun.connect(channel, [])
+
+ assert %{channel | adapter_payload: %{conn_pid: result.adapter_payload.conn_pid}} == result
+ end
+
+ test "connects insecurely (custom options)", %{port: port, credential: credential} do
+ channel = build(:channel, port: port, host: "localhost", cred: credential)
+
+ # Ensure that it works
+ assert {:ok, result} = Gun.connect(channel, transport_opts: [ip: :loopback])
+ assert %{channel | adapter_payload: %{conn_pid: result.adapter_payload.conn_pid}} == result
+
+ # Ensure that changing one of the options breaks things
+ assert {:error, {:down, :badarg}} ==
+ Gun.connect(channel, transport_opts: [ip: "256.0.0.0"])
+ end
+
+ test "connects securely (default options)", %{port: port, credential: credential} do
+ channel =
+ build(:channel,
+ port: port,
+ scheme: "https",
+ host: "localhost",
+ cred: credential
+ )
+
+ assert {:ok, result} = Gun.connect(channel, tls_opts: channel.cred.ssl)
+
+ assert %{channel | adapter_payload: %{conn_pid: result.adapter_payload.conn_pid}} == result
+ end
+
+ test "connects securely (custom options)", %{port: port, credential: credential} do
+ channel =
+ build(:channel,
+ port: port,
+ scheme: "https",
+ host: "localhost",
+ cred: credential
+ )
+
+ # Ensure that it works
+ assert {:ok, result} =
+ Gun.connect(channel,
+ transport_opts: [certfile: credential.ssl[:certfile], ip: :loopback]
+ )
+
+ assert %{channel | adapter_payload: %{conn_pid: result.adapter_payload.conn_pid}} == result
+
+ # Ensure that changing one of the options breaks things
+ assert {:error, :timeout} ==
+ Gun.connect(channel,
+ transport_opts: [
+ certfile: credential.ssl[:certfile] <> "invalidsuffix",
+ ip: :loopback
+ ]
+ )
+ end
+ end
+end
diff --git a/test/grpc/integration/codec_test.exs b/test/grpc/integration/codec_test.exs
index ef28b96a..31fbf967 100644
--- a/test/grpc/integration/codec_test.exs
+++ b/test/grpc/integration/codec_test.exs
@@ -20,32 +20,30 @@ defmodule GRPC.Integration.CodecTest do
defmodule HelloServer do
use GRPC.Server,
service: Helloworld.Greeter.Service,
- codecs: [GRPC.Codec.Proto, GRPC.Codec.Erlpack]
+ codecs: [GRPC.Codec.Proto, GRPC.Codec.Erlpack, GRPC.Codec.WebText]
def say_hello(req, _stream) do
Helloworld.HelloReply.new(message: "Hello, #{req.name}")
end
end
- defmodule HelloErlpackStub do
+ defmodule HelloStub do
use GRPC.Stub, service: Helloworld.Greeter.Service
end
- test "Says hello over erlpack" do
+ test "Says hello over erlpack, GRPC-web-text" do
run_server(HelloServer, fn port ->
{:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
name = "Mairbek"
req = Helloworld.HelloRequest.new(name: name)
- {:ok, reply} = channel |> HelloErlpackStub.say_hello(req, codec: GRPC.Codec.Erlpack)
- assert reply.message == "Hello, #{name}"
-
- # verify that proto still works
- {:ok, reply} = channel |> HelloErlpackStub.say_hello(req, codec: GRPC.Codec.Proto)
- assert reply.message == "Hello, #{name}"
+ for codec <- [GRPC.Codec.Erlpack, GRPC.Codec.WebText, GRPC.Codec.Proto] do
+ {:ok, reply} = HelloStub.say_hello(channel, req, codec: codec)
+ assert reply.message == "Hello, #{name}"
+ end
# codec not registered
- {:error, reply} = channel |> HelloErlpackStub.say_hello(req, codec: NotRegisteredCodec)
+ {:error, reply} = HelloStub.say_hello(channel, req, codec: NotRegisteredCodec)
assert %GRPC.RPCError{
status: GRPC.Status.unimplemented(),
@@ -53,4 +51,23 @@ defmodule GRPC.Integration.CodecTest do
} == reply
end)
end
+
+ test "sets the correct content-type based on codec name" do
+ run_server(HelloServer, fn port ->
+ {:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
+ name = "Mairbek"
+ req = Helloworld.HelloRequest.new(name: name)
+
+ for {expected_content_type, codec} <- [
+ {"grpc-web-text", GRPC.Codec.WebText},
+ {"grpc+erlpack", GRPC.Codec.Erlpack},
+ {"grpc+proto", GRPC.Codec.Proto}
+ ] do
+ {:ok, _reply, headers} =
+ HelloStub.say_hello(channel, req, codec: codec, return_headers: true)
+
+ assert headers[:headers]["content-type"] == "application/#{expected_content_type}"
+ end
+ end)
+ end
end
diff --git a/test/grpc/integration/connection_test.exs b/test/grpc/integration/connection_test.exs
index 94a46186..fe68d005 100644
--- a/test/grpc/integration/connection_test.exs
+++ b/test/grpc/integration/connection_test.exs
@@ -5,19 +5,11 @@ defmodule GRPC.Integration.ConnectionTest do
@key_path Path.expand("./tls/server1.key", :code.priv_dir(:grpc))
@ca_path Path.expand("./tls/ca.pem", :code.priv_dir(:grpc))
- defmodule FeatureServer do
- use GRPC.Server, service: Routeguide.RouteGuide.Service
-
- def get_feature(point, _stream) do
- Routeguide.Feature.new(location: point, name: "#{point.latitude},#{point.longitude}")
- end
- end
-
test "reconnection works" do
server = FeatureServer
{:ok, _, port} = GRPC.Server.start(server, 0)
point = Routeguide.Point.new(latitude: 409_146_138, longitude: -746_188_906)
- {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", adapter_opts: %{retry_timeout: 10})
+ {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", adapter_opts: [retry_timeout: 10])
assert {:ok, _} = channel |> Routeguide.RouteGuide.Stub.get_feature(point)
:ok = GRPC.Server.stop(server)
{:ok, _, _} = reconnect_server(server, port)
@@ -31,7 +23,7 @@ defmodule GRPC.Integration.ConnectionTest do
File.rm(socket_path)
{:ok, _, _} = GRPC.Server.start(server, 0, ip: {:local, socket_path})
- {:ok, channel} = GRPC.Stub.connect(socket_path, adapter_opts: %{retry_timeout: 10})
+ {:ok, channel} = GRPC.Stub.connect(socket_path, adapter_opts: [retry_timeout: 10])
point = Routeguide.Point.new(latitude: 409_146_138, longitude: -746_188_906)
assert {:ok, _} = channel |> Routeguide.RouteGuide.Stub.get_feature(point)
@@ -41,6 +33,8 @@ defmodule GRPC.Integration.ConnectionTest do
test "authentication works" do
server = FeatureServer
+ tls_versions = [:"tlsv1.2"]
+
cred =
GRPC.Credential.new(
ssl: [
@@ -48,7 +42,8 @@ defmodule GRPC.Integration.ConnectionTest do
cacertfile: @ca_path,
keyfile: @key_path,
verify: :verify_peer,
- fail_if_no_peer_cert: true
+ fail_if_no_peer_cert: true,
+ versions: tls_versions
]
)
@@ -56,7 +51,12 @@ defmodule GRPC.Integration.ConnectionTest do
try do
point = Routeguide.Point.new(latitude: 409_146_138, longitude: -746_188_906)
- client_cred = GRPC.Credential.new(ssl: [certfile: @cert_path, keyfile: @key_path])
+
+ client_cred =
+ GRPC.Credential.new(
+ ssl: [certfile: @cert_path, keyfile: @key_path, versions: tls_versions]
+ )
+
{:ok, channel} = GRPC.Stub.connect("localhost:#{port}", cred: client_cred)
assert {:ok, _} = Routeguide.RouteGuide.Stub.get_feature(channel, point)
catch
diff --git a/test/grpc/integration/endpoint_test.exs b/test/grpc/integration/endpoint_test.exs
index f5055bd4..a0015cf1 100644
--- a/test/grpc/integration/endpoint_test.exs
+++ b/test/grpc/integration/endpoint_test.exs
@@ -13,7 +13,7 @@ defmodule GRPC.Integration.EndpointTest do
defmodule HelloEndpoint do
use GRPC.Endpoint
- intercept GRPC.Logger.Server, level: :info
+ intercept GRPC.Logger.Server, level: :info, accepted_comparators: [:lt, :eq, :gt]
run HelloServer
end
@@ -51,14 +51,14 @@ defmodule GRPC.Integration.EndpointTest do
defmodule FeatureEndpoint do
use GRPC.Endpoint
- intercept GRPC.Logger.Server
+ intercept GRPC.Logger.Server, accepted_comparators: [:lt, :eq, :gt]
run FeatureServer
end
defmodule FeatureAndHelloHaltEndpoint do
use GRPC.Endpoint
- intercept GRPC.Logger.Server
+ intercept GRPC.Logger.Server, accepted_comparators: [:lt, :eq, :gt]
run HelloServer, interceptors: [HelloHaltInterceptor]
run FeatureServer
end
diff --git a/test/grpc/integration/server_test.exs b/test/grpc/integration/server_test.exs
index c80f814b..e2455719 100644
--- a/test/grpc/integration/server_test.exs
+++ b/test/grpc/integration/server_test.exs
@@ -98,6 +98,12 @@ defmodule GRPC.Integration.ServerTest do
end
end
+ defmodule HTTP1Server do
+ def init(req, state) do
+ {:ok, :cowboy_req.reply(200, %{}, "OK", req), state}
+ end
+ end
+
test "multiple servers works" do
run_server([FeatureServer, HelloServer], fn port ->
{:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
@@ -111,6 +117,27 @@ defmodule GRPC.Integration.ServerTest do
end)
end
+ test "HTTP/1 status handler can be started along a gRPC server" do
+ status_handler = {"/status", HTTP1Server, []}
+
+ run_server(
+ [HelloServer],
+ fn port ->
+ {:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
+ req = Helloworld.HelloRequest.new(name: "Elixir")
+ {:ok, reply} = channel |> Helloworld.Greeter.Stub.say_hello(req)
+ assert reply.message == "Hello, Elixir"
+
+ {:ok, conn_pid} = :gun.open('localhost', port)
+ stream_ref = :gun.get(conn_pid, "/status")
+
+ assert_receive {:gun_response, ^conn_pid, ^stream_ref, :nofin, 200, _headers}
+ end,
+ 0,
+ adapter_opts: [status_handler: status_handler]
+ )
+ end
+
test "returns appropriate error for unary requests" do
run_server([HelloErrorServer], fn port ->
{:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
diff --git a/test/grpc/integration/service_test.exs b/test/grpc/integration/service_test.exs
index 3da896f9..497bf1cd 100644
--- a/test/grpc/integration/service_test.exs
+++ b/test/grpc/integration/service_test.exs
@@ -132,7 +132,7 @@ defmodule GRPC.Integration.ServiceTest do
FeatureServer,
fn port ->
{:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
- stream = channel |> Routeguide.RouteGuide.Stub.async_route_chat()
+ stream = channel |> Routeguide.RouteGuide.Stub.route_chat()
task =
Task.async(fn ->
@@ -152,7 +152,7 @@ defmodule GRPC.Integration.ServiceTest do
notes =
Enum.map(result_enum, fn {:ok, note} ->
- assert "Reply: " <> msg = note.message
+ assert "Reply: " <> _msg = note.message
if note.message == "Reply: Message 5" do
point = Routeguide.Point.new(latitude: 0, longitude: rem(6, 3) + 1)
diff --git a/test/grpc/logger/server_test.exs b/test/grpc/logger/server_test.exs
new file mode 100644
index 00000000..b6667622
--- /dev/null
+++ b/test/grpc/logger/server_test.exs
@@ -0,0 +1,77 @@
+defmodule GRPC.Logger.ServerTest do
+ use ExUnit.Case, async: false
+
+ import ExUnit.CaptureLog
+
+ alias GRPC.Logger.Server
+
+ alias GRPC.Server.Stream
+
+ test "request id is only set if not previously set" do
+ assert Logger.metadata() == []
+
+ request_id = to_string(System.monotonic_time())
+ stream = %Stream{server: :server, rpc: {1, 2, 3}, request_id: request_id}
+
+ Server.call(
+ :request,
+ stream,
+ fn :request, ^stream -> {:ok, :ok} end,
+ Server.init(level: :info)
+ )
+
+ assert [request_id: request_id] == Logger.metadata()
+
+ stream = %{stream | request_id: nil}
+
+ Server.call(
+ :request,
+ stream,
+ fn :request, ^stream -> {:ok, :ok} end,
+ Server.init(level: :info)
+ )
+
+ assert request_id == Logger.metadata()[:request_id]
+ end
+
+ test "accepted_comparators filter logs correctly" do
+ for {configured_level, accepted_comparators, should_log} <-
+ [
+ {:error, [:lt], false},
+ {:error, [:eq], false},
+ {:error, [:gt], true},
+ {:debug, [:eq], false},
+ {:debug, [:eq, :gt], false},
+ {:info, [:lt, :eq], true}
+ ] do
+ server_name = :"server_#{System.unique_integer()}"
+
+ logger_level = Logger.level()
+ assert logger_level == :info
+
+ logs =
+ capture_log(fn ->
+ stream = %Stream{server: server_name, rpc: {1, 2, 3}, request_id: "1234"}
+
+ Server.call(
+ :request,
+ stream,
+ fn :request, ^stream -> {:ok, :ok} end,
+ Server.init(
+ level: configured_level,
+ accepted_comparators: accepted_comparators
+ )
+ )
+ end)
+
+ if should_log do
+ assert Regex.match?(
+ ~r/\[#{configured_level}\]\s+Handled by #{inspect(server_name)}/,
+ logs
+ )
+ else
+ assert logs == ""
+ end
+ end
+ end
+end
diff --git a/test/grpc/message/protobuf_test.exs b/test/grpc/message/protobuf_test.exs
deleted file mode 100644
index 3e1b8dae..00000000
--- a/test/grpc/message/protobuf_test.exs
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule GRPC.Message.ProtobufTest do
- use ExUnit.Case, async: true
-
- defmodule Helloworld.HelloRequest do
- use Protobuf
-
- defstruct [:name]
- field(:name, 1, optional: true, type: :string)
- end
-
- defmodule Helloworld.HelloReply do
- use Protobuf
-
- defstruct [:message]
- field(:message, 1, optional: true, type: :string)
- end
-
- test "encode/2 works for matched arguments" do
- request = Helloworld.HelloRequest.new(name: "elixir")
-
- assert <<10, 6, 101, 108, 105, 120, 105, 114>> =
- GRPC.Message.Protobuf.encode(Helloworld.HelloRequest, request)
- end
-
- test "decode/2 works" do
- msg = <<10, 6, 101, 108, 105, 120, 105, 114>>
- request = Helloworld.HelloRequest.new(name: "elixir")
- assert ^request = GRPC.Message.Protobuf.decode(Helloworld.HelloRequest, msg)
- end
-
- test "decode/2 returns wrong result for mismatched arguments" do
- # encoded HelloRequest
- msg = <<10, 6, 101, 108, 105, 120, 105, 114>>
- request = Helloworld.HelloReply.new(message: "elixir")
- assert ^request = GRPC.Message.Protobuf.decode(Helloworld.HelloReply, msg)
- end
-end
diff --git a/test/grpc/message_test.exs b/test/grpc/message_test.exs
index 765e76f3..04b29f32 100644
--- a/test/grpc/message_test.exs
+++ b/test/grpc/message_test.exs
@@ -15,4 +15,23 @@ defmodule GRPC.MessageTest do
assert {:ok, message} == GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, data)
end
+
+ test "iodata can be passed to and returned from `to_data/2`" do
+ message = List.duplicate("foo", 100)
+
+ assert {:ok, data, 32} =
+ GRPC.Message.to_data(message, iolist: true, compressor: GRPC.Compressor.Gzip)
+
+ assert is_list(data)
+ binary = IO.iodata_to_binary(data)
+
+ assert {:ok, IO.iodata_to_binary(message)} ==
+ GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, binary)
+ end
+
+ test "to_data/2 invokes codec.pack_for_channel on the gRPC body if codec implements it" do
+ message = "web-text"
+ assert {:ok, base64_payload, _} = GRPC.Message.to_data(message, %{codec: GRPC.Codec.WebText})
+ assert message == GRPC.Message.from_data(Base.decode64!(base64_payload))
+ end
end
diff --git a/test/grpc/server/supervisor_test.exs b/test/grpc/server/supervisor_test.exs
new file mode 100644
index 00000000..495b6b0d
--- /dev/null
+++ b/test/grpc/server/supervisor_test.exs
@@ -0,0 +1,44 @@
+defmodule GRPC.Server.SupervisorTest do
+ use ExUnit.Case, async: false
+
+ alias GRPC.Server.Supervisor
+
+ defmodule MockEndpoint do
+ def __meta__(_), do: [FeatureServer]
+ end
+
+ describe "init/1" do
+ test "does not start children if opts sets false" do
+ assert {:ok, {%{strategy: :one_for_one}, []}} =
+ Supervisor.init(endpoint: MockEndpoint, port: 1234, start_server: false)
+ end
+
+ test "fails if a tuple is passed" do
+ assert_raise ArgumentError,
+ "passing a tuple as configuration for GRPC.Server.Supervisor is no longer supported. See the documentation for more information on how to configure.",
+ fn ->
+ Supervisor.init({MockEndpoint, 1234})
+ end
+
+ assert_raise ArgumentError,
+ "passing a tuple as configuration for GRPC.Server.Supervisor is no longer supported. See the documentation for more information on how to configure.",
+ fn ->
+ Supervisor.init({MockEndpoint, 1234, start_server: true})
+ end
+ end
+
+ test "starts children if opts sets true" do
+ endpoint_str = "#{Macro.to_string(MockEndpoint)}"
+
+ assert {:ok,
+ {%{strategy: :one_for_one},
+ [
+ %{
+ id: {:ranch_listener_sup, ^endpoint_str},
+ start: _,
+ type: :supervisor
+ }
+ ]}} = Supervisor.init(endpoint: MockEndpoint, port: 1234, start_server: true)
+ end
+ end
+end
diff --git a/test/grpc/transport/http2_test.exs b/test/grpc/transport/http2_test.exs
index b8fcd169..07ca5fe6 100644
--- a/test/grpc/transport/http2_test.exs
+++ b/test/grpc/transport/http2_test.exs
@@ -1,10 +1,12 @@
defmodule GRPC.Transport.HTTP2Test do
use ExUnit.Case, async: true
- alias GRPC.Channel
+ alias GRPC.{Channel, Status}
alias GRPC.Transport.HTTP2
- @channel %Channel{scheme: "http", host: "grpc.io"}
alias GRPC.Client.Stream
+ alias GRPC.Server.Stream, as: ServerStream
+
+ @channel %Channel{scheme: "http", host: "grpc.io"}
defp assert_header({key, _v} = pair, headers) do
assert pair == Enum.find(headers, nil, fn {k, _v} -> if k == key, do: true end)
@@ -99,4 +101,28 @@ defmodule GRPC.Transport.HTTP2Test do
assert {_, "application/grpc+custom-codec"} =
Enum.find(headers, fn {key, _} -> key == "content-type" end)
end
+
+ test "server_headers/3 sets content-type based on the codec name" do
+ for {expected_content_type, codec} <- [
+ {"grpc-web-text", GRPC.Codec.WebText},
+ {"grpc+erlpack", GRPC.Codec.Erlpack}
+ ] do
+ stream = %ServerStream{codec: codec}
+
+ assert %{"content-type" => "application/" <> ^expected_content_type} =
+ HTTP2.server_headers(stream)
+ end
+ end
+
+ test "decode_headers/2 url decodes grpc-message" do
+ trailers = HTTP2.server_trailers(Status.unknown(), "Unknown error")
+ assert %{"grpc-message" => "Unknown error"} = HTTP2.decode_headers(trailers)
+ end
+
+ test "server_trailers/3 sets url encoded grpc-message" do
+ assert %{"grpc-message" => "Ok"} = HTTP2.server_trailers(Status.ok(), "Ok")
+
+ assert %{"grpc-message" => "Unknown%20error"} =
+ HTTP2.server_trailers(Status.unknown(), "Unknown error")
+ end
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
new file mode 100644
index 00000000..fceaae03
--- /dev/null
+++ b/test/support/factory.ex
@@ -0,0 +1,52 @@
+defmodule GRPC.Factory do
+ @moduledoc false
+
+ alias GRPC.Channel
+ alias GRPC.Credential
+
+ @cert_path Path.expand("./tls/server1.pem", :code.priv_dir(:grpc))
+ @key_path Path.expand("./tls/server1.key", :code.priv_dir(:grpc))
+ @ca_path Path.expand("./tls/ca.pem", :code.priv_dir(:grpc))
+
+ def build(resource, attrs \\ %{}) do
+ name = :"#{resource}_factory"
+
+ data =
+ if function_exported?(__MODULE__, name, 1) do
+ apply(__MODULE__, name, [attrs])
+ else
+ apply(__MODULE__, name, [])
+ end
+
+ Map.merge(data, Map.new(attrs))
+ end
+
+ def channel_factory do
+ %Channel{
+ host: "localhost",
+ port: 1337,
+ scheme: "http",
+ cred: build(:credential),
+ adapter: GRPC.Client.Adapters.Gun,
+ adapter_payload: %{},
+ codec: GRPC.Codec.Proto,
+ interceptors: [],
+ compressor: nil,
+ accepted_compressors: [],
+ headers: []
+ }
+ end
+
+ def credential_factory do
+ %Credential{
+ ssl: [
+ certfile: @cert_path,
+ cacertfile: @ca_path,
+ keyfile: @key_path,
+ verify: :verify_peer,
+ fail_if_no_peer_cert: true,
+ versions: [:"tlsv1.2"]
+ ]
+ }
+ end
+end
diff --git a/test/support/feature_server.ex b/test/support/feature_server.ex
new file mode 100644
index 00000000..3951ea8b
--- /dev/null
+++ b/test/support/feature_server.ex
@@ -0,0 +1,7 @@
+defmodule FeatureServer do
+ use GRPC.Server, service: Routeguide.RouteGuide.Service
+
+ def get_feature(point, _stream) do
+ Routeguide.Feature.new(location: point, name: "#{point.latitude},#{point.longitude}")
+ end
+end
diff --git a/test/support/helloworld.pb.ex b/test/support/helloworld.pb.ex
index f8758c31..1da2351c 100644
--- a/test/support/helloworld.pb.ex
+++ b/test/support/helloworld.pb.ex
@@ -1,52 +1,35 @@
defmodule Helloworld.HelloRequest do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- name: String.t()
- }
- defstruct [:name]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :name, 1, type: :string
end
defmodule Helloworld.HelloReply do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- message: String.t()
- }
- defstruct [:message]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :message, 1, type: :string
end
defmodule Helloworld.HeaderRequest do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{}
- defstruct []
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
end
defmodule Helloworld.HeaderReply do
@moduledoc false
- use Protobuf, syntax: :proto3
-
- @type t :: %__MODULE__{
- authorization: String.t()
- }
- defstruct [:authorization]
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
field :authorization, 1, type: :string
end
defmodule Helloworld.Greeter.Service do
@moduledoc false
- use GRPC.Service, name: "helloworld.Greeter"
+ use GRPC.Service, name: "helloworld.Greeter", protoc_gen_elixir_version: "0.10.0"
rpc :SayHello, Helloworld.HelloRequest, Helloworld.HelloReply
+
rpc :CheckHeaders, Helloworld.HeaderRequest, Helloworld.HeaderReply
end
diff --git a/test/support/integration_test_case.ex b/test/support/integration_test_case.ex
index df1daaa3..24467ca2 100644
--- a/test/support/integration_test_case.ex
+++ b/test/support/integration_test_case.ex
@@ -9,8 +9,8 @@ defmodule GRPC.Integration.TestCase do
end
end
- def run_server(servers, func, port \\ 0) do
- {:ok, _pid, port} = GRPC.Server.start(servers, port)
+ def run_server(servers, func, port \\ 0, opts \\ []) do
+ {:ok, _pid, port} = GRPC.Server.start(servers, port, opts)
try do
func.(port)
diff --git a/test/support/route_guide.pb.ex b/test/support/route_guide.pb.ex
index 6ad6f559..66a64c91 100644
--- a/test/support/route_guide.pb.ex
+++ b/test/support/route_guide.pb.ex
@@ -1,82 +1,59 @@
defmodule Routeguide.Point do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- latitude: integer,
- longitude: integer
- }
- defstruct [:latitude, :longitude]
-
- field :latitude, 1, optional: true, type: :int32
- field :longitude, 2, optional: true, type: :int32
+ field :latitude, 1, type: :int32
+ field :longitude, 2, type: :int32
end
defmodule Routeguide.Rectangle do
- use Protobuf
-
- @type t :: %__MODULE__{
- lo: Routeguide.Point.t(),
- hi: Routeguide.Point.t()
- }
- defstruct [:lo, :hi]
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :lo, 1, optional: true, type: Routeguide.Point
- field :hi, 2, optional: true, type: Routeguide.Point
+ field :lo, 1, type: Routeguide.Point
+ field :hi, 2, type: Routeguide.Point
end
defmodule Routeguide.Feature do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- name: String.t(),
- location: Routeguide.Point.t()
- }
- defstruct [:name, :location]
-
- field :name, 1, optional: true, type: :string
- field :location, 2, optional: true, type: Routeguide.Point
+ field :name, 1, type: :string
+ field :location, 2, type: Routeguide.Point
end
defmodule Routeguide.RouteNote do
- use Protobuf
-
- @type t :: %__MODULE__{
- location: Routeguide.Point.t(),
- message: String.t()
- }
- defstruct [:location, :message]
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- field :location, 1, optional: true, type: Routeguide.Point
- field :message, 2, optional: true, type: :string
+ field :location, 1, type: Routeguide.Point
+ field :message, 2, type: :string
end
defmodule Routeguide.RouteSummary do
- use Protobuf
+ @moduledoc false
+ use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3
- @type t :: %__MODULE__{
- point_count: integer,
- feature_count: integer,
- distance: integer,
- elapsed_time: integer
- }
- defstruct [:point_count, :feature_count, :distance, :elapsed_time]
-
- field :point_count, 1, optional: true, type: :int32
- field :feature_count, 2, optional: true, type: :int32
- field :distance, 3, optional: true, type: :int32
- field :elapsed_time, 4, optional: true, type: :int32
+ 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
- use GRPC.Service, name: "routeguide.RouteGuide"
+ @moduledoc false
+ use GRPC.Service, name: "routeguide.RouteGuide", protoc_gen_elixir_version: "0.10.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)
- rpc :AsyncRouteChat, 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/test/support/test_adapter.exs b/test/support/test_adapter.exs
index 38453434..3f5d84d0 100644
--- a/test/support/test_adapter.exs
+++ b/test/support/test_adapter.exs
@@ -1,8 +1,15 @@
defmodule GRPC.Test.ClientAdapter do
+ @behaviour GRPC.Client.Adapter
+
def connect(channel, _opts), do: {:ok, channel}
+ def disconnect(channel), do: {:ok, channel}
+ def send_request(stream, _message, _opts), do: stream
+ def receive_data(_stream, _opts), do: {:ok, nil}
end
defmodule GRPC.Test.ServerAdapter do
+ @behaviour GRPC.Server.Adapter
+
def start(s, h, p, opts) do
{s, h, p, opts}
end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 4c5aef46..805a2a64 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1,3 +1,10 @@
Code.require_file("./support/test_adapter.exs", __DIR__)
-ExUnit.start()
+codecs = [
+ GRPC.Codec.Erlpack,
+ GRPC.Codec.WebText,
+ GRPC.Codec.Proto
+]
+
+Enum.each(codecs, &Code.ensure_loaded/1)
+ExUnit.start(capture_log: true)