Current section

Files

Jump to
bandit lib bandit http1 handler.ex
Raw

lib/bandit/http1/handler.ex

defmodule Bandit.HTTP1.Handler do
@moduledoc false
# An HTTP 1.0 & 1.1 Thousand Island Handler
use ThousandIsland.Handler
@impl ThousandIsland.Handler
def handle_data(data, socket, state) do
transport = %Bandit.HTTP1.Socket{socket: socket, buffer: data, opts: state.opts}
connection_span = ThousandIsland.Socket.telemetry_span(socket)
conn_data = Bandit.SocketHelpers.conn_data(socket)
case Bandit.Pipeline.run(transport, state.plug, connection_span, conn_data, state.opts) do
{:ok, transport} -> maybe_keepalive(transport, state)
{:error, _reason} -> {:close, state}
{:upgrade, _transport, :websocket, opts} -> do_websocket_upgrade(opts, state)
end
end
defp maybe_keepalive(transport, state) do
requests_processed = Map.get(state, :requests_processed, 0) + 1
request_limit = Keyword.get(state.opts.http_1, :max_requests, 0)
under_limit = request_limit == 0 || requests_processed < request_limit
if under_limit && transport.keepalive do
if Keyword.get(state.opts.http_1, :clear_process_dict, true), do: clear_process_dict()
gc_every_n_requests = Keyword.get(state.opts.http_1, :gc_every_n_keepalive_requests, 5)
if rem(requests_processed, gc_every_n_requests) == 0, do: :erlang.garbage_collect()
state = Map.put(state, :requests_processed, requests_processed)
# We have bytes that we've read but haven't yet processed, tail call handle_data to start
# reading the next request
if IO.iodata_length(transport.buffer) != 0 do
handle_data(transport.buffer, transport.socket, state)
else
{:continue, state}
end
else
{:close, state}
end
end
defp clear_process_dict do
Process.get_keys()
|> Enum.each(
&if &1 not in ~w[$ancestors $initial_call $process_label]a, do: Process.delete(&1)
)
end
defp do_websocket_upgrade(upgrade_opts, state) do
:erlang.garbage_collect()
{:switch, Bandit.WebSocket.Handler, Map.put(state, :upgrade_opts, upgrade_opts)}
end
def handle_info({:plug_conn, :sent}, {socket, state}),
do: {:noreply, {socket, state}, socket.read_timeout}
def handle_info({:EXIT, _pid, :normal}, {socket, state}),
do: {:noreply, {socket, state}, socket.read_timeout}
def handle_info(msg, {socket, state}) do
if Keyword.get(state.opts.http_1, :log_unknown_messages, false), do: log_no_handle_info(msg)
{:noreply, {socket, state}, socket.read_timeout}
end
def handle_info(msg, state) do
log_no_handle_info(msg)
{:noreply, state}
end
defp log_no_handle_info(msg) do
# Copied verbatim from lib/elixir/lib/gen_server.ex
proc =
case Process.info(self(), :registered_name) do
{_, []} -> self()
{_, name} -> name
end
:logger.error(
%{
label: {GenServer, :no_handle_info},
report: %{
module: __MODULE__,
message: msg,
name: proc
}
},
%{
domain: [:otp, :elixir],
error_logger: %{tag: :error_msg},
report_cb: &GenServer.format_report/1
}
)
end
end