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/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), 8000000),
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} ->
Amount_read = gleam@bit_builder:byte_size(Data),
parse_body(
Socket,
{body_buffer,
erlang:element(2, Buffer)
- Amount_read,
<<(erlang:element(3, Buffer))/bitstring,
(gleam@bit_builder:to_bit_string(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 => 115})
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 => 249})
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 => 253})
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
).