diff --git a/.gitignore b/.gitignore index 2013848..76bfc3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.rebar3 doc/*.html !doc/tpl.html _build diff --git a/.travis.yml b/.travis.yml index 155265c..b42fd93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,18 +3,9 @@ language: erlang install: true script: make travis otp_release: -- 18.0 -- 18.1 -- 18.2 -- 18.2.1 - 18.3 -- 19.0 -- 19.1 -- 19.2 - 19.3 -- 20.0 -- 20.1 -- 20.2 -- 21.0 -- 21.1 -- 21.2 +- 20.3 +- 21.3 +- 22.3 +- 23.0.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf80f5..2162ccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v3.3.0 + + * Do not use x-forwarded-for for peer #75 + * Handle arguments with no value in (post|get)_arg_decoded #82 + * Fix compile-time warnings on missing record info. from aleppo #81 + ## v3.2.0 * Quell warnings on OTP-21: https://github.com/elli-lib/elli/pull/61 diff --git a/rebar.config b/rebar.config index a320072..d2a68e4 100644 --- a/rebar.config +++ b/rebar.config @@ -26,7 +26,7 @@ {rebar3_lint, "v0.1.10"} ]}. -{provider_hooks, [{pre, [{eunit, lint}]}]}. +%% {provider_hooks, [{pre, [{eunit, lint}]}]}. {dialyzer, [{plt_extra_apps, [ssl]}]}. {cover_enabled, true}. diff --git a/src/elli.app.src b/src/elli.app.src index dd31c4d..f3dee87 100644 --- a/src/elli.app.src +++ b/src/elli.app.src @@ -1,7 +1,7 @@ {application, elli, [ {description, "Erlang web server for HTTP APIs"}, - {vsn, "3.2.0"}, + {vsn, "3.3.0"}, {modules, [ elli, elli_example_callback, diff --git a/src/elli_http.erl b/src/elli_http.erl index 6ec9575..1db2a96 100644 --- a/src/elli_http.erl +++ b/src/elli_http.erl @@ -87,6 +87,7 @@ keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) -> Callback :: elli_handler:callback(), ConnToken :: {'keep_alive' | 'close', binary()}. handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> + elli_tcp:setopts(S, [{active, 100}]), {Method, RawPath, V, B0} = get_request(S, PrevB, Opts, Callback), t(headers_start), {RequestHeaders, B1} = get_headers(S, V, B0, Opts, Callback), @@ -404,7 +405,7 @@ send_chunk(Socket, Data) -> %% @doc Retrieve the request line. get_request(Socket, <<>>, Options, Callback) -> - NewBuffer = recv_request(Socket, <<>>, Options, Callback), + NewBuffer = recv_request(Socket, <<>>, request_timeout(Options), Options, Callback), get_request(Socket, NewBuffer, Options, Callback); get_request(Socket, Buffer, Options, Callback) -> t(request_start), @@ -413,7 +414,7 @@ get_request(Socket, Buffer, Options, Callback) -> get_request_(Socket, Buffer, Options, {Mod, Args} = Callback) -> case erlang:decode_packet(http_bin, Buffer, []) of {more, _} -> - NewBuffer = recv_request(Socket, Buffer, Options, Callback), + NewBuffer = recv_request(Socket, Buffer, request_timeout(Options), Options, Callback), get_request_(Socket, NewBuffer, Options, Callback); {ok, {http_request, Method, RawPath, Version}, Rest} -> {Method, RawPath, Version, Rest}; @@ -427,19 +428,49 @@ get_request_(Socket, Buffer, Options, {Mod, Args} = Callback) -> exit(normal) end. -recv_request(Socket, Buffer, Options, {Mod, Args} = _Callback) -> - case elli_tcp:recv(Socket, 0, request_timeout(Options)) of - {ok, Data} -> +recv_request(Socket, Buffer, Timeout, Options, {Mod, Args} = Callback) -> + receive + {tcp, _Socket, Data} -> <>; - {error, timeout} -> - handle_event(Mod, request_timeout, [], Args), + {tcp_passive, _Socket} -> + elli_tcp:setopts(Socket, [{active, 100}]), + recv_request(Socket, Buffer, Timeout, Options, Callback); + {tcp_closed, _Socket} -> + handle_event(Mod, request_closed, [], Args), elli_tcp:close(Socket), exit(normal); - {error, Closed} when Closed =:= closed orelse - Closed =:= enotconn -> - handle_event(Mod, request_closed, [], Args), + {tcp_error, _Socket, timeout} -> + handle_event(Mod, request_timeout, [], Args), elli_tcp:close(Socket), exit(normal) + %% TODO: Handle other error reasons? + %% {tcp_error, Socket, _} -> + %% handle_event(Mod, request_closed, [], Args), + %% elli_tcp:close(Socket), + %% exit(normal) + after + Timeout -> + handle_event(Mod, request_timeout, [], Args), + elli_tcp:close(Socket), + exit(normal) + end. + +recv(Socket, Timeout) -> + receive + {tcp, _Socket, Data} -> + {ok, Data}; + {tcp_passive, _Socket} -> + elli_tcp:setopts(Socket, [{active, 100}]), + recv(Socket, Timeout); + {tcp_closed, _Socket} -> + {error, closed}; + {tcp_error, _Socket, timeout} -> + {error, timeout}; + {tcp_error, _Socket, Reason} -> + {error, Reason} + after + Timeout -> + {error, timeout} end. -spec get_headers(Socket, V, Buffer, Opts, Callback) -> Headers when @@ -470,12 +501,12 @@ get_headers(Socket, Buffer, Headers, Count, Opts, {Mod, Args} = Callback) -> {ok, {http_error, _}, Rest} -> get_headers(Socket, Rest, Headers, Count, Opts, Callback); {more, _} -> - case elli_tcp:recv(Socket, 0, header_timeout(Opts)) of + case recv(Socket, header_timeout(Opts)) of {ok, Data} -> get_headers(Socket, <>, Headers, Count, Opts, Callback); - {error, Closed} when Closed =:= closed orelse - Closed =:= enotconn -> + {error, Reason} when Reason =:= closed orelse + Reason =:= enotconn -> handle_event(Mod, client_closed, [receiving_headers], Args), elli_tcp:close(Socket), exit(normal); @@ -532,20 +563,56 @@ get_body(Socket, Headers, Buffer, Opts, Callback) -> Result end. -do_get_body(Socket, Buffer, Opts, N, {Mod, Args}) -> - case elli_tcp:recv(Socket, N, body_timeout(Opts)) of - {ok, Data} -> - {<>, <<>>}; - {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> - handle_event(Mod, client_closed, [receiving_body], Args), - ok = elli_tcp:close(Socket), +do_get_body(Socket, Buffer, Opts, N, Callback) -> + {Body, Rest} = do_get_body_(Socket, <<>>, Opts, N, Callback), + {<>, Rest}. + +do_get_body_(Socket, Buffer, Opts, N, {Mod, Args} = Callback) -> + Timeout = body_timeout(Opts), + receive + {tcp, _Socket, <>} -> + Body = <>, + {Body, Rest}; + {tcp, _Socket, Data} -> + Body = <>, + do_get_body_(Socket, Body, Opts, N - byte_size(Body), Callback); + {tcp_passive, _Socket} -> + elli_tcp:setopts(Socket, [{active, 100}]), + do_get_body_(Socket, Buffer, Opts, N, Callback); + {tcp_closed, _Socket} -> + handle_event(Mod, request_closed, [], Args), + elli_tcp:close(Socket), exit(normal); - {error, timeout} -> - handle_event(Mod, client_timeout, [receiving_body], Args), - ok = elli_tcp:close(Socket), + {tcp_error, _Socket, timeout} -> + handle_event(Mod, request_timeout, [], Args), + elli_tcp:close(Socket), + exit(normal) + %% TODO: Handle other error reasons? + %% {tcp_error, Socket, _} -> + %% handle_event(Mod, request_closed, [], Args), + %% elli_tcp:close(Socket), + %% exit(normal) + after + Timeout -> + handle_event(Mod, request_timeout, [], Args), + elli_tcp:close(Socket), exit(normal) end. + + %% case elli_tcp:recv(Socket, N, ) of + %% {ok, Data} -> + %% {<>, <<>>}; + %% {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> + %% handle_event(Mod, client_closed, [receiving_body], Args), + %% ok = elli_tcp:close(Socket), + %% exit(normal); + %% {error, timeout} -> + %% handle_event(Mod, client_timeout, [receiving_body], Args), + %% ok = elli_tcp:close(Socket), + %% exit(normal) + %% end. + ensure_binary(Bin) when is_binary(Bin) -> Bin; ensure_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, latin1).