Packages
mist
0.3.0
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
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
).