Current section

Files

Jump to
ewe src ewe@internal@http.erl
Raw

src/ewe@internal@http.erl

-module(ewe@internal@http).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ewe/internal/http.gleam").
-export([transform_connection/1, parse_body_chunk/1, read_from_socket/5, parse_request/2, read_chunked_body/6, read_body/2]).
-export_type([parse_error/0, connection/0, http_version/0, parsed_request/0, body_chunk/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-type parse_error() :: invalid_method |
invalid_target |
invalid_version |
invalid_headers |
missing_host |
invalid_body |
body_too_large |
packet_loss.
-type connection() :: {connection,
glisten@transport:transport(),
glisten@socket:socket(),
bitstring()}.
-type http_version() :: http10 | http11.
-type parsed_request() :: {parsed_request,
gleam@http@request:request(connection()),
http_version()}.
-type body_chunk() :: done | incomplete | {chunk, bitstring(), bitstring()}.
-file("src/ewe/internal/http.gleam", 44).
?DOC(false).
-spec transform_connection(glisten:connection(any())) -> connection().
transform_connection(Connection) ->
{connection,
erlang:element(3, Connection),
erlang:element(2, Connection),
<<>>}.
-file("src/ewe/internal/http.gleam", 300).
?DOC(false).
-spec parse_body_chunk(bitstring()) -> {ok, body_chunk()} |
{error, parse_error()}.
parse_body_chunk(Buffer) ->
case binary:split(Buffer, <<"\r\n"/utf8>>, []) of
[<<"0"/utf8>>, _] ->
{ok, done};
[Chunk_size, Rest] ->
gleam@result:'try'(
begin
_pipe = gleam@bit_array:to_string(Chunk_size),
_pipe@1 = gleam@result:'try'(
_pipe,
fun(_capture) -> gleam@int:base_parse(_capture, 16) end
),
gleam@result:replace_error(_pipe@1, invalid_body)
end,
fun(Size) -> case binary:split(Rest, <<"\r\n"/utf8>>, []) of
[Chunk, Rest@1] ->
case erlang:byte_size(Chunk) =:= Size of
true ->
{ok, {chunk, Chunk, Rest@1}};
false ->
{error, invalid_body}
end;
_ ->
{ok, incomplete}
end end
);
_ ->
{ok, incomplete}
end.
-file("src/ewe/internal/http.gleam", 58).
?DOC(false).
-spec read_from_socket(
glisten@transport:transport(),
glisten@socket:socket(),
integer(),
bitstring(),
parse_error()
) -> {ok, bitstring()} | {error, parse_error()}.
read_from_socket(Transport, Socket, Amount, Buffer, On_error) ->
Read_size = gleam@int:min(Amount, 1000000),
gleam@result:'try'(
begin
_pipe = glisten@transport:receive_timeout(
Transport,
Socket,
Read_size,
10000
),
gleam@result:replace_error(_pipe, On_error)
end,
fun(Data) ->
Amount@1 = Amount - Read_size,
Buffer@1 = <<Buffer/bitstring, Data/bitstring>>,
case Amount@1 > 0 of
true ->
read_from_socket(
Transport,
Socket,
Amount@1,
Buffer@1,
On_error
);
false ->
{ok, Buffer@1}
end
end
).
-file("src/ewe/internal/http.gleam", 172).
?DOC(false).
-spec parse_headers(
glisten@transport:transport(),
glisten@socket:socket(),
bitstring(),
gleam@dict:dict(binary(), binary())
) -> {ok, {gleam@dict:dict(binary(), binary()), bitstring()}} |
{error, parse_error()}.
parse_headers(Transport, Socket, Buffer, Headers) ->
case ewe_ffi:decode_packet(httph_bin, Buffer, []) of
{ok, {packet, http_eoh, Rest}} ->
{ok, {Headers, Rest}};
{ok, {packet, {http_header, Field, Value}, Rest@1}} ->
gleam@result:'try'(
begin
_pipe = gleam@bit_array:to_string(Field),
_pipe@1 = gleam@result:map(_pipe, fun string:lowercase/1),
gleam@result:replace_error(_pipe@1, invalid_headers)
end,
fun(Field@1) ->
gleam@result:'try'(
begin
_pipe@2 = gleam@bit_array:to_string(Value),
_pipe@3 = gleam@result:map(
_pipe@2,
fun gleam@string:trim/1
),
gleam@result:replace_error(_pipe@3, invalid_headers)
end,
fun(Value@1) ->
_pipe@4 = gleam@dict:insert(
Headers,
Field@1,
Value@1
),
parse_headers(Transport, Socket, Rest@1, _pipe@4)
end
)
end
);
{ok, {more, Size}} ->
Read_size = gleam@option:unwrap(Size, 0),
gleam@result:'try'(
read_from_socket(
Transport,
Socket,
Read_size,
Buffer,
invalid_headers
),
fun(Buffer@1) ->
parse_headers(Transport, Socket, Buffer@1, Headers)
end
);
_ ->
{error, invalid_headers}
end.
-file("src/ewe/internal/http.gleam", 90).
?DOC(false).
-spec parse_request(connection(), bitstring()) -> {ok, parsed_request()} |
{error, parse_error()}.
parse_request(Connection, Buffer) ->
case ewe_ffi:decode_packet(http_bin, Buffer, []) of
{ok,
{packet,
{http_request, Atom_method, {abs_path, Target}, Version},
Rest}} ->
gleam@result:'try'(
begin
_pipe = ewe@internal@decoder:decode_method(Atom_method),
gleam@result:replace_error(_pipe, invalid_method)
end,
fun(Method) ->
gleam@result:'try'(
begin
_pipe@1 = gleam@bit_array:to_string(Target),
_pipe@2 = gleam@result:'try'(
_pipe@1,
fun gleam_stdlib:uri_parse/1
),
gleam@result:replace_error(_pipe@2, invalid_target)
end,
fun(Uri) -> gleam@result:'try'(case Version of
{1, 0} ->
{ok, http10};
{1, 1} ->
{ok, http11};
_ ->
{error, invalid_version}
end, fun(Version@1) ->
gleam@result:'try'(
parse_headers(
erlang:element(2, Connection),
erlang:element(3, Connection),
Rest,
maps:new()
),
fun(_use0) ->
{Headers, Rest@1} = _use0,
Scheme = case erlang:element(
2,
Connection
) of
tcp ->
http;
ssl ->
https
end,
gleam@result:'try'(
begin
_pipe@3 = gleam_stdlib:map_get(
Headers,
<<"host"/utf8>>
),
_pipe@4 = gleam@result:'try'(
_pipe@3,
fun(_capture) ->
gleam@string:split_once(
_capture,
<<":"/utf8>>
)
end
),
gleam@result:replace_error(
_pipe@4,
missing_host
)
end,
fun(_use0@1) ->
{Host, Port} = _use0@1,
Port@1 = begin
_pipe@5 = gleam_stdlib:parse_int(
Port
),
gleam@result:unwrap(
_pipe@5,
case Scheme of
http ->
80;
https ->
443
end
)
end,
{ok,
{parsed_request,
{request,
Method,
maps:to_list(
Headers
),
{connection,
erlang:element(
2,
Connection
),
erlang:element(
3,
Connection
),
Rest@1},
Scheme,
Host,
{some, Port@1},
erlang:element(
6,
Uri
),
erlang:element(
7,
Uri
)},
Version@1}}
end
)
end
)
end) end
)
end
);
{ok, {more, Size}} ->
Read_size = gleam@option:unwrap(Size, 0),
gleam@result:'try'(
read_from_socket(
erlang:element(2, Connection),
erlang:element(3, Connection),
Read_size,
erlang:element(4, Connection),
packet_loss
),
fun(Buffer@1) -> parse_request(Connection, Buffer@1) end
);
_ ->
{error, packet_loss}
end.
-file("src/ewe/internal/http.gleam", 261).
?DOC(false).
-spec read_chunked_body(
glisten@transport:transport(),
glisten@socket:socket(),
bitstring(),
bitstring(),
integer(),
integer()
) -> {ok, bitstring()} | {error, parse_error()}.
read_chunked_body(Transport, Socket, Buffer, Body, Size_limit, Total_size) ->
gleam@bool:guard(
Total_size > Size_limit,
{error, body_too_large},
fun() -> case parse_body_chunk(Buffer) of
{ok, done} ->
{ok, Body};
{ok, incomplete} ->
gleam@result:'try'(
read_from_socket(
Transport,
Socket,
0,
Buffer,
invalid_body
),
fun(Buffer@1) ->
read_chunked_body(
Transport,
Socket,
Buffer@1,
Body,
Size_limit,
Total_size
)
end
);
{ok, {chunk, Chunk, Rest}} ->
Body@1 = <<Body/bitstring, Chunk/bitstring>>,
Total_size@1 = Total_size + erlang:byte_size(Chunk),
read_chunked_body(
Transport,
Socket,
Rest,
Body@1,
Size_limit,
Total_size@1
);
{error, Error} ->
{error, Error}
end end
).
-file("src/ewe/internal/http.gleam", 213).
?DOC(false).
-spec read_body(gleam@http@request:request(connection()), integer()) -> {ok,
gleam@http@request:request(bitstring())} |
{error, parse_error()}.
read_body(Req, Size_limit) ->
Transfer_encoding = begin
_pipe = gleam@http@request:get_header(Req, <<"transfer-encoding"/utf8>>),
gleam@result:map(_pipe, fun string:lowercase/1)
end,
case Transfer_encoding of
{ok, <<"chunked"/utf8>>} ->
gleam@result:'try'(
read_chunked_body(
erlang:element(2, erlang:element(4, Req)),
erlang:element(3, erlang:element(4, Req)),
erlang:element(4, erlang:element(4, Req)),
<<>>,
Size_limit,
0
),
fun(Body) -> {ok, gleam@http@request:set_body(Req, Body)} end
);
_ ->
Content_length = begin
_pipe@1 = gleam@http@request:get_header(
Req,
<<"content-length"/utf8>>
),
_pipe@2 = gleam@result:'try'(
_pipe@1,
fun gleam_stdlib:parse_int/1
),
gleam@result:unwrap(_pipe@2, 0)
end,
gleam@bool:guard(
Content_length > Size_limit,
{error, body_too_large},
fun() ->
Left = Content_length - erlang:byte_size(
erlang:element(4, erlang:element(4, Req))
),
_pipe@3 = case {Content_length, Left} of
{0, 0} ->
{ok, <<>>};
{0, _} ->
{ok, erlang:element(4, erlang:element(4, Req))};
{_, 0} ->
{ok, erlang:element(4, erlang:element(4, Req))};
{_, _} ->
read_from_socket(
erlang:element(2, erlang:element(4, Req)),
erlang:element(3, erlang:element(4, Req)),
Left,
erlang:element(4, erlang:element(4, Req)),
invalid_body
)
end,
gleam@result:map(
_pipe@3,
fun(_capture) ->
gleam@http@request:set_body(Req, _capture)
end
)
end
)
end.