%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: The top supervisor for the http server (httpd) hangs under
%% inets_sup.
%%----------------------------------------------------------------------
-module(httpd_sup).
-behaviour(supervisor).
%% Internal application API
-export([start_link/1, start_link/2]).
-export([start_child/1, restart_child/2, stop_child/2]).
%% Supervisor callback
-export([init/1]).
-export([listen_init/4]).
-define(TIMEOUT, 15000).
-include("httpd_internal.hrl").
-include("inets_internal.hrl").
%%%=========================================================================
%%% API
%%%=========================================================================
start_link(HttpdServices) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [HttpdServices]).
start_link(HttpdServices, stand_alone) ->
supervisor:start_link(?MODULE, [HttpdServices]).
start_child(Config) ->
try httpd_config(Config) of
{ok, NewConfig} ->
Spec = httpd_child_spec(NewConfig, ?TIMEOUT, []),
case supervisor:start_child(?MODULE, Spec) of
{error, {invalid_child_spec, Error}} ->
Error;
Other ->
Other
end
catch
throw:Error ->
Error
end.
restart_child(Address, Port) ->
Name = id(Address, Port),
case supervisor:terminate_child(?MODULE, Name) of
ok ->
supervisor:restart_child(?MODULE, Name);
Error ->
Error
end.
stop_child(Address, Port) ->
Name = id(Address, Port),
case supervisor:terminate_child(?MODULE, Name) of
ok ->
supervisor:delete_child(?MODULE, Name);
Error ->
Error
end.
id(Address, Port) ->
{httpd_instance_sup, Address, Port}.
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
init([HttpdServices]) ->
?hdrd("starting", [{httpd_service, HttpdServices}]),
RestartStrategy = one_for_one,
MaxR = 10,
MaxT = 3600,
Children = child_specs(HttpdServices, []),
{ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
%% The format of the httpd service is:
%% httpd_service() -> {httpd,httpd()}
%% httpd() -> [httpd_config()] | file()
%% httpd_config() -> {file,file()} |
%% {debug,debug()} |
%% {accept_timeout,integer()}
%% debug() -> disable | [debug_options()]
%% debug_options() -> {all_functions,modules()} |
%% {exported_functions,modules()} |
%% {disable,modules()}
%% modules() -> [atom()]
child_specs([], Acc) ->
Acc;
child_specs([{httpd, HttpdService} | Rest], Acc) ->
?hdrd("child specs", [{httpd, HttpdService}]),
NewHttpdService = (catch mk_tuple_list(HttpdService)),
?hdrd("child specs", [{new_httpd, NewHttpdService}]),
case catch child_spec(NewHttpdService) of
{error, Reason} ->
?hdri("failed generating child spec", [{reason, Reason}]),
error_msg("Failed to start service: ~n~p ~n due to: ~p~n",
[HttpdService, Reason]),
child_specs(Rest, Acc);
Spec ->
?hdrt("child spec", [{child_spec, Spec}]),
child_specs(Rest, [Spec | Acc])
end.
child_spec(HttpdService) ->
{ok, Config} = httpd_config(HttpdService),
?hdrt("child spec", [{config, Config}]),
Debug = proplists:get_value(debug, Config, []),
AcceptTimeout = proplists:get_value(accept_timeout, Config, 15000),
httpd_util:valid_options(Debug, AcceptTimeout, Config),
httpd_child_spec(Config, AcceptTimeout, Debug).
httpd_config([Value| _] = Config) when is_tuple(Value) ->
case proplists:get_value(file, Config) of
undefined ->
case proplists:get_value(proplist_file, Config) of
undefined ->
httpd_conf:validate_properties(Config);
File ->
try file:consult(File) of
{ok, [PropList]} ->
httpd_conf:validate_properties(PropList)
catch
exit:_ ->
throw({error,
{could_not_consult_proplist_file, File}})
end
end;
File ->
{ok, File}
end.
httpd_child_spec([Value| _] = Config, AcceptTimeout, Debug)
when is_tuple(Value) ->
?hdrt("httpd_child_spec - entry", [{accept_timeout, AcceptTimeout},
{debug, Debug}]),
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
httpd_child_spec(Config, AcceptTimeout, Debug, Address, Port);
%% In this case the AcceptTimeout and Debug will only have default values...
httpd_child_spec(ConfigFile, AcceptTimeoutDef, DebugDef) ->
?hdrt("httpd_child_spec - entry", [{config_file, ConfigFile},
{accept_timeout_def, AcceptTimeoutDef},
{debug_def, DebugDef}]),
case httpd_conf:load(ConfigFile) of
{ok, ConfigList} ->
?hdrt("httpd_child_spec - loaded", [{config_list, ConfigList}]),
case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
?hdrt("httpd_child_spec - validated", [{config, Config}]),
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
AcceptTimeout =
proplists:get_value(accept_timeout, Config,
AcceptTimeoutDef),
Debug =
proplists:get_value(debug, Config, DebugDef),
httpd_child_spec([{file, ConfigFile} | Config],
AcceptTimeout, Debug, Address, Port);
Error ->
Error
end;
Error ->
Error
end.
httpd_child_spec(Config, AcceptTimeout, Debug, Addr, Port) ->
Fd = proplists:get_value(fd, Config, undefined),
case Port == 0 orelse Fd =/= undefined of
true ->
httpd_child_spec_listen(Config, AcceptTimeout, Debug, Addr, Port);
false ->
httpd_child_spec_nolisten(Config, AcceptTimeout, Debug, Addr, Port)
end.
httpd_child_spec_listen(Config, AcceptTimeout, Debug, Addr, Port) ->
case start_listen(Addr, Port, Config) of
{Pid, {NewPort, NewConfig, ListenSocket}} ->
Name = {httpd_instance_sup, Addr, NewPort},
StartFunc = {httpd_instance_sup, start_link,
[NewConfig, AcceptTimeout,
{Pid, ListenSocket}, Debug]},
Restart = permanent,
Shutdown = infinity,
Modules = [httpd_instance_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules};
{Pid, {error, Reason}} ->
exit(Pid, normal),
{error, Reason}
end.
httpd_child_spec_nolisten(Config, AcceptTimeout, Debug, Addr, Port) ->
Name = {httpd_instance_sup, Addr, Port},
StartFunc = {httpd_instance_sup, start_link,
[Config, AcceptTimeout, Debug]},
Restart = permanent,
Shutdown = infinity,
Modules = [httpd_instance_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
mk_tuple_list([]) ->
[];
mk_tuple_list([H={_,_}|T]) ->
[H|mk_tuple_list(T)];
mk_tuple_list(F) ->
[{file, F}].
error_msg(F, A) ->
error_logger:error_msg(F ++ "~n", A).
listen(Address, Port, Config) ->
try socket_type(Config) of
SocketType ->
case http_transport:start(SocketType) of
ok ->
Fd = proplists:get_value(fd, Config),
IpFamily = proplists:get_value(ipfamily, Config, inet6fb4),
case http_transport:listen(SocketType, Address, Port, Fd, IpFamily) of
{ok, ListenSocket} ->
NewConfig = proplists:delete(port, Config),
{NewPort, _} = http_transport:sockname(SocketType, ListenSocket),
{NewPort, [{port, NewPort} | NewConfig], ListenSocket};
{error, Reason} ->
{error, {listen, Reason}}
end;
{error, Reason} ->
{error, {socket_start_failed, Reason}}
end
catch
_:Reason ->
{error, {socket_start_failed, Reason}}
end.
start_listen(Address, Port, Config) ->
Pid = listen_owner(Address, Port, Config),
receive
{Pid, Result} ->
{Pid, Result}
end.
listen_owner(Address, Port, Config) ->
spawn(?MODULE, listen_init, [self(), Address, Port, Config]).
listen_init(From, Address, Port, Config) ->
process_flag(trap_exit, true),
Result = listen(Address, Port, Config),
From ! {self(), Result},
listen_loop().
listen_loop() ->
receive
{'EXIT', _, _} ->
ok
end.
socket_type(Config) ->
SocketType = proplists:get_value(socket_type, Config, ip_comm),
socket_type(SocketType, Config).
socket_type(ip_comm = SocketType, _) ->
SocketType;
socket_type({essl, _} = SocketType, _) ->
SocketType;
socket_type(_, Config) ->
{essl, ssl_config(Config)}.
%%% Backwards compatibility
ssl_config(Config) ->
ssl_certificate_key_file(Config) ++
ssl_verify_client(Config) ++
ssl_ciphers(Config) ++
ssl_password(Config) ++
ssl_verify_depth(Config) ++
ssl_ca_certificate_file(Config).
ssl_certificate_key_file(Config) ->
case proplists:get_value(ssl_certificate_key_file, Config) of
undefined ->
[];
SSLCertificateKeyFile ->
[{keyfile,SSLCertificateKeyFile}]
end.
ssl_verify_client(Config) ->
case proplists:get_value(ssl_verify_client, Config) of
undefined ->
[];
SSLVerifyClient ->
[{verify,SSLVerifyClient}]
end.
ssl_ciphers(Config) ->
case proplists:get_value(ssl_ciphers, Config) of
undefined ->
[];
Ciphers ->
[{ciphers, Ciphers}]
end.
ssl_password(Config) ->
case proplists:get_value(ssl_password_callback_module, Config) of
undefined ->
[];
Module ->
case proplists:get_value(ssl_password_callback_function, Config) of
undefined ->
[];
Function ->
Args = case proplists:get_value(ssl_password_callback_arguments, Config) of
undefined ->
[];
Arguments ->
[Arguments]
end,
Password = apply(Module, Function, Args),
[{password, Password}]
end
end.
ssl_verify_depth(Config) ->
case proplists:get_value(ssl_verify_client_depth, Config) of
undefined ->
[];
Depth ->
[{depth, Depth}]
end.
ssl_ca_certificate_file(Config) ->
case proplists:get_value(ssl_ca_certificate_file, Config) of
undefined ->
[];
File ->
[{cacertfile, File}]
end.