1%% @author Bob Ippolito <bob@mochimedia.com> 2%% @copyright 2007 Mochi Media, Inc. 3 4%% @doc MochiWeb socket server. 5 6-module(mochiweb_socket_server). 7-author('bob@mochimedia.com'). 8-behaviour(gen_server). 9 10-include("internal.hrl"). 11 12-export([start/1, start_link/1, stop/1]). 13-export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3, 14 handle_info/2]). 15-export([get/2, set/3]). 16 17-record(mochiweb_socket_server, 18 {port, 19 loop, 20 name=undefined, 21 max=2048, 22 ip=any, 23 listen=null, 24 nodelay=false, 25 backlog=128, 26 active_sockets=0, 27 acceptor_pool_size=16, 28 ssl=false, 29 ssl_opts=[{ssl_imp, new}], 30 acceptor_pool=sets:new(), 31 profile_fun=undefined}). 32 33-define(is_old_state(State), not is_record(State, mochiweb_socket_server)). 34 35start_link(Options) -> 36 start_server(start_link, parse_options(Options)). 37 38start(Options) -> 39 case lists:keytake(link, 1, Options) of 40 {value, {_Key, false}, Options1} -> 41 start_server(start, parse_options(Options1)); 42 _ -> 43 %% TODO: https://github.com/mochi/mochiweb/issues/58 44 %% [X] Phase 1: Add new APIs (Sep 2011) 45 %% [_] Phase 2: Add deprecation warning 46 %% [_] Phase 3: Change default to {link, false} and ignore link 47 %% [_] Phase 4: Add deprecation warning for {link, _} option 48 %% [_] Phase 5: Remove support for {link, _} option 49 start_link(Options) 50 end. 51 52get(Name, Property) -> 53 gen_server:call(Name, {get, Property}). 54 55set(Name, profile_fun, Fun) -> 56 gen_server:cast(Name, {set, profile_fun, Fun}); 57set(Name, Property, _Value) -> 58 error_logger:info_msg("?MODULE:set for ~p with ~p not implemented~n", 59 [Name, Property]). 60 61stop(Name) when is_atom(Name) orelse is_pid(Name) -> 62 gen_server:call(Name, stop); 63stop({Scope, Name}) when Scope =:= local orelse Scope =:= global -> 64 stop(Name); 65stop(Options) -> 66 State = parse_options(Options), 67 stop(State#mochiweb_socket_server.name). 68 69%% Internal API 70 71parse_options(State=#mochiweb_socket_server{}) -> 72 State; 73parse_options(Options) -> 74 parse_options(Options, #mochiweb_socket_server{}). 75 76parse_options([], State=#mochiweb_socket_server{acceptor_pool_size=PoolSize, 77 max=Max}) -> 78 case Max < PoolSize of 79 true -> 80 error_logger:info_report([{warning, "max is set lower than acceptor_pool_size"}, 81 {max, Max}, 82 {acceptor_pool_size, PoolSize}]); 83 false -> 84 ok 85 end, 86 State; 87parse_options([{name, L} | Rest], State) when is_list(L) -> 88 Name = {local, list_to_atom(L)}, 89 parse_options(Rest, State#mochiweb_socket_server{name=Name}); 90parse_options([{name, A} | Rest], State) when A =:= undefined -> 91 parse_options(Rest, State#mochiweb_socket_server{name=A}); 92parse_options([{name, A} | Rest], State) when is_atom(A) -> 93 Name = {local, A}, 94 parse_options(Rest, State#mochiweb_socket_server{name=Name}); 95parse_options([{name, Name} | Rest], State) -> 96 parse_options(Rest, State#mochiweb_socket_server{name=Name}); 97parse_options([{port, L} | Rest], State) when is_list(L) -> 98 Port = list_to_integer(L), 99 parse_options(Rest, State#mochiweb_socket_server{port=Port}); 100parse_options([{port, Port} | Rest], State) -> 101 parse_options(Rest, State#mochiweb_socket_server{port=Port}); 102parse_options([{ip, Ip} | Rest], State) -> 103 ParsedIp = case Ip of 104 any -> 105 any; 106 Ip when is_tuple(Ip) -> 107 Ip; 108 Ip when is_list(Ip) -> 109 {ok, IpTuple} = inet_parse:address(Ip), 110 IpTuple 111 end, 112 parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); 113parse_options([{loop, Loop} | Rest], State) -> 114 parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); 115parse_options([{backlog, Backlog} | Rest], State) -> 116 parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); 117parse_options([{nodelay, NoDelay} | Rest], State) -> 118 parse_options(Rest, State#mochiweb_socket_server{nodelay=NoDelay}); 119parse_options([{acceptor_pool_size, Max} | Rest], State) -> 120 MaxInt = ensure_int(Max), 121 parse_options(Rest, 122 State#mochiweb_socket_server{acceptor_pool_size=MaxInt}); 123parse_options([{max, Max} | Rest], State) -> 124 MaxInt = ensure_int(Max), 125 parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}); 126parse_options([{ssl, Ssl} | Rest], State) when is_boolean(Ssl) -> 127 parse_options(Rest, State#mochiweb_socket_server{ssl=Ssl}); 128parse_options([{ssl_opts, SslOpts} | Rest], State) when is_list(SslOpts) -> 129 SslOpts1 = [{ssl_imp, new} | proplists:delete(ssl_imp, SslOpts)], 130 parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1}); 131parse_options([{profile_fun, ProfileFun} | Rest], State) when is_function(ProfileFun) -> 132 parse_options(Rest, State#mochiweb_socket_server{profile_fun=ProfileFun}). 133 134 135start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) -> 136 ok = prep_ssl(Ssl), 137 case Name of 138 undefined -> 139 gen_server:F(?MODULE, State, []); 140 _ -> 141 gen_server:F(Name, ?MODULE, State, []) 142 end. 143 144prep_ssl(true) -> 145 ok = mochiweb:ensure_started(crypto), 146 ok = mochiweb:ensure_started(asn1), 147 ok = mochiweb:ensure_started(public_key), 148 ok = mochiweb:ensure_started(ssl); 149prep_ssl(false) -> 150 ok. 151 152ensure_int(N) when is_integer(N) -> 153 N; 154ensure_int(S) when is_list(S) -> 155 list_to_integer(S). 156 157ipv6_supported() -> 158 case (catch inet:getaddr("localhost", inet6)) of 159 {ok, _Addr} -> 160 true; 161 {error, _} -> 162 false 163 end. 164 165init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog, nodelay=NoDelay}) -> 166 process_flag(trap_exit, true), 167 BaseOpts = [binary, 168 {reuseaddr, true}, 169 {packet, 0}, 170 {backlog, Backlog}, 171 {recbuf, ?RECBUF_SIZE}, 172 {exit_on_close, false}, 173 {active, false}, 174 {nodelay, NoDelay}], 175 Opts = case Ip of 176 any -> 177 case ipv6_supported() of % IPv4, and IPv6 if supported 178 true -> [inet, inet6 | BaseOpts]; 179 _ -> BaseOpts 180 end; 181 {_, _, _, _} -> % IPv4 182 [inet, {ip, Ip} | BaseOpts]; 183 {_, _, _, _, _, _, _, _} -> % IPv6 184 [inet6, {ip, Ip} | BaseOpts] 185 end, 186 listen(Port, Opts, State). 187 188new_acceptor_pool(Listen, 189 State=#mochiweb_socket_server{acceptor_pool=Pool, 190 acceptor_pool_size=Size, 191 loop=Loop}) -> 192 F = fun (_, S) -> 193 Pid = mochiweb_acceptor:start_link(self(), Listen, Loop), 194 sets:add_element(Pid, S) 195 end, 196 Pool1 = lists:foldl(F, Pool, lists:seq(1, Size)), 197 State#mochiweb_socket_server{acceptor_pool=Pool1}. 198 199listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts}) -> 200 case mochiweb_socket:listen(Ssl, Port, Opts, SslOpts) of 201 {ok, Listen} -> 202 {ok, ListenPort} = mochiweb_socket:port(Listen), 203 {ok, new_acceptor_pool( 204 Listen, 205 State#mochiweb_socket_server{listen=Listen, 206 port=ListenPort})}; 207 {error, Reason} -> 208 {stop, Reason} 209 end. 210 211do_get(port, #mochiweb_socket_server{port=Port}) -> 212 Port; 213do_get(waiting_acceptors, #mochiweb_socket_server{acceptor_pool=Pool}) -> 214 sets:size(Pool); 215do_get(active_sockets, #mochiweb_socket_server{active_sockets=ActiveSockets}) -> 216 ActiveSockets. 217 218 219state_to_proplist(#mochiweb_socket_server{name=Name, 220 port=Port, 221 active_sockets=ActiveSockets}) -> 222 [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}]. 223 224upgrade_state(State = #mochiweb_socket_server{}) -> 225 State; 226upgrade_state({mochiweb_socket_server, Port, Loop, Name, 227 Max, IP, Listen, NoDelay, Backlog, ActiveSockets, 228 AcceptorPoolSize, SSL, SSL_opts, 229 AcceptorPool}) -> 230 #mochiweb_socket_server{port=Port, loop=Loop, name=Name, max=Max, ip=IP, 231 listen=Listen, nodelay=NoDelay, backlog=Backlog, 232 active_sockets=ActiveSockets, 233 acceptor_pool_size=AcceptorPoolSize, 234 ssl=SSL, 235 ssl_opts=SSL_opts, 236 acceptor_pool=AcceptorPool}. 237 238handle_call(Req, From, State) when ?is_old_state(State) -> 239 handle_call(Req, From, upgrade_state(State)); 240handle_call({get, Property}, _From, State) -> 241 Res = do_get(Property, State), 242 {reply, Res, State}; 243handle_call(stop, _From, State) -> 244 {stop, normal, ok, State}; 245handle_call(_Message, _From, State) -> 246 Res = error, 247 {reply, Res, State}. 248 249 250handle_cast(Req, State) when ?is_old_state(State) -> 251 handle_cast(Req, upgrade_state(State)); 252handle_cast({accepted, Pid, Timing}, 253 State=#mochiweb_socket_server{active_sockets=ActiveSockets}) -> 254 State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets}, 255 case State#mochiweb_socket_server.profile_fun of 256 undefined -> 257 undefined; 258 F when is_function(F) -> 259 catch F([{timing, Timing} | state_to_proplist(State1)]) 260 end, 261 {noreply, recycle_acceptor(Pid, State1)}; 262handle_cast({set, profile_fun, ProfileFun}, State) -> 263 State1 = case ProfileFun of 264 ProfileFun when is_function(ProfileFun); ProfileFun =:= undefined -> 265 State#mochiweb_socket_server{profile_fun=ProfileFun}; 266 _ -> 267 State 268 end, 269 {noreply, State1}. 270 271 272terminate(Reason, State) when ?is_old_state(State) -> 273 terminate(Reason, upgrade_state(State)); 274terminate(_Reason, #mochiweb_socket_server{listen=Listen}) -> 275 mochiweb_socket:close(Listen). 276 277code_change(_OldVsn, State, _Extra) -> 278 State. 279 280recycle_acceptor(Pid, State=#mochiweb_socket_server{ 281 acceptor_pool=Pool, 282 acceptor_pool_size=PoolSize, 283 listen=Listen, 284 loop=Loop, 285 max=Max, 286 active_sockets=ActiveSockets}) -> 287 case sets:is_element(Pid, Pool) of 288 true -> 289 Pool1 = sets:del_element(Pid, Pool), 290 case ActiveSockets + sets:size(Pool1) < Max of 291 true -> 292 Acceptor = mochiweb_acceptor:start_link(self(), Listen, Loop), 293 Pool2 = sets:add_element(Acceptor, Pool1), 294 State#mochiweb_socket_server{acceptor_pool=Pool2}; 295 false -> 296 State#mochiweb_socket_server{acceptor_pool=Pool1} 297 end; 298 false -> 299 case sets:size(Pool) < PoolSize of 300 true -> 301 Acceptor = mochiweb_acceptor:start_link(self(), Listen, Loop), 302 Pool1 = sets:add_element(Acceptor, Pool), 303 State#mochiweb_socket_server{active_sockets=ActiveSockets, 304 acceptor_pool=Pool1}; 305 false -> 306 State#mochiweb_socket_server{active_sockets=ActiveSockets - 1, 307 acceptor_pool=Pool} 308 end 309 end. 310 311handle_info(Msg, State) when ?is_old_state(State) -> 312 handle_info(Msg, upgrade_state(State)); 313handle_info({'EXIT', Pid, normal}, State) -> 314 {noreply, recycle_acceptor(Pid, State)}; 315handle_info({'EXIT', Pid, Reason}, 316 State=#mochiweb_socket_server{acceptor_pool=Pool}) -> 317 case sets:is_element(Pid, Pool) of 318 true -> 319 %% If there was an unexpected error accepting, log and sleep. 320 error_logger:error_report({?MODULE, ?LINE, 321 {acceptor_error, Reason}}), 322 timer:sleep(100); 323 false -> 324 ok 325 end, 326 {noreply, recycle_acceptor(Pid, State)}; 327 328% this is what release_handler needs to get a list of modules, 329% since our supervisor modules list is set to 'dynamic' 330% see sasl-2.1.9.2/src/release_handler_1.erl get_dynamic_mods 331handle_info({From, Tag, get_modules}, State = #mochiweb_socket_server{name={local,Mod}}) -> 332 From ! {element(2,Tag), [Mod]}, 333 {noreply, State}; 334 335% If for some reason we can't get the module name, send empty list to avoid release_handler timeout: 336handle_info({From, Tag, get_modules}, State) -> 337 error_logger:info_msg("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]), 338 From ! {element(2,Tag), []}, 339 {noreply, State}; 340 341handle_info(Info, State) -> 342 error_logger:info_report([{'INFO', Info}, {'State', State}]), 343 {noreply, State}. 344 345 346 347%% 348%% Tests 349%% 350-ifdef(TEST). 351-include_lib("eunit/include/eunit.hrl"). 352 353upgrade_state_test() -> 354 OldState = {mochiweb_socket_server, 355 port, loop, name, 356 max, ip, listen, 357 nodelay, backlog, 358 active_sockets, 359 acceptor_pool_size, 360 ssl, ssl_opts, acceptor_pool}, 361 State = upgrade_state(OldState), 362 CmpState = #mochiweb_socket_server{port=port, loop=loop, 363 name=name, max=max, ip=ip, 364 listen=listen, nodelay=nodelay, 365 backlog=backlog, 366 active_sockets=active_sockets, 367 acceptor_pool_size=acceptor_pool_size, 368 ssl=ssl, ssl_opts=ssl_opts, 369 acceptor_pool=acceptor_pool, 370 profile_fun=undefined}, 371 ?assertEqual(CmpState, State). 372 373-endif. 374