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/4, read_data/4, parse_request/3, read_body/2, upgrade_socket/1, upgrade/3]).
-export_type([packet_type/0, http_uri/0, http_packet/0, decoded_packet/0, decode_error/0, buffer/0, body/0, http_response_body/0]).
-type packet_type() :: http | httph_bin | http_bin.
-type http_uri() :: {abs_path, bitstring()}.
-type http_packet() :: {http_request,
gleam@dynamic:dynamic(),
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() :: malformed_request |
invalid_method |
invalid_path |
unknown_header |
unknown_method |
invalid_body |
discard_packet.
-type buffer() :: {buffer, integer(), bitstring()}.
-opaque body() :: {unread, bitstring(), glisten@socket:socket()} |
{read, bitstring()}.
-type http_response_body() :: {bit_builder_body,
gleam@bit_builder:bit_builder()} |
{file_body, mist@file:file_descriptor(), binary(), integer(), integer()}.
-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 => 64})
end,
gleam@string:lowercase(Value@2).
-spec parse_headers(
bitstring(),
glisten@socket:socket(),
glisten@socket:transport(),
gleam@map:map_(binary(), binary())
) -> {ok, {gleam@map:map_(binary(), binary()), bitstring()}} |
{error, decode_error()}.
parse_headers(Bs, Socket, Transport, 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 => 82})
end,
_pipe = Headers,
_pipe@1 = gleam@map:insert(_pipe, Field@1, Value@2),
parse_headers(Rest, Socket, Transport, _pipe@1);
{ok, {end_of_headers, Rest@1}} ->
{ok, {Headers, Rest@1}};
{ok, {more_data, Size}} ->
Amount_to_read = gleam@option:unwrap(Size, 0),
case read_data(
Socket,
Transport,
{buffer, Amount_to_read, Bs},
unknown_header
) of
{error, _try@1} -> {error, _try@1};
{ok, Next} ->
parse_headers(Next, Socket, Transport, Headers)
end;
_@3 ->
{error, unknown_header}
end.
-spec read_data(
glisten@socket:socket(),
glisten@socket:transport(),
buffer(),
decode_error()
) -> {ok, bitstring()} | {error, decode_error()}.
read_data(Socket, Transport, Buffer, Error) ->
Receive_timeout = case Transport of
tcp ->
fun glisten@tcp:receive_timeout/3;
ssl ->
fun glisten@ssl:receive_timeout/3
end,
To_read = gleam@int:min(erlang:element(2, Buffer), 1000000),
Timeout = 15000,
case begin
_pipe = Socket,
_pipe@1 = Receive_timeout(_pipe, To_read, Timeout),
gleam@result:replace_error(_pipe@1, Error)
end of
{error, _try} -> {error, _try};
{ok, Data} ->
Next_buffer = {buffer,
erlang:element(2, Buffer)
- To_read,
<<(erlang:element(3, Buffer))/bitstring,
Data/bitstring>>},
case erlang:element(2, Next_buffer) > 0 of
true ->
read_data(Socket, Transport, Next_buffer, Error);
false ->
{ok, erlang:element(3, Next_buffer)}
end
end.
-spec read_chunk(
glisten@socket:socket(),
glisten@socket:transport(),
buffer(),
gleam@bit_builder:bit_builder()
) -> {ok, gleam@bit_builder:bit_builder()} | {error, decode_error()}.
read_chunk(Socket, Transport, Buffer, Body) ->
case {erlang:element(3, Buffer),
http_ffi:binary_match(
erlang:element(3, Buffer),
<<13/integer, 10/integer>>
)} of
{_@1, {ok, {Offset, _@2}}} ->
<<Chunk@1:Offset/binary, _@5/integer, _@6/integer, Rest@1/binary>> = case erlang:element(
3,
Buffer
) of
<<Chunk:Offset/binary, _@3/integer, _@4/integer, Rest/binary>> -> <<Chunk:Offset/binary,
_@3/integer,
_@4/integer,
Rest/binary>>;
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/http"/utf8>>,
function => <<"read_chunk"/utf8>>,
line => 146})
end,
case begin
_pipe = Chunk@1,
_pipe@1 = gleam@bit_string:to_string(_pipe),
_pipe@2 = gleam@result:map(
_pipe@1,
fun gleam@erlang@charlist:from_string/1
),
gleam@result:replace_error(_pipe@2, invalid_body)
end of
{error, _try@1} -> {error, _try@1};
{ok, Chunk_size} ->
case begin
_pipe@3 = http_ffi:string_to_int(Chunk_size, 16),
gleam@result:replace_error(_pipe@3, invalid_body)
end of
{error, _try@2} -> {error, _try@2};
{ok, Size} ->
case Size of
0 ->
{ok, Body};
Size@1 ->
case Rest@1 of
<<Next_chunk:Size@1/binary,
13/integer,
10/integer,
Rest@2/binary>> ->
read_chunk(
Socket,
Transport,
{buffer, 0, Rest@2},
gleam@bit_builder:append(
Body,
Next_chunk
)
);
_@7 ->
case read_data(
Socket,
Transport,
{buffer,
0,
erlang:element(3, Buffer)},
invalid_body
) of
{error, _try@3} -> {error, _try@3};
{ok, Next} ->
read_chunk(
Socket,
Transport,
{buffer, 0, Next},
Body
)
end
end
end
end
end;
{<<>>, _@8} ->
case read_data(
Socket,
Transport,
{buffer, 0, erlang:element(3, Buffer)},
invalid_body
) of
{error, _try@4} -> {error, _try@4};
{ok, Next@1} ->
read_chunk(Socket, Transport, {buffer, 0, Next@1}, Body)
end;
{_@9, {error, nil}} ->
{error, invalid_body}
end.
-spec parse_request(
bitstring(),
glisten@socket:socket(),
glisten@socket:transport()
) -> {ok, gleam@http@request:request(body())} | {error, decode_error()}.
parse_request(Bs, Socket, Transport) ->
case http_ffi:decode_packet(http_bin, Bs, []) of
{ok,
{binary_data, {http_request, Http_method, {abs_path, Path}, _@1}, Rest}} ->
case begin
_pipe = Http_method,
_pipe@1 = gleam@erlang@atom:from_dynamic(_pipe),
_pipe@2 = gleam@result:map(
_pipe@1,
fun gleam@erlang@atom:to_string/1
),
_pipe@3 = gleam@result:'or'(
_pipe@2,
gleam@dynamic:string(Http_method)
),
_pipe@4 = gleam@result:nil_error(_pipe@3),
_pipe@5 = gleam@result:then(
_pipe@4,
fun gleam@http:parse_method/1
),
gleam@result:replace_error(_pipe@5, unknown_method)
end of
{error, _try} -> {error, _try};
{ok, Method} ->
case parse_headers(Rest, Socket, Transport, gleam@map:new()) of
{error, _try@1} -> {error, _try@1};
{ok, {Headers, Rest@1}} ->
case begin
_pipe@6 = Path,
_pipe@7 = gleam@bit_string:to_string(_pipe@6),
gleam@result:replace_error(
_pipe@7,
invalid_path
)
end of
{error, _try@2} -> {error, _try@2};
{ok, Path@1} ->
{Path@4, Query@1} = case gleam@string:split(
Path@1,
<<"?"/utf8>>
) of
[Path@2] ->
{Path@2, []};
[Path@3, Query_string] ->
Query = begin
_pipe@8 = Query_string,
_pipe@9 = gleam@uri:parse_query(
_pipe@8
),
gleam@result:unwrap(_pipe@9, [])
end,
{Path@3, Query}
end,
Req = begin
_pipe@10 = gleam@http@request:new(),
_pipe@11 = gleam@http@request:set_body(
_pipe@10,
{unread, Rest@1, Socket}
),
_pipe@12 = gleam@http@request:set_method(
_pipe@11,
Method
),
_pipe@13 = gleam@http@request:set_path(
_pipe@12,
Path@4
),
gleam@http@request:set_query(
_pipe@13,
Query@1
)
end,
{ok,
erlang:setelement(
3,
Req,
gleam@map:to_list(Headers)
)}
end
end
end;
_@2 ->
{error, discard_packet}
end.
-spec read_body(gleam@http@request:request(body()), glisten@socket:transport()) -> {ok,
gleam@http@request:request(bitstring())} |
{error, decode_error()}.
read_body(Req, Transport) ->
case {gleam@http@request:get_header(Req, <<"transfer-encoding"/utf8>>),
erlang:element(4, Req)} of
{{ok, <<"chunked"/utf8>>}, {unread, Rest, Socket}} ->
case read_chunk(
Socket,
Transport,
{buffer, 0, Rest},
gleam@bit_builder:new()
) of
{error, _try} -> {error, _try};
{ok, Chunk} ->
{ok,
gleam@http@request:set_body(
Req,
gleam@bit_builder:to_bit_string(Chunk)
)}
end;
{_@1, {unread, Rest@1, Socket@1}} ->
Body_size = begin
_pipe = erlang:element(3, Req),
_pipe@1 = gleam@list:find(
_pipe,
fun(Tup) ->
gleam@pair:first(Tup)
=:= <<"content-length"/utf8>>
end
),
_pipe@2 = gleam@result:map(_pipe@1, fun gleam@pair:second/1),
_pipe@3 = gleam@result:then(_pipe@2, fun gleam@int:parse/1),
gleam@result:unwrap(_pipe@3, 0)
end,
Remaining = Body_size - gleam@bit_string:byte_size(Rest@1),
_pipe@4 = case {Body_size, Remaining} of
{0, 0} ->
{ok, <<>>};
{0, _@2} ->
{ok, Rest@1};
{_@3, 0} ->
{ok, Rest@1};
{_@4, _@5} ->
read_data(
Socket@1,
Transport,
{buffer, Remaining, Rest@1},
invalid_body
)
end,
_pipe@5 = gleam@result:map(
_pipe@4,
fun(_capture) -> gleam@http@request:set_body(Req, _capture) end
),
gleam@result:replace_error(_pipe@5, invalid_body);
{_@6, {read, _@7}} ->
{error, invalid_body}
end.
-spec upgrade_socket(gleam@http@request:request(body())) -> {ok,
gleam@http@response:response(gleam@bit_builder:bit_builder())} |
{error, gleam@http@request:request(body())}.
upgrade_socket(Req) ->
case begin
_pipe = gleam@http@request:get_header(Req, <<"upgrade"/utf8>>),
gleam@result:replace_error(_pipe, Req)
end of
{error, _try} -> {error, _try};
{ok, _@1} ->
case begin
_pipe@1 = gleam@http@request:get_header(
Req,
<<"sec-websocket-key"/utf8>>
),
gleam@result:replace_error(_pipe@1, Req)
end of
{error, _try@1} -> {error, _try@1};
{ok, Key} ->
case begin
_pipe@2 = gleam@http@request:get_header(
Req,
<<"sec-websocket-version"/utf8>>
),
gleam@result:replace_error(_pipe@2, Req)
end of
{error, _try@2} -> {error, _try@2};
{ok, _@2} ->
Accept_key = mist@websocket:parse_key(Key),
_pipe@3 = gleam@http@response:new(101),
_pipe@4 = gleam@http@response:set_body(
_pipe@3,
gleam@bit_builder:new()
),
_pipe@5 = gleam@http@response:prepend_header(
_pipe@4,
<<"Upgrade"/utf8>>,
<<"websocket"/utf8>>
),
_pipe@6 = gleam@http@response:prepend_header(
_pipe@5,
<<"Connection"/utf8>>,
<<"Upgrade"/utf8>>
),
_pipe@7 = gleam@http@response:prepend_header(
_pipe@6,
<<"Sec-WebSocket-Accept"/utf8>>,
Accept_key
),
{ok, _pipe@7}
end
end
end.
-spec upgrade(
glisten@socket:socket(),
glisten@socket:transport(),
gleam@http@request:request(body())
) -> {ok, nil} | {error, nil}.
upgrade(Socket, Transport, Req) ->
case begin
_pipe = upgrade_socket(Req),
gleam@result:nil_error(_pipe)
end of
{error, _try} -> {error, _try};
{ok, Resp} ->
Send = case Transport of
tcp ->
fun glisten@tcp:send/2;
ssl ->
fun glisten@ssl:send/2
end,
case begin
_pipe@1 = Resp,
_pipe@2 = mist@encoder:to_bit_builder(_pipe@1),
_pipe@3 = Send(Socket, _pipe@2),
gleam@result:nil_error(_pipe@3)
end of
{error, _try@1} -> {error, _try@1};
{ok, _@1} ->
{ok, nil}
end
end.