Skip to main content

src/fcgi.erl

-module(fcgi).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fcgi.gleam").
-export([new/1, listen_unix/2, listen_tcp/3, max_body_size/2, body_read_timeout/2, read_all/1, bytes/1, send_file/3, stream/1, string/1, send_chunk/2, to_http_request/2, start/1, supervised/1]).
-export_type([address/0, builder/1, context/0, read/0, read_error/0, file_error/0, response_data/0, stream_sender/0, start_error/0, server/0, connection/0, next_request/0, after_response/0, request_error/0, partial_request/0, body_context/0, body_snapshot/0, handle/0, socket/0, socket_error/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(" FastCGI Responder server.\n").

-opaque address() :: {path_address, binary()} |
    {tcp_address, binary(), integer()}.

-opaque builder(GQY) :: {builder,
        fun((gleam@http@request:request(fun(() -> {ok, read()} |
            {error, read_error()})), context()) -> gleam@http@response:response(response_data())),
        GQY,
        integer(),
        integer()}.

-type context() :: {context,
        gleam@option:option(binary()),
        gleam@option:option(integer()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@dict:dict(binary(), binary())}.

-type read() :: {chunk,
        bitstring(),
        fun(() -> {ok, read()} | {error, read_error()})} |
    'end'.

-type read_error() :: client_disconnected |
    read_timeout |
    body_too_large |
    request_aborted.

-type file_error() :: {file_not_found, binary()} |
    {file_access_denied, binary()} |
    {file_is_directory, binary()} |
    {file_other, binary(), binary()} |
    {invalid_range, integer(), gleam@option:option(integer())}.

-opaque response_data() :: {bytes, gleam@bytes_tree:bytes_tree()} |
    {file, handle(), integer(), integer()} |
    {stream, fun((stream_sender()) -> nil)}.

-opaque stream_sender() :: {stream_sender, socket(), integer()}.

-type start_error() :: {listener_error, binary()} |
    {invalid_max_body_size, integer()} |
    {invalid_body_read_timeout, integer()}.

-type server() :: {server,
        gleam@otp@static_supervisor:supervisor(),
        gleam@option:option(integer())}.

-type connection() :: {connection,
        socket(),
        integer(),
        integer(),
        fun((gleam@http@request:request(fun(() -> {ok, read()} |
            {error, read_error()})), context()) -> gleam@http@response:response(response_data()))}.

-type next_request() :: {ready,
        fcgi@internal@responder:state(),
        integer(),
        bitstring(),
        list(fcgi@internal@responder:event()),
        boolean()} |
    closed.

-type after_response() :: continue | {drain_body, body_snapshot()}.

-type request_error() :: missing_method | {invalid_method, binary()}.

-type partial_request() :: {partial_request,
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        list({binary(), binary()}),
        context()}.

-type body_context() :: {body_context,
        fcgi@internal@responder:state(),
        bitstring(),
        boolean(),
        boolean(),
        connection(),
        gleam@erlang@process:subject(body_snapshot())}.

-type body_snapshot() :: {body_snapshot,
        fcgi@internal@responder:state(),
        boolean(),
        boolean(),
        boolean()}.

-type handle() :: any().

-type socket() :: any().

-type socket_error() :: {path_exists, binary()} |
    {invalid_host, binary()} |
    {posix, gleam@erlang@atom:atom_()}.

-file("src/fcgi.gleam", 64).
?DOC(
    " Build a new FastCGI server with the given handler.\n"
    "\n"
    " The handler is invoked once `Params` is fully received. The request\n"
    " body is delivered incrementally via a `BodyReader` in `req.body`;\n"
    " call it to obtain the first `Read` and thread `consume` to advance,\n"
    " or pass it to `read_all` for the buffered case.\n"
    "\n"
    " Default: 256 MiB max body, 30 s body read timeout.\n"
).
-spec new(
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data()))
) -> builder(nil).
new(Handler) ->
    {builder, Handler, nil, 268435456, 30000}.

-file("src/fcgi.gleam", 74).
?DOC(" Set the Unix domain socket path the server listens on.\n").
-spec listen_unix(builder(any()), binary()) -> builder(address()).
listen_unix(Builder, Path) ->
    {builder,
        erlang:element(2, Builder),
        {path_address, Path},
        erlang:element(4, Builder),
        erlang:element(5, Builder)}.

-file("src/fcgi.gleam", 89).
?DOC(
    " Set the TCP `host` and `port` the server listens on. `host` must be a\n"
    " numeric IP literal: either IPv4 (e.g. `\"127.0.0.1\"`, `\"0.0.0.0\"`) or\n"
    " IPv6 (e.g. `\"::1\"`, `\"::\"`).\n"
).
-spec listen_tcp(builder(any()), binary(), integer()) -> builder(address()).
listen_tcp(Builder, Host, Port) ->
    {builder,
        erlang:element(2, Builder),
        {tcp_address, Host, Port},
        erlang:element(4, Builder),
        erlang:element(5, Builder)}.

-file("src/fcgi.gleam", 112).
?DOC(
    " Set the maximum body bytes the server will deliver to the handler in\n"
    " total across all body reads. Must be `>= 0`; `start` returns\n"
    " `InvalidMaxBodySize(bytes)` for negative values.\n"
    "\n"
    " When the peer sends more than this many bytes, the next body read\n"
    " returns `Error(BodyTooLarge)`. The handler may respond as it sees fit,\n"
    " but the connection is closed after the response is sent because\n"
    " remaining body bytes cannot be safely drained.\n"
    "\n"
    " Default: 256 MiB.\n"
).
-spec max_body_size(builder(GRR), integer()) -> builder(GRR).
max_body_size(Builder, Bytes) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        Bytes,
        erlang:element(5, Builder)}.

-file("src/fcgi.gleam", 124).
?DOC(
    " Set how long the server waits for the next stdin or params record\n"
    " before giving up. Must be `> 0`. Applies between successive socket\n"
    " reads, including the wait for the first record after `accept`, not\n"
    " to the request as a whole. Returns `Error(ReadTimeout)` to body\n"
    " readers. Default: 30,000 ms.\n"
).
-spec body_read_timeout(builder(GRU), integer()) -> builder(GRU).
body_read_timeout(Builder, Milliseconds) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        Milliseconds}.

-file("src/fcgi.gleam", 201).
-spec read_all_loop(
    {ok, read()} | {error, read_error()},
    gleam@bytes_tree:bytes_tree()
) -> {ok, gleam@bytes_tree:bytes_tree()} | {error, read_error()}.
read_all_loop(Read, Acc) ->
    case Read of
        {error, Reason} ->
            {error, Reason};

        {ok, 'end'} ->
            {ok, Acc};

        {ok, {chunk, Data, Consume}} ->
            read_all_loop(Consume(), gleam@bytes_tree:append(Acc, Data))
    end.

-file("src/fcgi.gleam", 197).
?DOC(
    " Buffer the entire body into a `BytesTree`. The server's\n"
    " `max_body_size` setting is the upper bound; the underlying reader\n"
    " returns `BodyTooLarge` if the peer exceeds it.\n"
    "\n"
    " The buffered length reflects what the upstream proxy actually sent,\n"
    " not what `CONTENT_LENGTH` advertised.\n"
).
-spec read_all(fun(() -> {ok, read()} | {error, read_error()})) -> {ok,
        gleam@bytes_tree:bytes_tree()} |
    {error, read_error()}.
read_all(Read) ->
    read_all_loop(Read(), gleam@bytes_tree:new()).

-file("src/fcgi.gleam", 243).
?DOC(
    " Build an in-memory response body. The whole `BytesTree` is sent in\n"
    " one or more `STDOUT` records.\n"
).
-spec bytes(gleam@bytes_tree:bytes_tree()) -> response_data().
bytes(Content) ->
    {bytes, Content}.

-file("src/fcgi.gleam", 252).
?DOC(
    " Open a file and return a response body that streams it via\n"
    " `file:sendfile/5` when the response is sent.\n"
    "\n"
    " Returns `FileError` if the file is missing, inaccessible, a\n"
    " directory, or if `offset` or `limit` is negative.\n"
).
-spec send_file(binary(), integer(), gleam@option:option(integer())) -> {ok,
        response_data()} |
    {error, file_error()}.
send_file(Path, Offset, Limit) ->
    gleam@bool:guard(
        (Offset < 0) orelse (gleam@option:unwrap(Limit, 0) < 0),
        {error, {invalid_range, Offset, Limit}},
        fun() ->
            gleam@result:'try'(
                fcgi_ffi:open_and_size(Path),
                fun(_use0) ->
                    {Handle, Total_size} = _use0,
                    Max_length = gleam@option:unwrap(Limit, Total_size),
                    Length = gleam@int:clamp(Total_size - Offset, 0, Max_length),
                    {ok, {file, Handle, Offset, Length}}
                end
            )
        end
    ).

-file("src/fcgi.gleam", 273).
?DOC(
    " Build a streaming response body. The server calls `producer(sender)`\n"
    " after sending the response headers; each call to `send_chunk(sender,\n"
    " data)` writes one or more `STDOUT` records to the upstream proxy.\n"
    "\n"
    " A panic raised by `producer` is caught; the response end records are\n"
    " still emitted so the upstream proxy sees a clean end-of-request.\n"
).
-spec stream(fun((stream_sender()) -> nil)) -> response_data().
stream(Producer) ->
    {stream, Producer}.

-file("src/fcgi.gleam", 278).
?DOC(" Build a response body from a `String`.\n").
-spec string(binary()) -> response_data().
string(Content) ->
    {bytes, gleam_stdlib:wrap_list(Content)}.

-file("src/fcgi.gleam", 1136).
-spec send_if_nonempty(socket(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} |
    {error, nil}.
send_if_nonempty(Socket, Bytes) ->
    case erlang:iolist_size(Bytes) of
        0 ->
            {ok, nil};

        _ ->
            fcgi_ffi:send(Socket, Bytes)
    end.

-file("src/fcgi.gleam", 287).
?DOC(
    " Emit a chunk of body bytes from inside a `Stream` producer.\n"
    "\n"
    " Returns `Ok(Nil)` when the chunk is written, or `Error(Nil)` when the\n"
    " underlying socket write fails (for example, the upstream proxy has\n"
    " disconnected).\n"
).
-spec send_chunk(stream_sender(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} |
    {error, nil}.
send_chunk(Sender, Data) ->
    {stream_sender, Socket, Request_id} = Sender,
    send_if_nonempty(
        Socket,
        fcgi@internal@responder:encode_stdout_chunk(Request_id, Data)
    ).

-file("src/fcgi.gleam", 550).
-spec describe_transport_error(socket_error()) -> binary().
describe_transport_error(Error) ->
    case Error of
        {posix, Reason} ->
            erlang:atom_to_binary(Reason);

        {path_exists, _} ->
            <<"unexpected transport error"/utf8>>;

        {invalid_host, _} ->
            <<"unexpected transport error"/utf8>>
    end.

-file("src/fcgi.gleam", 529).
-spec cleanup_socket(socket(), address()) -> nil.
cleanup_socket(Socket, Address) ->
    fcgi_ffi:close_socket(Socket),
    case Address of
        {path_address, Path} ->
            fcgi_ffi:delete_path(Path);

        {tcp_address, _, _} ->
            nil
    end.

-file("src/fcgi.gleam", 537).
-spec describe_address(address()) -> binary().
describe_address(Address) ->
    case Address of
        {path_address, Path} ->
            <<"unix:"/utf8, Path/binary>>;

        {tcp_address, Host, Port} ->
            Host@1 = case gleam_stdlib:contains_string(Host, <<":"/utf8>>) of
                true ->
                    <<<<"["/utf8, Host/binary>>/binary, "]"/utf8>>;

                false ->
                    Host
            end,
            <<<<Host@1/binary, ":"/utf8>>/binary,
                (erlang:integer_to_binary(Port))/binary>>
    end.

-file("src/fcgi.gleam", 510).
-spec start_supervisor(
    gleam@otp@static_supervisor:builder(),
    socket(),
    address()
) -> {ok, gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
    {error, start_error()}.
start_supervisor(Builder, Socket, Address) ->
    case gleam@otp@static_supervisor:start(Builder) of
        {error, Error} ->
            cleanup_socket(Socket, Address),
            Reason@1 = case Error of
                init_timeout ->
                    <<"supervisor init timeout"/utf8>>;

                {init_failed, Reason} ->
                    Reason;

                {init_exited, _} ->
                    <<"supervisor init exited"/utf8>>
            end,
            {error, {listener_error, Reason@1}};

        {ok, Started} ->
            {ok, Started}
    end.

-file("src/fcgi.gleam", 603).
-spec start_connection(
    socket(),
    gleam@otp@factory_supervisor:supervisor(connection(), gleam@erlang@process:subject(nil)),
    integer(),
    integer(),
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data()))
) -> nil.
start_connection(
    Client_socket,
    Factory,
    Max_body_size,
    Body_read_timeout_ms,
    Handler
) ->
    Connection = {connection,
        Client_socket,
        Max_body_size,
        Body_read_timeout_ms,
        Handler},
    case gleam@otp@factory_supervisor:start_child(Factory, Connection) of
        {error, _} ->
            fcgi_ffi:close_socket(Client_socket);

        {ok, Started} ->
            case fcgi_ffi:controlling_process(
                Client_socket,
                erlang:element(2, Started)
            ) of
                {error, _} ->
                    fcgi_ffi:close_socket(Client_socket),
                    gleam@erlang@process:send_exit(erlang:element(2, Started));

                {ok, nil} ->
                    gleam@erlang@process:send(erlang:element(3, Started), nil)
            end
    end.

-file("src/fcgi.gleam", 557).
-spec accept_loop(
    socket(),
    gleam@otp@factory_supervisor:supervisor(connection(), gleam@erlang@process:subject(nil)),
    integer(),
    integer(),
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data()))
) -> nil.
accept_loop(
    Listen_socket,
    Factory,
    Max_body_size,
    Body_read_timeout_ms,
    Handler
) ->
    case gen_tcp:accept(Listen_socket) of
        {error, Reason} ->
            case erlang:atom_to_binary(Reason) of
                <<"closed"/utf8>> ->
                    logging:log(
                        info,
                        <<"acceptor stopping: listener closed"/utf8>>
                    );

                Name ->
                    logging:log(
                        warning,
                        <<<<"accept failed: "/utf8, Name/binary>>/binary,
                            ", retrying"/utf8>>
                    ),
                    gleam_erlang_ffi:sleep(100),
                    accept_loop(
                        Listen_socket,
                        Factory,
                        Max_body_size,
                        Body_read_timeout_ms,
                        Handler
                    )
            end;

        {ok, Client_socket} ->
            start_connection(
                Client_socket,
                Factory,
                Max_body_size,
                Body_read_timeout_ms,
                Handler
            ),
            accept_loop(
                Listen_socket,
                Factory,
                Max_body_size,
                Body_read_timeout_ms,
                Handler
            )
    end.

-file("src/fcgi.gleam", 486).
-spec acceptor_supervised(
    socket(),
    gleam@erlang@process:name(gleam@otp@factory_supervisor:message(connection(), gleam@erlang@process:subject(nil))),
    integer(),
    integer(),
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data()))
) -> gleam@otp@supervision:child_specification(nil).
acceptor_supervised(
    Socket,
    Factory_name,
    Max_body_size,
    Body_read_timeout_ms,
    Handler
) ->
    _pipe = gleam@otp@supervision:worker(
        fun() ->
            Factory = gleam@otp@factory_supervisor:get_by_name(Factory_name),
            Pid = proc_lib:spawn_link(
                fun() ->
                    accept_loop(
                        Socket,
                        Factory,
                        Max_body_size,
                        Body_read_timeout_ms,
                        Handler
                    )
                end
            ),
            {ok, {started, Pid, nil}}
        end
    ),
    gleam@otp@supervision:restart(_pipe, transient).

-file("src/fcgi.gleam", 1205).
-spec collect_body_events_loop(
    list(fcgi@internal@responder:event()),
    bitstring(),
    boolean(),
    boolean()
) -> {bitstring(), boolean(), boolean()}.
collect_body_events_loop(Events, Data, Ended, Overflowed) ->
    case Events of
        [] ->
            {Data, Ended, Overflowed};

        [{body_chunk, Chunk} | Rest] ->
            collect_body_events_loop(
                Rest,
                <<Data/bitstring, Chunk/bitstring>>,
                Ended,
                Overflowed
            );

        [body_end | Rest@1] ->
            collect_body_events_loop(Rest@1, Data, true, Overflowed);

        [body_too_large | Rest@2] ->
            collect_body_events_loop(Rest@2, Data, true, true);

        [{request_ready, _, _, _} | Rest@3] ->
            collect_body_events_loop(Rest@3, Data, Ended, Overflowed)
    end.

-file("src/fcgi.gleam", 1199).
-spec collect_body_events(list(fcgi@internal@responder:event())) -> {bitstring(),
    boolean(),
    boolean()}.
collect_body_events(Events) ->
    collect_body_events_loop(Events, <<>>, false, false).

-file("src/fcgi.gleam", 711).
-spec step_and_flush(connection(), fcgi@internal@responder:state(), bitstring()) -> fcgi@internal@responder:outcome().
step_and_flush(Connection, State, Bytes) ->
    Outcome = fcgi@internal@responder:step(
        State,
        Bytes,
        erlang:element(3, Connection)
    ),
    _ = send_if_nonempty(
        erlang:element(2, Connection),
        erlang:element(3, Outcome)
    ),
    Outcome.

-file("src/fcgi.gleam", 1276).
-spec step_body(body_snapshot(), bitstring(), connection()) -> {bitstring(),
    body_snapshot()}.
step_body(Prior, Bytes, Connection) ->
    Outcome = step_and_flush(Connection, erlang:element(2, Prior), Bytes),
    {Data, Ended, Overflowed} = collect_body_events(erlang:element(4, Outcome)),
    Aborted = case erlang:element(5, Outcome) of
        close_connection ->
            true;

        wait_for_more ->
            false
    end,
    Snapshot = {body_snapshot,
        erlang:element(2, Outcome),
        erlang:element(3, Prior) orelse Ended,
        erlang:element(4, Prior) orelse Overflowed,
        erlang:element(5, Prior) orelse Aborted},
    {Data, Snapshot}.

-file("src/fcgi.gleam", 1298).
-spec recv_connection(connection()) -> {ok, bitstring()} | {error, read_error()}.
recv_connection(Connection) ->
    case fcgi_ffi:recv(
        erlang:element(2, Connection),
        0,
        erlang:element(4, Connection)
    ) of
        {error, {posix, Reason}} ->
            case erlang:atom_to_binary(Reason) of
                <<"timeout"/utf8>> ->
                    {error, read_timeout};

                _ ->
                    {error, client_disconnected}
            end;

        {error, _} ->
            {error, client_disconnected};

        {ok, <<>>} ->
            {error, client_disconnected};

        {ok, More} ->
            {ok, More}
    end.

-file("src/fcgi.gleam", 1311).
-spec drain_body_loop(body_snapshot(), connection()) -> {ok,
        fcgi@internal@responder:state()} |
    {error, nil}.
drain_body_loop(Snap, Connection) ->
    gleam@bool:guard(
        erlang:element(4, Snap) orelse erlang:element(5, Snap),
        {error, nil},
        fun() ->
            gleam@bool:guard(
                erlang:element(3, Snap),
                {ok, erlang:element(2, Snap)},
                fun() -> case recv_connection(Connection) of
                        {error, _} ->
                            {error, nil};

                        {ok, More} ->
                            {_, Next} = step_body(Snap, More, Connection),
                            drain_body_loop(Next, Connection)
                    end end
            )
        end
    ).

-file("src/fcgi.gleam", 1131).
-spec send_padding(socket(), integer()) -> {ok, nil} | {error, nil}.
send_padding(Socket, Padding_length) ->
    gleam@bool:guard(
        Padding_length =:= 0,
        {ok, nil},
        fun() ->
            fcgi_ffi:send(Socket, <<0:(erlang:max(0, (Padding_length * 8)))>>)
        end
    ).

-file("src/fcgi.gleam", 1116).
-spec drain_sendfile_loop(socket(), handle(), integer(), integer()) -> {ok, nil} |
    {error, nil}.
drain_sendfile_loop(Socket, Handle, Offset, Remaining) ->
    gleam@bool:guard(
        Remaining =< 0,
        {ok, nil},
        fun() -> case fcgi_ffi:sendfile(Handle, Socket, Offset, Remaining) of
                {error, _} ->
                    {error, nil};

                {ok, 0} ->
                    {error, nil};

                {ok, Sent} ->
                    drain_sendfile_loop(
                        Socket,
                        Handle,
                        Offset + Sent,
                        Remaining - Sent
                    )
            end end
    ).

-file("src/fcgi.gleam", 1092).
-spec send_via_sendfile(socket(), integer(), handle(), integer(), integer()) -> {ok,
        nil} |
    {error, nil}.
send_via_sendfile(Socket, Request_id, Handle, Offset, Remaining) ->
    gleam@bool:guard(
        Remaining =< 0,
        {ok, nil},
        fun() ->
            Chunk_size = gleam@int:min(Remaining, 65535),
            {Header, Padding_length} = fcgi@internal@protocol:encode_stdout_record_header(
                Request_id,
                Chunk_size
            ),
            gleam@result:'try'(
                fcgi_ffi:send(Socket, Header),
                fun(_) ->
                    gleam@result:'try'(
                        drain_sendfile_loop(Socket, Handle, Offset, Chunk_size),
                        fun(_) ->
                            gleam@result:'try'(
                                send_padding(Socket, Padding_length),
                                fun(_) ->
                                    send_via_sendfile(
                                        Socket,
                                        Request_id,
                                        Handle,
                                        Offset + Chunk_size,
                                        Remaining - Chunk_size
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/fcgi.gleam", 1048).
-spec send_response(
    socket(),
    integer(),
    gleam@http@response:response(response_data())
) -> {ok, nil} | {error, nil}.
send_response(Socket, Request_id, Response) ->
    Headers = fcgi@internal@responder:encode_response_headers(
        Request_id,
        Response
    ),
    End_records = fcgi@internal@responder:encode_response_end_records(
        Request_id
    ),
    case erlang:element(4, Response) of
        {bytes, Data} ->
            Body = fcgi@internal@responder:encode_stdout_chunk(Request_id, Data),
            Combined = gleam_stdlib:identity([Headers, Body, End_records]),
            send_if_nonempty(Socket, Combined);

        {file, Handle, Offset, Length} ->
            exception_ffi:defer(
                fun() -> fcgi_ffi:close_file(Handle) end,
                fun() ->
                    gleam@result:'try'(
                        fcgi_ffi:send(Socket, Headers),
                        fun(_) ->
                            gleam@result:'try'(
                                send_via_sendfile(
                                    Socket,
                                    Request_id,
                                    Handle,
                                    Offset,
                                    Length
                                ),
                                fun(_) -> fcgi_ffi:send(Socket, End_records) end
                            )
                        end
                    )
                end
            );

        {stream, Producer} ->
            gleam@result:'try'(
                fcgi_ffi:send(Socket, Headers),
                fun(_) ->
                    Sender = {stream_sender, Socket, Request_id},
                    case exception_ffi:rescue(fun() -> Producer(Sender) end) of
                        {ok, _} ->
                            nil;

                        {error, Exception} ->
                            logging:log(
                                error,
                                <<"stream producer crashed: "/utf8,
                                    (gleam@string:inspect(Exception))/binary>>
                            )
                    end,
                    fcgi_ffi:send(Socket, End_records)
                end
            )
    end.

-file("src/fcgi.gleam", 1041).
-spec dispose_response(gleam@http@response:response(response_data())) -> nil.
dispose_response(Response) ->
    case erlang:element(4, Response) of
        {file, Handle, _, _} ->
            fcgi_ffi:close_file(Handle);

        {bytes, _} ->
            nil;

        {stream, _} ->
            nil
    end.

-file("src/fcgi.gleam", 826).
-spec error_response(integer(), binary()) -> gleam@http@response:response(response_data()).
error_response(Status, Message) ->
    _pipe = gleam@http@response:new(Status),
    _pipe@1 = gleam@http@response:set_header(
        _pipe,
        <<"content-type"/utf8>>,
        <<"text/plain; charset=utf-8"/utf8>>
    ),
    gleam@http@response:set_body(
        _pipe@1,
        {bytes, gleam_stdlib:wrap_list(Message)}
    ).

-file("src/fcgi.gleam", 1024).
-spec run_user_handler(
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data())),
    gleam@http@request:request(fun(() -> {ok, read()} | {error, read_error()})),
    context()
) -> gleam@http@response:response(response_data()).
run_user_handler(Handler_fn, Req, Ctx) ->
    case exception_ffi:rescue(fun() -> Handler_fn(Req, Ctx) end) of
        {ok, Resp} ->
            Resp;

        {error, Exception} ->
            logging:log(
                error,
                <<"handler crashed: "/utf8,
                    (gleam@string:inspect(Exception))/binary>>
            ),
            error_response(500, <<"internal server error"/utf8>>)
    end.

-file("src/fcgi.gleam", 1163).
-spec replace_snapshot(
    gleam@erlang@process:subject(body_snapshot()),
    body_snapshot()
) -> nil.
replace_snapshot(Mailbox, Snap) ->
    _ = gleam@erlang@process:'receive'(Mailbox, 0),
    gleam@erlang@process:send(Mailbox, Snap).

-file("src/fcgi.gleam", 1246).
-spec deliver_pending(body_context()) -> {ok, read()} | {error, read_error()}.
deliver_pending(Ctx) ->
    Next_ctx = {body_context,
        erlang:element(2, Ctx),
        <<>>,
        erlang:element(4, Ctx),
        erlang:element(5, Ctx),
        erlang:element(6, Ctx),
        erlang:element(7, Ctx)},
    {ok, {chunk, erlang:element(3, Ctx), fun() -> next_read(Next_ctx) end}}.

-file("src/fcgi.gleam", 1251).
-spec pull_more(body_context()) -> {ok, read()} | {error, read_error()}.
pull_more(Ctx) ->
    Prior = {body_snapshot,
        erlang:element(2, Ctx),
        erlang:element(4, Ctx),
        erlang:element(5, Ctx),
        false},
    gleam@result:'try'(
        recv_connection(erlang:element(6, Ctx)),
        fun(More) ->
            {New_data, Next} = step_body(Prior, More, erlang:element(6, Ctx)),
            replace_snapshot(erlang:element(7, Ctx), Next),
            gleam@bool:guard(
                erlang:element(5, Next),
                {error, request_aborted},
                fun() ->
                    next_read(
                        {body_context,
                            erlang:element(2, Next),
                            <<(erlang:element(3, Ctx))/bitstring,
                                New_data/bitstring>>,
                            erlang:element(3, Next),
                            erlang:element(4, Next),
                            erlang:element(6, Ctx),
                            erlang:element(7, Ctx)}
                    )
                end
            )
        end
    ).

-file("src/fcgi.gleam", 1237).
-spec next_read(body_context()) -> {ok, read()} | {error, read_error()}.
next_read(Ctx) ->
    gleam@bool:guard(
        erlang:element(5, Ctx),
        {error, body_too_large},
        fun() ->
            case {erlang:byte_size(erlang:element(3, Ctx)),
                erlang:element(4, Ctx)} of
                {0, true} ->
                    {ok, 'end'};

                {0, false} ->
                    pull_more(Ctx);

                {_, _} ->
                    deliver_pending(Ctx)
            end
        end
    ).

-file("src/fcgi.gleam", 1229).
-spec buffered_reader(bitstring(), boolean()) -> fun(() -> {ok, read()} |
    {error, read_error()}).
buffered_reader(Data, Overflowed) ->
    case {Overflowed, erlang:byte_size(Data)} of
        {true, _} ->
            fun() -> {error, body_too_large} end;

        {false, 0} ->
            fun() -> {ok, 'end'} end;

        {false, _} ->
            fun() -> {ok, {chunk, Data, fun() -> {ok, 'end'} end}} end
    end.

-file("src/fcgi.gleam", 1171).
-spec build_body_reader(
    connection(),
    fcgi@internal@responder:state(),
    list(fcgi@internal@responder:event())
) -> {fun(() -> {ok, read()} | {error, read_error()}),
    gleam@option:option(gleam@erlang@process:subject(body_snapshot()))}.
build_body_reader(Connection, State, Events) ->
    {Data, Ended, Overflowed} = collect_body_events(Events),
    case Ended orelse Overflowed of
        true ->
            {buffered_reader(Data, Overflowed), none};

        false ->
            Snapshot = gleam@erlang@process:new_subject(),
            gleam@erlang@process:send(
                Snapshot,
                {body_snapshot, State, false, false, false}
            ),
            Ctx = {body_context,
                State,
                Data,
                false,
                false,
                Connection,
                Snapshot},
            {fun() -> next_read(Ctx) end, {some, Snapshot}}
    end.

-file("src/fcgi.gleam", 1017).
-spec request_error_message(request_error()) -> binary().
request_error_message(Error) ->
    case Error of
        missing_method ->
            <<"missing REQUEST_METHOD parameter"/utf8>>;

        {invalid_method, Method} ->
            <<"invalid REQUEST_METHOD: "/utf8, Method/binary>>
    end.

-file("src/fcgi.gleam", 1002).
-spec resolve_unbracketed_host(binary(), gleam@option:option(integer())) -> {binary(),
    gleam@option:option(integer())}.
resolve_unbracketed_host(Raw, Server_port) ->
    case gleam@string:split_once(Raw, <<":"/utf8>>) of
        {ok, {Host, Port_str}} when Host =/= <<""/utf8>> ->
            Port = begin
                _pipe = gleam_stdlib:parse_int(Port_str),
                gleam@option:from_result(_pipe)
            end,
            {Host, gleam@option:'or'(Port, Server_port)};

        _ ->
            {Raw, Server_port}
    end.

-file("src/fcgi.gleam", 988).
-spec resolve_bracketed_host(binary(), gleam@option:option(integer())) -> {binary(),
    gleam@option:option(integer())}.
resolve_bracketed_host(Raw, Server_port) ->
    case gleam@string:split_once(gleam@string:drop_start(Raw, 1), <<"]"/utf8>>) of
        {ok, {Host, <<""/utf8>>}} when Host =/= <<""/utf8>> ->
            {Host, Server_port};

        {ok, {Host@1, <<":"/utf8, Port_str/binary>>}} when Host@1 =/= <<""/utf8>> ->
            Port = begin
                _pipe = gleam_stdlib:parse_int(Port_str),
                gleam@option:from_result(_pipe)
            end,
            {Host@1, gleam@option:'or'(Port, Server_port)};

        _ ->
            {Raw, Server_port}
    end.

-file("src/fcgi.gleam", 976).
-spec resolve_http_host(binary(), binary(), gleam@option:option(integer())) -> {binary(),
    gleam@option:option(integer())}.
resolve_http_host(Raw, Server_name, Server_port) ->
    gleam@bool:guard(
        Raw =:= <<""/utf8>>,
        {Server_name, Server_port},
        fun() -> case gleam_stdlib:string_starts_with(Raw, <<"["/utf8>>) of
                true ->
                    resolve_bracketed_host(Raw, Server_port);

                false ->
                    resolve_unbracketed_host(Raw, Server_port)
            end end
    ).

-file("src/fcgi.gleam", 969).
-spec https_scheme_from_value(binary()) -> gleam@http:scheme().
https_scheme_from_value(Value) ->
    case string:lowercase(Value) of
        <<""/utf8>> ->
            http;

        <<"off"/utf8>> ->
            http;

        _ ->
            https
    end.

-file("src/fcgi.gleam", 931).
-spec finalize_request(partial_request(), GTO) -> {ok,
        {gleam@http@request:request(GTO), context()}} |
    {error, request_error()}.
finalize_request(Acc, Body) ->
    gleam@result:'try'(
        gleam@option:to_result(erlang:element(2, Acc), missing_method),
        fun(Method_str) ->
            gleam@result:'try'(
                begin
                    _pipe = gleam@http:parse_method(Method_str),
                    gleam@result:replace_error(
                        _pipe,
                        {invalid_method, Method_str}
                    )
                end,
                fun(Method) ->
                    Scheme = case erlang:element(3, Acc) of
                        {some, Value} ->
                            https_scheme_from_value(Value);

                        none ->
                            http
                    end,
                    Server_name = gleam@option:unwrap(
                        erlang:element(4, Acc),
                        <<"localhost"/utf8>>
                    ),
                    Server_port = gleam@option:then(
                        erlang:element(5, Acc),
                        fun(Raw) -> _pipe@1 = gleam_stdlib:parse_int(Raw),
                            gleam@option:from_result(_pipe@1) end
                    ),
                    {Host, Port} = case erlang:element(6, Acc) of
                        {some, Raw@1} ->
                            resolve_http_host(Raw@1, Server_name, Server_port);

                        none ->
                            {Server_name, Server_port}
                    end,
                    Path = gleam@option:unwrap(
                        erlang:element(7, Acc),
                        <<"/"/utf8>>
                    ),
                    Req = {request,
                        Method,
                        erlang:element(9, Acc),
                        Body,
                        Scheme,
                        Host,
                        Port,
                        Path,
                        erlang:element(8, Acc)},
                    {ok, {Req, erlang:element(10, Acc)}}
                end
            )
        end
    ).

-file("src/fcgi.gleam", 916).
-spec apply_context_var(context(), binary(), binary()) -> context().
apply_context_var(Ctx, Key, Value) ->
    case Key of
        <<"REMOTE_ADDR"/utf8>> ->
            {context,
                {some, Value},
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"REMOTE_PORT"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                begin
                    _pipe = gleam_stdlib:parse_int(Value),
                    gleam@option:from_result(_pipe)
                end,
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"REMOTE_HOST"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                {some, Value},
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"REMOTE_USER"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                {some, Value},
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"AUTH_TYPE"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                {some, Value},
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"SCRIPT_NAME"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                {some, Value},
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"SERVER_PROTOCOL"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                {some, Value},
                erlang:element(9, Ctx),
                erlang:element(10, Ctx)};

        <<"SERVER_SOFTWARE"/utf8>> ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                {some, Value},
                erlang:element(10, Ctx)};

        _ ->
            {context,
                erlang:element(2, Ctx),
                erlang:element(3, Ctx),
                erlang:element(4, Ctx),
                erlang:element(5, Ctx),
                erlang:element(6, Ctx),
                erlang:element(7, Ctx),
                erlang:element(8, Ctx),
                erlang:element(9, Ctx),
                gleam@dict:insert(erlang:element(10, Ctx), Key, Value)}
    end.

-file("src/fcgi.gleam", 886).
-spec apply_pair(partial_request(), binary(), binary()) -> partial_request().
apply_pair(Acc, Key, Value) ->
    case Key of
        <<"REQUEST_METHOD"/utf8>> ->
            {partial_request,
                {some, Value},
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"HTTPS"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                {some, Value},
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"SERVER_NAME"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                {some, Value},
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"SERVER_PORT"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                {some, Value},
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"HTTP_HOST"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                {some, Value},
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                [{<<"host"/utf8>>, Value} | erlang:element(9, Acc)],
                erlang:element(10, Acc)};

        <<"PATH_INFO"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                {some, Value},
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"QUERY_STRING"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                {some, Value},
                erlang:element(9, Acc),
                erlang:element(10, Acc)};

        <<"CONTENT_TYPE"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                [{<<"content-type"/utf8>>, Value} | erlang:element(9, Acc)],
                erlang:element(10, Acc)};

        <<"CONTENT_LENGTH"/utf8>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                [{<<"content-length"/utf8>>, Value} | erlang:element(9, Acc)],
                erlang:element(10, Acc)};

        <<"HTTP_"/utf8, Name/binary>> ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                [{gleam@string:replace(
                            string:lowercase(Name),
                            <<"_"/utf8>>,
                            <<"-"/utf8>>
                        ),
                        Value} |
                    erlang:element(9, Acc)],
                erlang:element(10, Acc)};

        _ ->
            {partial_request,
                erlang:element(2, Acc),
                erlang:element(3, Acc),
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                erlang:element(6, Acc),
                erlang:element(7, Acc),
                erlang:element(8, Acc),
                erlang:element(9, Acc),
                apply_context_var(erlang:element(10, Acc), Key, Value)}
    end.

-file("src/fcgi.gleam", 853).
?DOC(false).
-spec to_http_request(list({binary(), binary()}), GTK) -> {ok,
        {gleam@http@request:request(GTK), context()}} |
    {error, request_error()}.
to_http_request(Pairs, Body) ->
    Initial = {partial_request,
        none,
        none,
        none,
        none,
        none,
        none,
        none,
        [],
        {context, none, none, none, none, none, none, none, none, maps:new()}},
    _pipe = gleam@list:fold(
        Pairs,
        Initial,
        fun(Acc, Pair) ->
            {Key, Value} = Pair,
            apply_pair(Acc, Key, Value)
        end
    ),
    finalize_request(_pipe, Body).

-file("src/fcgi.gleam", 817).
-spec parse_params(bitstring()) -> {ok,
        {gleam@http@request:request(nil), context()}} |
    {error, binary()}.
parse_params(Params) ->
    case fcgi@internal@protocol:parse_name_value_pairs(Params) of
        {error, _} ->
            {error, <<"malformed FastCGI parameters"/utf8>>};

        {ok, Pairs} ->
            _pipe = to_http_request(Pairs, nil),
            gleam@result:map_error(_pipe, fun request_error_message/1)
    end.

-file("src/fcgi.gleam", 765).
-spec serve_request(
    connection(),
    fcgi@internal@responder:state(),
    integer(),
    bitstring(),
    list(fcgi@internal@responder:event())
) -> {ok, after_response()} | {error, nil}.
serve_request(Connection, State, Request_id, Params, Events) ->
    case parse_params(Params) of
        {error, Message} ->
            logging:log(warning, <<"rejecting request: "/utf8, Message/binary>>),
            Response = error_response(400, Message),
            _pipe = send_response(
                erlang:element(2, Connection),
                Request_id,
                Response
            ),
            gleam@result:replace(_pipe, continue);

        {ok, {Req, Cgi}} ->
            Overflowed = gleam@list:contains(Events, body_too_large),
            {Reader, Snapshot} = build_body_reader(Connection, State, Events),
            Req@1 = gleam@http@request:set_body(Req, Reader),
            Response@1 = run_user_handler(
                erlang:element(5, Connection),
                Req@1,
                Cgi
            ),
            Latest = begin
                _pipe@1 = Snapshot,
                _pipe@2 = gleam@option:to_result(_pipe@1, nil),
                gleam@result:'try'(
                    _pipe@2,
                    fun(_capture) ->
                        gleam@erlang@process:'receive'(_capture, 0)
                    end
                )
            end,
            Aborted = begin
                _pipe@3 = Latest,
                _pipe@4 = gleam@result:map(
                    _pipe@3,
                    fun(Snap) -> erlang:element(5, Snap) end
                ),
                gleam@result:unwrap(_pipe@4, false)
            end,
            gleam@bool:lazy_guard(
                Aborted,
                fun() ->
                    dispose_response(Response@1),
                    {error, nil}
                end,
                fun() ->
                    gleam@result:'try'(
                        send_response(
                            erlang:element(2, Connection),
                            Request_id,
                            Response@1
                        ),
                        fun(_) ->
                            gleam@bool:lazy_guard(
                                Overflowed,
                                fun() ->
                                    logging:log(
                                        warning,
                                        <<<<"closing connection: body exceeded max_body_size of "/utf8,
                                                (erlang:integer_to_binary(
                                                    erlang:element(
                                                        3,
                                                        Connection
                                                    )
                                                ))/binary>>/binary,
                                            " bytes"/utf8>>
                                    ),
                                    {error, nil}
                                end,
                                fun() -> case Latest of
                                        {error, _} ->
                                            {ok, continue};

                                        {ok, Snap@1} ->
                                            {ok, {drain_body, Snap@1}}
                                    end end
                            )
                        end
                    )
                end
            )
    end.

-file("src/fcgi.gleam", 754).
-spec wait_for_bytes(connection(), fcgi@internal@responder:state()) -> next_request().
wait_for_bytes(Connection, State) ->
    case fcgi_ffi:recv(
        erlang:element(2, Connection),
        0,
        erlang:element(4, Connection)
    ) of
        {error, _} ->
            closed;

        {ok, <<>>} ->
            closed;

        {ok, More} ->
            await_request(Connection, State, More)
    end.

-file("src/fcgi.gleam", 722).
-spec await_request(connection(), fcgi@internal@responder:state(), bitstring()) -> next_request().
await_request(Connection, State, Pending) ->
    Buffered = case State of
        {idle, Buf} ->
            Buf;

        {receiving, Recv} ->
            erlang:element(2, Recv)
    end,
    gleam@bool:lazy_guard(
        (erlang:byte_size(Pending) =:= 0) andalso (erlang:byte_size(Buffered)
        =:= 0),
        fun() -> wait_for_bytes(Connection, State) end,
        fun() ->
            Outcome = step_and_flush(Connection, State, Pending),
            case erlang:element(4, Outcome) of
                [{request_ready, Request_id, Params, Keep_conn} | Rest] ->
                    {ready,
                        erlang:element(2, Outcome),
                        Request_id,
                        Params,
                        Rest,
                        Keep_conn};

                _ ->
                    case erlang:element(5, Outcome) of
                        close_connection ->
                            closed;

                        wait_for_more ->
                            wait_for_bytes(
                                Connection,
                                erlang:element(2, Outcome)
                            )
                    end
            end
        end
    ).

-file("src/fcgi.gleam", 688).
-spec run_connection_loop(connection(), fcgi@internal@responder:state()) -> nil.
run_connection_loop(Connection, State) ->
    case await_request(Connection, State, <<>>) of
        closed ->
            nil;

        {ready, State@1, Request_id, Params, Remaining_events, Keep_conn} ->
            case serve_request(
                Connection,
                State@1,
                Request_id,
                Params,
                Remaining_events
            ) of
                {error, _} ->
                    nil;

                {ok, After_response} ->
                    case {Keep_conn, After_response} of
                        {false, continue} ->
                            nil;

                        {false, {drain_body, _}} ->
                            nil;

                        {true, continue} ->
                            run_connection_loop(Connection, State@1);

                        {true, {drain_body, Snap}} ->
                            case drain_body_loop(Snap, Connection) of
                                {error, _} ->
                                    nil;

                                {ok, Next_state} ->
                                    run_connection_loop(Connection, Next_state)
                            end
                    end
            end
    end.

-file("src/fcgi.gleam", 630).
-spec start_connection_process(connection()) -> {ok,
        gleam@otp@actor:started(gleam@erlang@process:subject(nil))} |
    {error, gleam@otp@actor:start_error()}.
start_connection_process(Connection) ->
    Report_back = gleam@erlang@process:new_subject(),
    Pid = proc_lib:spawn_link(
        fun() ->
            Go = gleam@erlang@process:new_subject(),
            gleam@erlang@process:send(Report_back, Go),
            case gleam@erlang@process:'receive'(Go, 5000) of
                {ok, nil} ->
                    run_connection_loop(Connection, {idle, <<>>}),
                    fcgi_ffi:close_socket(erlang:element(2, Connection));

                {error, nil} ->
                    fcgi_ffi:close_socket(erlang:element(2, Connection))
            end
        end
    ),
    case gleam@erlang@process:'receive'(Report_back, 5000) of
        {ok, Go@1} ->
            {ok, {started, Pid, Go@1}};

        {error, nil} ->
            gleam@erlang@process:send_exit(Pid),
            {error,
                {init_failed, <<"connection process did not report back"/utf8>>}}
    end.

-file("src/fcgi.gleam", 477).
-spec connection_factory_supervised(
    gleam@erlang@process:name(gleam@otp@factory_supervisor:message(connection(), gleam@erlang@process:subject(nil)))
) -> gleam@otp@supervision:child_specification(gleam@otp@factory_supervisor:supervisor(connection(), gleam@erlang@process:subject(nil))).
connection_factory_supervised(Name) ->
    _pipe = gleam@otp@factory_supervisor:worker_child(
        fun start_connection_process/1
    ),
    _pipe@1 = gleam@otp@factory_supervisor:named(_pipe, Name),
    _pipe@2 = gleam@otp@factory_supervisor:supervised(_pipe@1),
    gleam@otp@supervision:restart(_pipe@2, transient).

-file("src/fcgi.gleam", 459).
-spec start_path_janitor(binary()) -> {ok, gleam@otp@actor:started(nil)} |
    {error, gleam@otp@actor:start_error()}.
start_path_janitor(Path) ->
    _pipe@4 = gleam@otp@actor:new_with_initialiser(
        1000,
        fun(_) ->
            gleam_erlang_ffi:trap_exits(true),
            Selector = begin
                _pipe = gleam_erlang_ffi:new_selector(),
                gleam@erlang@process:select_trapped_exits(
                    _pipe,
                    fun(_) -> nil end
                )
            end,
            _pipe@1 = gleam@otp@actor:initialised(Path),
            _pipe@2 = gleam@otp@actor:selecting(_pipe@1, Selector),
            _pipe@3 = gleam@otp@actor:returning(_pipe@2, nil),
            {ok, _pipe@3}
        end
    ),
    _pipe@5 = gleam@otp@actor:on_message(
        _pipe@4,
        fun(Path@1, _) ->
            fcgi_ffi:delete_path(Path@1),
            gleam@otp@actor:stop()
        end
    ),
    gleam@otp@actor:start(_pipe@5).

-file("src/fcgi.gleam", 452).
-spec path_janitor_supervised(binary()) -> gleam@otp@supervision:child_specification(nil).
path_janitor_supervised(Path) ->
    _pipe = gleam@otp@supervision:worker(fun() -> start_path_janitor(Path) end),
    gleam@otp@supervision:restart(_pipe, transient).

-file("src/fcgi.gleam", 426).
-spec build_supervisor(
    address(),
    socket(),
    gleam@erlang@process:name(gleam@otp@factory_supervisor:message(connection(), gleam@erlang@process:subject(nil))),
    integer(),
    integer(),
    fun((gleam@http@request:request(fun(() -> {ok, read()} |
        {error, read_error()})), context()) -> gleam@http@response:response(response_data()))
) -> gleam@otp@static_supervisor:builder().
build_supervisor(
    Address,
    Socket,
    Factory_name,
    Max_body_size,
    Body_read_timeout_ms,
    Handler
) ->
    Supervisor = gleam@otp@static_supervisor:new(rest_for_one),
    Supervisor@1 = case Address of
        {path_address, Path} ->
            gleam@otp@static_supervisor:add(
                Supervisor,
                path_janitor_supervised(Path)
            );

        {tcp_address, _, _} ->
            Supervisor
    end,
    _pipe = Supervisor@1,
    _pipe@1 = gleam@otp@static_supervisor:add(
        _pipe,
        connection_factory_supervised(Factory_name)
    ),
    gleam@otp@static_supervisor:add(
        _pipe@1,
        acceptor_supervised(
            Socket,
            Factory_name,
            Max_body_size,
            Body_read_timeout_ms,
            Handler
        )
    ).

-file("src/fcgi.gleam", 407).
-spec resolve_bound_port(socket(), address()) -> {ok,
        gleam@option:option(integer())} |
    {error, start_error()}.
resolve_bound_port(Socket, Address) ->
    case Address of
        {path_address, _} ->
            {ok, none};

        {tcp_address, _, _} ->
            case fcgi_ffi:socket_port(Socket) of
                {ok, Port} ->
                    {ok, {some, Port}};

                {error, Error} ->
                    fcgi_ffi:close_socket(Socket),
                    {error,
                        {listener_error,
                            <<"bound port lookup failed: "/utf8,
                                (describe_transport_error(Error))/binary>>}}
            end
    end.

-file("src/fcgi.gleam", 382).
-spec listen_on_address(address()) -> {ok, socket()} | {error, start_error()}.
listen_on_address(Address) ->
    case Address of
        {path_address, Path} ->
            _pipe = fcgi_ffi:listen_unix(Path),
            gleam@result:map_error(_pipe, fun(Error) -> case Error of
                        {path_exists, Path@1} ->
                            {listener_error,
                                <<"socket path already exists: "/utf8,
                                    Path@1/binary>>};

                        _ ->
                            {listener_error,
                                <<"listen failed: "/utf8,
                                    (describe_transport_error(Error))/binary>>}
                    end end);

        {tcp_address, Host, Port} ->
            _pipe@1 = fcgi_ffi:listen_tcp(Host, Port),
            gleam@result:map_error(_pipe@1, fun(Error@1) -> case Error@1 of
                        {invalid_host, Host@1} ->
                            {listener_error,
                                <<"host must be a numeric IP literal: "/utf8,
                                    Host@1/binary>>};

                        _ ->
                            {listener_error,
                                <<"listen failed: "/utf8,
                                    (describe_transport_error(Error@1))/binary>>}
                    end end)
    end.

-file("src/fcgi.gleam", 312).
?DOC(" Start the server.\n").
-spec start(builder(address())) -> {ok, gleam@otp@actor:started(server())} |
    {error, start_error()}.
start(Builder) ->
    gleam@bool:guard(
        erlang:element(4, Builder) < 0,
        {error, {invalid_max_body_size, erlang:element(4, Builder)}},
        fun() ->
            gleam@bool:guard(
                erlang:element(5, Builder) =< 0,
                {error, {invalid_body_read_timeout, erlang:element(5, Builder)}},
                fun() ->
                    Address = erlang:element(3, Builder),
                    gleam@result:'try'(
                        listen_on_address(Address),
                        fun(Socket) ->
                            gleam@result:'try'(
                                resolve_bound_port(Socket, Address),
                                fun(Bound_port) ->
                                    Factory_name = gleam_erlang_ffi:new_name(
                                        <<"fcgi_server_factory"/utf8>>
                                    ),
                                    Supervisor = build_supervisor(
                                        Address,
                                        Socket,
                                        Factory_name,
                                        erlang:element(4, Builder),
                                        erlang:element(5, Builder),
                                        erlang:element(2, Builder)
                                    ),
                                    gleam@result:'try'(
                                        start_supervisor(
                                            Supervisor,
                                            Socket,
                                            Address
                                        ),
                                        fun(Started) ->
                                            case fcgi_ffi:controlling_process(
                                                Socket,
                                                erlang:element(2, Started)
                                            ) of
                                                {ok, nil} ->
                                                    logging:log(
                                                        info,
                                                        <<"fcgi listening on "/utf8,
                                                            (describe_address(
                                                                Address
                                                            ))/binary>>
                                                    ),
                                                    Server = {server,
                                                        erlang:element(
                                                            3,
                                                            Started
                                                        ),
                                                        Bound_port},
                                                    {ok,
                                                        {started,
                                                            erlang:element(
                                                                2,
                                                                Started
                                                            ),
                                                            Server}};

                                                {error, Error} ->
                                                    gleam@erlang@process:unlink(
                                                        erlang:element(
                                                            2,
                                                            Started
                                                        )
                                                    ),
                                                    gleam@erlang@process:send_abnormal_exit(
                                                        erlang:element(
                                                            2,
                                                            Started
                                                        ),
                                                        erlang:binary_to_atom(
                                                            <<"shutdown"/utf8>>
                                                        )
                                                    ),
                                                    cleanup_socket(
                                                        Socket,
                                                        Address
                                                    ),
                                                    {error,
                                                        {listener_error,
                                                            <<"controlling_process failed: "/utf8,
                                                                (describe_transport_error(
                                                                    Error
                                                                ))/binary>>}}
                                            end
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/fcgi.gleam", 362).
?DOC(
    " Build a `supervision.ChildSpecification` so the server runs under an\n"
    " OTP supervisor.\n"
).
-spec supervised(builder(address())) -> gleam@otp@supervision:child_specification(server()).
supervised(Builder) ->
    gleam@otp@supervision:supervisor(fun() -> _pipe = start(Builder),
            gleam@result:map_error(
                _pipe,
                fun(Error) ->
                    Reason@1 = case Error of
                        {listener_error, Reason} ->
                            Reason;

                        {invalid_max_body_size, Bytes} ->
                            <<"max_body_size must be non-negative; got "/utf8,
                                (erlang:integer_to_binary(Bytes))/binary>>;

                        {invalid_body_read_timeout, Milliseconds} ->
                            <<"body_read_timeout must be positive; got "/utf8,
                                (erlang:integer_to_binary(Milliseconds))/binary>>
                    end,
                    {init_failed, Reason@1}
                end
            ) end).