Packages
bandit
0.6.1
1.12.0
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
retired
1.9.0
1.8.0
1.7.0
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-pre.18
1.0.0-pre.17
1.0.0-pre.16
1.0.0-pre.15
1.0.0-pre.14
1.0.0-pre.13
1.0.0-pre.12
1.0.0-pre.11
1.0.0-pre.10
1.0.0-pre.9
1.0.0-pre.8
1.0.0-pre.7
1.0.0-pre.6
1.0.0-pre.5
1.0.0-pre.4
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
A pure-Elixir HTTP server built for Plug & WebSock apps
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/bandit/http1/handler.ex
defmodule Bandit.HTTP1.Handler do
@moduledoc false
# An HTTP 1.0 & 1.1 Thousand Island Handler
use ThousandIsland.Handler
alias Bandit.HTTP1.Adapter
# credo:disable-for-this-file Credo.Check.Design.AliasUsage
@impl ThousandIsland.Handler
def handle_data(data, socket, %{plug: plug} = state) do
with req <- %Adapter{socket: socket, buffer: data},
{:ok, conn} <- build_conn(req),
{:ok, conn} <- call_plug(conn, plug),
{:ok, :no_upgrade} <- maybe_upgrade(conn),
{:ok, conn} <- commit_response(conn, plug) do
case keepalive?(conn) do
true -> {:continue, state}
false -> {:close, state}
end
else
{:error, reason} ->
{:error, reason, state}
{:ok, :websocket, upgrade_opts} ->
{:switch, Bandit.WebSocket.Handler, Map.put(state, :upgrade_opts, upgrade_opts)}
end
end
defp build_conn(req) do
case Adapter.read_headers(req) do
{:ok, headers, method, request_target, req} ->
%{address: remote_ip} = Adapter.get_peer_data(req)
# Parse a string to build a URI struct. This is quite a hack. In general, canonicalizing
# URIs is a delicate process & rather than building a half-baked implementation here it's
# better to leave a simple and ugly hack in place so that future improvements are obvious.
# Future paths here are discussed at https://github.com/elixir-plug/plug/issues/948)
{"host", host} = List.keyfind(headers, "host", 0, {"host", nil})
scheme = if Adapter.secure?(req), do: :https, else: :http
uri = build_uri(scheme, host, request_target)
{:ok, Plug.Conn.Adapter.conn({Adapter, req}, method, uri, remote_ip, headers)}
{:error, :timeout} ->
attempt_to_send_fallback(req, 408)
{:error, "timeout reading request"}
{:error, reason} ->
attempt_to_send_fallback(req, 400)
{:error, reason}
end
end
# Build URI dependent on request target type
defp build_uri(scheme, host, {:abs_path, path}),
do: URI.parse("#{scheme}://#{host}#{path}")
defp build_uri(_scheme, _host, {:absoluteURI, scheme, host, :undefined, path}),
do: URI.parse("#{scheme}://#{host}#{path}")
defp build_uri(_scheme, _host, {:absoluteURI, scheme, host, port, path}),
do: URI.parse("#{scheme}://#{host}:#{port}#{path}")
defp build_uri(scheme, host, :*) do
URI.parse("#{scheme}://#{host}/*")
|> Map.put(:path, "*")
end
defp call_plug(%Plug.Conn{adapter: {Adapter, req}} = conn, {plug, plug_opts}) do
{:ok, plug.call(conn, plug_opts)}
rescue
exception ->
# Raise here so that users can see useful stacktraces
attempt_to_send_fallback(req, 500)
reraise(exception, __STACKTRACE__)
end
defp maybe_upgrade(
%Plug.Conn{
state: :upgraded,
adapter:
{Adapter, %Adapter{upgrade: {:websocket, {websock, websock_opts, connection_opts}}}}
} = conn
) do
# We can safely unset the state, since we match on :upgraded above
case Bandit.WebSocket.Handshake.handshake(%{conn | state: :unset}, connection_opts) do
{:ok, connection_opts} ->
{:ok, :websocket, {websock, websock_opts, connection_opts}}
{:error, reason} ->
%{conn | state: :unset} |> Plug.Conn.send_resp(400, reason)
{:error, reason}
end
end
defp maybe_upgrade(_conn), do: {:ok, :no_upgrade}
defp commit_response(conn, plug) do
case conn do
%Plug.Conn{state: :unset} ->
{:error, "Plug did not send a response"}
%Plug.Conn{state: :set} ->
{:ok, Plug.Conn.send_resp(conn)}
%Plug.Conn{state: :chunked, adapter: {Adapter, req}} ->
Adapter.chunk(req, "")
{:ok, conn}
%Plug.Conn{} ->
{:ok, conn}
other ->
{:error, "Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}"}
end
end
defp keepalive?(%Plug.Conn{adapter: {_, req}}), do: Adapter.keepalive?(req)
defp attempt_to_send_fallback(req, code) do
Adapter.send_resp(req, code, [], <<>>)
rescue
_ -> :ok
end
def handle_info({:plug_conn, :sent}, state), do: {:noreply, state}
def handle_info({:EXIT, _pid, :normal}, state), do: {:noreply, state}
end