1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2000-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21 22-module(httpd_manager). 23 24-include("httpd.hrl"). 25 26-behaviour(gen_server). 27 28%% Application internal API 29-export([start/2, start_link/2, start_link/3, start_link/4, 30 stop/1, reload/2]). 31-export([new_connection/1]). 32-export([config_match/3, config_match/4]). 33-export([block/2, block/3, unblock/1]). 34 35%% gen_server exports 36-export([init/1, 37 handle_call/3, handle_cast/2, handle_info/2, 38 terminate/2, 39 code_change/3]). 40 41-record(state,{socket_type = ip_comm, 42 config_file, 43 config_db = null, 44 connection_sup, 45 admin_state = unblocked, 46 blocker_ref = undefined, 47 blocking_from = undefined, 48 shutdown_poller = undefined, 49 status = []}). 50%%%-------------------------------------------------------------------- 51%%% Application internal API 52%%%-------------------------------------------------------------------- 53 54%% Deprecated 55start(ConfigFile, ConfigList) -> 56 Port = proplists:get_value(port,ConfigList,80), 57 Addr = proplists:get_value(bind_address, ConfigList), 58 Profile = proplists:get_value(profile, ConfigList, default), 59 Name = make_name(Addr, Port, Profile), 60 gen_server:start({local,Name},?MODULE, 61 [ConfigFile, ConfigList, 15000, Addr, Port],[]). 62 63%% Deprecated 64start_link(ConfigFile, ConfigList) -> 65 start_link(ConfigFile, ConfigList, 15000). 66 67start_link(ConfigFile, ConfigList, AcceptTimeout) -> 68 Port = proplists:get_value(port, ConfigList, 80), 69 Addr = proplists:get_value(bind_address, ConfigList), 70 Profile = proplists:get_value(profile, ConfigList, default), 71 Name = make_name(Addr, Port, Profile), 72 73 gen_server:start_link({local, Name},?MODULE, 74 [ConfigFile, ConfigList, 75 AcceptTimeout, Addr, Port],[]). 76 77start_link(ConfigFile, ConfigList, AcceptTimeout, ListenSocket) -> 78 Port = proplists:get_value(port, ConfigList, 80), 79 Addr = proplists:get_value(bind_address, ConfigList), 80 Profile = proplists:get_value(profile, ConfigList, default), 81 Name = make_name(Addr, Port, Profile), 82 83 gen_server:start_link({local, Name},?MODULE, 84 [ConfigFile, ConfigList, AcceptTimeout, Addr, 85 Port, ListenSocket],[]). 86stop(ServerRef) -> 87 call(ServerRef, stop). 88 89reload(ServerRef, Conf) -> 90 call(ServerRef, {reload, Conf}). 91 92block(ServerRef, Method) -> 93 block(ServerRef, Method, infinity). 94 95block(ServerRef, Method, Timeout) -> 96 call(ServerRef, {block, self(), Method, Timeout}). 97 98unblock(ServerRef) -> 99 call(ServerRef,{unblock, self()}). 100 101new_connection(Manager) -> 102 call(Manager, {new_connection, self()}). 103 104config_match(Port, Profile, Pattern) -> 105 config_match(undefined,Port, Profile, Pattern). 106config_match(Addr, Port, Profile, Pattern) -> 107 Name = httpd_util:make_name("httpd",Addr,Port, Profile), 108 call(whereis(Name), {config_match, Pattern}). 109 110%%%-------------------------------------------------------------------- 111%%% gen_server callbacks functions 112%%%-------------------------------------------------------------------- 113init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port]) -> 114 process_flag(trap_exit, true), 115 case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port)) of 116 {error, Reason} -> 117 String = lists:flatten( 118 io_lib:format("Failed initiating web server: " 119 "~n~p" 120 "~n~p" 121 "~n", [ConfigFile, Reason])), 122 error_logger:error_report(String), 123 {stop, {error, Reason}}; 124 {ok, State} -> 125 {ok, State} 126 end; 127init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo]) -> 128 process_flag(trap_exit, true), 129 case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, 130 Addr, Port, ListenInfo)) of 131 {error, Reason} -> 132 String = lists:flatten( 133 io_lib:format("Failed initiating web server: " 134 "~n~p" 135 "~n~p" 136 "~n", [ConfigFile, Reason])), 137 error_logger:error_report(String), 138 {stop, {error, Reason}}; 139 {ok, State} -> 140 {ok, State} 141 end. 142 143do_init(ConfigFile, ConfigList, _AcceptTimeout, Addr, Port) -> 144 Sup = httpd_util:make_name("httpd_connection_sup", Addr, Port), 145 NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), 146 ConfigDB = do_initial_store(ConfigList), 147 SocketType = httpd_conf:lookup_socket_type(ConfigDB), 148 Status = [{max_conn, 0}, 149 {last_heavy_load, never}, 150 {last_connection, never}], 151 State = #state{socket_type = SocketType, 152 config_file = NewConfigFile, 153 config_db = ConfigDB, 154 connection_sup = Sup, 155 status = Status}, 156 {ok, State}. 157 158do_init(ConfigFile, ConfigList, _AcceptTimeout, Addr, Port, _ListenInfo) -> 159 Sup = httpd_util:make_name("httpd_connection_sup", Addr, Port), 160 NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), 161 ConfigDB = do_initial_store(ConfigList), 162 SocketType = httpd_conf:lookup_socket_type(ConfigDB), 163 Status = [{max_conn,0}, {last_heavy_load,never}, 164 {last_connection,never}], 165 State = #state{socket_type = SocketType, 166 config_file = NewConfigFile, 167 config_db = ConfigDB, 168 connection_sup = Sup, 169 status = Status}, 170 {ok, State}. 171 172 173do_initial_store(ConfigList) -> 174 case httpd_conf:store(ConfigList) of 175 {ok, ConfigDB} -> 176 ConfigDB; 177 {error, Reason} -> 178 throw({error, Reason}) 179 end. 180 181handle_call(stop, _From, State) -> 182 {stop, normal, ok, State}; 183 184handle_call({config_match, Query}, _From, State) -> 185 Res = ets:match_object(State#state.config_db, Query), 186 {reply, Res, State}; 187 188handle_call({reload, Conf}, _From, #state{admin_state = blocked} = State) -> 189 case handle_reload(Conf, State) of 190 {stop, Reply,S1} -> 191 {stop, Reply, S1}; 192 {_, Reply, S1} -> 193 {reply,Reply,S1} 194 end; 195 196handle_call({reload, _}, _From, State) -> 197 {reply,{error,{invalid_admin_state,State#state.admin_state}},State}; 198 199handle_call({block , Blocker, Mode, Timeout}, From, 200 #state{admin_state = unblocked, 201 connection_sup = CSup} = State) -> 202 Monitor = erlang:monitor(process, Blocker), 203 case count_children(CSup) of 204 0 -> 205 %% Already in idle usage state => go directly to blocked 206 {reply, ok, State#state{admin_state = blocked, 207 blocker_ref = {Blocker, Monitor}, 208 blocking_from = From}}; 209 _ -> 210 handle_block(Mode, Timeout, 211 State#state{blocker_ref = {Blocker, Monitor}, 212 blocking_from = From}) 213 end; 214handle_call({block , _, _, _}, _, State) -> 215 {reply, {error, blocked}, State}; 216 217handle_call({unblock, Blocker}, _, #state{blocker_ref = {Blocker, Monitor}, 218 admin_state = blocked} = State) -> 219 220 erlang:demonitor(Monitor), 221 {reply, ok, 222 State#state{admin_state = unblocked, blocker_ref = undefined}}; 223 224handle_call({unblock, _}, _, State) -> 225 {reply, {error, only_blocker_may_unblock}, State}; 226 227handle_call({new_connection, Pid}, _From, State) -> 228 {Status, NewState} = handle_new_connection(State, Pid), 229 {reply, Status, NewState}; 230 231handle_call(Request, From, State) -> 232 String = 233 lists:flatten( 234 io_lib:format("Unknown request " 235 "~n ~p" 236 "~nto manager (~p)" 237 "~nfrom ~p", 238 [Request, self(), From])), 239 report_error(State,String), 240 {reply, ok, State}. 241 242handle_cast(Message, State) -> 243 String = 244 lists:flatten( 245 io_lib:format("Unknown message " 246 "~n ~p" 247 "~nto manager (~p)", 248 [Message, self()])), 249 report_error(State, String), 250 {noreply, State}. 251 252handle_info(connections_terminated, #state{admin_state = shutting_down, 253 blocking_from = From} = State) -> 254 gen_server:reply(From, ok), 255 {noreply, State#state{admin_state = blocked, blocking_from = undefined}}; 256handle_info(connections_terminated, State) -> 257 {noreply, State}; 258 259handle_info({block_timeout, non_disturbing, Blocker}, 260 #state{admin_state = shutting_down, 261 blocking_from = From, 262 blocker_ref = {_, Monitor} = Blocker} = State) -> 263 erlang:demonitor(Monitor), 264 gen_server:reply(From, {error, timeout}), 265 {noreply, State#state{admin_state = unblocked, blocking_from = undefined, 266 blocker_ref = undefined}}; 267handle_info({block_timeout, disturbing, Blocker}, 268 #state{admin_state = shutting_down, 269 blocking_from = From, 270 blocker_ref = Blocker, 271 connection_sup = Sup} = State) -> 272 SupPid = whereis(Sup), 273 shutdown_connections(SupPid), 274 gen_server:reply(From, ok), 275 {noreply, State#state{admin_state = blocked, 276 blocking_from = undefined}}; 277handle_info({block_timeout, _, _}, State) -> 278 {noreply, State}; 279 280handle_info({'DOWN', _, process, Pid, _Info}, 281 #state{admin_state = Admin, 282 blocker_ref = {Pid, Monitor}} = State) when 283 Admin =/= unblocked -> 284 erlang:demonitor(Monitor), 285 {noreply, State#state{admin_state = unblocked, 286 blocking_from = undefined, 287 blocker_ref = undefined}}; 288handle_info({'DOWN', _, process, _, _}, State) -> 289 {noreply, State}; 290 291handle_info({'EXIT', _, normal}, State) -> 292 {noreply, State}; 293 294handle_info({'EXIT', _, shutdown}, State) -> 295 {stop, shutdown, State}; 296 297handle_info(Info, State) -> 298 String = 299 lists:flatten( 300 io_lib:format("Unknown info " 301 "~n ~p" 302 "~nto manager (~p)", 303 [Info, self()])), 304 report_error(State, String), 305 {noreply, State}. 306 307terminate(_, #state{config_db = Db}) -> 308 httpd_conf:remove_all(Db), 309 ok. 310 311code_change({down,_ToVsn}, State, _Extra) -> 312 {ok,State}; 313 314code_change(_FromVsn, State, _Extra) -> 315 {ok,State}. 316 317%%%-------------------------------------------------------------------- 318%%% Internal functions 319%%%-------------------------------------------------------------------- 320handle_new_connection(#state{admin_state = AdminState} = State, Handler) -> 321 UsageState = get_ustate(State), 322 handle_new_connection(UsageState, AdminState, State, Handler). 323 324handle_new_connection(_UsageState, unblocked, 325 #state{config_db = Db, connection_sup = CSup} = 326 State, _) -> 327 Max = httpd_util:lookup(Db, max_clients), 328 case count_children(CSup) of 329 Count when Count =< Max -> 330 {{ok, accept}, State}; 331 _ -> 332 {{reject, busy}, State} 333 end; 334 335handle_new_connection(_UsageState, _AdminState, State, _Handler) -> 336 {{reject, blocked}, State}. 337 338handle_block(disturbing, infinity, 339 #state{connection_sup = CSup, 340 blocking_from = From} = State) -> 341 SupPid = whereis(CSup), 342 shutdown_connections(SupPid), 343 gen_server:reply(From, ok), 344 {noreply, State#state{admin_state = blocked, 345 blocking_from = undefined}}; 346handle_block(disturbing, Timeout, #state{connection_sup = CSup, blocker_ref = Blocker} = State) -> 347 Manager = self(), 348 spawn_link(fun() -> wait_for_shutdown(CSup, Manager) end), 349 erlang:send_after(Timeout, self(), {block_timeout, disturbing, Blocker}), 350 {noreply, State#state{admin_state = shutting_down}}; 351 352handle_block(non_disturbing, infinity, 353 #state{connection_sup = CSup} = State) -> 354 Manager = self(), 355 spawn_link(fun() -> wait_for_shutdown(CSup, Manager) end), 356 {noreply, State#state{admin_state = shutting_down}}; 357 358handle_block(non_disturbing, Timeout, 359 #state{connection_sup = CSup, blocker_ref = Blocker} = State) -> 360 Manager = self(), 361 spawn_link(fun() -> wait_for_shutdown(CSup, Manager) end), 362 erlang:send_after(Timeout, self(), {block_timeout, non_disturbing, Blocker}), 363 {noreply, State#state{admin_state = shutting_down}}. 364 365handle_reload(undefined, #state{config_file = undefined} = State) -> 366 {continue, {error, undefined_config_file}, State}; 367handle_reload(undefined, #state{config_file = ConfigFile, admin_state = AdminState} = State) -> 368 try httpd:reload_config(ConfigFile, AdminState) of 369 Result -> 370 Result 371 catch throw:Err -> 372 {config_file, Err, State} 373 end; 374handle_reload(Config, State) -> 375 do_reload(Config, State). 376 377do_reload(Config, #state{config_db = Db} = State) -> 378 case (catch check_constant_values(Db, Config)) of 379 ok -> 380 %% If something goes wrong between the remove 381 %% and the store where fu-ed 382 httpd_conf:remove_all(Db), 383 case httpd_conf:store(Config) of 384 {ok, NewConfigDB} -> 385 {continue, ok, State#state{config_db = NewConfigDB}}; 386 Error -> 387 {stop, Error, State} 388 end; 389 Error -> 390 {continue, Error, State} 391 end. 392 393check_constant_values(Db, Config) -> 394 %% Check port number 395 Port = httpd_util:lookup(Db,port), 396 case proplists:get_value(port,Config) of %% MUST be equal 397 Port -> 398 ok; 399 OtherPort -> 400 throw({error,{port_number_changed,Port,OtherPort}}) 401 end, 402 403 %% Check bind address 404 Addr = httpd_util:lookup(Db,bind_address), 405 case proplists:get_value(bind_address, Config) of %% MUST be equal 406 Addr -> 407 ok; 408 OtherAddr -> 409 throw({error,{addr_changed,Addr,OtherAddr}}) 410 end, 411 412 %% Check socket type 413 SockType = httpd_util:lookup(Db, socket_type), 414 case proplists:get_value(socket_type, Config) of %% MUST be equal 415 SockType -> 416 ok; 417 OtherSockType -> 418 throw({error,{sock_type_changed,SockType,OtherSockType}}) 419 end, 420 ok. 421 422 423%% get_ustate(State) -> idle | active | busy 424%% 425%% Retrieve the usage state of the HTTP server: 426%% 0 active connection -> idle 427%% max_clients active connections -> busy 428%% Otherwise -> active 429%% 430get_ustate(State) -> 431 get_ustate(count_children(State#state.connection_sup),State). 432 433get_ustate(0,_State) -> 434 idle; 435get_ustate(ConnectionCnt,State) -> 436 ConfigDB = State#state.config_db, 437 case httpd_util:lookup(ConfigDB, max_clients, 150) of 438 ConnectionCnt -> 439 busy; 440 _ -> 441 active 442 end. 443 444make_name(Addr, Port, Profile) -> 445 httpd_util:make_name("httpd", Addr, Port, Profile). 446 447 448report_error(State,String) -> 449 Cdb = State#state.config_db, 450 error_logger:error_report(String), 451 mod_log:report_error(Cdb,String), 452 mod_disk_log:report_error(Cdb,String). 453 454call(ServerRef, Request) -> 455 try gen_server:call(ServerRef, Request, infinity) 456 catch 457 exit:_ -> 458 {error, closed} 459 end. 460 461count_children(Sup) -> 462 Children = supervisor:count_children(whereis(Sup)), 463 proplists:get_value(workers, Children). 464 465shutdown_connections(Sup) -> 466 Children = [Child || {_,Child,_,_} <- supervisor:which_children(Sup)], 467 lists:foreach(fun(Pid) -> exit(Pid, kill) end, 468 Children). 469 470wait_for_shutdown(CSup, Manager) -> 471 case count_children(CSup) of 472 0 -> 473 Manager ! connections_terminated; 474 _ -> 475 receive 476 after 500 -> 477 ok 478 end, 479 wait_for_shutdown(CSup, Manager) 480 end. 481 482