Skip to content

Commit

Permalink
Merge branch 'maint'
Browse files Browse the repository at this point in the history
* maint:
  Check for logger error events
  Make shutdown connections synchronous
  Guard #state{} from incorrect field values
  Make start failure synchronous
  • Loading branch information
RaimoNiskanen committed Aug 11, 2023
2 parents 4c05f38 + e7c3faa commit 863c2b6
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 54 deletions.
4 changes: 2 additions & 2 deletions lib/inets/src/http_server/httpd_connection_sup.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -58,7 +58,7 @@ init([[Addr, Port]]) ->
Name = undefined, % As simple_one_for_one is used.
StartFunc = {httpd_request_handler, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
Shutdown = brutal_kill,
Modules = [httpd_request_handler],
Type = worker,

Expand Down
33 changes: 16 additions & 17 deletions lib/inets/src/http_server/httpd_manager.erl
Original file line number Diff line number Diff line change
Expand Up @@ -472,20 +472,19 @@ count_children(Sup) ->
Children = supervisor:count_children(whereis(Sup)),
proplists:get_value(workers, Children).

shutdown_connections(Sup) ->
Children = [Child || {_,Child,_,_} <- supervisor:which_children(Sup)],
lists:foreach(fun(Pid) -> exit(Pid, kill) end,
Children).

wait_for_shutdown(CSup, Manager) ->
case count_children(CSup) of
0 ->
Manager ! connections_terminated;
_ ->
receive
after 500 ->
ok
end,
wait_for_shutdown(CSup, Manager)
end.

shutdown_connections(CSup) ->
Children = [Child || {_,Child,_,_} <- supervisor:which_children(CSup)],
lists:foreach(
fun(Child) ->
_ = supervisor:terminate_child(CSup, Child)
end, Children).

wait_for_shutdown(CSup, Manager) ->
Children = [Child || {_,Child,_,_} <- supervisor:which_children(CSup)],
Monitors = [erlang:monitor(process, Child) || Child <- Children],
lists:foreach(
fun(Mref) ->
receive {'DOWN', Mref, _, _, _} -> ok end
end, Monitors),
Manager ! connections_terminated,
ok.
48 changes: 26 additions & 22 deletions lib/inets/src/http_server/httpd_request_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,10 @@ await_socket_ownership_transfer(AcceptTimeout) ->
end.


handle_msg(Body, State) when is_binary(Body) ->
handle_response(State#state{body = Body});
%%% Internal chunking of client body
handle_msg({{continue, Chunk}, Module, Function, Args}, #state{chunk = {_, CbState}} = State) ->
handle_msg({{continue, Chunk}, Module, Function, Args}, #state{chunk = {_, CbState}} = State) when is_binary(Chunk) ->
handle_internal_chunk(State#state{chunk = {continue, CbState},
body = Chunk}, Module, Function, Args);
handle_msg({continue, Module, Function, Args}, #state{mod = ModData} = State) ->
Expand All @@ -388,47 +390,51 @@ handle_msg({last, Body}, #state{headers = Headers, chunk = {_, CbState}} = State
headers = NewHeaders,
body = Body});
%%% Last data chunked by client
handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {ChunkState, CbState}} = State) when ChunkState =/= undefined ->
handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {ChunkState, CbState}} = State) when ChunkState =/= undefined, is_binary(Body) ->
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
handle_response(State#state{chunk = {last, CbState},
headers = NewHeaders,
body = Body});
handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {undefined, _}} = State) ->
handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {undefined, _}} = State) when is_binary(Body) ->
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
handle_response(State#state{headers = NewHeaders,
body = Body});
%%%
handle_msg(Result, State) ->
handle_http_msg(Result, State).

handle_http_msg({_, _, Version, {_, _}, _},
#state{status = busy, mod = ModData} = State) ->
handle_manager_busy(State#state{mod =
%% status = busy
handle_http_msg({_, _, Version, {_, _}, _},
#state{status = busy, mod = ModData} = State) ->
handle_manager_busy(State#state{mod =
ModData#mod{http_version = Version}}),
{stop, normal, State};
{stop, normal, State};

handle_http_msg({_, _, Version, {_, _}, _},
%% status = blocked
handle_http_msg({_, _, Version, {_, _}, _},
#state{status = blocked, mod = ModData} = State) ->
handle_manager_blocked(State#state{mod =
handle_manager_blocked(State#state{mod =
ModData#mod{http_version = Version}}),
{stop, normal, State};
{stop, normal, State};

%% status = accept
handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
#state{status = accept, mod = ModData} = State) ->
#state{status = accept, mod = ModData} = State) ->
true = is_binary(Body),
case httpd_request:validate(Method, Uri, Version) of
{ok, NormalizedURI} ->
{ok, NewModData} =
{ok, NewModData} =
httpd_request:update_mod_data(ModData, Method, NormalizedURI,
Version, Headers),

case is_host_specified_if_required(NewModData#mod.absolute_uri,
RecordHeaders, Version) of
true ->
handle_body(State#state{headers = RecordHeaders,
body = Body,
mod = NewModData});
false ->
httpd_response:send_status(ModData#mod{http_version =
Version},
httpd_response:send_status(ModData#mod{http_version =
Version},
400, none),
{stop, normal, State#state{response_sent = true}}
end;
Expand All @@ -445,9 +451,7 @@ handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
ModData#mod{http_version = httpd_request:default_version()},
400, Ver, {malformed_syntax, Ver}),
{stop, normal, State#state{response_sent = true}}
end;
handle_http_msg(Body, State) ->
handle_response(State#state{body = Body}).
end.

handle_manager_busy(#state{mod = #mod{config_db = ConfigDB}} = State) ->
MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150),
Expand Down Expand Up @@ -492,7 +496,7 @@ handle_body(#state{headers = Headers, body = Body,
{noreply, State#state{mfa =
{Module, Function, Args},
chunk = chunk_start(MaxChunk)}};
{ok, {ChunkedHeaders, NewBody}} ->
{ok, {ChunkedHeaders, NewBody}} when is_binary(NewBody) ->
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
handle_response(State#state{headers = NewHeaders,
body = NewBody,
Expand Down Expand Up @@ -525,12 +529,12 @@ handle_body(#state{headers = Headers, body = Body,
setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
{noreply, State#state{mfa =
{Module, Function, Args}}};
{ok, {{continue, Chunk}, Module, Function, Args}} ->
{ok, {{continue, Chunk}, Module, Function, Args}} when is_binary(Chunk) ->
handle_internal_chunk(State#state{chunk = chunk_start(MaxChunk),
body = Chunk}, Module, Function, Args);
%% Whole body delivered, if chunking mechanism is enabled the whole
%% body fits in one chunk.
{ok, NewBody} ->
{ok, NewBody} when is_binary(NewBody) ->
handle_response(State#state{chunk = chunk_finish(ChunkState,
CbState, MaxChunk),
headers = Headers,
Expand Down Expand Up @@ -678,7 +682,7 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData,
mfa = MFA,
max_keep_alive_request = decrease(Max),
headers = #http_request_h{},
body = undefined,
body = <<>>,
chunk = chunk_start(MaxChunk),
response_sent = false},

Expand Down
12 changes: 6 additions & 6 deletions lib/inets/src/http_server/mod_esi.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -297,7 +297,7 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid) ->
deliver_webpage_chunk(ModData, Pid, Timeout).

deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
case receive_headers(Timeout) of
case receive_headers(Pid, Timeout) of
{error, Reason} ->
%% Happens when webpage generator callback/3 is undefined
{error, Reason};
Expand Down Expand Up @@ -334,17 +334,17 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
{proceed,[{response, {already_sent, 504, 0}} | ModData#mod.data]}
end.

receive_headers(Timeout) ->
receive_headers(Pid, Timeout) ->
receive
{esi_data, Chunk} ->
httpd_esi:parse_headers(lists:flatten(Chunk));
{ok, Chunk} ->
httpd_esi:parse_headers(lists:flatten(Chunk));
{'EXIT', Pid, erl_scheme_webpage_chunk_undefined} when is_pid(Pid) ->
{'EXIT', Pid, erl_scheme_webpage_chunk_undefined} ->
{error, erl_scheme_webpage_chunk_undefined};
{'EXIT', Pid, {continue, _} = Continue} when is_pid(Pid) ->
{'EXIT', Pid, {continue, _} = Continue} ->
Continue;
{'EXIT', Pid, Reason} when is_pid(Pid) ->
{'EXIT', Pid, Reason} ->
exit({mod_esi_linked_process_died, Pid, Reason})
after Timeout ->
timeout
Expand Down
68 changes: 61 additions & 7 deletions lib/inets/test/httpd_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,7 @@ disturbing_1_0(Config) when is_list(Config) ->
disturbing([{http_version, "HTTP/1.0"} | Config]).

disturbing(Config) when is_list(Config)->
LogWatcher = start_log_watcher(),
Server = proplists:get_value(server_pid, Config),
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Expand All @@ -1615,12 +1616,13 @@ disturbing(Config) when is_list(Config)->
Close = list_to_atom((typestr(Type)) ++ "_closed"),
receive
{Close, Socket} ->
ok;
Msg ->
ct:fail({{expected, {Close, Socket}}, {got, Msg}})
end,
inets_test_lib:close(Type, Socket),
[{server_name, "httpd_disturbing_" ++ Version}] = httpd:info(Server, [server_name]).
inets_test_lib:close(Type, Socket),
[{server_name, "httpd_disturbing_" ++ Version}] =
httpd:info(Server, [server_name]),
[] = stop_log_watcher(LogWatcher),
[] = inets_test_lib:flush(),
ok
end.
%%-------------------------------------------------------------------------
non_disturbing_1_1(Config) when is_list(Config) ->
non_disturbing([{http_version, "HTTP/1.1"} | Config]).
Expand Down Expand Up @@ -1680,7 +1682,6 @@ reload_config_file(Config) when is_list(Config) ->
ok = file:write_file(HttpdConf, NewConfig),
ok = httpd:reload_config(HttpdConf, non_disturbing),
"httpd_test_new" = proplists:get_value(server_name, httpd:info(Server)).

%%-------------------------------------------------------------------------
mime_types_format(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
Expand Down Expand Up @@ -2654,3 +2655,56 @@ peer(Config) ->
_ ->
"false"
end.

start_log_watcher() ->
Spawner = self(),
EventDest = erlang:alias(),
HandlerId = ?MODULE,
_ =
spawn(
fun () ->
MonAlias =
monitor(process, Spawner, [{alias,reply_demonitor}]),
EventDest ! {started,EventDest,MonAlias},
receive
{stop,MonAlias} ->
_ = logger:remove_handler(HandlerId),
EventDest ! {stopped,EventDest},
ok;
{'DOWN',MonAlias,_,_,_} ->
_ = logger:remove_handler(HandlerId),
ok
end
end),
receive
{started,EventDest,Watcher} ->
Config = #{ config => EventDest },
ok = logger:add_handler(HandlerId, ?MODULE, Config),
{EventDest,Watcher}
end.

stop_log_watcher({EventDest,Watcher}) ->
Watcher ! {stop,Watcher},
receive
{stopped,EventDest} ->
true = unalias(EventDest),
stop_log_watcher_collect(EventDest)
end.
%%
stop_log_watcher_collect(EventDest) ->
receive
{event,EventDest,Event} ->
[Event | stop_log_watcher_collect(EventDest)]
after 0 ->
[]
end.

log(#{level := Level} = Event, #{ config := EventDest }) ->
%% Pass on events of level 'error' or worse
case logger:compare_levels(Level, error) of
lt ->
ok;
_ ->
EventDest ! {event,EventDest,Event},
ok
end.

0 comments on commit 863c2b6

Please sign in to comment.