Packages
raxx
0.8.2
1.1.0
1.0.1
1.0.0
1.0.0-rc.3
1.0.0-rc.2
retired
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.18.1
0.18.0
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
retired
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.14
0.14.13
0.14.12
0.14.11
0.14.10
0.14.9
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.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
0.0.1
Interface for HTTP webservers, frameworks and clients.
Current section
Files
Jump to
Current section
Files
lib/ace/http/handler.ex
# TODO Rename on next breaking release
defmodule Raxx.Adapters.Ace.Handler do
@moduledoc false
def init(conn, app) do
partial = {:start_line, conn}
buffer = ""
# Need to keep track of conn for keep-alive, 4th spot might also be where to keep upgrade
{:nosend, {app, partial, buffer}}
end
# If streaming response all new packets should be added to the buffer
# States are
# - basic (all new packets slowly build a request, dispatched as soon as ready)
# - chunked (all new packets are buffered)
# - streaming/websockets (new packets are streamed to some process)
# - create an app protocol so that it can be called for many updates
# %Basic{app: {mod, state}}
# %Chunked{mod: mod}
# Server.handle_request(app, request)
# erlang deliveres messages in order so assume that info messages arrive in order.
def handle_packet(packet, {app, partial, buffer}) do
case process_buffer(buffer <> packet, partial) do
{:more, partial, buffer} ->
{:nosend, {app, partial, buffer}}
{:ok, request, buffer} ->
{mod, state} = app
# call process_request function
case mod.handle_request(request, state) do
%{body: body, headers: headers, status: status_code} ->
raw = [
HTTP.StatusLine.status_line(status_code),
header_lines(headers),
"\r\n",
body
]
# Check keep alive status
# {:keep_alive, response} for 100
# {:continue, response} for 100
# {:close, response}
{:send, raw, {app, {:start_line, %{}}, buffer}}
upgrade = %Raxx.Chunked{} ->
headers = upgrade.headers
headers = if !List.keymember?(headers, "content-type", 0) do
headers ++ [{"content-type", "text/plain"}]
end || headers
headers = headers ++ [{"transfer-encoding", "chunked"}]
response = [
HTTP.StatusLine.status_line(200),
header_lines(headers),
"\r\n"
]
# make sure next requests can keep coming in.
# QUERY will a client keep sending request content if the response is chunked
{:send, response, {upgrade, request, buffer}}
end
end
end
# Move to `Raxx.Headers` or `HTTP.Headers`
defp header_lines(headers) do
Enum.map(headers, &header_line/1)
end
defp header_line({field_name, field_value}) do
"#{field_name}: #{field_value}\r\n"
end
def handle_info(message, {%Raxx.Chunked{app: {mod, state}}, partial, buffer}) do
case mod.handle_info(message, state) do
{:chunk, data, state} ->
{:send, Raxx.Chunked.to_packet(data), {%Raxx.Chunked{app: {mod, state}}, partial, buffer}}
{:close, state} ->
{:send, Raxx.Chunked.end_chunk, {%Raxx.Chunked{app: {mod, state}}, partial, buffer}}
end
end
def terminate(_reason, {_app, _partial, _buffer}) do
:ok
end
# Process part sould look like a function that you can pass to reduce
def process_buffer(buffer, {:start_line, conn}) do
case :erlang.decode_packet(:http_bin, buffer, []) do
{:more, :undefined} ->
{:more, {:start_line, conn}, buffer}
{:ok, {:http_request, method, {:abs_path, path_string}, _version}, rest} ->
%{path: path, query: query_string} = URI.parse(path_string)
{:ok, query} = URI2.Query.decode(query_string || "")
path = Raxx.Request.split_path(path)
request = %Raxx.Request{method: method, path: path, query: query, headers: []}
process_buffer(rest, {:headers, request})
end
end
def process_buffer(buffer, {:headers, request}) do
case :erlang.decode_packet(:httph_bin, buffer, []) do
{:more, :undefined} ->
{:more, {:headers, request}, buffer}
# Key values is binary for unknown headers, atom and capitalised for known.
{:ok, {:http_header, _, key, _, value}, rest} ->
process_buffer(rest, {:headers, add_header(request, key, value)})
{:ok, :http_eoh, rest} ->
process_buffer(rest, {:body, request})
end
end
def process_buffer(buffer, {:body, request = %{headers: headers}}) do
case :proplists.get_value("content-length", headers) do
:undefined ->
{:ok, request, buffer}
raw ->
length = :erlang.binary_to_integer(raw)
case buffer do
<<body :: binary-size(length)>> <> rest ->
{:ok, %{request | body: body}, rest}
_ ->
{:more, {:body, request}, buffer}
end
end
end
def add_header(request = %{headers: headers}, :Host, location) do
[host, port] = case String.split(location, ":") do
[host, port] -> [host, :erlang.binary_to_integer(port)]
[host] -> [host, 80]
end
headers = headers ++ [{"host", location}]
%{request | headers: headers, host: host, port: port, scheme: "http"}
end
def add_header(request = %{headers: headers}, key, value) do
key = String.downcase("#{key}")
headers = headers ++ [{key, value}]
%{request | headers: headers}
end
end