Packages
🐑 a fluffy Gleam web server
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/ewe.erl
-module(ewe).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ewe.gleam").
-export([client_stats/1, ip_address_to_string/1, new/1, bind/2, bind_all/1, with_port/2, with_ipv6/1, with_tls/3, on_start/2, start/1, supervised/1, read_body/2]).
-export_type([ip_address/0, builder/0, body_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.
-type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} |
{ip_v6,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer()}.
-opaque builder() :: {builder,
fun((gleam@http@request:request(ewe@internal@http:connection())) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree())),
integer(),
binary(),
boolean(),
gleam@option:option({binary(), binary()}),
fun((gleam@http:scheme(), ip_address(), integer()) -> nil)}.
-type body_error() :: body_too_large | invalid_body.
-file("src/ewe.gleam", 45).
-spec glisten_to_ewe_ip(glisten:ip_address()) -> ip_address().
glisten_to_ewe_ip(Ip) ->
case Ip of
{ip_v4, N1, N2, N3, N4} ->
{ip_v4, N1, N2, N3, N4};
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8} ->
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8}
end.
-file("src/ewe.gleam", 53).
-spec glisten_options_to_ewe_ip(glisten@socket@options:ip_address()) -> ip_address().
glisten_options_to_ewe_ip(Ip) ->
case Ip of
{ip_v4, N1, N2, N3, N4} ->
{ip_v4, N1, N2, N3, N4};
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8} ->
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8}
end.
-file("src/ewe.gleam", 37).
?DOC(" Performs an attempt to get the client's IP address and port.\n").
-spec client_stats(ewe@internal@http:connection()) -> {ok,
{ip_address(), integer()}} |
{error, nil}.
client_stats(Connection) ->
_pipe = glisten@transport:peername(
erlang:element(2, Connection),
erlang:element(3, Connection)
),
gleam@result:map(
_pipe,
fun(Tuple) ->
{Ip, Port} = Tuple,
{glisten_options_to_ewe_ip(Ip), Port}
end
).
-file("src/ewe.gleam", 61).
-spec ewe_to_glisten_ip(ip_address()) -> glisten:ip_address().
ewe_to_glisten_ip(Ip) ->
case Ip of
{ip_v4, N1, N2, N3, N4} ->
{ip_v4, N1, N2, N3, N4};
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8} ->
{ip_v6, N1@1, N2@1, N3@1, N4@1, N5, N6, N7, N8}
end.
-file("src/ewe.gleam", 31).
?DOC(" Converts an `IpAddress` to a string for later printing.\n").
-spec ip_address_to_string(ip_address()) -> binary().
ip_address_to_string(Address) ->
_pipe = ewe_to_glisten_ip(Address),
glisten:ip_address_to_string(_pipe).
-file("src/ewe.gleam", 96).
?DOC(
" Creates new server builder with handler provided.\n"
" \n"
" Default configuration:\n"
" - port: `8080`\n"
" - interface: `127.0.0.1`\n"
" - No ipv6 support\n"
" - No TLS support\n"
" - on_start: prints `Listening on <scheme>://<ip_address>:<port>`\n"
).
-spec new(
fun((gleam@http@request:request(ewe@internal@http:connection())) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree()))
) -> builder().
new(Handler) ->
{builder,
Handler,
8080,
<<"127.0.0.1"/utf8>>,
false,
none,
fun(Scheme, Ip_address, Port) ->
Address = case Ip_address of
{ip_v6, _, _, _, _, _, _, _, _} ->
<<<<"["/utf8, (ip_address_to_string(Ip_address))/binary>>/binary,
"]"/utf8>>;
{ip_v4, _, _, _, _} ->
ip_address_to_string(Ip_address)
end,
Url = <<<<<<<<(gleam@http:scheme_to_string(Scheme))/binary,
"://"/utf8>>/binary,
Address/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(Port))/binary>>,
gleam_stdlib:println(<<"Listening on "/utf8, Url/binary>>)
end}.
-file("src/ewe.gleam", 122).
?DOC(" Binds server to a specific interface. Crashes program if interface is invalid.\n").
-spec bind(builder(), binary()) -> builder().
bind(Builder, Interface) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
Interface,
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder)}.
-file("src/ewe.gleam", 127).
?DOC(" Binds server to all interfaces.\n").
-spec bind_all(builder()) -> builder().
bind_all(Builder) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
<<"0.0.0.0"/utf8>>,
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder)}.
-file("src/ewe.gleam", 132).
?DOC(" Sets listening port for server.\n").
-spec with_port(builder(), integer()) -> builder().
with_port(Builder, Port) ->
{builder,
erlang:element(2, Builder),
Port,
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder)}.
-file("src/ewe.gleam", 137).
?DOC(" Enables IPv6 support.\n").
-spec with_ipv6(builder()) -> builder().
with_ipv6(Builder) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
true,
erlang:element(6, Builder),
erlang:element(7, Builder)}.
-file("src/ewe.gleam", 142).
?DOC(" Enables TLS support, requires certificate and key file.\n").
-spec with_tls(builder(), binary(), binary()) -> builder().
with_tls(Builder, Certificate, Keyfile) ->
Cert = case ewe@internal@file:open(Certificate) of
{ok, _} ->
Certificate;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Failed to find cert file"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe"/utf8>>,
function => <<"with_tls"/utf8>>,
line => 149})
end,
Key = case ewe@internal@file:open(Keyfile) of
{ok, _} ->
Keyfile;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Failed to find key file"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe"/utf8>>,
function => <<"with_tls"/utf8>>,
line => 154})
end,
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
{some, {Cert, Key}},
erlang:element(7, Builder)}.
-file("src/ewe.gleam", 161).
?DOC(" Sets a custom handler that will be called after server starts.\n").
-spec on_start(
builder(),
fun((gleam@http:scheme(), ip_address(), integer()) -> nil)
) -> builder().
on_start(Builder, On_start) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
On_start}.
-file("src/ewe.gleam", 169).
?DOC(" Starts the server.\n").
-spec start(builder()) -> {ok,
gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
{error, gleam@otp@actor:start_error()}.
start(Builder) ->
Name = gleam_erlang_ffi:new_name(<<"ewe_glisten"/utf8>>),
_pipe@2 = glisten:new(
fun(Conn) -> {ewe@internal@http:transform_connection(Conn), none} end,
fun(Http_conn, Msg, Conn@1) ->
Msg@2 = case Msg of
{packet, Msg@1} -> Msg@1;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe"/utf8>>,
function => <<"start"/utf8>>,
line => 177,
value => _assert_fail,
start => 4867,
'end' => 4903,
pattern_start => 4878,
pattern_end => 4897})
end,
case ewe@internal@http:parse_request(Http_conn, Msg@2) of
{ok, {parsed_request, Request, Version}} ->
Resp = begin
_pipe = (erlang:element(2, Builder))(Request),
_pipe@1 = ewe@internal@response:append_default_headers(
_pipe,
Version
),
ewe@internal@response:encode(_pipe@1)
end,
case glisten@transport:send(
erlang:element(3, Conn@1),
erlang:element(2, Conn@1),
Resp
) of
{ok, nil} ->
glisten:continue(Http_conn);
{error, _} ->
glisten:stop()
end;
{error, _} ->
glisten:stop()
end
end
),
_pipe@3 = glisten:bind(_pipe@2, erlang:element(4, Builder)),
_pipe@4 = (fun(Glisten_builder) -> case erlang:element(5, Builder) of
true ->
glisten:with_ipv6(Glisten_builder);
false ->
Glisten_builder
end end)(_pipe@3),
_pipe@5 = (fun(Glisten_builder@1) -> case erlang:element(6, Builder) of
{some, {Cert, Key}} ->
glisten:with_tls(Glisten_builder@1, Cert, Key);
none ->
Glisten_builder@1
end end)(_pipe@4),
_pipe@6 = glisten:start_with_listener_name(
_pipe@5,
erlang:element(3, Builder),
Name
),
gleam@result:map(
_pipe@6,
fun(Started) ->
Scheme = case erlang:element(6, Builder) of
{some, {_, _}} ->
https;
none ->
http
end,
Server_info = glisten:get_server_info(Name, 10000),
Ip_address = glisten_to_ewe_ip(erlang:element(3, Server_info)),
(erlang:element(7, Builder))(
Scheme,
Ip_address,
erlang:element(2, Server_info)
),
Started
end
).
-file("src/ewe.gleam", 224).
?DOC(" Creates a supervisor that can be appended to a supervision tree.\n").
-spec supervised(builder()) -> gleam@otp@supervision:child_specification(gleam@otp@static_supervisor:supervisor()).
supervised(Builder) ->
gleam@otp@supervision:supervisor(fun() -> start(Builder) end).
-file("src/ewe.gleam", 240).
?DOC(
" Reads body from a request. If request body is malformed, `InvalidBody` error is returned. On success, returns a request with body converted to `BitArray`.\n"
" - When `transfer-encoding` header set as `chunked`, `BodyTooLarge` error is returned if\n"
" accumulated body is larger than `size_limit`.\n"
" - Ensures that `content-length` is in `size_limit` scope.\n"
).
-spec read_body(
gleam@http@request:request(ewe@internal@http:connection()),
integer()
) -> {ok, gleam@http@request:request(bitstring())} | {error, body_error()}.
read_body(Req, Size_limit) ->
case ewe@internal@http:read_body(Req, Size_limit) of
{ok, Req@1} ->
{ok, Req@1};
{error, body_too_large} ->
{error, body_too_large};
{error, _} ->
{error, invalid_body}
end.