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_request/1, 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, 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.
-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 => 53})
end,
gleam@string:lowercase(Value@2).
-spec parse_headers(bitstring(), list({binary(), binary()})) -> {ok,
{list({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 => 65})
end,
parse_headers(Rest, [{Field@1, Value@2} | Headers]);
{ok, {end_of_headers, Rest@1}} ->
{ok, {Headers, Rest@1}};
_@3 ->
{error, unknown_header}
end.
-spec parse_request(bitstring()) -> {ok,
gleam@http@request:request(bitstring())} |
{error, decode_error()}.
parse_request(Bs) ->
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 => 78})
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, []) 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} ->
Req@1 = begin
_pipe@5 = gleam@http@request:new(),
_pipe@6 = gleam@http@request:set_body(
_pipe@5,
Rest@1
),
_pipe@7 = gleam@http@request:set_method(
_pipe@6,
Method@2
),
gleam@http@request:set_path(
_pipe@7,
Path@2
)
end,
{ok, erlang:setelement(3, Req@1, Headers)}
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>>, <<"close"/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:socket(), nil}) -> gleam@otp@actor:next({glisten@tcp:socket(),
nil})).
handler(Func) ->
glisten@tcp:handler(
fun(Msg, State) ->
{Socket, _@1} = State,
_pipe = Msg,
_pipe@1 = parse_request(_pipe),
_pipe@6 = gleam@result:map(
_pipe@1,
fun(Req) ->
_pipe@2 = Req,
_pipe@3 = Func(_pipe@2),
_pipe@4 = to_bit_builder(_pipe@3),
_pipe@5 = glisten@tcp:send(Socket, _pipe@4),
gleam@result:replace_error(_pipe@5, nil)
end
),
_pipe@7 = gleam@result:replace(_pipe@6, {stop, normal}),
gleam@result:unwrap(_pipe@7, {stop, normal})
end
).