Packages
mist
0.9.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/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.