Packages
mist
0.13.1
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
src/mist.erl
-module(mist).
-compile([no_auto_import, nowarn_unused_vars]).
-export([send_file/3, read_body/2, stream/1, new/1, port/2, read_request_body/3, after_start/2, start_http/1, start_https/3, websocket/1, selecting/2, with_state/2, on_message/2, send_binary_frame/2, send_text_frame/2, upgrade/1]).
-export_type([response_data/0, file_error/0, read_error/0, chunk/0, chunk_state/0, builder/2, websocket_message/1, websocket_builder/2]).
-type response_data() :: {websocket,
gleam@erlang@process:selector(gleam@erlang@process:process_down())} |
{bytes, gleam@bit_builder:bit_builder()} |
{chunked, gleam@iterator:iterator(gleam@bit_builder:bit_builder())} |
{file, mist@internal@file:file_descriptor(), integer(), integer()}.
-type file_error() :: is_dir | no_access | no_entry | unknown_file_error.
-type read_error() :: excess_body | malformed_body.
-type chunk() :: {chunk,
bitstring(),
fun((integer()) -> {ok, chunk()} | {error, read_error()})} |
done.
-type chunk_state() :: {chunk_state,
mist@internal@buffer:buffer(),
mist@internal@buffer:buffer(),
boolean()}.
-opaque builder(JJN, JJO) :: {builder,
integer(),
fun((gleam@http@request:request(JJN)) -> gleam@http@response:response(JJO)),
fun((integer()) -> nil)}.
-type websocket_message(JJP) :: {text, bitstring()} |
{binary, bitstring()} |
closed |
shutdown |
{custom, JJP}.
-opaque websocket_builder(JJQ, JJR) :: {websocket_builder,
gleam@http@request:request(mist@internal@http:connection()),
JJQ,
fun((JJQ, mist@internal@websocket:websocket_connection(), mist@internal@websocket:valid_message(JJR)) -> gleam@otp@actor:next(JJQ)),
gleam@option:option(gleam@erlang@process:selector(JJR))}.
-spec convert_file_errors(mist@internal@file:file_error()) -> file_error().
convert_file_errors(Err) ->
case Err of
is_dir ->
is_dir;
no_access ->
no_access;
no_entry ->
no_entry;
unknown_file_error ->
unknown_file_error
end.
-spec send_file(binary(), integer(), gleam@option:option(integer())) -> {ok,
response_data()} |
{error, file_error()}.
send_file(Path, Offset, Limit) ->
_pipe = Path,
_pipe@1 = gleam@bit_string:from_string(_pipe),
_pipe@2 = mist@internal@file:stat(_pipe@1),
_pipe@3 = gleam@result:map_error(_pipe@2, fun convert_file_errors/1),
gleam@result:map(
_pipe@3,
fun(Stat) ->
{file,
erlang:element(2, Stat),
Offset,
gleam@option:unwrap(Limit, erlang:element(3, Stat))}
end
).
-spec read_body(
gleam@http@request:request(mist@internal@http:connection()),
integer()
) -> {ok, gleam@http@request:request(bitstring())} | {error, read_error()}.
read_body(Req, Max_body_limit) ->
_pipe = Req,
_pipe@1 = gleam@http@request:get_header(_pipe, <<"content-length"/utf8>>),
_pipe@2 = gleam@result:then(_pipe@1, fun gleam@int:parse/1),
_pipe@3 = gleam@result:unwrap(_pipe@2, 0),
(fun(Content_length) -> case Content_length of
Value when Value =< Max_body_limit ->
_pipe@4 = mist@internal@http:read_body(Req),
gleam@result:replace_error(_pipe@4, malformed_body);
_ ->
{error, excess_body}
end end)(_pipe@3).
-spec do_stream(
gleam@http@request:request(mist@internal@http:connection()),
mist@internal@buffer:buffer()
) -> fun((integer()) -> {ok, chunk()} | {error, read_error()}).
do_stream(Req, Buffer) ->
fun(Size) ->
Socket = erlang:element(3, erlang:element(4, Req)),
Transport = erlang:element(4, erlang:element(4, Req)),
Byte_size = gleam@bit_string:byte_size(erlang:element(3, Buffer)),
case {erlang:element(2, Buffer), Byte_size} of
{0, 0} ->
{ok, done};
{0, _} ->
{Data, Rest} = mist@internal@buffer:slice(Buffer, Size),
{ok,
{chunk,
Data,
do_stream(Req, mist@internal@buffer:new(Rest))}};
{_, Buffer_size} when Buffer_size >= Size ->
{Data@1, Rest@1} = mist@internal@buffer:slice(Buffer, Size),
New_buffer = erlang:setelement(3, Buffer, Rest@1),
{ok, {chunk, Data@1, do_stream(Req, New_buffer)}};
{_, _} ->
_pipe = mist@internal@http:read_data(
Socket,
Transport,
mist@internal@buffer:empty(),
invalid_body
),
_pipe@1 = gleam@result:replace_error(_pipe, malformed_body),
gleam@result:map(
_pipe@1,
fun(Data@2) ->
Fetched_data = gleam@bit_string:byte_size(Data@2),
New_buffer@1 = {buffer,
gleam@int:max(
0,
erlang:element(2, Buffer) - Fetched_data
),
gleam@bit_string:append(
erlang:element(3, Buffer),
Data@2
)},
{New_data, Rest@2} = mist@internal@buffer:slice(
New_buffer@1,
Size
),
{chunk,
New_data,
do_stream(
Req,
erlang:setelement(3, New_buffer@1, Rest@2)
)}
end
)
end
end.
-spec fetch_chunks_until(
glisten@socket:socket(),
glisten@socket@transport:transport(),
chunk_state(),
integer()
) -> {ok, {bitstring(), chunk_state()}} | {error, read_error()}.
fetch_chunks_until(Socket, Transport, State, Byte_size) ->
Data_size = gleam@bit_string:byte_size(
erlang:element(3, erlang:element(2, State))
),
case {erlang:element(4, State), Data_size} of
{_, Size} when Size >= Byte_size ->
{Value, Rest} = mist@internal@buffer:slice(
erlang:element(2, State),
Byte_size
),
{ok,
{Value,
erlang:setelement(2, State, mist@internal@buffer:new(Rest))}};
{true, _} ->
{ok,
{erlang:element(3, erlang:element(2, State)),
erlang:setelement(4, State, true)}};
{false, _} ->
case mist@internal@http:parse_chunk(
erlang:element(3, erlang:element(3, State))
) of
complete ->
Updated_state = erlang:setelement(
4,
erlang:setelement(
3,
State,
mist@internal@buffer:empty()
),
true
),
fetch_chunks_until(
Socket,
Transport,
Updated_state,
Byte_size
);
{chunk, <<>>, Next_buffer} ->
_pipe = mist@internal@http:read_data(
Socket,
Transport,
Next_buffer,
invalid_body
),
_pipe@1 = gleam@result:replace_error(_pipe, malformed_body),
gleam@result:then(
_pipe@1,
fun(New_data) ->
Updated_state@1 = erlang:setelement(
3,
State,
mist@internal@buffer:new(New_data)
),
fetch_chunks_until(
Socket,
Transport,
Updated_state@1,
Byte_size
)
end
);
{chunk, Data, Next_buffer@1} ->
Updated_state@2 = erlang:setelement(
3,
erlang:setelement(
2,
State,
mist@internal@buffer:append(
erlang:element(2, State),
Data
)
),
Next_buffer@1
),
fetch_chunks_until(
Socket,
Transport,
Updated_state@2,
Byte_size
)
end
end.
-spec do_stream_chunked(
gleam@http@request:request(mist@internal@http:connection()),
chunk_state()
) -> fun((integer()) -> {ok, chunk()} | {error, read_error()}).
do_stream_chunked(Req, State) ->
Socket = erlang:element(3, erlang:element(4, Req)),
Transport = erlang:element(4, erlang:element(4, Req)),
fun(Size) -> case fetch_chunks_until(Socket, Transport, State, Size) of
{ok, {Data, {chunk_state, _, _, true}}} ->
{ok, {chunk, Data, fun(_) -> {ok, done} end}};
{ok, {Data@1, State@1}} ->
{ok, {chunk, Data@1, do_stream_chunked(Req, State@1)}};
{error, _} ->
{error, malformed_body}
end end.
-spec stream(gleam@http@request:request(mist@internal@http:connection())) -> {ok,
fun((integer()) -> {ok, chunk()} | {error, read_error()})} |
{error, read_error()}.
stream(Req) ->
Continue = begin
_pipe = Req,
_pipe@1 = mist@internal@http:handle_continue(_pipe),
gleam@result:replace_error(_pipe@1, malformed_body)
end,
gleam@result:map(
Continue,
fun(_) ->
Is_chunked = case gleam@http@request:get_header(
Req,
<<"transfer-encoding"/utf8>>
) of
{ok, <<"chunked"/utf8>>} ->
true;
_ ->
false
end,
_assert_subject = erlang:element(2, erlang:element(4, Req)),
{initial, Data} = case _assert_subject of
{initial, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"mist"/utf8>>,
function => <<"stream"/utf8>>,
line => 265})
end,
case Is_chunked of
true ->
State = {chunk_state,
mist@internal@buffer:new(<<>>),
mist@internal@buffer:new(Data),
false},
do_stream_chunked(Req, State);
false ->
Content_length = begin
_pipe@2 = Req,
_pipe@3 = gleam@http@request:get_header(
_pipe@2,
<<"content-length"/utf8>>
),
_pipe@4 = gleam@result:then(
_pipe@3,
fun gleam@int:parse/1
),
gleam@result:unwrap(_pipe@4, 0)
end,
Initial_size = gleam@bit_string:byte_size(Data),
Buffer = {buffer,
gleam@int:max(0, Content_length - Initial_size),
Data},
do_stream(Req, Buffer)
end
end
).
-spec new(
fun((gleam@http@request:request(JKM)) -> gleam@http@response:response(JKO))
) -> builder(JKM, JKO).
new(Handler) ->
{builder,
4000,
Handler,
fun(Port) ->
Message = <<"Listening on localhost:"/utf8,
(gleam@int:to_string(Port))/binary>>,
gleam@io:println(Message)
end}.
-spec port(builder(JKS, JKT), integer()) -> builder(JKS, JKT).
port(Builder, Port) ->
erlang:setelement(2, Builder, Port).
-spec read_request_body(
builder(bitstring(), JKY),
integer(),
gleam@http@response:response(JKY)
) -> builder(mist@internal@http:connection(), JKY).
read_request_body(Builder, Bytes_limit, Failure_response) ->
Handler = fun(Request) -> case read_body(Request, Bytes_limit) of
{ok, Request@1} ->
(erlang:element(3, Builder))(Request@1);
{error, _} ->
Failure_response
end end,
{builder, erlang:element(2, Builder), Handler, erlang:element(4, Builder)}.
-spec after_start(builder(JLE, JLF), fun((integer()) -> nil)) -> builder(JLE, JLF).
after_start(Builder, After_start) ->
erlang:setelement(4, Builder, After_start).
-spec convert_body_types(gleam@http@response:response(response_data())) -> gleam@http@response:response(mist@internal@handler:response_data()).
convert_body_types(Resp) ->
New_body = case erlang:element(4, Resp) of
{websocket, Selector} ->
{websocket, Selector};
{bytes, Data} ->
{bytes, Data};
{file, Descriptor, Offset, Length} ->
{file, Descriptor, Offset, Length};
{chunked, Iter} ->
{chunked, Iter}
end,
gleam@http@response:set_body(Resp, New_body).
-spec start_http(builder(mist@internal@http:connection(), response_data())) -> {ok,
nil} |
{error, glisten:start_error()}.
start_http(Builder) ->
_pipe = erlang:element(3, Builder),
_pipe@1 = gleam@function:compose(_pipe, fun convert_body_types/1),
_pipe@2 = mist@internal@handler:with_func(_pipe@1),
_pipe@3 = glisten@acceptor:new_pool_with_data(
_pipe@2,
mist@internal@handler:new_state()
),
_pipe@4 = glisten:serve(erlang:element(2, Builder), _pipe@3),
gleam@result:map(
_pipe@4,
fun(Nil) ->
(erlang:element(4, Builder))(erlang:element(2, Builder)),
Nil
end
).
-spec start_https(
builder(mist@internal@http:connection(), response_data()),
binary(),
binary()
) -> {ok, nil} | {error, glisten:start_error()}.
start_https(Builder, Certfile, Keyfile) ->
_pipe = erlang:element(3, Builder),
_pipe@1 = gleam@function:compose(_pipe, fun convert_body_types/1),
_pipe@2 = mist@internal@handler:with_func(_pipe@1),
_pipe@3 = glisten@acceptor:new_pool_with_data(
_pipe@2,
mist@internal@handler:new_state()
),
_pipe@4 = glisten:serve_ssl(
erlang:element(2, Builder),
Certfile,
Keyfile,
_pipe@3
),
gleam@result:map(
_pipe@4,
fun(Nil) ->
(erlang:element(4, Builder))(erlang:element(2, Builder)),
Nil
end
).
-spec internal_to_public_ws_message(mist@internal@websocket:valid_message(JLU)) -> websocket_message(JLU).
internal_to_public_ws_message(Msg) ->
case Msg of
{internal, {data, {text_frame, _, Data}}} ->
{text, Data};
{internal, {data, {binary_frame, _, Data@1}}} ->
{binary, Data@1};
socket_closed ->
closed;
{user, Msg@1} ->
{custom, Msg@1}
end.
-spec websocket(gleam@http@request:request(mist@internal@http:connection())) -> websocket_builder(nil, any()).
websocket(Request) ->
{websocket_builder, Request, nil, fun(_, _, _) -> {stop, normal} end, none}.
-spec selecting(websocket_builder(JMB, JMC), gleam@erlang@process:selector(JMC)) -> websocket_builder(JMB, JMC).
selecting(Builder, Selector) ->
erlang:setelement(5, Builder, {some, Selector}).
-spec with_state(websocket_builder(JMI, JMJ), JMI) -> websocket_builder(JMI, JMJ).
with_state(Builder, State) ->
erlang:setelement(3, Builder, State).
-spec on_message(
websocket_builder(JMO, JMP),
fun((JMO, mist@internal@websocket:websocket_connection(), websocket_message(JMP)) -> gleam@otp@actor:next(JMO))
) -> websocket_builder(JMO, JMP).
on_message(Builder, Handler) ->
Handler@1 = fun(State, Connection, Message) -> _pipe = Message,
_pipe@1 = internal_to_public_ws_message(_pipe),
Handler(State, Connection, _pipe@1) end,
{websocket_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
Handler@1,
erlang:element(5, Builder)}.
-spec send_binary_frame(
mist@internal@websocket:websocket_connection(),
bitstring()
) -> {ok, nil} | {error, glisten@socket:socket_reason()}.
send_binary_frame(Connection, Frame) ->
_pipe = Frame,
_pipe@1 = mist@internal@websocket:to_binary_frame(_pipe),
(erlang:element(11, erlang:element(3, Connection)))(
erlang:element(2, Connection),
_pipe@1
).
-spec send_text_frame(
mist@internal@websocket:websocket_connection(),
bitstring()
) -> {ok, nil} | {error, glisten@socket:socket_reason()}.
send_text_frame(Connection, Frame) ->
_pipe = Frame,
_pipe@1 = mist@internal@websocket:to_text_frame(_pipe),
(erlang:element(11, erlang:element(3, Connection)))(
erlang:element(2, Connection),
_pipe@1
).
-spec upgrade(websocket_builder(any(), any())) -> gleam@http@response:response(response_data()).
upgrade(Builder) ->
Socket = erlang:element(3, erlang:element(4, erlang:element(2, Builder))),
Transport = erlang:element(4, erlang:element(4, erlang:element(2, Builder))),
_pipe = erlang:element(2, Builder),
_pipe@1 = mist@internal@http:upgrade(Socket, Transport, _pipe),
_pipe@2 = gleam@result:then(
_pipe@1,
fun(_) ->
mist@internal@websocket:initialize_connection(
erlang:element(3, Builder),
erlang:element(5, Builder),
erlang:element(4, Builder),
Socket,
Transport
)
end
),
_pipe@5 = gleam@result:map(
_pipe@2,
fun(Subj) ->
Ws_process = gleam@erlang@process:subject_owner(Subj),
Monitor = gleam@erlang@process:monitor_process(Ws_process),
Selector = begin
_pipe@3 = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:selecting_process_down(
_pipe@3,
Monitor,
fun gleam@function:identity/1
)
end,
_pipe@4 = gleam@http@response:new(200),
gleam@http@response:set_body(_pipe@4, {websocket, Selector})
end
),
gleam@result:lazy_unwrap(
_pipe@5,
fun() -> _pipe@6 = gleam@http@response:new(400),
gleam@http@response:set_body(
_pipe@6,
{bytes, gleam@bit_builder:new()}
) end
).