/deps/mochiweb/src/mochiweb_socket_server.erl

http://github.com/zotonic/zotonic · Erlang · 371 lines · 316 code · 42 blank · 13 comment · 1 complexity · 2b38c00374bf4c708d81769bee01df60 MD5 · raw file

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