Packages
ace
0.16.0
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/worker.ex
defmodule Ace.HTTP.Worker do
@moduledoc """
Run a Raxx application in isolation to handle a single HTTP exchange.
- The application consists of a behaviour module and initial state.
- An HTTP exchange is a single response to a single request.
See `Raxx.Server` for details on implementing a valid module.
A worker must be started for each message sent.
Even if messages are sent on a single connection,
e.g. HTTP pipelining or HTTP/2 streams.
"""
use GenServer
@typep application :: {module, any}
@doc """
Start a new worker linked to the calling process.
"""
@spec start_link(application) :: GenServer.on_start()
def start_link({module, config}) do
GenServer.start_link(__MODULE__, {module, config, nil}, [])
end
@doc false
def child_spec(app) do
# NOTE child spec is called only once so possibly expensive call to `Code.ensure_compiled?` is not repeated
# `start_link` in this module could be protected by dialyzer
case Raxx.verify_application(app) do
{:ok, app} ->
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [app]},
type: :worker,
restart: :temporary,
shutdown: 500
}
{:error, message} ->
raise message
end
end
## Server Callbacks
@impl GenServer
def handle_info({client, request = %Raxx.Request{}}, {mod, state, nil}) do
case client do
{:http1, endpoint, _id} ->
Process.monitor(endpoint)
{:stream, endpoint, _id, _ref} ->
Process.monitor(endpoint)
end
mod.handle_head(request, state)
|> normalise_reaction(state)
|> do_send({mod, state, client})
end
def handle_info({client, data = %Raxx.Data{}}, {mod, state, client}) do
mod.handle_data(data.data, state)
|> normalise_reaction(state)
|> do_send({mod, state, client})
end
def handle_info({client, tail = %Raxx.Tail{}}, {mod, state, client}) do
mod.handle_tail(tail.headers, state)
|> normalise_reaction(state)
|> do_send({mod, state, client})
end
def handle_info({:DOWN, _r, :process, p, reason}, {mod, state, client = {:http1, p, _id}}) do
{:stop, reason, {mod, state, client}}
end
def handle_info(
{:DOWN, _r, :process, p, reason},
{mod, state, client = {:stream, p, _id, _ref}}
) do
{:stop, reason, {mod, state, client}}
end
def handle_info(other, {mod, state, client}) do
mod.handle_info(other, state)
|> normalise_reaction(state)
|> do_send({mod, state, client})
end
defp normalise_reaction(response = %Raxx.Response{}, state) do
{[response], state}
end
defp normalise_reaction({parts, new_state}, _old_state) do
parts = Enum.map(parts, &fix_part/1)
{parts, new_state}
end
defp do_send({parts, new_state}, {mod, _old_state, client}) do
case parts do
[] ->
{:noreply, {mod, new_state, client}}
parts ->
case client do
ref = {:http1, pid, _count} ->
send(pid, {ref, parts})
stream = {:stream, _, _, _} ->
Enum.each(parts, fn part ->
Ace.HTTP2.send(stream, part)
end)
end
case List.last(parts) do
%{body: false} ->
{:stop, :normal, {mod, new_state, client}}
%Raxx.Tail{} ->
{:stop, :normal, {mod, new_state, client}}
%{body: body} when is_binary(body) ->
{:stop, :normal, {mod, new_state, client}}
_ ->
{:noreply, {mod, new_state, client}}
end
end
end
# TODO remove this special case
defp fix_part({:promise, request}) do
request =
request
|> Map.put(:scheme, request.scheme || :https)
{:promise, request}
end
defp fix_part(part) do
part
end
end