-module(mist@handler). -compile(no_auto_import). -export([new_state/0, with_func/1, with/2]). -export_type([handler_response/0, handler_error/0, state/0]). -type handler_response() :: {response, gleam@http@response:response(mist@http:http_response_body())} | {upgrade, mist@websocket:websocket_handler()}. -type handler_error() :: {invalid_request, mist@http:decode_error()} | not_found. -type state() :: {state, gleam@option:option(gleam@otp@process:timer()), gleam@option:option(mist@websocket:websocket_handler())}. -spec new_state() -> state(). new_state() -> {state, none, none}. -spec with_func( fun((gleam@http@request:request(mist@http:body())) -> handler_response()) ) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))). with_func(Handler) -> glisten@tcp:handler( fun(Msg, Socket_state) -> {loop_state, Socket, _@1, State} = Socket_state, case erlang:element(3, State) of {some, Ws_handler} -> handle_websocket_message(Socket_state, Ws_handler, Msg); none -> _pipe@7 = begin _@3 = case erlang:element(2, State) of {some, T} -> gleam@otp@process:cancel_timer(T); _@2 -> timer_not_found end, _pipe = Msg, _pipe@1 = mist@http:parse_request(_pipe, Socket), _pipe@2 = gleam@result:map_error( _pipe@1, fun(Err) -> case Err of discard_packet -> nil; _@4 -> mist@logger:error(Err), glisten@tcp:close(Socket), nil end end ), _pipe@3 = gleam@result:replace_error( _pipe@2, {stop, normal} ), _pipe@6 = gleam@result:then( _pipe@3, fun(Req) -> _pipe@4 = gleam_erlang_ffi:rescue( fun() -> Handler(Req) end ), _pipe@5 = gleam@result:map( _pipe@4, fun(Resp) -> {Req, Resp} end ), gleam@result:map_error( _pipe@5, fun(_capture) -> log_and_error( _capture, erlang:element(2, Socket_state) ) end ) end ), gleam@result:map( _pipe@6, fun(Req_resp) -> {Req@1, Response} = Req_resp, case Response of {response, {response, _@5, _@6, {bit_builder_body, Body}} = Resp@1} -> handle_bit_builder_body( Resp@1, Body, Socket_state ); {response, {response, _@7, _@8, {file_body, _@9, _@10, _@11, _@12}} = Resp@2} -> handle_file_body(Resp@2, Socket_state); {upgrade, With_handler} -> handle_upgrade( Req@1, With_handler, Socket_state ) end end ) end, gleam@result:unwrap_both(_pipe@7) end end ). -spec handle_websocket_message( glisten@tcp:loop_state(state()), mist@websocket:websocket_handler(), bitstring() ) -> gleam@otp@actor:next(glisten@tcp:loop_state(state())). handle_websocket_message(State, Handler, Msg) -> case mist@websocket:frame_from_message(erlang:element(2, State), Msg) of {ok, {ping_frame, _@1, _@2}} -> {ok, _@4} = case glisten@tcp:send( erlang:element(2, State), mist@websocket:frame_to_bit_builder({pong_frame, 0, <<>>}) ) of {ok, _@3} -> {ok, _@3}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/handler"/utf8>>, function => <<"handle_websocket_message"/utf8>>, line => 107}) end, {continue, State}; {ok, {close_frame, _@5, _@6} = Frame} -> {ok, _@8} = case glisten@tcp:send( erlang:element(2, State), mist@websocket:frame_to_bit_builder(Frame) ) of {ok, _@7} -> {ok, _@7}; _try@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try@1, module => <<"mist/handler"/utf8>>, function => <<"handle_websocket_message"/utf8>>, line => 115}) end, _@10 = case erlang:element(2, Handler) of {some, Func} -> Func(erlang:element(3, State)); _@9 -> nil end, {stop, normal}; {ok, {pong_frame, _@11, _@12}} -> {stop, normal}; {ok, Frame@1} -> _pipe = case Frame@1 of {text_frame, _@13, Payload} -> {ok, Msg@2} = case gleam@bit_string:to_string(Payload) of {ok, Msg@1} -> {ok, Msg@1}; _try@2 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try@2, module => <<"mist/handler"/utf8>>, function => <<"handle_websocket_message"/utf8>>, line => 127}) end, {text_message, Msg@2}; _@14 -> {binary_message, erlang:element(3, Frame@1)} end, _pipe@1 = (fun(Ws_msg) -> gleam_erlang_ffi:rescue( fun() -> (erlang:element(4, Handler))( Ws_msg, erlang:element(3, State) ) end ) end)(_pipe), _pipe@2 = gleam@result:replace(_pipe@1, {continue, State}), _pipe@3 = gleam@result:map_error( _pipe@2, fun(Err) -> mist@logger:error(Err), _@16 = case erlang:element(2, Handler) of {some, Func@1} -> Func@1(erlang:element(3, State)); _@15 -> nil end, Err end ), _pipe@4 = gleam@result:replace_error(_pipe@3, {stop, normal}), gleam@result:unwrap_both(_pipe@4); {error, _@17} -> _@19 = case erlang:element(2, Handler) of {some, Func@2} -> Func@2(erlang:element(3, State)); _@18 -> nil end, {stop, normal} end. -spec log_and_error(gleam@erlang:crash(), glisten@tcp:socket()) -> gleam@otp@actor:next(glisten@tcp:loop_state(state())). log_and_error(Error, Socket) -> case Error of {exited, Msg} -> mist@logger:error(Error), _pipe = gleam@http@response:new(500), _pipe@1 = gleam@http@response:set_body( _pipe, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@2 = gleam@http@response:prepend_header( _pipe@1, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@3 = mist@encoder:to_bit_builder(_pipe@2), glisten@tcp:send(Socket, _pipe@3), glisten@tcp:close(Socket), {stop, {abnormal, Msg}}; {thrown, Msg} -> mist@logger:error(Error), _pipe = gleam@http@response:new(500), _pipe@1 = gleam@http@response:set_body( _pipe, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@2 = gleam@http@response:prepend_header( _pipe@1, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@3 = mist@encoder:to_bit_builder(_pipe@2), glisten@tcp:send(Socket, _pipe@3), glisten@tcp:close(Socket), {stop, {abnormal, Msg}}; {errored, Msg} -> mist@logger:error(Error), _pipe = gleam@http@response:new(500), _pipe@1 = gleam@http@response:set_body( _pipe, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@2 = gleam@http@response:prepend_header( _pipe@1, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@3 = mist@encoder:to_bit_builder(_pipe@2), glisten@tcp:send(Socket, _pipe@3), glisten@tcp:close(Socket), {stop, {abnormal, Msg}} end. -spec handle_bit_builder_body( gleam@http@response:response(mist@http:http_response_body()), gleam@bit_builder:bit_builder(), glisten@tcp:loop_state(state()) ) -> gleam@otp@actor:next(glisten@tcp:loop_state(state())). handle_bit_builder_body(Resp, Body, State) -> _pipe = Resp, _pipe@1 = gleam@http@response:set_body(_pipe, Body), _pipe@2 = mist@encoder:to_bit_builder(_pipe@1), _pipe@3 = glisten@tcp:send(erlang:element(2, State), _pipe@2), _pipe@4 = gleam@result:map( _pipe@3, fun(_) -> case gleam@http@response:get_header(Resp, <<"connection"/utf8>>) of {ok, <<"close"/utf8>>} -> glisten@tcp:close(erlang:element(2, State)), {stop, normal}; _@1 -> Timer = gleam@otp@process:send_after( erlang:element(3, State), 10000, close ), {continue, erlang:setelement( 4, State, erlang:setelement( 2, erlang:element(4, State), {some, Timer} ) )} end end ), _pipe@5 = gleam@result:replace_error(_pipe@4, {stop, normal}), gleam@result:unwrap_both(_pipe@5). -spec handle_file_body( gleam@http@response:response(mist@http:http_response_body()), glisten@tcp:loop_state(state()) ) -> gleam@otp@actor:next(glisten@tcp:loop_state(state())). handle_file_body(Resp, State) -> {file_body, File_descriptor@1, Content_type@1, Offset@1, Length@1} = case erlang:element( 4, Resp ) of {file_body, File_descriptor, Content_type, Offset, Length} -> {file_body, File_descriptor, Content_type, Offset, Length}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/handler"/utf8>>, function => <<"handle_file_body"/utf8>>, line => 211}) end, _pipe = Resp, _pipe@1 = gleam@http@response:prepend_header( _pipe, <<"content-length"/utf8>>, gleam@int:to_string(Length@1 - Offset@1) ), _pipe@2 = gleam@http@response:prepend_header( _pipe@1, <<"content-type"/utf8>>, Content_type@1 ), _pipe@3 = gleam@http@response:set_body(_pipe@2, gleam@bit_builder:new()), _pipe@4 = (fun(R) -> mist@encoder:response_builder( erlang:element(2, Resp), erlang:element(3, R) ) end)(_pipe@3), _pipe@5 = glisten@tcp:send(erlang:element(2, State), _pipe@4), _pipe@6 = gleam@result:map( _pipe@5, fun(_) -> mist@file:sendfile( File_descriptor@1, erlang:element(2, State), Offset@1, Length@1, [] ) end ), _pipe@7 = gleam@result:replace(_pipe@6, {continue, State}), _pipe@8 = gleam@result:replace_error(_pipe@7, {stop, normal}), gleam@result:unwrap_both(_pipe@8). -spec handle_upgrade( gleam@http@request:request(mist@http:body()), mist@websocket:websocket_handler(), glisten@tcp:loop_state(state()) ) -> gleam@otp@actor:next(glisten@tcp:loop_state(state())). handle_upgrade(Req, Handler, State) -> _pipe = Req, _pipe@1 = mist@http:upgrade(erlang:element(2, State), _pipe), _pipe@2 = gleam@result:map( _pipe@1, fun(_) -> _@2 = case erlang:element(3, Handler) of {some, Func} -> Func(erlang:element(3, State)); _@1 -> nil end end ), _pipe@3 = gleam@result:replace( _pipe@2, {continue, erlang:setelement( 4, State, erlang:setelement(3, erlang:element(4, State), {some, Handler}) )} ), _pipe@4 = gleam@result:replace_error(_pipe@3, {stop, normal}), gleam@result:unwrap_both(_pipe@4). -spec with( fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder())), integer() ) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))). with(Handler, Max_body_limit) -> Bad_request = begin _pipe = gleam@http@response:new(400), gleam@http@response:set_body(_pipe, gleam@bit_builder:new()) end, with_func( fun(Req) -> _pipe@14 = case {gleam@http@request:get_header( Req, <<"content-length"/utf8>> ), gleam@http@request:get_header( Req, <<"transfer-encoding"/utf8>> )} of {{ok, <<"0"/utf8>>}, _@1} -> _pipe@1 = Req, _pipe@2 = gleam@http@request:set_body(_pipe@1, <<>>), Handler(_pipe@2); {{error, nil}, {error, nil}} -> _pipe@1 = Req, _pipe@2 = gleam@http@request:set_body(_pipe@1, <<>>), Handler(_pipe@2); {_@2, {ok, <<"chunked"/utf8>>}} -> _pipe@3 = Req, _pipe@4 = mist@http:read_body(_pipe@3), _pipe@5 = gleam@result:map(_pipe@4, Handler), gleam@result:unwrap(_pipe@5, Bad_request); {{ok, Size}, _@3} -> _pipe@6 = Size, _pipe@7 = gleam@int:parse(_pipe@6), _pipe@13 = gleam@result:map( _pipe@7, fun(Size@1) -> case Size@1 > Max_body_limit of true -> _pipe@8 = gleam@http@response:new(413), _pipe@9 = gleam@http@response:set_body( _pipe@8, gleam@bit_builder:new() ), gleam@http@response:prepend_header( _pipe@9, <<"connection"/utf8>>, <<"close"/utf8>> ); false -> _pipe@10 = Req, _pipe@11 = mist@http:read_body(_pipe@10), _pipe@12 = gleam@result:map( _pipe@11, Handler ), gleam@result:unwrap(_pipe@12, Bad_request) end end ), gleam@result:unwrap(_pipe@13, Bad_request) end, _pipe@15 = gleam@http@response:map( _pipe@14, fun(A) -> {bit_builder_body, A} end ), {response, _pipe@15} end ).