PageRenderTime 78ms CodeModel.GetById 13ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 0ms

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