Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
781a7cc
chore: release new version
May 10, 2025
ce286dc
Merge remote-tracking branch 'upstream/master'
May 14, 2025
dae7deb
merge
May 28, 2025
9fe3c55
Merge remote-tracking branch 'upstream/master'
Jul 15, 2025
2a76e93
Merge remote-tracking branch 'upstream/master'
Jul 31, 2025
3af0153
Merge remote-tracking branch 'upstream/master'
sleipnir Sep 9, 2025
4feee64
Merge remote-tracking branch 'upstream/master'
sleipnir Sep 11, 2025
c83632e
initial client lb implementation
sleipnir Sep 11, 2025
c050706
chore: added tests and better interfaces
sleipnir Sep 11, 2025
5587cda
Update lib/grpc/client/resolver.ex
sleipnir Sep 11, 2025
0da6da4
chore: add lb
sleipnir Sep 11, 2025
815121a
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Sep 11, 2025
e3e0f24
chore: adjustments in behaviors & create Conn module
sleipnir Sep 12, 2025
9192dab
chore: simplifying the test
sleipnir Sep 15, 2025
1c3887c
chore: adjust in tests
sleipnir Sep 15, 2025
0d09fb6
feat: make lb work
sleipnir Sep 15, 2025
6761963
refact: remove deprecated feature
sleipnir Sep 15, 2025
4d64962
fix: return all the arities of the connect function
sleipnir Sep 15, 2025
6d8f667
chore: added deprecated
sleipnir Sep 15, 2025
1de3d4d
update benchmark
sleipnir Sep 15, 2025
10728ae
interop tests
sleipnir Sep 15, 2025
c6f5d2e
fix: parallel execution
sleipnir Sep 22, 2025
c0b6e94
fix: correct stop
sleipnir Sep 22, 2025
16a55db
chore: correct refresh logic
sleipnir Sep 22, 2025
5f385ec
chore: added some documentation
sleipnir Sep 22, 2025
c89da0f
Update lib/grpc/client/load_balacing/round_robin.ex
sleipnir Sep 26, 2025
88882c4
Update lib/grpc/client/load_balacing/round_robin.ex
sleipnir Sep 26, 2025
e4eee2d
accept string
sleipnir Sep 26, 2025
0594fa6
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Sep 26, 2025
148dcfa
use string
sleipnir Sep 26, 2025
3f81a71
Update lib/grpc/client/resolver/ipv4.ex
sleipnir Sep 26, 2025
12cf52f
Update lib/grpc/client/resolver/ipv6.ex
sleipnir Sep 26, 2025
b300c88
refactor: more readable code
sleipnir Sep 26, 2025
1b6427e
fix: correct code and fix tests
sleipnir Sep 26, 2025
f989823
Update lib/grpc/client/conn.ex
sleipnir Sep 26, 2025
18f316e
Update lib/grpc/client/conn.ex
sleipnir Sep 26, 2025
9c0d685
refactor: rename and minor adjustments
sleipnir Sep 26, 2025
7d36b3b
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Sep 26, 2025
a83930e
ref: rename Conn to Connection
sleipnir Sep 26, 2025
9f87397
ref: Adjusts in function declaration order
sleipnir Sep 26, 2025
c2f9860
Update lib/grpc/client/connection.ex
sleipnir Sep 26, 2025
9006f06
Update lib/grpc/client/connection.ex
sleipnir Sep 26, 2025
3c5de96
Update lib/grpc/client/connection.ex
sleipnir Sep 26, 2025
46fad67
fix: translate to english
sleipnir Sep 26, 2025
46c80c5
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Sep 26, 2025
e4f85b6
accept indeterminism
sleipnir Sep 26, 2025
96ba172
fix: pid is always pid
sleipnir Sep 26, 2025
a73d9ee
fix: correct references
sleipnir Sep 26, 2025
fefb49a
remove warning
sleipnir Sep 26, 2025
baa98e8
feat: added client supervisor
sleipnir Sep 26, 2025
42c9e9e
Merge branch 'master' into feat/grpc-lb
sleipnir Sep 26, 2025
b43b3bd
fix: charlist errors
sleipnir Oct 4, 2025
2a505b7
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Oct 4, 2025
c0ae664
Merge branch 'master' into feat/grpc-lb
sleipnir Oct 8, 2025
6e5ca48
Merge branch 'master' into feat/grpc-lb
sleipnir Oct 9, 2025
06fde43
chore: add stream operators documentation
sleipnir Oct 13, 2025
e775d95
feat: added load balancing documentation
sleipnir Oct 13, 2025
1681300
chore: added comment about endpoint refresh
sleipnir Oct 13, 2025
aff3352
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Oct 13, 2025
bae8b92
Merge remote-tracking branch 'upstream/master'
sleipnir Oct 13, 2025
6a344bf
Merge branch 'master' into feat/grpc-lb
sleipnir Oct 13, 2025
8da2224
fix: address virtual channel key
sleipnir Oct 14, 2025
18bde09
doc: added client supervisor example
sleipnir Oct 14, 2025
d628eae
refactor: better validations
sleipnir Oct 15, 2025
ee53402
Merge branch 'master' into feat/grpc-lb
sleipnir Oct 15, 2025
e824e3e
fix: no case clause
sleipnir Oct 15, 2025
5142061
Merge branch 'feat/grpc-lb' of https://github.com/sleipnir/grpc into …
sleipnir Oct 15, 2025
602a7bb
fix: handle dns no address
sleipnir Oct 15, 2025
bb87b78
chore: raise runtime error if not started client supervisor
sleipnir Oct 15, 2025
89965bf
Atualizar o connection.ex
sleipnir Oct 16, 2025
5db3eec
Atualizar o connection.ex
sleipnir Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 136 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
- [Bidirectional Streaming](#bidirectional-streaming)
- [Application Startup](#application-startup)
- [Client Usage](#client-usage)
- [Basic Connection and RPC](#basic-connection-and-rpc)
- [Using Interceptors](#using-interceptors)
- [Target Schemes and Resolvers](#target-schemes-and-resolvers)
- [Supported formats](#supported-formats)
- [Example (DNS)](#example-dns)
- [Example (Unix socket)](#example-unix-socket)
- [Compression and Metadata](#compression-and-metadata)
- [Client Adapters](#client-adapters)
- [Using Mint Adapter](#using-mint-adapter)
- [HTTP Transcoding](#http-transcoding)
- [CORS](#cors)
- [Features](#features)
Expand Down Expand Up @@ -78,6 +87,8 @@ protoc --elixir_out=plugins=grpc:./lib -I./priv/protos helloworld.proto

All RPC calls must be implemented using the stream-based API, even for unary requests.

>__NOTE__: The old API was deprecated based on `GRPC.Server.send_reply/2` and direct `struct` returns was deprecated as of version `0.10.x`.

### Unary RPC using Stream API

```elixir
Expand Down Expand Up @@ -133,7 +144,30 @@ def say_bid_stream_hello(request, materializer) do
|> GRPC.Stream.run_with(materializer)
end
```
__💡__ The Stream API supports composable stream transformations via `ask`, `map`, `run` and others functions, enabling clean and declarative stream pipelines. For a complete list of available operators see [here](lib/grpc/stream.ex).
The Stream API supports composable stream transformations via `ask`, `map`, `run` and others functions, enabling clean and declarative stream pipelines. See the table below:

| Function | Description | Parameters / Options |
|:---------------------------------|:-------------|:----------------------|
| **`from(input, opts \\\\ [])`** | Converts a gRPC stream (or list) into a `Flow` with backpressure support. Allows joining with external `GenStage` producers. | **Parameters:**<br>• `input` — stream, list, or gRPC struct.<br>**Options:**<br>• `:join_with` — PID or name of an external `GenStage` producer.<br>• `:dispatcher` — dispatcher module (default: `GenStage.DemandDispatcher`).<br>• `:propagate_context` — if `true`, propagates the materializer context.<br>• `:materializer` — the current `%GRPC.Server.Stream{}`.<br>• Other options supported by `Flow`. |
| **`unary(input, opts \\\\ [])`** | Creates a `Flow` from a single gRPC request (unary). Useful for non-streaming calls that still leverage the Flow API. | **Parameters:**<br>• `input` — single gRPC message.<br>**Options:** same as `from/2`. |
| **`to_flow(stream)`** | Returns the underlying `Flow` from a `GRPC.Stream`. If uninitialized, returns `Flow.from_enumerable([])`. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}` struct. |
| **`run(stream)`** | Executes the `Flow` for a unary stream and returns the first materialized result. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}` with `unary: true` option. |
| **`run_with(stream, materializer, opts \\\\ [])`** | Executes the `Flow` and sends responses into the gRPC server stream. Supports `:dry_run` for test mode without sending messages. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `materializer` — `%GRPC.Server.Stream{}`.<br>**Options:**<br>• `:dry_run` — if `true`, responses are not sent. |
| **`ask(stream, target, timeout \\\\ 5000)`** | Sends a request to an external process (`PID` or named process) and waits for a response (`{:response, msg}`). Returns an updated stream or an error. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `target` — PID or atom.<br>• `timeout` — in milliseconds. |
| **`ask!(stream, target, timeout \\\\ 5000)`** | Same as `ask/3`, but raises an exception on failure (aborts the Flow). | Same parameters as `ask/3`. |
| **`filter(stream, fun)`** | Filters items in the stream by applying a concurrent predicate function. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `fun` — function `(item -> boolean)`. |
| **`flat_map(stream, fun)`** | Applies a function returning a list or enumerable, flattening the results. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `fun` — `(item -> Enumerable.t())`. |
| **`map(stream, fun)`** | Applies a transformation function to each item in the stream. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `fun` — `(item -> term)`. |
| **`map_with_context(stream, fun)`** | Applies a function to each item, passing the stream context (e.g., headers) as an additional argument. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `fun` — `(context, item -> term)`. |
| **`partition(stream, opts \\\\ [])`** | Partitions the stream to group items by key or condition before stateful operations like `reduce/3`. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `opts` — partitioning options (`Flow.partition/2`). |
| **`reduce(stream, acc_fun, reducer_fun)`** | Reduces the stream using an accumulator, useful for aggregations. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `acc_fun` — initializer function `() -> acc`.<br>• `reducer_fun` — `(item, acc -> acc)`. |
| **`uniq(stream)`** | Emits only distinct items from the stream (no custom uniqueness criteria). | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`. |
| **`uniq_by(stream, fun)`** | Emits only unique items based on the return value of the provided function. | **Parameters:**<br>• `stream` — `%GRPC.Stream{}`.<br>• `fun` — `(item -> term)` for uniqueness determination. |
| **`get_headers(stream)`** | Retrieves HTTP/2 headers from a `%GRPC.Server.Stream{}`. | **Parameters:**<br>• `stream` — `%GRPC.Server.Stream{}`.<br>**Returns:** `map` containing decoded headers. |

For a complete list of available operators see [here](lib/grpc/stream.ex).

---

## Application Startup

Expand Down Expand Up @@ -166,38 +200,125 @@ end

# Client Usage

This section demonstrates how to establish client connections and perform RPC calls using the Elixir gRPC client.

---

## Basic Connection and RPC


Typically, you start this client supervisor as part of your application's supervision tree:

```elixir
children = [
{GRPC.Client.Supervisor, []}
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
```

You can also start it manually in scripts or test environments:
```elixir
{:ok, _pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: GRPC.Client.Supervisor)
```

Then connect with gRPC server:

```elixir
iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051")
iex> request = Helloworld.HelloRequest.new(name: "grpc-elixir")
iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request)
```

---

## Using Interceptors

Client interceptors allow you to add logic to the request/response lifecycle, such as logging, tracing, or authentication.

```elixir
iex> {:ok, channel} =
...> GRPC.Stub.connect("localhost:50051",
...> interceptors: [GRPC.Client.Interceptors.Logger]
...> )
iex> request = Helloworld.HelloRequest.new(name: "Alice")
iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request)
```

---

## Target Schemes and Resolvers

The `connect/2` function supports URI-like targets that are resolved via the internal **gRPC** [Resolver](lib/grpc/client/resolver.ex).
You can connect using `DNS`, `Unix Domain sockets`, `IPv4/IPv6`, or even `xDS-based endpoints`.

### Supported formats:

# With interceptors
iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [GRPC.Client.Interceptors.Logger])
...
| Scheme | Example | Description |
|:----------|:----------------------------|:---------------------------------------------|
| `dns://` | `"dns://example.com:50051"` | Resolves via DNS `A/AAAA` records |
| `ipv4:` | `"ipv4:10.0.0.5:50051"` | Connects directly to an IPv4 address |
| `unix:` | `"unix:/tmp/service.sock"` | Connects via a Unix domain socket |
| `xds:///` | `"xds:///my-service"` | Resolves via xDS control plane (Envoy/Istio) |
| none | `"127.0.0.1:50051"` | Implicit DNS (default port `50051`) |

### Example (DNS):

```elixir
iex> {:ok, channel} = GRPC.Stub.connect("dns://orders.prod.svc.cluster.local:50051")
iex> request = Orders.GetOrderRequest.new(id: "123")
iex> {:ok, reply} = channel |> Orders.OrderService.Stub.get_order(request)
```

Check the [examples](examples) and [interop](interop) directories in the project's source code for some examples.
### Example (Unix socket):

```elixir
iex> {:ok, channel} = GRPC.Stub.connect("unix:/tmp/my.sock")
```

>__NOTE__: When using `DNS` or `xDS` targets, the connection layer periodically refreshes endpoints.
---

## Compression and Metadata

You can specify message compression and attach default headers to all requests.

```elixir
iex> {:ok, channel} =
...> GRPC.Stub.connect("localhost:50051",
...> compressor: GRPC.Compressor.Gzip,
...> headers: [{"authorization", "Bearer my-token"}]
...> )
```

---

## Client Adapters

## Client Adapter and Configuration
By default, `GRPC.Stub.connect/2` uses the **Gun** adapter.
You can switch to **Mint** (pure Elixir HTTP/2) or other adapters as needed.

The default adapter used by `GRPC.Stub.connect/2` is `GRPC.Client.Adapter.Gun`. Another option is to use `GRPC.Client.Adapters.Mint` instead, like so:
### Using Mint Adapter

```elixir
GRPC.Stub.connect("localhost:50051",
# Use Mint adapter instead of default Gun
adapter: GRPC.Client.Adapters.Mint
)
iex> GRPC.Stub.connect("localhost:50051",
...> adapter: GRPC.Client.Adapters.Mint
...> )
```

The `GRPC.Client.Adapters.Mint` adapter accepts custom configuration. To do so, you can configure it from your mix application via:
You can configure adapter options globally via your application’s config:

```elixir
# File: your application's config file.
config :grpc, GRPC.Client.Adapters.Mint, custom_opts
# File: config/config.exs
config :grpc, GRPC.Client.Adapters.Mint,
timeout: 10_000,
transport_opts: [cacertfile: "/etc/ssl/certs/ca-certificates.crt"]
```

The accepted options for configuration are the ones listed on [Mint.HTTP.connect/4](https://hexdocs.pm/mint/Mint.HTTP.html#connect/4-options)
The accepted options are the same as [`Mint.HTTP.connect/4`](https://hexdocs.pm/mint/Mint.HTTP.html#connect/4-options).

---

### **HTTP Transcoding**

Expand Down
8 changes: 4 additions & 4 deletions benchmark/lib/grpc/core/stats.pb.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Grpc.Core.Bucket do
@moduledoc false

use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3
use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3

field :start, 1, type: :double
field :count, 2, type: :uint64
Expand All @@ -10,15 +10,15 @@ end
defmodule Grpc.Core.Histogram do
@moduledoc false

use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3
use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3

field :buckets, 1, repeated: true, type: Grpc.Core.Bucket
end

defmodule Grpc.Core.Metric do
@moduledoc false

use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3
use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3

oneof :value, 0

Expand All @@ -30,7 +30,7 @@ end
defmodule Grpc.Core.Stats do
@moduledoc false

use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3
use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3

field :metrics, 1, repeated: true, type: Grpc.Core.Metric
end
20 changes: 20 additions & 0 deletions benchmark/lib/grpc/testing/benchmark_service.pb.ex
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
defmodule Grpc.Testing.BenchmarkService.Service do
@moduledoc false

use GRPC.Service, name: "grpc.testing.BenchmarkService", protoc_gen_elixir_version: "0.14.0"

rpc :UnaryCall, Grpc.Testing.SimpleRequest, Grpc.Testing.SimpleResponse

rpc :StreamingCall, stream(Grpc.Testing.SimpleRequest), stream(Grpc.Testing.SimpleResponse)

rpc :StreamingFromClient, stream(Grpc.Testing.SimpleRequest), Grpc.Testing.SimpleResponse

rpc :StreamingFromServer, Grpc.Testing.SimpleRequest, stream(Grpc.Testing.SimpleResponse)

rpc :StreamingBothWays, stream(Grpc.Testing.SimpleRequest), stream(Grpc.Testing.SimpleResponse)
end

defmodule Grpc.Testing.BenchmarkService.Stub do
@moduledoc false

use GRPC.Stub, service: Grpc.Testing.BenchmarkService.Service
end
Loading
Loading