Current section

Files

Jump to
mist src mist@http.erl
Raw

src/mist@http.erl

-module(mist@http).
-compile(no_auto_import).
-export([from_header/1, parse_headers/3, read_data/3, parse_request/2, new_state/0, handler/1, handler_func/1]).
-export_type([packet_type/0, http_uri/0, http_packet/0, decoded_packet/0, decode_error/0, buffer/0, handler_error/0, state/0, http_response_body/0, handler_response/0]).
-type packet_type() :: http | httph_bin | http_bin.
-type http_uri() :: {abs_path, bitstring()}.
-type http_packet() :: {http_request,
gleam@erlang@atom:atom_(),
http_uri(),
{integer(), integer()}} |
{http_header,
integer(),
gleam@erlang@atom:atom_(),
bitstring(),
bitstring()}.
-type decoded_packet() :: {binary_data, http_packet(), bitstring()} |
{end_of_headers, bitstring()} |
{more_data, gleam@option:option(integer())}.
-type decode_error() :: invalid_method |
invalid_path |
unknown_header |
invalid_body.
-type buffer() :: {buffer, integer(), bitstring()}.
-type handler_error() :: {invalid_request, decode_error()} | not_found.
-type state() :: {state,
gleam@option:option(gleam@otp@process:timer()),
gleam@option:option(mist@websocket:websocket_handler())}.
-type http_response_body() :: {bit_builder_body,
gleam@bit_builder:bit_builder()} |
{file_body, mist@file:file_descriptor(), binary(), integer(), integer()}.
-type handler_response() :: {response,
gleam@http@response:response(http_response_body())} |
{upgrade, mist@websocket:websocket_handler()}.
-spec from_header(bitstring()) -> binary().
from_header(Value) ->
{ok, Value@2} = case gleam@bit_string:to_string(Value) of
{ok, Value@1} -> {ok, Value@1};
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/http"/utf8>>,
function => <<"from_header"/utf8>>,
line => 58})
end,
gleam@string:lowercase(Value@2).
-spec parse_headers(
bitstring(),
glisten@tcp:socket(),
gleam@map:map_(binary(), binary())
) -> {ok, {gleam@map:map_(binary(), binary()), bitstring()}} |
{error, decode_error()}.
parse_headers(Bs, Socket, Headers) ->
case http_ffi:decode_packet(httph_bin, Bs, []) of
{ok, {binary_data, {http_header, _@1, _@2, Field, Value}, Rest}} ->
Field@1 = from_header(Field),
{ok, Value@2} = case gleam@bit_string:to_string(Value) of
{ok, Value@1} -> {ok, Value@1};
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/http"/utf8>>,
function => <<"parse_headers"/utf8>>,
line => 75})
end,
_pipe = Headers,
_pipe@1 = gleam@map:insert(_pipe, Field@1, Value@2),
parse_headers(Rest, Socket, _pipe@1);
{ok, {end_of_headers, Rest@1}} ->
{ok, {Headers, Rest@1}};
{ok, {more_data, Size}} ->
Amount_to_read = gleam@option:unwrap(Size, 0),
case read_data(Socket, {buffer, Amount_to_read, Bs}, unknown_header) of
{error, _try@1} -> {error, _try@1};
{ok, Next} ->
parse_headers(Next, Socket, Headers)
end;
_@3 ->
{error, unknown_header}
end.
-spec read_data(glisten@tcp:socket(), buffer(), decode_error()) -> {ok,
bitstring()} |
{error, decode_error()}.
read_data(Socket, Buffer, Error) ->
To_read = gleam@int:min(erlang:element(2, Buffer), 1000000),
Timeout = 15000,
case begin
_pipe = Socket,
_pipe@1 = glisten@tcp:receive_timeout(_pipe, To_read, Timeout),
gleam@result:replace_error(_pipe@1, Error)
end of
{error, _try} -> {error, _try};
{ok, Data} ->
Next_buffer = {buffer,
erlang:element(2, Buffer)
- To_read,
<<(erlang:element(3, Buffer))/bitstring,
Data/bitstring>>},
case erlang:element(2, Next_buffer) > 0 of
true ->
read_data(Socket, Next_buffer, Error);
false ->
{ok, erlang:element(3, Next_buffer)}
end
end.
-spec parse_request(bitstring(), glisten@tcp:socket()) -> {ok,
gleam@http@request:request(bitstring())} |
{error, decode_error()}.
parse_request(Bs, Socket) ->
case http_ffi:decode_packet(http_bin, Bs, []) of
{error, _try} -> {error, _try};
{ok, {binary_data, Req, Rest}} ->
{http_request, Method@1, {abs_path, Path@1}, _@2} = case Req of
{http_request, Method, {abs_path, Path}, _@1} -> {http_request,
Method,
{abs_path,
Path},
_@1};
_try@1 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@1,
module => <<"mist/http"/utf8>>,
function => <<"parse_request"/utf8>>,
line => 120})
end,
case begin
_pipe = Method@1,
_pipe@1 = gleam@erlang@atom:to_string(_pipe),
_pipe@2 = gleam@http:parse_method(_pipe@1),
gleam@result:replace_error(_pipe@2, invalid_method)
end of
{error, _try@2} -> {error, _try@2};
{ok, Method@2} ->
case parse_headers(Rest, Socket, gleam@map:new()) of
{error, _try@3} -> {error, _try@3};
{ok, {Headers, Rest@1}} ->
case begin
_pipe@3 = Path@1,
_pipe@4 = gleam@bit_string:to_string(_pipe@3),
gleam@result:replace_error(
_pipe@4,
invalid_path
)
end of
{error, _try@4} -> {error, _try@4};
{ok, Path@2} ->
Body_size = begin
_pipe@5 = Headers,
_pipe@6 = gleam@map:get(
_pipe@5,
<<"content-length"/utf8>>
),
_pipe@7 = gleam@result:then(
_pipe@6,
fun gleam@int:parse/1
),
gleam@result:unwrap(_pipe@7, 0)
end,
Remaining = Body_size
- gleam@bit_string:byte_size(Rest@1),
case case {Body_size, Remaining} of
{0, 0} ->
{ok, <<>>};
{0, _@3} ->
{ok, Rest@1};
{_@4, 0} ->
{ok, Rest@1};
{_@5, _@6} ->
read_data(
Socket,
{buffer, Remaining, Rest@1},
invalid_body
)
end of
{error, _try@5} -> {error, _try@5};
{ok, Body} ->
Req@1 = begin
_pipe@8 = gleam@http@request:new(
),
_pipe@9 = gleam@http@request:set_body(
_pipe@8,
Body
),
_pipe@10 = gleam@http@request:set_method(
_pipe@9,
Method@2
),
gleam@http@request:set_path(
_pipe@10,
Path@2
)
end,
{ok,
erlang:setelement(
3,
Req@1,
gleam@map:to_list(Headers)
)}
end
end
end
end
end.
-spec new_state() -> state().
new_state() ->
{state, none, none}.
-spec handler(
fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))
) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))).
handler(Handler) ->
handler_func(
fun(Req) ->
_pipe = Req,
_pipe@1 = Handler(_pipe),
_pipe@2 = gleam@http@response:map(
_pipe@1,
fun(A) -> {bit_builder_body, A} end
),
{response, _pipe@2}
end
).
-spec handler_func(
fun((gleam@http@request:request(bitstring())) -> handler_response())
) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))).
handler_func(Handler) ->
glisten@tcp:handler(
fun(Msg, Socket_state) ->
{loop_state, Socket, Sender, State} = Socket_state,
case erlang:element(3, State) of
{some, Ws_handler} ->
case mist@websocket:frame_from_message(Msg) of
{ok, {text_frame, _@1, Payload}} ->
_pipe = Payload,
_pipe@1 = {text_message, _pipe},
_pipe@2 = (fun(Ws_msg) ->
gleam_erlang_ffi:rescue(
fun() ->
(erlang:element(4, Ws_handler))(
Ws_msg,
Socket
)
end
)
end)(_pipe@1),
_pipe@3 = gleam@result:replace(
_pipe@2,
{continue, Socket_state}
),
_pipe@4 = gleam@result:map_error(
_pipe@3,
fun(Err) ->
mist@logger:error(Err),
_@3 = case erlang:element(2, Ws_handler) of
{some, Func} ->
Func(Sender);
_@2 ->
nil
end,
Err
end
),
_pipe@5 = gleam@result:replace_error(
_pipe@4,
{stop, normal}
),
gleam@result:unwrap_both(_pipe@5);
{ok, {close_frame, _@4, _@5} = Frame} ->
{ok, _@7} = case glisten@tcp:send(
Socket,
mist@websocket:frame_to_bit_builder(Frame)
) of
{ok, _@6} -> {ok, _@6};
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/http"/utf8>>,
function => <<"handler_func"/utf8>>,
line => 235})
end,
_@9 = case erlang:element(2, Ws_handler) of
{some, Func@1} ->
Func@1(Sender);
_@8 ->
nil
end,
{stop, normal};
{error, _@10} ->
_@12 = case erlang:element(2, Ws_handler) of
{some, Func@2} ->
Func@2(Sender);
_@11 ->
nil
end,
{stop, normal}
end;
none ->
_@14 = case erlang:element(2, State) of
{some, T} ->
gleam@otp@process:cancel_timer(T);
_@13 ->
timer_not_found
end,
_pipe@6 = Msg,
_pipe@7 = parse_request(_pipe@6, Socket),
_pipe@8 = gleam@result:replace_error(
_pipe@7,
{stop, normal}
),
_pipe@29 = gleam@result:map(
_pipe@8,
fun(Req) ->
case gleam_erlang_ffi:rescue(
fun() -> Handler(Req) end
) of
{ok,
{response,
{response,
_@15,
_@16,
{bit_builder_body, Body}} = Resp}} ->
_pipe@9 = Resp,
_pipe@10 = gleam@http@response:set_body(
_pipe@9,
Body
),
_pipe@11 = mist@encoder:to_bit_builder(
_pipe@10
),
_pipe@12 = glisten@tcp:send(
Socket,
_pipe@11
),
_pipe@13 = gleam@result:map(
_pipe@12,
fun(_) ->
case gleam@http@response:get_header(
Resp,
<<"connection"/utf8>>
) of
{ok, <<"close"/utf8>>} ->
glisten@tcp:close(Socket),
{stop, normal};
_@17 ->
Timer = gleam@otp@process:send_after(
Sender,
10000,
close
),
{continue,
erlang:setelement(
4,
Socket_state,
erlang:setelement(
2,
State,
{some, Timer}
)
)}
end
end
),
_pipe@14 = gleam@result:replace_error(
_pipe@13,
{stop, normal}
),
gleam@result:unwrap_both(_pipe@14);
{ok,
{response,
{response,
_@18,
_@19,
{file_body,
File_descriptor,
Content_type,
Offset,
Length}} = Resp@1}} ->
_pipe@15 = Resp@1,
_pipe@16 = gleam@http@response:prepend_header(
_pipe@15,
<<"content-length"/utf8>>,
gleam@int:to_string(Length - Offset)
),
_pipe@17 = gleam@http@response:prepend_header(
_pipe@16,
<<"content-type"/utf8>>,
Content_type
),
_pipe@18 = gleam@http@response:set_body(
_pipe@17,
gleam@bit_builder:new()
),
_pipe@19 = (fun(R) ->
mist@encoder:response_builder(
erlang:element(2, Resp@1),
erlang:element(3, R)
)
end)(_pipe@18),
_pipe@20 = glisten@tcp:send(
Socket,
_pipe@19
),
_pipe@21 = gleam@result:map(
_pipe@20,
fun(_) ->
mist@file:sendfile(
File_descriptor,
Socket,
Offset,
Length,
[]
)
end
),
_pipe@22 = gleam@result:replace(
_pipe@21,
{continue, Socket_state}
),
_pipe@23 = gleam@result:replace_error(
_pipe@22,
{stop, normal}
),
gleam@result:unwrap_both(_pipe@23);
{ok, {upgrade, With_handler}} ->
_pipe@24 = Req,
_pipe@25 = mist@websocket:upgrade(
Socket,
_pipe@24
),
_pipe@26 = gleam@result:map(
_pipe@25,
fun(_) ->
_@21 = case erlang:element(
3,
With_handler
) of
{some, Func@3} ->
Func@3(Sender);
_@20 ->
nil
end
end
),
_pipe@27 = gleam@result:replace(
_pipe@26,
{continue,
erlang:setelement(
4,
Socket_state,
erlang:setelement(
3,
State,
{some, With_handler}
)
)}
),
_pipe@28 = gleam@result:replace_error(
_pipe@27,
{stop, normal}
),
gleam@result:unwrap_both(_pipe@28);
{error, {exited, Msg@1} = Err@1} ->
mist@logger:error(Err@1),
_pipe@29 = gleam@http@response:new(500),
_pipe@30 = gleam@http@response:set_body(
_pipe@29,
gleam@bit_builder:from_bit_string(
<<"Internal Server Error"/utf8>>
)
),
_pipe@31 = gleam@http@response:prepend_header(
_pipe@30,
<<"content-length"/utf8>>,
<<"21"/utf8>>
),
_pipe@32 = mist@encoder:to_bit_builder(
_pipe@31
),
glisten@tcp:send(Socket, _pipe@32),
glisten@tcp:close(Socket),
{stop, {abnormal, Msg@1}};
{error, {thrown, Msg@1} = Err@1} ->
mist@logger:error(Err@1),
_pipe@29 = gleam@http@response:new(500),
_pipe@30 = gleam@http@response:set_body(
_pipe@29,
gleam@bit_builder:from_bit_string(
<<"Internal Server Error"/utf8>>
)
),
_pipe@31 = gleam@http@response:prepend_header(
_pipe@30,
<<"content-length"/utf8>>,
<<"21"/utf8>>
),
_pipe@32 = mist@encoder:to_bit_builder(
_pipe@31
),
glisten@tcp:send(Socket, _pipe@32),
glisten@tcp:close(Socket),
{stop, {abnormal, Msg@1}};
{error, {errored, Msg@1} = Err@1} ->
mist@logger:error(Err@1),
_pipe@29 = gleam@http@response:new(500),
_pipe@30 = gleam@http@response:set_body(
_pipe@29,
gleam@bit_builder:from_bit_string(
<<"Internal Server Error"/utf8>>
)
),
_pipe@31 = gleam@http@response:prepend_header(
_pipe@30,
<<"content-length"/utf8>>,
<<"21"/utf8>>
),
_pipe@32 = mist@encoder:to_bit_builder(
_pipe@31
),
glisten@tcp:send(Socket, _pipe@32),
glisten@tcp:close(Socket),
{stop, {abnormal, Msg@1}}
end
end
),
gleam@result:unwrap_both(_pipe@29)
end
end
).