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, upgrade_websocket/3, append_default_headers/2, parse_request/2, read_body/2]).
-export_type([response_body/0, parse_error/0, connection/0, http_version/0, body_chunk/0, upgrade_websocket_error/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 response_body() :: {text_data, binary()} |
{bytes_data, gleam@bytes_tree:bytes_tree()} |
{bits_data, bitstring()} |
{string_tree_data, gleam@string_tree:string_tree()} |
{websocket_connection,
gleam@erlang@process:selector(gleam@erlang@process:down())} |
empty.
-type parse_error() :: invalid_method |
invalid_target |
invalid_version |
invalid_headers |
missing_host |
invalid_body |
body_too_large |
malformed_request |
packet_discard.
-type connection() :: {connection,
glisten@transport:transport(),
glisten@socket:socket(),
bitstring(),
gleam@option:option(http_version())}.
-type http_version() :: http10 | http11.
-type body_chunk() :: done | incomplete | {chunk, bitstring(), bitstring()}.
-type upgrade_websocket_error() :: version_not11_or_greater |
method_not_get |
missing_connection_header |
invalid_connection_header |
missing_upgrade_header |
invalid_upgrade_header |
missing_websocket_version |
missing_websocket_key.
-file("src/ewe/internal/http.gleam", 72).
?DOC(false).
-spec transform_connection(glisten:connection(any())) -> connection().
transform_connection(Connection) ->
{connection,
erlang:element(3, Connection),
erlang:element(2, Connection),
<<>>,
none}.
-file("src/ewe/internal/http.gleam", 258).
?DOC(false).
-spec available_cookie_key(gleam@dict:dict(binary(), binary()), integer()) -> binary().
available_cookie_key(Headers, Idx) ->
Key = case Idx of
0 ->
<<"set-cookie"/utf8>>;
N ->
<<"set-cookie-"/utf8, (erlang:integer_to_binary(N))/binary>>
end,
case gleam@dict:has_key(Headers, Key) of
true ->
available_cookie_key(Headers, Idx + 1);
false ->
Key
end.
-file("src/ewe/internal/http.gleam", 241).
?DOC(false).
-spec insert_header(gleam@dict:dict(binary(), binary()), binary(), binary()) -> gleam@dict:dict(binary(), binary()).
insert_header(Headers, Field, Value) ->
case Field /= <<"set-cookie"/utf8>> of
true ->
gleam@dict:upsert(Headers, Field, fun(Target) -> case Target of
{some, Existing} ->
<<<<Existing/binary, ", "/utf8>>/binary,
Value/binary>>;
none ->
Value
end end);
false ->
gleam@dict:insert(Headers, available_cookie_key(Headers, 0), Value)
end.
-file("src/ewe/internal/http.gleam", 323).
?DOC(false).
-spec handle_continue(gleam@http@request:request(connection())) -> {ok, nil} |
{error, parse_error()}.
handle_continue(Req) ->
Expect = begin
_pipe = erlang:element(3, Req),
gleam@list:find(
_pipe,
fun(Tupple) ->
(erlang:element(1, Tupple) =:= <<"expect"/utf8>>) andalso (string:lowercase(
erlang:element(2, Tupple)
)
=:= <<"100-continue"/utf8>>)
end
)
end,
case Expect of
{ok, _} ->
_pipe@1 = gleam@http@response:new(100),
_pipe@2 = gleam@http@response:set_body(
_pipe@1,
gleam@bytes_tree:new()
),
_pipe@3 = ewe@internal@encoder:encode_response(_pipe@2),
_pipe@4 = glisten@transport:send(
erlang:element(2, erlang:element(4, Req)),
erlang:element(3, erlang:element(4, Req)),
_pipe@3
),
gleam@result:replace_error(_pipe@4, malformed_request);
{error, nil} ->
{ok, nil}
end.
-file("src/ewe/internal/http.gleam", 417).
?DOC(false).
-spec upgrade_websocket(
gleam@http@request:request(connection()),
glisten@transport:transport(),
glisten@socket:socket()
) -> {ok, nil} | {error, upgrade_websocket_error()}.
upgrade_websocket(Req, Transport, Socket) ->
Http_version@1 = case erlang:element(5, erlang:element(4, Req)) of
{some, Http_version} -> Http_version;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ewe/internal/http"/utf8>>,
function => <<"upgrade_websocket"/utf8>>,
line => 422,
value => _assert_fail,
start => 9711,
'end' => 9771,
pattern_start => 9722,
pattern_end => 9747})
end,
gleam@bool:guard(
Http_version@1 /= http11,
{error, version_not11_or_greater},
fun() ->
gleam@bool:guard(
erlang:element(2, Req) /= get,
{error, method_not_get},
fun() ->
gleam@result:'try'(
case gleam@http@request:get_header(
Req,
<<"connection"/utf8>>
) of
{ok, <<"Upgrade"/utf8>>} ->
{ok, nil};
{ok, _} ->
{error, invalid_connection_header};
{error, _} ->
{error, missing_connection_header}
end,
fun(_) ->
gleam@result:'try'(
case gleam@http@request:get_header(
Req,
<<"upgrade"/utf8>>
) of
{ok, <<"websocket"/utf8>>} ->
{ok, nil};
{ok, _} ->
{error, invalid_upgrade_header};
{error, _} ->
{error, missing_upgrade_header}
end,
fun(_) ->
gleam@bool:guard(
gleam@http@request:get_header(
Req,
<<"sec-websocket-version"/utf8>>
)
=:= {error, nil},
{error, missing_websocket_version},
fun() ->
gleam@result:'try'(
begin
_pipe = gleam@http@request:get_header(
Req,
<<"sec-websocket-key"/utf8>>
),
gleam@result:replace_error(
_pipe,
missing_websocket_key
)
end,
fun(Key) ->
Accept_key = gramps@websocket:parse_websocket_key(
Key
),
_ = begin
_pipe@1 = gleam@http@request:get_header(
Req,
<<"sec-websocket-extensions"/utf8>>
),
_pipe@2 = gleam@result:map(
_pipe@1,
fun(_capture) ->
gleam@string:split(
_capture,
<<";"/utf8>>
)
end
),
gleam@result:unwrap(
_pipe@2,
[]
)
end,
_ = begin
_pipe@3 = gleam@http@response:new(
101
),
_pipe@4 = gleam@http@response:set_body(
_pipe@3,
gleam@bytes_tree:new(
)
),
_pipe@5 = gleam@http@response:set_header(
_pipe@4,
<<"connection"/utf8>>,
<<"upgrade"/utf8>>
),
_pipe@6 = gleam@http@response:set_header(
_pipe@5,
<<"upgrade"/utf8>>,
<<"websocket"/utf8>>
),
_pipe@7 = gleam@http@response:set_header(
_pipe@6,
<<"sec-websocket-accept"/utf8>>,
Accept_key
),
_pipe@8 = gleam@http@response:set_header(
_pipe@7,
<<"sec-websocket-version"/utf8>>,
<<"13"/utf8>>
),
_pipe@9 = ewe@internal@encoder:encode_response(
_pipe@8
),
glisten@transport:send(
Transport,
Socket,
_pipe@9
)
end,
{ok, nil}
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/ewe/internal/http.gleam", 470).
?DOC(false).
-spec append_default_headers(
gleam@http@response:response(gleam@bytes_tree:bytes_tree()),
http_version()
) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree()).
append_default_headers(Resp, Version) ->
Body_size = erlang:iolist_size(erlang:element(4, Resp)),
Resp@1 = case gleam@http@response:get_header(
Resp,
<<"content-length"/utf8>>
) of
{ok, _} ->
Resp;
{error, nil} ->
gleam@http@response:set_header(
Resp,
<<"content-length"/utf8>>,
erlang:integer_to_binary(Body_size)
)
end,
case Version of
http10 ->
gleam@http@response:set_header(
Resp@1,
<<"connection"/utf8>>,
<<"close"/utf8>>
);
http11 ->
case gleam@http@response:get_header(Resp@1, <<"connection"/utf8>>) of
{ok, _} ->
Resp@1;
{error, nil} ->
gleam@http@response:set_header(
Resp@1,
<<"connection"/utf8>>,
<<"keep-alive"/utf8>>
)
end
end.
-file("src/ewe/internal/http.gleam", 381).
?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", 84).
?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", 198).
?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, Idx, Field, Value}, Rest@1}} ->
gleam@result:'try'(
case ewe@internal@decoder:formatted_field_by_idx(Idx) of
{ok, Field@1} ->
{ok, Field@1};
{error, nil} ->
_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@2) ->
gleam@result:'try'(
begin
_pipe@2 = gleam@bit_array:to_string(Value),
gleam@result:replace_error(_pipe@2, invalid_headers)
end,
fun(Value@1) ->
_pipe@3 = insert_header(Headers, Field@2, Value@1),
parse_headers(Transport, Socket, Rest@1, _pipe@3)
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", 112).
?DOC(false).
-spec parse_request(connection(), bitstring()) -> {ok,
gleam@http@request:request(connection())} |
{error, parse_error()}.
parse_request(Connection, Buffer) ->
Transport = erlang:element(2, Connection),
Socket = erlang:element(3, Connection),
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(
Transport,
Socket,
Rest,
maps:new()
),
fun(_use0) ->
{Headers, Rest@1} = _use0,
Scheme = case Transport 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,
{request,
Method,
maps:to_list(
Headers
),
{connection,
erlang:element(
2,
Connection
),
erlang:element(
3,
Connection
),
Rest@1,
{some,
Version@1}},
Scheme,
Host,
{some, Port@1},
erlang:element(
6,
Uri
),
erlang:element(
7,
Uri
)}}
end
)
end
)
end) end
)
end
);
{ok, {more, Size}} ->
Read_size = gleam@option:unwrap(Size, 0),
gleam@result:'try'(
read_from_socket(
Transport,
Socket,
Read_size,
erlang:element(4, Connection),
malformed_request
),
fun(Buffer@1) -> parse_request(Connection, Buffer@1) end
);
_ ->
{error, packet_discard}
end.
-file("src/ewe/internal/http.gleam", 342).
?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", 270).
?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) ->
gleam@result:'try'(
handle_continue(Req),
fun(_) ->
Transport = erlang:element(2, erlang:element(4, Req)),
Socket = erlang:element(3, erlang:element(4, Req)),
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(
Transport,
Socket,
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(
Transport,
Socket,
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
end
).