-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).