Packages
ace
0.16.7
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/http1/endpoint.ex
defmodule Ace.HTTP1.Endpoint do
@moduledoc false
require Logger
@packet_timeout 10000
alias Raxx.{Response, Data, Tail}
alias Ace.HTTP1
use GenServer
@enforce_keys [
:status,
:receive_state,
:socket,
:worker,
:monitor,
:channel,
:keep_alive
]
defstruct @enforce_keys
@impl GenServer
def init(args) do
{:ok, args}
end
@impl GenServer
def handle_call({:send, channel, parts}, from, state = %{channel: channel}) do
{outbound, state} =
Enum.reduce(parts, {"", state}, fn part, {buffer, state} ->
{:ok, {outbound, next_state}} = send_part(part, state)
{[buffer, outbound], next_state}
end)
Ace.Socket.send(state.socket, outbound)
case state.status do
{_, :complete} ->
GenServer.reply(from, {:ok, channel})
{:stop, :normal, state}
{_, _incomplete} ->
{:reply, {:ok, channel}, state}
end
end
@impl GenServer
def handle_info({t, s, packet}, state = %{socket: {t, s}}) do
case HTTP1.Parser.parse(packet, state.receive_state) do
{:ok, {parts, receive_state}} ->
Enum.each(parts, fn part ->
part = normalise_part(part, t)
send(state.worker, {state.channel, part})
end)
:ok = Ace.Socket.set_active(state.socket)
timeout = if HTTP1.Parser.done?(receive_state), do: :infinity, else: @packet_timeout
{:noreply, %{state | receive_state: receive_state}, timeout}
{:error, {:invalid_start_line, _line}} ->
{:ok, {outbound, new_state}} = send_part(Raxx.response(:bad_request), state)
Ace.Socket.send(state.socket, outbound)
{:stop, :normal, state}
{:error, {:invalid_header_line, _line}} ->
{:ok, {outbound, new_state}} = send_part(Raxx.response(:bad_request), state)
Ace.Socket.send(state.socket, outbound)
{:stop, :normal, state}
{:error, :start_line_too_long} ->
{:ok, {outbound, new_state}} = send_part(Raxx.response(:uri_too_long), state)
Ace.Socket.send(state.socket, outbound)
{:stop, :normal, new_state}
end
end
def handle_info(:timeout, state) do
{:ok, {outbound, new_state}} = send_part(Raxx.response(:request_timeout), state)
Ace.Socket.send(state.socket, outbound)
{:stop, :normal, new_state}
end
def handle_info({transport, _socket}, state)
when transport in [:tcp_closed, :ssl_closed] do
{:stop, :normal, state}
end
# NOTE if any data already sent then canot send 500
def handle_info(
{:DOWN, _ref, :process, pid, _reason},
state = %{worker: pid, status: {_, :response}}
) do
{:ok, {outbound, new_state}} = send_part(Raxx.response(:internal_server_error), state)
Ace.Socket.send(state.socket, outbound)
{:stop, :normal, new_state}
end
def handle_info({:DOWN, _ref, :process, pid, reason}, state = %{worker: pid}) do
{:stop, reason, state}
end
defp normalise_part(request = %{scheme: nil}, :tcp), do: %{request | scheme: :http}
defp normalise_part(request = %{scheme: nil}, :ssl), do: %{request | scheme: :https}
defp normalise_part(part, _transport), do: part
defp send_part(response = %Response{body: true}, state = %{status: {up, :response}}) do
case content_length(response) do
nil ->
headers = [{"connection", "close"}, {"transfer-encoding", "chunked"} | response.headers]
new_status = {up, :chunked_body}
new_state = %{state | status: new_status}
outbound = HTTP1.serialize_response(response.status, headers, "")
{:ok, {outbound, new_state}}
content_length when content_length > 0 ->
headers = [{"connection", "close"} | response.headers]
new_status = {up, {:body, content_length}}
new_state = %{state | status: new_status}
outbound = HTTP1.serialize_response(response.status, headers, "")
{:ok, {outbound, new_state}}
end
end
defp send_part(response = %Response{body: false}, state = %{status: {up, :response}}) do
case content_length(response) do
nil ->
headers = [{"connection", "close"}, {"content-length", "0"} | response.headers]
new_status = {up, :complete}
new_state = %{state | status: new_status}
outbound = HTTP1.serialize_response(response.status, headers, "")
{:ok, {outbound, new_state}}
end
end
defp send_part(response = %Response{body: body}, state = %{status: {up, :response}})
when is_binary(body) do
case content_length(response) do
nil ->
content_length = :erlang.iolist_size(body) |> to_string
headers = [{"connection", "close"}, {"content-length", content_length} | response.headers]
new_status = {up, :complete}
new_state = %{state | status: new_status}
outbound = HTTP1.serialize_response(response.status, headers, response.body)
{:ok, {outbound, new_state}}
_content_length ->
headers = [{"connection", "close"} | response.headers]
new_status = {up, :complete}
new_state = %{state | status: new_status}
outbound = HTTP1.serialize_response(response.status, headers, response.body)
{:ok, {outbound, new_state}}
end
end
defp send_part(data = %Data{}, state = %{status: {up, {:body, remaining}}}) do
remaining = remaining - :erlang.iolist_size(data.data)
new_status = {up, {:body, remaining}}
new_state = %{state | status: new_status}
{:ok, {[data.data], new_state}}
end
defp send_part(%Tail{headers: []}, state = %{status: {up, {:body, 0}}}) do
new_status = {up, :complete}
new_state = %{state | status: new_status}
{:ok, {[], new_state}}
end
defp send_part(%Data{data: data}, state = %{status: {_up, :chunked_body}}) do
chunk = HTTP1.serialize_chunk(data)
{:ok, {[chunk], state}}
end
defp send_part(%Tail{headers: []}, state = %{status: {up, :chunked_body}}) do
chunk = HTTP1.serialize_chunk("")
new_status = {up, :complete}
new_state = %{state | status: new_status}
{:ok, {[chunk], new_state}}
end
defp content_length(%{headers: headers}) do
case :proplists.get_value("content-length", headers) do
:undefined ->
nil
binary ->
{content_length, ""} = Integer.parse(binary)
content_length
end
end
end