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([ip_address_to_string/1, get_client_info/1, get_server_info/1, text/2, bytes/2, bits/2, string_tree/2, empty/1, json/2, new/1, bind/2, bind_all/1, listening/2, listening_random/1, enable_ipv6/1, enable_tls/3, set_information_name/2, on_start/2, quiet/1, on_crash/2, start/1, supervised/1, read_body/2, stream_body/1, continue/1, stop/0, stop_abnormal/1, upgrade_websocket/4, send_binary_frame/2, send_text_frame/2, use_expression/1]).
-export_type([ip_address/0, socket_address/0, response_body/0, builder/0, body_error/0, stream/0, next/1, websocket_message/1]).
-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(
" <style>\n"
" .content > h4,\n"
" .content > ul {\n"
" display: none;\n"
" }\n"
" </style>\n"
" <script>\n"
" // https://gitlab.com/arkandos/smol/-/blob/main/src/smol.gleam?ref_type=heads\n"
" (callback => document.readyState !== 'loading' ? callback() : document.addEventListener('DOMContentLoaded', callback, { once: true }))(() => {\n"
" const list = document.querySelector('.sidebar > ul:last-of-type')\n"
" const sortedLists = document.createDocumentFragment()\n"
" const sortedMembers = document.createDocumentFragment()\n"
"\n"
" for (const header of document.querySelectorAll('main > h4')) {\n"
" sortedLists.append((() => {\n"
" const node = document.createElement('h3')\n"
" node.append(header.textContent)\n"
" return node\n"
" })())\n"
" sortedMembers.append((() => {\n"
" const node = document.createElement('h2')\n"
" node.append(header.textContent)\n"
" return node\n"
" })())\n"
"\n"
" const sortedList = document.createElement('ul')\n"
" sortedLists.append(sortedList)\n"
"\n"
" for (const anchor of header.nextElementSibling.querySelectorAll('a')) {\n"
" const href = anchor.getAttribute('href')\n"
" const member = document.querySelector(`.member:has(h2 > a[href=\"${href}\"])`)\n"
" const sidebar = list.querySelector(`li:has(a[href=\"${href}\"])`)\n"
" sortedList.append(sidebar)\n"
" sortedMembers.append(member)\n"
" }\n"
" }\n"
"\n"
" document.querySelector('.sidebar').insertBefore(sortedLists, list)\n"
" document.querySelector('.module-members:has(#module-values)').insertBefore(sortedMembers, document.querySelector('#module-values').nextSibling)\n"
" })\n"
" </script>\n"
" #### IP Address\n"
" - [ip_address_to_string](#ip_address_to_string)\n"
" #### Information\n"
" - [get_client_info](#get_client_info)\n"
" - [get_server_info](#get_server_info)\n"
" #### Builder\n"
" - [new](#new)\n"
" - [bind](#bind)\n"
" - [bind_all](#bind_all)\n"
" - [with_port](#with_port)\n"
" - [with_random_port](#with_random_port)\n"
" - [with_ipv6](#with_ipv6)\n"
" - [with_tls](#with_tls)\n"
" - [with_name](#with_name)\n"
" - [on_start](#on_start)\n"
" - [on_crash](#on_crash)\n"
" #### Server\n"
" - [start](#start)\n"
" - [supervised](#supervised)\n"
" #### Request\n"
" - [read_body](#read_body)\n"
" - [stream_body](#stream_body)\n"
" #### Response\n"
" - [text](#text)\n"
" - [bytes](#bytes)\n"
" - [bits](#bits)\n"
" - [string_tree](#string_tree)\n"
" - [empty](#empty)\n"
" - [json](#json)\n"
" #### Websocket\n"
" - [upgrade_websocket](#upgrade_websocket)\n"
" - [send_binary_frame](#send_binary_frame)\n"
" - [send_text_frame](#send_text_frame)\n"
" - [continue](#continue)\n"
" - [stop](#stop)\n"
" - [stop_abnormal](#stop_abnormal)\n"
" #### Experimental\n"
" - [use_expression](#use_expression)\n"
).
-type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} |
{ip_v6,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer()}.
-type socket_address() :: {socket_address, ip_address(), integer()}.
-opaque response_body() :: {text_data, binary()} |
{bytes_data, gleam@bytes_tree:bytes_tree()} |
{bits_data, bitstring()} |
{string_tree_data, gleam@string_tree:string_tree()} |
{websocket_connection,
gleam@erlang@process:selector(gleam@erlang@process:down())} |
empty.
-opaque builder() :: {builder,
fun((gleam@http@request:request(ewe@internal@http:connection())) -> gleam@http@response:response(response_body())),
integer(),
binary(),
boolean(),
gleam@option:option({binary(), binary()}),
fun((gleam@http:scheme(), socket_address()) -> nil),
gleam@http@response:response(response_body()),
gleam@erlang@process:name(ewe@internal@information:message(socket_address()))}.
-type body_error() :: body_too_large | invalid_body.
-type stream() :: {consumed,
bitstring(),
fun((integer()) -> {ok, stream()} | {error, body_error()})} |
done.
-opaque next(LDP) :: {continue, LDP} | normal_stop | {abnormal_stop, binary()}.
-type websocket_message(LDQ) :: {text, binary()} |
{binary, bitstring()} |
{user, LDQ}.
-file("src/ewe.gleam", 144).
-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", 152).
-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", 160).
-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", 139).
?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", 181).
?DOC(" Performs an attempt to get the client's socket address.\n").
-spec get_client_info(ewe@internal@http:connection()) -> {ok, socket_address()} |
{error, nil}.
get_client_info(Connection) ->
_pipe = glisten@transport:peername(
erlang:element(2, Connection),
erlang:element(3, Connection)
),
gleam@result:map(
_pipe,
fun(Server_info) ->
{socket_address,
glisten_options_to_ewe_ip(erlang:element(1, Server_info)),
erlang:element(2, Server_info)}
end
).
-file("src/ewe.gleam", 193).
?DOC(
" Retrieves server's socket address. Requires the same name as the one used in\n"
" `ewe.with_name` and server to be started. Otherwise, will crash the program.\n"
).
-spec get_server_info(
gleam@erlang@process:name(ewe@internal@information:message(socket_address()))
) -> {ok, socket_address()} | {error, nil}.
get_server_info(Name) ->
ewe@internal@information:get(gleam@erlang@process:named_subject(Name)).
-file("src/ewe.gleam", 224).
-spec transform_response_body(gleam@http@response:response(response_body())) -> gleam@http@response:response(ewe@internal@http:response_body()).
transform_response_body(Resp) ->
gleam@http@response:set_body(Resp, case erlang:element(4, Resp) of
{text_data, Text} ->
{text_data, Text};
{bytes_data, Bytes} ->
{bytes_data, Bytes};
{bits_data, Bits} ->
{bits_data, Bits};
{string_tree_data, String_tree} ->
{string_tree_data, String_tree};
{websocket_connection, Selector} ->
{websocket_connection, Selector};
empty ->
empty
end).
-file("src/ewe.gleam", 240).
?DOC(
" Sets response body from string, sets `content-type` to\n"
" `text/plain; charset=utf-8` and `content-length` headers.\n"
).
-spec text(gleam@http@response:response(any()), binary()) -> gleam@http@response:response(response_body()).
text(Response, Text) ->
_pipe = gleam@http@response:set_body(Response, {text_data, Text}),
_pipe@1 = gleam@http@response:set_header(
_pipe,
<<"content-type"/utf8>>,
<<"text/plain; charset=utf-8"/utf8>>
),
gleam@http@response:set_header(
_pipe@1,
<<"content-length"/utf8>>,
erlang:integer_to_binary(erlang:byte_size(Text))
).
-file("src/ewe.gleam", 252).
?DOC(
" Sets response body from bytes, sets `content-length` header. Doesn't set\n"
" `content-type` header.\n"
).
-spec bytes(gleam@http@response:response(any()), gleam@bytes_tree:bytes_tree()) -> gleam@http@response:response(response_body()).
bytes(Response, Bytes) ->
_pipe = gleam@http@response:set_body(Response, {bytes_data, Bytes}),
gleam@http@response:set_header(
_pipe,
<<"content-length"/utf8>>,
erlang:integer_to_binary(erlang:iolist_size(Bytes))
).
-file("src/ewe.gleam", 263).
?DOC(
" Sets response body from bits, sets `content-length` header. Doesn't set\n"
" `content-type` header.\n"
).
-spec bits(gleam@http@response:response(any()), bitstring()) -> gleam@http@response:response(response_body()).
bits(Response, Bits) ->
_pipe = gleam@http@response:set_body(Response, {bits_data, Bits}),
gleam@http@response:set_header(
_pipe,
<<"content-length"/utf8>>,
erlang:integer_to_binary(erlang:byte_size(Bits))
).
-file("src/ewe.gleam", 274).
?DOC(
" Sets response body from string tree, sets `content-length` header. Doesn't\n"
" set `content-type` header.\n"
).
-spec string_tree(
gleam@http@response:response(any()),
gleam@string_tree:string_tree()
) -> gleam@http@response:response(response_body()).
string_tree(Response, String_tree) ->
_pipe = gleam@http@response:set_body(
Response,
{string_tree_data, String_tree}
),
gleam@http@response:set_header(
_pipe,
<<"content-length"/utf8>>,
erlang:integer_to_binary(erlang:iolist_size(String_tree))
).
-file("src/ewe.gleam", 287).
?DOC(" Sets response body to empty, sets `content-length` header to `0`.\n").
-spec empty(gleam@http@response:response(any())) -> gleam@http@response:response(response_body()).
empty(Response) ->
_pipe = gleam@http@response:set_body(Response, empty),
gleam@http@response:set_header(
_pipe,
<<"content-length"/utf8>>,
<<"0"/utf8>>
).
-file("src/ewe.gleam", 296).
?DOC(
" Sets response body from string tree (use `gleam_json` package and encode\n"
" using `json.to_string_tree`), sets `content-type` to `application/json;\n"
" charset=utf-8` and `content-length` headers.\n"
).
-spec json(gleam@http@response:response(any()), gleam@string_tree:string_tree()) -> gleam@http@response:response(response_body()).
json(Response, Json) ->
_pipe = string_tree(Response, Json),
gleam@http@response:set_header(
_pipe,
<<"content-type"/utf8>>,
<<"application/json; charset=utf-8"/utf8>>
).
-file("src/ewe.gleam", 351).
?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"
" - Default process name for server information retrieval\n"
" - on_start: prints `Listening on <scheme>://<ip_address>:<port>`\n"
" - on_crash: empty 500 response\n"
).
-spec new(
fun((gleam@http@request:request(ewe@internal@http:connection())) -> gleam@http@response:response(response_body()))
) -> builder().
new(Handler) ->
{builder,
Handler,
8080,
<<"127.0.0.1"/utf8>>,
false,
none,
fun(Scheme, Server) ->
Address = case erlang:element(2, Server) of
{ip_v6, _, _, _, _, _, _, _, _} ->
<<<<"["/utf8,
(ip_address_to_string(erlang:element(2, Server)))/binary>>/binary,
"]"/utf8>>;
{ip_v4, _, _, _, _} ->
ip_address_to_string(erlang:element(2, Server))
end,
Url = <<<<<<<<(gleam@http:scheme_to_string(Scheme))/binary,
"://"/utf8>>/binary,
Address/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(3, Server)))/binary>>,
gleam_stdlib:println(<<"Listening on "/utf8, Url/binary>>)
end,
begin
_pipe = gleam@http@response:new(500),
gleam@http@response:set_body(_pipe, empty)
end,
gleam_erlang_ffi:new_name(<<"ewe_server_info"/utf8>>)}.
-file("src/ewe.gleam", 380).
?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),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 386).
?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),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 392).
?DOC(" Sets listening port for server.\n").
-spec listening(builder(), integer()) -> builder().
listening(Builder, Port) ->
{builder,
erlang:element(2, Builder),
Port,
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 398).
?DOC(" Sets listening port for server to a random port. Useful for testing.\n").
-spec listening_random(builder()) -> builder().
listening_random(Builder) ->
{builder,
erlang:element(2, Builder),
0,
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 404).
?DOC(" Enables IPv6 support.\n").
-spec enable_ipv6(builder()) -> builder().
enable_ipv6(Builder) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
true,
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 410).
?DOC(" Enables TLS support, requires certificate and key file.\n").
-spec enable_tls(builder(), binary(), binary()) -> builder().
enable_tls(Builder, Certificate_file, Key_file) ->
Cert = case ewe@internal@file:open(Certificate_file) of
{ok, _} ->
Certificate_file;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Failed to find cert file"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe"/utf8>>,
function => <<"enable_tls"/utf8>>,
line => 417})
end,
Key = case ewe@internal@file:open(Key_file) of
{ok, _} ->
Key_file;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Failed to find key file"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe"/utf8>>,
function => <<"enable_tls"/utf8>>,
line => 422})
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),
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 431).
?DOC(
" Sets a custom process name for server information retrieval, allowing to\n"
" use `ewe.get_server_info` after server starts.\n"
).
-spec set_information_name(
builder(),
gleam@erlang@process:name(ewe@internal@information:message(socket_address()))
) -> builder().
set_information_name(Builder, Name) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder),
Name}.
-file("src/ewe.gleam", 440).
?DOC(" Sets a custom handler that will be called after server starts.\n").
-spec on_start(builder(), fun((gleam@http:scheme(), socket_address()) -> 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,
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 449).
?DOC(" Sets empty `on_start` function.\n").
-spec quiet(builder()) -> builder().
quiet(Builder) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
fun(_, _) -> nil end,
erlang:element(8, Builder),
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 455).
?DOC(" Sets a custom response that will be sent when server crashes.\n").
-spec on_crash(builder(), gleam@http@response:response(response_body())) -> builder().
on_crash(Builder, On_crash) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
On_crash,
erlang:element(9, Builder)}.
-file("src/ewe.gleam", 465).
?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>>),
Handler = fun(Req) ->
transform_response_body((erlang:element(2, Builder))(Req))
end,
On_crash = transform_response_body(erlang:element(8, Builder)),
Subject = gleam@erlang@process:named_subject(erlang:element(9, Builder)),
Information = ewe@internal@information:worker(erlang:element(9, Builder)),
Glisten_supervisor = begin
_pipe = glisten:new(
fun(_) -> {nil, none} end,
ewe@internal@handler:loop(Handler, On_crash)
),
_pipe@1 = glisten:bind(_pipe, erlang:element(4, Builder)),
_pipe@2 = (fun(Glisten_builder) -> case erlang:element(5, Builder) of
true ->
glisten:with_ipv6(Glisten_builder);
false ->
Glisten_builder
end end)(_pipe@1),
_pipe@3 = (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@2),
_pipe@4 = glisten:start_with_listener_name(
_pipe@3,
erlang:element(3, Builder),
Name
),
gleam@result:map(
_pipe@4,
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)),
Server = {socket_address,
Ip_address,
erlang:element(2, Server_info)},
ewe@internal@information:set(Subject, Server),
(erlang:element(7, Builder))(Scheme, Server),
Started
end
)
end,
Glisten_child = gleam@otp@supervision:supervisor(
fun() -> Glisten_supervisor end
),
_pipe@5 = gleam@otp@static_supervisor:new(one_for_all),
_pipe@6 = gleam@otp@static_supervisor:add(_pipe@5, Glisten_child),
_pipe@7 = gleam@otp@static_supervisor:add(_pipe@6, Information),
gleam@otp@static_supervisor:start(_pipe@7).
-file("src/ewe.gleam", 520).
?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", 544).
?DOC(
" Reads body from a request. If request body is malformed, `InvalidBody`\n"
" error is returned. On success, returns a request with body converted to\n"
" `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, Bytes_limit) ->
case ewe@internal@http:read_body(Req, Bytes_limit) of
{ok, Req@1} ->
{ok, Req@1};
{error, body_too_large} ->
{error, body_too_large};
{error, _} ->
{error, invalid_body}
end.
-file("src/ewe.gleam", 575).
-spec consumer_adapter(
fun((integer()) -> {ok, ewe@internal@http:stream()} |
{error, ewe@internal@http:parse_error()})
) -> fun((integer()) -> {ok, stream()} | {error, body_error()}).
consumer_adapter(Internal_consumer) ->
fun(Size) -> case Internal_consumer(Size) of
{ok, done} ->
{ok, done};
{ok, {consumed, Data, Next}} ->
{ok, {consumed, Data, consumer_adapter(Next)}};
{error, _} ->
{error, invalid_body}
end end.
-file("src/ewe.gleam", 567).
?DOC(" Streams the request body.\n").
-spec stream_body(gleam@http@request:request(ewe@internal@http:connection())) -> {ok,
fun((integer()) -> {ok, stream()} | {error, body_error()})} |
{error, body_error()}.
stream_body(Req) ->
case ewe@internal@http:stream_body(Req) of
{ok, Consumer} ->
{ok, consumer_adapter(Consumer)};
{error, _} ->
{error, invalid_body}
end.
-file("src/ewe.gleam", 610).
?DOC(" Instructs WebSocket connection to continue processing.\n").
-spec continue(LFL) -> next(LFL).
continue(User_state) ->
{continue, User_state}.
-file("src/ewe.gleam", 616).
?DOC(" Instructs WebSocket connection to stop.\n").
-spec stop() -> next(any()).
stop() ->
normal_stop.
-file("src/ewe.gleam", 622).
?DOC(" Instructs WebSocket connection to stop with abnormal reason.\n").
-spec stop_abnormal(binary()) -> next(any()).
stop_abnormal(Reason) ->
{abnormal_stop, Reason}.
-file("src/ewe.gleam", 626).
-spec to_internal_next(next(LFR)) -> ewe@internal@websocket:websocket_next(LFR).
to_internal_next(Next) ->
case Next of
{continue, User_state} ->
{continue, User_state};
normal_stop ->
normal_stop;
{abnormal_stop, Reason} ->
{abnormal_stop, Reason}
end.
-file("src/ewe.gleam", 643).
-spec transform_websocket_message(ewe@internal@websocket:websocket_message(LFU)) -> {ok,
websocket_message(LFU)} |
{error, nil}.
transform_websocket_message(Message) ->
case Message of
{websocket_frame, {data, {text_frame, Text}}} ->
_pipe = gleam@bit_array:to_string(Text),
gleam@result:map(_pipe, fun(Field@0) -> {text, Field@0} end);
{websocket_frame, {data, {binary_frame, Binary}}} ->
{ok, {binary, Binary}};
{user_message, User_message} ->
{ok, {user, User_message}};
_ ->
{error, nil}
end.
-file("src/ewe.gleam", 661).
?DOC(
" Upgrade request to a WebSocket connection. If the initial request is not\n"
" valid for WebSocket upgrade, 400 response is sent. Handler must return\n"
" instruction on how WebSocket connection should proceed.\n"
).
-spec upgrade_websocket(
gleam@http@request:request(ewe@internal@http:connection()),
fun((ewe@internal@websocket:websocket_connection(), gleam@erlang@process:selector(LGA)) -> {LGC,
gleam@erlang@process:selector(LGA)}),
fun((ewe@internal@websocket:websocket_connection(), LGC, websocket_message(LGA)) -> next(LGC)),
fun((ewe@internal@websocket:websocket_connection(), LGC) -> nil)
) -> gleam@http@response:response(response_body()).
upgrade_websocket(Req, On_init, Handler, On_close) ->
Handler@1 = fun(Conn, State, Msg) ->
_pipe = transform_websocket_message(Msg),
_pipe@1 = gleam@result:map(
_pipe,
fun(_capture) -> Handler(Conn, State, _capture) end
),
_pipe@2 = gleam@result:unwrap(_pipe@1, continue(State)),
to_internal_next(_pipe@2)
end,
Transport = erlang:element(2, erlang:element(4, Req)),
Socket = erlang:element(3, erlang:element(4, Req)),
Resp = begin
gleam@result:'try'(
begin
_pipe@3 = ewe@internal@http:upgrade_websocket(
Req,
Transport,
Socket
),
gleam@result:replace_error(
_pipe@3,
begin
_pipe@4 = gleam@http@response:new(400),
gleam@http@response:set_body(_pipe@4, empty)
end
)
end,
fun(_use0) ->
{Extensions, Permessage_deflate} = _use0,
gleam@result:'try'(
begin
_pipe@5 = ewe@internal@websocket:start(
Transport,
Socket,
On_init,
Handler@1,
On_close,
Extensions,
Permessage_deflate
),
gleam@result:replace_error(
_pipe@5,
begin
_pipe@6 = gleam@http@response:new(500),
gleam@http@response:set_body(_pipe@6, empty)
end
)
end,
fun(Selector) -> _pipe@7 = gleam@http@response:new(500),
_pipe@8 = gleam@http@response:set_body(
_pipe@7,
{websocket_connection, Selector}
),
{ok, _pipe@8} end
)
end
)
end,
gleam@result:unwrap_both(Resp).
-file("src/ewe.gleam", 712).
?DOC(" Sends a binary frame to the websocket client.\n").
-spec send_binary_frame(
ewe@internal@websocket:websocket_connection(),
bitstring()
) -> {ok, nil} | {error, glisten@socket:socket_reason()}.
send_binary_frame(Conn, Bits) ->
ewe@internal@websocket:send_frame(
fun gramps@websocket:encode_binary_frame/3,
erlang:element(2, Conn),
erlang:element(3, Conn),
erlang:element(4, Conn),
Bits
).
-file("src/ewe.gleam", 727).
?DOC(" Sends a text frame to the websocket client.\n").
-spec send_text_frame(ewe@internal@websocket:websocket_connection(), binary()) -> {ok,
nil} |
{error, glisten@socket:socket_reason()}.
send_text_frame(Conn, Text) ->
ewe@internal@websocket:send_frame(
fun gramps@websocket:encode_text_frame/3,
erlang:element(2, Conn),
erlang:element(3, Conn),
erlang:element(4, Conn),
Text
).
-file("src/ewe.gleam", 777).
?DOC(
" Experimental function that simplifies error handling in handlers when\n"
" working with `Result` type.\n"
" \n"
" ## Example\n"
" \n"
" ```gleam\n"
" pub fn handle_echo(\n"
" req: Request(ewe.Connection),\n"
" ) -> Response(bytes_tree.BytesTree) {\n"
" let content_type =\n"
" request.get_header(req, \"content-type\")\n"
" |> result.unwrap(\"text/plain\")\n"
"\n"
" // Start the use_expression block\n"
" use <- ewe.use_expression()\n"
"\n"
" // Now you can use result.try with use expressions\n"
" // If any step fails, the error response is automatically returned\n"
" use req <- result.try(\n"
" ewe.read_body(req, 1024)\n"
" |> result.replace_error(\n"
" response.new(400)\n"
" |> ewe.json(error_json(\"Invalid request body\")),\n"
" ),\n"
" )\n"
"\n"
" response.new(200)\n"
" |> ewe.bits(req.body)\n"
" |> response.set_header(\"content-type\", content_type)\n"
" |> Ok \n"
"}\n"
" ```\n"
).
-spec use_expression(
fun(() -> {ok, gleam@http@response:response(response_body())} |
{error, gleam@http@response:response(response_body())})
) -> gleam@http@response:response(response_body()).
use_expression(Handler) ->
gleam@result:unwrap_both(Handler()).