%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2018. 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.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(httpd).
-behaviour(inets_service).
-include("httpd.hrl").
-include("httpd_internal.hrl").
%% Behavior callbacks
-export([
start_standalone/1,
start_service/1,
stop_service/1,
services/0,
service_info/1
]).
%% API
-export([
parse_query/1,
reload_config/2,
info/1,
info/2,
info/3
]).
%%%========================================================================
%%% API
%%%========================================================================
parse_query(String) ->
SplitString = re:split(String,"[&;]", [{return, list}]),
foreach(SplitString).
reload_config(Config = [Value| _], Mode) when is_tuple(Value) ->
do_reload_config(Config, Mode);
reload_config(ConfigFile, Mode) ->
try file:consult(ConfigFile) of
{ok, [PropList]} ->
%% Erlang terms format
do_reload_config(PropList, Mode);
{error, _ } ->
%% Apache format
case httpd_conf:load(ConfigFile) of
{ok, ConfigList} ->
do_reload_config(ConfigList, Mode);
Error ->
Error
end
catch
exit:_ ->
throw({error, {could_not_consult_proplist_file, ConfigFile}})
end.
info(Pid) when is_pid(Pid) ->
info(Pid, []).
info(Pid, Properties) when is_pid(Pid) andalso is_list(Properties) ->
{ok, ServiceInfo} = service_info(Pid),
Address = proplists:get_value(bind_address, ServiceInfo),
Port = proplists:get_value(port, ServiceInfo),
Profile = proplists:get_value(profile, ServiceInfo, default),
case Properties of
[] ->
info(Address, Port, Profile);
_ ->
info(Address, Port, Profile, Properties)
end;
info(Address, Port) when is_integer(Port) ->
info(Address, Port, default).
info(Address, Port, Profile) when is_integer(Port), is_atom(Profile) ->
httpd_conf:get_config(Address, Port, Profile);
info(Address, Port, Properties) when is_integer(Port) andalso
is_list(Properties) ->
httpd_conf:get_config(Address, Port, default, Properties).
info(Address, Port, Profile, Properties) when is_integer(Port) andalso
is_atom(Profile) andalso is_list(Properties) ->
httpd_conf:get_config(Address, Port, Profile, Properties).
%%%========================================================================
%%% Behavior callbacks
%%%========================================================================
start_standalone(Config) ->
httpd_sup:start_link([{httpd, Config}], stand_alone).
start_service(Conf) ->
httpd_sup:start_child(Conf).
stop_service({Address, Port}) ->
stop_service({Address, Port, ?DEFAULT_PROFILE});
stop_service({Address, Port, Profile}) ->
Name = httpd_util:make_name("httpd_instance_sup", Address, Port, Profile),
Pid = whereis(Name),
MonitorRef = erlang:monitor(process, Pid),
Result = httpd_sup:stop_child(Address, Port, Profile),
receive
{'DOWN', MonitorRef, _, _, _} ->
Result
end;
stop_service(Pid) when is_pid(Pid) ->
case service_info(Pid) of
{ok, Info} ->
Address = proplists:get_value(bind_address, Info),
Port = proplists:get_value(port, Info),
Profile = proplists:get_value(profile, Info, ?DEFAULT_PROFILE),
stop_service({Address, Port, Profile});
Error ->
Error
end.
services() ->
[{httpd, ChildPid} || {_, ChildPid, _, _} <-
supervisor:which_children(httpd_sup)].
service_info(Pid) ->
try
[{ChildName, ChildPid} ||
{ChildName, ChildPid, _, _} <-
supervisor:which_children(httpd_sup)] of
Children ->
child_name2info(child_name(Pid, Children))
catch
exit:{noproc, _} ->
{error, service_not_available}
end.
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
child_name(_, []) ->
undefined;
child_name(Pid, [{Name, Pid} | _]) ->
Name;
child_name(Pid, [_ | Children]) ->
child_name(Pid, Children).
child_name2info(undefined) ->
{error, no_such_service};
child_name2info({httpd_instance_sup, any, Port, Profile}) ->
{ok, Host} = inet:gethostname(),
Info = info(any, Port, Profile, [server_name]),
{ok, [{bind_address, any}, {host, Host}, {port, Port} | Info]};
child_name2info({httpd_instance_sup, Address, Port, Profile}) ->
Info = info(Address, Port, Profile, [server_name]),
case inet:gethostbyaddr(Address) of
{ok, {_, Host, _, _,_, _}} ->
{ok, [{bind_address, Address},
{host, Host}, {port, Port} | Info]};
_ ->
{ok, [{bind_address, Address}, {port, Port} | Info]}
end.
reload(Config, Address, Port, Profile) ->
Name = make_name(Address,Port, Profile),
case whereis(Name) of
Pid when is_pid(Pid) ->
httpd_manager:reload(Pid, Config);
_ ->
{error,not_started}
end.
%%% =========================================================
%%% Function: block/3, block/4
%%% block(Addr, Port, Mode)
%%% block(ConfigFile, Mode, Timeout)
%%% block(Addr, Port, Mode, Timeout)
%%%
%%% Returns: ok | {error,Reason}
%%%
%%% Description: This function is used to block an HTTP server.
%%% The blocking can be done in two ways,
%%% disturbing or non-disturbing. Default is disturbing.
%%% When a HTTP server is blocked, all requests are rejected
%%% (status code 503).
%%%
%%% disturbing:
%%% By performing a disturbing block, the server
%%% is blocked forcefully and all ongoing requests
%%% are terminated. No new connections are accepted.
%%% If a timeout time is given then, on-going requests
%%% are given this much time to complete before the
%%% server is forcefully blocked. In this case no new
%%% connections is accepted.
%%%
%%% non-disturbing:
%%% A non-disturbing block is more gracefull. No
%%% new connections are accepted, but the ongoing
%%% requests are allowed to complete.
%%% If a timeout time is given, it waits this long before
%%% giving up (the block operation is aborted and the
%%% server state is once more not-blocked).
%%%
%%% Types: Port -> integer()
%%% Addr -> {A,B,C,D} | string() | undefined
%%% ConfigFile -> string()
%%% Mode -> disturbing | non_disturbing
%%% Timeout -> integer()
%%%
block(Addr, Port, Profile, disturbing) when is_integer(Port) ->
do_block(Addr, Port, Profile, disturbing);
block(Addr, Port, Profile, non_disturbing) when is_integer(Port) ->
do_block(Addr, Port, Profile, non_disturbing).
do_block(Addr, Port, Profile, Mode) when is_integer(Port) andalso is_atom(Mode) ->
Name = make_name(Addr, Port, Profile),
case whereis(Name) of
Pid when is_pid(Pid) ->
httpd_manager:block(Pid, Mode);
_ ->
{error,not_started}
end.
%%% =========================================================
%%% Function: unblock/2
%%% unblock(Addr, Port)
%%%
%%% Description: This function is used to reverse a previous block
%%% operation on the HTTP server.
%%%
%%% Types: Port -> integer()
%%% Addr -> {A,B,C,D} | string() | undefined
%%% ConfigFile -> string()
%%%
unblock(Addr, Port, Profile) when is_integer(Port) ->
Name = make_name(Addr,Port, Profile),
case whereis(Name) of
Pid when is_pid(Pid) ->
httpd_manager:unblock(Pid);
_ ->
{error,not_started}
end.
foreach([]) ->
[];
foreach([KeyValue|Rest]) ->
Plus2Space = re:replace(KeyValue,"[\+]"," ", [{return,list}, global]),
case re:split(Plus2Space,"=", [{return, list}]) of
[Key|Value] ->
[{http_uri:decode(Key),
http_uri:decode(lists:flatten(Value))}|foreach(Rest)];
_ ->
foreach(Rest)
end.
make_name(Addr, Port, Profile) ->
httpd_util:make_name("httpd", Addr, Port, Profile).
do_reload_config(ConfigList, Mode) ->
case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
Profile = proplists:get_value(profile, Config, default),
case block(Address, Port, Profile, Mode) of
ok ->
reload(Config, Address, Port, Profile),
unblock(Address, Port, Profile);
Error ->
Error
end;
Error ->
Error
end.
%%%--------------------------------------------------------------
%%% Deprecated
%%%--------------------------------------------------------------