defmodule GRPC.Server.Supervisor do
@moduledoc """
A simple supervisor to start your servers.
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
children = [
{GRPC.Server.Supervisor, endpoint: Your.Endpoint, port: 50051, start_server: true, ...}]
Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
end
end
"""
use Supervisor
# TODO: remove this type after support Elixir 1.14 exclusively.
@type sup_flags() :: %{
strategy: Supervisor.strategy(),
intensity: non_neg_integer(),
period: pos_integer()
}
@default_adapter GRPC.Server.Adapters.Cowboy
require Logger
def start_link(endpoint) do
Supervisor.start_link(__MODULE__, endpoint)
end
@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, {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
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 opts[:start_server] do
[child_spec(endpoint_or_servers, opts[:port], opts)]
else
[]
end
Supervisor.init(children, strategy: :one_for_one)
end
@doc """
Return a child_spec to start server.
## Options
* `: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(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} =
try do
{endpoint, endpoint.__meta__(:servers)}
rescue
FunctionClauseError ->
Logger.warn(
"deprecated: servers as argument of GRPC.Server.Supervisor, please use GRPC.Endpoint"
)
{nil, endpoint}
end
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
servers = GRPC.Server.servers_to_map(servers)
adapter.child_spec(nil, servers, port, opts)
end
end