-module(mist@http). -compile(no_auto_import). -export([from_header/1, parse_headers/2, parse_body/2, parse_request/2, encode_headers/1, status_to_bit_string/1, to_bit_builder/1, handler/1]). -export_type([packet_type/0, http_uri/0, http_packet/0, decoded_packet/0, decode_error/0, body_buffer/0, handler_error/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 body_buffer() :: {body_buffer, integer(), bitstring()}. -type handler_error() :: {invalid_request, decode_error()} | not_found. -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 => 54}) end, gleam@string:lowercase(Value@2). -spec parse_headers(bitstring(), gleam@map:map_(binary(), binary())) -> {ok, {gleam@map:map_(binary(), binary()), bitstring()}} | {error, decode_error()}. parse_headers(Bs, 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 => 66}) end, _pipe = Headers, _pipe@1 = gleam@map:insert(_pipe, Field@1, Value@2), parse_headers(Rest, _pipe@1); {ok, {end_of_headers, Rest@1}} -> {ok, {Headers, Rest@1}}; _@3 -> {error, unknown_header} end. -spec parse_body(glisten@tcp:socket(), body_buffer()) -> {ok, bitstring()} | {error, decode_error()}. parse_body(Socket, Buffer) -> case erlang:element(2, Buffer) > 0 of true -> 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, invalid_body) end of {error, _try} -> {error, _try}; {ok, Data} -> parse_body( Socket, {body_buffer, erlang:element(2, Buffer) - To_read, <<(erlang:element(3, Buffer))/bitstring, Data/bitstring>>} ) end; false -> {ok, erlang:element(3, Buffer)} 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 => 111}) 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, 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, Initial_body} = case Body_size of 0 -> {0, <<>>}; _@3 -> {Body_size - gleam@bit_string:byte_size( Rest@1 ), Rest@1} end, case parse_body( Socket, {body_buffer, Remaining, Initial_body} ) 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 encode_headers(gleam@map:map_(binary(), binary())) -> gleam@bit_builder:bit_builder(). encode_headers(Headers) -> gleam@map:fold( Headers, gleam@bit_builder:new(), fun(Builder, Header, Value) -> _pipe = Builder, _pipe@1 = gleam@bit_builder:append_string(_pipe, Header), _pipe@2 = gleam@bit_builder:append(_pipe@1, <<": "/utf8>>), _pipe@3 = gleam@bit_builder:append_string(_pipe@2, Value), gleam@bit_builder:append(_pipe@3, <<"\r\n"/utf8>>) end ). -spec status_to_bit_string(integer()) -> bitstring(). status_to_bit_string(Status) -> case Status of 101 -> <<"Switching Protocols"/utf8>>; 200 -> <<"Ok"/utf8>>; 201 -> <<"Created"/utf8>>; 202 -> <<"Accepted"/utf8>>; 204 -> <<"No Content"/utf8>>; 301 -> <<"Moved Permanently"/utf8>>; 400 -> <<"Bad Request"/utf8>>; 401 -> <<"Unauthorized"/utf8>>; 403 -> <<"Forbidden"/utf8>>; 404 -> <<"Not Found"/utf8>>; 405 -> <<"Method Not Allowed"/utf8>>; 500 -> <<"Internal Server Error"/utf8>>; 502 -> <<"Bad Gateway"/utf8>>; 503 -> <<"Service Unavailable"/utf8>>; 504 -> <<"Gateway Timeout"/utf8>> end. -spec to_bit_builder(gleam@http@response:response(bitstring())) -> gleam@bit_builder:bit_builder(). to_bit_builder(Resp) -> Body_size = gleam@bit_string:byte_size(erlang:element(4, Resp)), Headers = begin _pipe = gleam@map:from_list( [{<<"content-type"/utf8>>, <<"text/plain; charset=utf-8"/utf8>>}, {<<"content-length"/utf8>>, gleam@int:to_string(Body_size)}, {<<"connection"/utf8>>, <<"keep-alive"/utf8>>}] ), gleam@list:fold( erlang:element(3, Resp), _pipe, fun(Defaults, Tup) -> {Key, Value} = Tup, gleam@map:insert(Defaults, Key, Value) end ) end, Body_builder = case Body_size of 0 -> gleam@bit_builder:new(); _@1 -> _pipe@1 = gleam@bit_builder:new(), _pipe@2 = gleam@bit_builder:append(_pipe@1, erlang:element(4, Resp)), gleam@bit_builder:append(_pipe@2, <<"\r\n"/utf8>>) end, Status_string = begin _pipe@3 = erlang:element(2, Resp), _pipe@4 = gleam@int:to_string(_pipe@3), _pipe@5 = gleam@bit_builder:from_string(_pipe@4), _pipe@6 = gleam@bit_builder:append(_pipe@5, <<" "/utf8>>), gleam@bit_builder:append( _pipe@6, status_to_bit_string(erlang:element(2, Resp)) ) end, _pipe@7 = gleam@bit_builder:new(), _pipe@8 = gleam@bit_builder:append(_pipe@7, <<"HTTP/1.1 "/utf8>>), _pipe@9 = gleam@bit_builder:append_builder(_pipe@8, Status_string), _pipe@10 = gleam@bit_builder:append(_pipe@9, <<"\r\n"/utf8>>), _pipe@11 = gleam@bit_builder:append_builder( _pipe@10, encode_headers(Headers) ), _pipe@12 = gleam@bit_builder:append(_pipe@11, <<"\r\n"/utf8>>), gleam@bit_builder:append_builder(_pipe@12, Body_builder). -spec handler( fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(bitstring())) ) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(gleam@option:option(gleam@otp@process:timer()))) -> gleam@otp@actor:next(glisten@tcp:loop_state(gleam@option:option(gleam@otp@process:timer())))). handler(Func) -> glisten@tcp:handler( fun(Msg, State) -> {loop_state, Socket, Sender, Timer} = State, _@2 = case Timer of {some, T} -> gleam@otp@process:cancel_timer(T); _@1 -> timer_not_found end, {ok, Req@1} = case parse_request(Msg, Socket) of {ok, Req} -> {ok, Req}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/http"/utf8>>, function => <<"handler"/utf8>>, line => 245}) end, Resp = Func(Req@1), Raw_response = to_bit_builder(Resp), {ok, _@4} = case glisten@tcp:send(Socket, Raw_response) of {ok, _@3} -> {ok, _@3}; _try@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try@1, module => <<"mist/http"/utf8>>, function => <<"handler"/utf8>>, line => 249}) end, case gleam@http@response:get_header(Resp, <<"connection"/utf8>>) of {ok, <<"close"/utf8>>} -> glisten@tcp:close(Socket), {stop, normal}; _@5 -> Timer@1 = gleam@otp@process:send_after(Sender, 10000, close), {continue, erlang:setelement(4, State, {some, Timer@1})} end end ).