Packages
ace
0.15.4
0.19.0
0.18.10
0.18.9
0.18.8
0.18.7
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
retired
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
retired
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
HTTP web server and client, supports http1 and http2
Current section
Files
Jump to
Current section
Files
lib/ace/http/server.ex
defmodule Ace.HTTP.Server do
@moduledoc false
use GenServer
require Logger
defstruct [:worker_supervisor, :settings, :socket]
def child_spec({worker_supervisor, settings}) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [worker_supervisor, settings]},
type: :worker,
restart: :temporary,
shutdown: 500
}
end
@doc """
Start a new `Ace.HTTP.Server` linked to the calling process.
A server is started with a reference to a supervisor that can start HTTP workers.
The server process is returned immediatly.
This is allow a supervisor to start several servers without waiting for connections.
To accept a connection `accept_connection/2` must be called.
A provisioned server will remain in an awaiting state until accept is called.
"""
def start_link(worker_supervisor, settings \\ []) when is_pid(worker_supervisor) do
state = %__MODULE__{
worker_supervisor: worker_supervisor,
settings: settings,
socket: nil
}
GenServer.start_link(__MODULE__, state)
end
@doc """
Manage a client connect with server
Accept can only be called once for each server.
After a connection has been closed the server will terminate.
"""
def accept_connection(endpoint, listen_socket) do
GenServer.call(endpoint, {:accept, listen_socket}, :infinity)
end
def handle_call({:accept, {:tcp, listen_socket}}, from, state) do
case :gen_tcp.accept(listen_socket) do
{:ok, socket} ->
:ok = :inet.setopts(socket, active: :once)
state = %{state | socket: socket}
{:ok, worker} = Supervisor.start_child(state.worker_supervisor, [:the_channel])
monitor = Process.monitor(worker)
state = %Ace.HTTP1.Endpoint{
status: {:request, :response},
socket: {:tcp, socket},
# Worker and channel could live on same key, there is no channel without a worker
channel: {:http1, self(), 1},
worker: worker
}
GenServer.reply(from, {:ok, self()})
:gen_server.enter_loop(Ace.HTTP1.Endpoint, [], {"", state})
{:error, reason} ->
{:stop, :normal, {:error, reason}, state}
end
end
def handle_call({:accept, listen_socket}, from, state) do
case ssl_accept_connection(listen_socket, from, state) do
{:ok, socket} ->
# DEBT returns :ok or {:error, :closed}
:ssl.setopts(socket, active: :once)
state = %{state | socket: socket}
case :ssl.negotiated_protocol(socket) do
{:ok, "h2"} ->
{:ok, local_settings} = Ace.HTTP2.Settings.for_server(state.settings)
{:ok, default_server_settings} = Ace.HTTP2.Settings.for_server()
initial_settings_frame =
Ace.HTTP2.Settings.update_frame(local_settings, default_server_settings)
{:ok, default_client_settings} = Ace.HTTP2.Settings.for_client()
# TODO max table size
decode_context = Ace.HPack.new_context(4096)
encode_context = Ace.HPack.new_context(4096)
{:ok, {_, port}} = :ssl.sockname(listen_socket)
initial_state = %Ace.HTTP2.Connection{
socket: socket,
outbound_window: 65535,
local_settings: default_server_settings,
queued_settings: [local_settings],
remote_settings: default_client_settings,
# DEBT set when handshaking settings
decode_context: decode_context,
encode_context: encode_context,
streams: %{},
stream_supervisor: state.worker_supervisor,
next_local_stream_id: 2,
name: "SERVER (port: #{port})"
}
state = %{initial_state | next: :handshake}
Logger.debug("#{state.name} sent: #{inspect(initial_settings_frame)}")
:ok = :ssl.send(state.socket, Ace.HTTP2.Frame.serialize(initial_settings_frame))
:ssl.setopts(socket, active: :once)
:gen_server.enter_loop(Ace.HTTP2.Connection, [], {:pending, initial_state})
response when response in [{:ok, "http/1.1"}, {:error, :protocol_not_negotiated}] ->
{:ok, worker} = Supervisor.start_child(state.worker_supervisor, [:the_channel])
monitor = Process.monitor(worker)
state = %Ace.HTTP1.Endpoint{
status: {:request, :response},
socket: socket,
# Worker and channel could live on same key, there is no channel without a worker
channel: {:http1, self(), 1},
worker: worker
}
GenServer.reply(from, {:ok, self()})
:gen_server.enter_loop(Ace.HTTP1.Endpoint, [], {"", state})
end
{:error, :closed} ->
{:reply, {:error, :closed}, state}
end
end
defp ssl_accept_connection(listen_socket, from, state = %{socket: nil}) do
case :ssl.transport_accept(listen_socket) do
{:ok, socket} ->
case :ssl.ssl_accept(socket) do
:ok ->
{:ok, socket}
{:error, :closed} ->
{:error, :econnaborted}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
end