12 |
7u83 |
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 |
recbuf=?RECBUF_SIZE,
|
|
|
26 |
buffer=undefined,
|
|
|
27 |
backlog=128,
|
|
|
28 |
active_sockets=0,
|
|
|
29 |
acceptor_pool_size=16,
|
|
|
30 |
ssl=false,
|
|
|
31 |
ssl_opts=[{ssl_imp, new}],
|
|
|
32 |
acceptor_pool=sets:new(),
|
|
|
33 |
profile_fun=undefined}).
|
|
|
34 |
|
|
|
35 |
-define(is_old_state(State), not is_record(State, mochiweb_socket_server)).
|
|
|
36 |
|
|
|
37 |
start_link(Options) ->
|
|
|
38 |
start_server(start_link, parse_options(Options)).
|
|
|
39 |
|
|
|
40 |
start(Options) ->
|
|
|
41 |
case lists:keytake(link, 1, Options) of
|
|
|
42 |
{value, {_Key, false}, Options1} ->
|
|
|
43 |
start_server(start, parse_options(Options1));
|
|
|
44 |
_ ->
|
|
|
45 |
%% TODO: https://github.com/mochi/mochiweb/issues/58
|
|
|
46 |
%% [X] Phase 1: Add new APIs (Sep 2011)
|
|
|
47 |
%% [_] Phase 2: Add deprecation warning
|
|
|
48 |
%% [_] Phase 3: Change default to {link, false} and ignore link
|
|
|
49 |
%% [_] Phase 4: Add deprecation warning for {link, _} option
|
|
|
50 |
%% [_] Phase 5: Remove support for {link, _} option
|
|
|
51 |
start_link(Options)
|
|
|
52 |
end.
|
|
|
53 |
|
|
|
54 |
get(Name, Property) ->
|
|
|
55 |
gen_server:call(Name, {get, Property}).
|
|
|
56 |
|
|
|
57 |
set(Name, profile_fun, Fun) ->
|
|
|
58 |
gen_server:cast(Name, {set, profile_fun, Fun});
|
|
|
59 |
set(Name, Property, _Value) ->
|
|
|
60 |
error_logger:info_msg("?MODULE:set for ~p with ~p not implemented~n",
|
|
|
61 |
[Name, Property]).
|
|
|
62 |
|
|
|
63 |
stop(Name) when is_atom(Name) orelse is_pid(Name) ->
|
|
|
64 |
gen_server:call(Name, stop);
|
|
|
65 |
stop({Scope, Name}) when Scope =:= local orelse Scope =:= global ->
|
|
|
66 |
stop(Name);
|
|
|
67 |
stop(Options) ->
|
|
|
68 |
State = parse_options(Options),
|
|
|
69 |
stop(State#mochiweb_socket_server.name).
|
|
|
70 |
|
|
|
71 |
%% Internal API
|
|
|
72 |
|
|
|
73 |
parse_options(State=#mochiweb_socket_server{}) ->
|
|
|
74 |
State;
|
|
|
75 |
parse_options(Options) ->
|
|
|
76 |
parse_options(Options, #mochiweb_socket_server{}).
|
|
|
77 |
|
|
|
78 |
parse_options([], State=#mochiweb_socket_server{acceptor_pool_size=PoolSize,
|
|
|
79 |
max=Max}) ->
|
|
|
80 |
case Max < PoolSize of
|
|
|
81 |
true ->
|
|
|
82 |
error_logger:info_report([{warning, "max is set lower than acceptor_pool_size"},
|
|
|
83 |
{max, Max},
|
|
|
84 |
{acceptor_pool_size, PoolSize}]);
|
|
|
85 |
false ->
|
|
|
86 |
ok
|
|
|
87 |
end,
|
|
|
88 |
State;
|
|
|
89 |
parse_options([{name, L} | Rest], State) when is_list(L) ->
|
|
|
90 |
Name = {local, list_to_atom(L)},
|
|
|
91 |
parse_options(Rest, State#mochiweb_socket_server{name=Name});
|
|
|
92 |
parse_options([{name, A} | Rest], State) when A =:= undefined ->
|
|
|
93 |
parse_options(Rest, State#mochiweb_socket_server{name=A});
|
|
|
94 |
parse_options([{name, A} | Rest], State) when is_atom(A) ->
|
|
|
95 |
Name = {local, A},
|
|
|
96 |
parse_options(Rest, State#mochiweb_socket_server{name=Name});
|
|
|
97 |
parse_options([{name, Name} | Rest], State) ->
|
|
|
98 |
parse_options(Rest, State#mochiweb_socket_server{name=Name});
|
|
|
99 |
parse_options([{port, L} | Rest], State) when is_list(L) ->
|
|
|
100 |
Port = list_to_integer(L),
|
|
|
101 |
parse_options(Rest, State#mochiweb_socket_server{port=Port});
|
|
|
102 |
parse_options([{port, Port} | Rest], State) ->
|
|
|
103 |
parse_options(Rest, State#mochiweb_socket_server{port=Port});
|
|
|
104 |
parse_options([{ip, Ip} | Rest], State) ->
|
|
|
105 |
ParsedIp = case Ip of
|
|
|
106 |
any ->
|
|
|
107 |
any;
|
|
|
108 |
Ip when is_tuple(Ip) ->
|
|
|
109 |
Ip;
|
|
|
110 |
Ip when is_list(Ip) ->
|
|
|
111 |
{ok, IpTuple} = inet_parse:address(Ip),
|
|
|
112 |
IpTuple
|
|
|
113 |
end,
|
|
|
114 |
parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp});
|
|
|
115 |
parse_options([{loop, Loop} | Rest], State) ->
|
|
|
116 |
parse_options(Rest, State#mochiweb_socket_server{loop=Loop});
|
|
|
117 |
parse_options([{backlog, Backlog} | Rest], State) ->
|
|
|
118 |
parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog});
|
|
|
119 |
parse_options([{nodelay, NoDelay} | Rest], State) ->
|
|
|
120 |
parse_options(Rest, State#mochiweb_socket_server{nodelay=NoDelay});
|
|
|
121 |
parse_options([{recbuf, RecBuf} | Rest], State) when is_integer(RecBuf) orelse
|
|
|
122 |
RecBuf == undefined ->
|
|
|
123 |
%% XXX: `recbuf' value which is passed to `gen_tcp'
|
|
|
124 |
%% and value reported by `inet:getopts(P, [recbuf])' may
|
|
|
125 |
%% differ. They depends on underlying OS. From linux mans:
|
|
|
126 |
%%
|
|
|
127 |
%% The kernel doubles this value (to allow space for
|
|
|
128 |
%% bookkeeping overhead) when it is set using setsockopt(2),
|
|
|
129 |
%% and this doubled value is returned by getsockopt(2).
|
|
|
130 |
%%
|
|
|
131 |
%% See: man 7 socket | grep SO_RCVBUF
|
|
|
132 |
%%
|
|
|
133 |
%% In case undefined is passed instead of the default buffer
|
|
|
134 |
%% size ?RECBUF_SIZE, no size is set and the OS can control it dynamically
|
|
|
135 |
parse_options(Rest, State#mochiweb_socket_server{recbuf=RecBuf});
|
|
|
136 |
parse_options([{buffer, Buffer} | Rest], State) when is_integer(Buffer) orelse
|
|
|
137 |
Buffer == undefined ->
|
|
|
138 |
%% `buffer` sets Erlang's userland socket buffer size. The size of this
|
|
|
139 |
%% buffer affects the maximum URL path that can be parsed. URL sizes that
|
|
|
140 |
%% are larger than this plus the size of the HTTP verb and some whitespace
|
|
|
141 |
%% will result in an `emsgsize` TCP error.
|
|
|
142 |
%%
|
|
|
143 |
%% If this value is not set Erlang sets it to 1460 which might be too low.
|
|
|
144 |
parse_options(Rest, State#mochiweb_socket_server{buffer=Buffer});
|
|
|
145 |
parse_options([{acceptor_pool_size, Max} | Rest], State) ->
|
|
|
146 |
MaxInt = ensure_int(Max),
|
|
|
147 |
parse_options(Rest,
|
|
|
148 |
State#mochiweb_socket_server{acceptor_pool_size=MaxInt});
|
|
|
149 |
parse_options([{max, Max} | Rest], State) ->
|
|
|
150 |
MaxInt = ensure_int(Max),
|
|
|
151 |
parse_options(Rest, State#mochiweb_socket_server{max=MaxInt});
|
|
|
152 |
parse_options([{ssl, Ssl} | Rest], State) when is_boolean(Ssl) ->
|
|
|
153 |
parse_options(Rest, State#mochiweb_socket_server{ssl=Ssl});
|
|
|
154 |
parse_options([{ssl_opts, SslOpts} | Rest], State) when is_list(SslOpts) ->
|
|
|
155 |
SslOpts1 = [{ssl_imp, new} | proplists:delete(ssl_imp, SslOpts)],
|
|
|
156 |
parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1});
|
|
|
157 |
parse_options([{profile_fun, ProfileFun} | Rest], State) when is_function(ProfileFun) ->
|
|
|
158 |
parse_options(Rest, State#mochiweb_socket_server{profile_fun=ProfileFun}).
|
|
|
159 |
|
|
|
160 |
|
|
|
161 |
start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) ->
|
|
|
162 |
ok = prep_ssl(Ssl),
|
|
|
163 |
case Name of
|
|
|
164 |
undefined ->
|
|
|
165 |
gen_server:F(?MODULE, State, []);
|
|
|
166 |
_ ->
|
|
|
167 |
gen_server:F(Name, ?MODULE, State, [])
|
|
|
168 |
end.
|
|
|
169 |
|
|
|
170 |
-ifdef(otp_21).
|
|
|
171 |
check_ssl_compatibility() ->
|
|
|
172 |
case lists:keyfind(ssl, 1, application:loaded_applications()) of
|
|
|
173 |
{_, _, V} when V =:= "9.1" orelse V =:= "9.1.1" ->
|
|
|
174 |
{error, "ssl-" ++ V ++ " (OTP 21.2 to 21.2.2) has a regression and is not safe to use with mochiweb. See https://bugs.erlang.org/browse/ERL-830"};
|
|
|
175 |
_ ->
|
|
|
176 |
ok
|
|
|
177 |
end.
|
|
|
178 |
-else.
|
|
|
179 |
check_ssl_compatibility() ->
|
|
|
180 |
ok.
|
|
|
181 |
-endif.
|
|
|
182 |
|
|
|
183 |
prep_ssl(true) ->
|
|
|
184 |
ok = mochiweb:ensure_started(crypto),
|
|
|
185 |
ok = mochiweb:ensure_started(asn1),
|
|
|
186 |
ok = mochiweb:ensure_started(public_key),
|
|
|
187 |
ok = mochiweb:ensure_started(ssl),
|
|
|
188 |
ok = check_ssl_compatibility(),
|
|
|
189 |
ok;
|
|
|
190 |
prep_ssl(false) ->
|
|
|
191 |
ok.
|
|
|
192 |
|
|
|
193 |
ensure_int(N) when is_integer(N) ->
|
|
|
194 |
N;
|
|
|
195 |
ensure_int(S) when is_list(S) ->
|
|
|
196 |
list_to_integer(S).
|
|
|
197 |
|
|
|
198 |
ipv6_supported() ->
|
|
|
199 |
case (catch inet:getaddr("localhost", inet6)) of
|
|
|
200 |
{ok, _Addr} ->
|
|
|
201 |
true;
|
|
|
202 |
{error, _} ->
|
|
|
203 |
false
|
|
|
204 |
end.
|
|
|
205 |
|
|
|
206 |
init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog,
|
|
|
207 |
nodelay=NoDelay, recbuf=RecBuf,
|
|
|
208 |
buffer=Buffer}) ->
|
|
|
209 |
process_flag(trap_exit, true),
|
|
|
210 |
|
|
|
211 |
BaseOpts = [binary,
|
|
|
212 |
{reuseaddr, true},
|
|
|
213 |
{packet, 0},
|
|
|
214 |
{backlog, Backlog},
|
|
|
215 |
{exit_on_close, false},
|
|
|
216 |
{active, false},
|
|
|
217 |
{nodelay, NoDelay}],
|
|
|
218 |
Opts = case Ip of
|
|
|
219 |
any ->
|
|
|
220 |
case ipv6_supported() of % IPv4, and IPv6 if supported
|
|
|
221 |
true -> [inet, inet6 | BaseOpts];
|
|
|
222 |
_ -> BaseOpts
|
|
|
223 |
end;
|
|
|
224 |
{_, _, _, _} -> % IPv4
|
|
|
225 |
[inet, {ip, Ip} | BaseOpts];
|
|
|
226 |
{_, _, _, _, _, _, _, _} -> % IPv6
|
|
|
227 |
[inet6, {ip, Ip} | BaseOpts]
|
|
|
228 |
end,
|
|
|
229 |
OptsBuf = set_buffer_opts(RecBuf, Buffer, Opts),
|
|
|
230 |
listen(Port, OptsBuf, State).
|
|
|
231 |
|
|
|
232 |
|
|
|
233 |
set_buffer_opts(undefined, undefined, Opts) ->
|
|
|
234 |
% If recbuf is undefined, user space buffer is set to the default 1460
|
|
|
235 |
% value. That unexpectedly break the {packet, http} parser and any URL
|
|
|
236 |
% lines longer than 1460 would error out with emsgsize. So when recbuf is
|
|
|
237 |
% undefined, use previous value of recbuf for buffer in order to keep older
|
|
|
238 |
% code from breaking.
|
|
|
239 |
[{buffer, ?RECBUF_SIZE} | Opts];
|
|
|
240 |
set_buffer_opts(RecBuf, undefined, Opts) ->
|
|
|
241 |
[{recbuf, RecBuf} | Opts];
|
|
|
242 |
set_buffer_opts(undefined, Buffer, Opts) ->
|
|
|
243 |
[{buffer, Buffer} | Opts];
|
|
|
244 |
set_buffer_opts(RecBuf, Buffer, Opts) ->
|
|
|
245 |
% Note: order matters, recbuf will override buffer unless buffer value
|
|
|
246 |
% comes first, except on older versions of Erlang (ex. 17.0) where it works
|
|
|
247 |
% exactly the opposite.
|
|
|
248 |
[{buffer, Buffer}, {recbuf, RecBuf} | Opts].
|
|
|
249 |
|
|
|
250 |
|
|
|
251 |
new_acceptor_pool(State=#mochiweb_socket_server{acceptor_pool_size=Size}) ->
|
|
|
252 |
lists:foldl(fun (_, S) -> new_acceptor(S) end, State, lists:seq(1, Size)).
|
|
|
253 |
|
|
|
254 |
new_acceptor(State=#mochiweb_socket_server{acceptor_pool=Pool,
|
|
|
255 |
recbuf=RecBuf,
|
|
|
256 |
loop=Loop,
|
|
|
257 |
listen=Listen}) ->
|
|
|
258 |
LoopOpts = [{recbuf, RecBuf}],
|
|
|
259 |
Pid = mochiweb_acceptor:start_link(self(), Listen, Loop, LoopOpts),
|
|
|
260 |
State#mochiweb_socket_server{
|
|
|
261 |
acceptor_pool=sets:add_element(Pid, Pool)}.
|
|
|
262 |
|
|
|
263 |
listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts}) ->
|
|
|
264 |
case mochiweb_socket:listen(Ssl, Port, Opts, SslOpts) of
|
|
|
265 |
{ok, Listen} ->
|
|
|
266 |
{ok, ListenPort} = mochiweb_socket:port(Listen),
|
|
|
267 |
{ok, new_acceptor_pool(State#mochiweb_socket_server{
|
|
|
268 |
listen=Listen,
|
|
|
269 |
port=ListenPort})};
|
|
|
270 |
{error, Reason} ->
|
|
|
271 |
{stop, Reason}
|
|
|
272 |
end.
|
|
|
273 |
|
|
|
274 |
do_get(port, #mochiweb_socket_server{port=Port}) ->
|
|
|
275 |
Port;
|
|
|
276 |
do_get(waiting_acceptors, #mochiweb_socket_server{acceptor_pool=Pool}) ->
|
|
|
277 |
sets:size(Pool);
|
|
|
278 |
do_get(active_sockets, #mochiweb_socket_server{active_sockets=ActiveSockets}) ->
|
|
|
279 |
ActiveSockets.
|
|
|
280 |
|
|
|
281 |
|
|
|
282 |
state_to_proplist(#mochiweb_socket_server{name=Name,
|
|
|
283 |
port=Port,
|
|
|
284 |
active_sockets=ActiveSockets}) ->
|
|
|
285 |
[{name, Name}, {port, Port}, {active_sockets, ActiveSockets}].
|
|
|
286 |
|
|
|
287 |
upgrade_state(State = #mochiweb_socket_server{}) ->
|
|
|
288 |
State;
|
|
|
289 |
upgrade_state({mochiweb_socket_server, Port, Loop, Name,
|
|
|
290 |
Max, IP, Listen, NoDelay, Backlog, ActiveSockets,
|
|
|
291 |
AcceptorPoolSize, SSL, SSL_opts,
|
|
|
292 |
AcceptorPool}) ->
|
|
|
293 |
#mochiweb_socket_server{port=Port, loop=Loop, name=Name, max=Max, ip=IP,
|
|
|
294 |
listen=Listen, nodelay=NoDelay, backlog=Backlog,
|
|
|
295 |
active_sockets=ActiveSockets,
|
|
|
296 |
acceptor_pool_size=AcceptorPoolSize,
|
|
|
297 |
ssl=SSL,
|
|
|
298 |
ssl_opts=SSL_opts,
|
|
|
299 |
acceptor_pool=AcceptorPool}.
|
|
|
300 |
|
|
|
301 |
handle_call(Req, From, State) when ?is_old_state(State) ->
|
|
|
302 |
handle_call(Req, From, upgrade_state(State));
|
|
|
303 |
handle_call({get, Property}, _From, State) ->
|
|
|
304 |
Res = do_get(Property, State),
|
|
|
305 |
{reply, Res, State};
|
|
|
306 |
handle_call(stop, _From, State) ->
|
|
|
307 |
{stop, normal, ok, State};
|
|
|
308 |
handle_call(_Message, _From, State) ->
|
|
|
309 |
Res = error,
|
|
|
310 |
{reply, Res, State}.
|
|
|
311 |
|
|
|
312 |
|
|
|
313 |
handle_cast(Req, State) when ?is_old_state(State) ->
|
|
|
314 |
handle_cast(Req, upgrade_state(State));
|
|
|
315 |
handle_cast({accepted, Pid, Timing},
|
|
|
316 |
State=#mochiweb_socket_server{active_sockets=ActiveSockets}) ->
|
|
|
317 |
State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets},
|
|
|
318 |
case State#mochiweb_socket_server.profile_fun of
|
|
|
319 |
undefined ->
|
|
|
320 |
undefined;
|
|
|
321 |
F when is_function(F) ->
|
|
|
322 |
catch F([{timing, Timing} | state_to_proplist(State1)])
|
|
|
323 |
end,
|
|
|
324 |
{noreply, recycle_acceptor(Pid, State1)};
|
|
|
325 |
handle_cast({set, profile_fun, ProfileFun}, State) ->
|
|
|
326 |
State1 = case ProfileFun of
|
|
|
327 |
ProfileFun when is_function(ProfileFun); ProfileFun =:= undefined ->
|
|
|
328 |
State#mochiweb_socket_server{profile_fun=ProfileFun};
|
|
|
329 |
_ ->
|
|
|
330 |
State
|
|
|
331 |
end,
|
|
|
332 |
{noreply, State1}.
|
|
|
333 |
|
|
|
334 |
|
|
|
335 |
terminate(Reason, State) when ?is_old_state(State) ->
|
|
|
336 |
terminate(Reason, upgrade_state(State));
|
|
|
337 |
terminate(_Reason, #mochiweb_socket_server{listen=Listen}) ->
|
|
|
338 |
mochiweb_socket:close(Listen).
|
|
|
339 |
|
|
|
340 |
code_change(_OldVsn, State, _Extra) ->
|
|
|
341 |
State.
|
|
|
342 |
|
|
|
343 |
recycle_acceptor(Pid, State=#mochiweb_socket_server{
|
|
|
344 |
acceptor_pool=Pool,
|
|
|
345 |
acceptor_pool_size=PoolSize,
|
|
|
346 |
max=Max,
|
|
|
347 |
active_sockets=ActiveSockets}) ->
|
|
|
348 |
%% A socket is considered to be active from immediately after it
|
|
|
349 |
%% has been accepted (see the {accepted, Pid, Timing} cast above).
|
|
|
350 |
%% This function will be called when an acceptor is transitioning
|
|
|
351 |
%% to an active socket, or when either type of Pid dies. An acceptor
|
|
|
352 |
%% Pid will always be in the acceptor_pool set, and an active socket
|
|
|
353 |
%% will be in that set during the transition but not afterwards.
|
|
|
354 |
Pool1 = sets:del_element(Pid, Pool),
|
|
|
355 |
NewSize = sets:size(Pool1),
|
|
|
356 |
ActiveSockets1 = case NewSize =:= sets:size(Pool) of
|
|
|
357 |
%% Pid has died and it is not in the acceptor set,
|
|
|
358 |
%% it must be an active socket.
|
|
|
359 |
true -> max(0, ActiveSockets - 1);
|
|
|
360 |
false -> ActiveSockets
|
|
|
361 |
end,
|
|
|
362 |
State1 = State#mochiweb_socket_server{
|
|
|
363 |
acceptor_pool=Pool1,
|
|
|
364 |
active_sockets=ActiveSockets1},
|
|
|
365 |
%% Spawn a new acceptor only if it will not overrun the maximum socket
|
|
|
366 |
%% count or the maximum pool size.
|
|
|
367 |
case NewSize + ActiveSockets1 < Max andalso NewSize < PoolSize of
|
|
|
368 |
true -> new_acceptor(State1);
|
|
|
369 |
false -> State1
|
|
|
370 |
end.
|
|
|
371 |
|
|
|
372 |
handle_info(Msg, State) when ?is_old_state(State) ->
|
|
|
373 |
handle_info(Msg, upgrade_state(State));
|
|
|
374 |
handle_info({'EXIT', Pid, normal}, State) ->
|
|
|
375 |
{noreply, recycle_acceptor(Pid, State)};
|
|
|
376 |
handle_info({'EXIT', Pid, Reason},
|
|
|
377 |
State=#mochiweb_socket_server{acceptor_pool=Pool}) ->
|
|
|
378 |
case sets:is_element(Pid, Pool) of
|
|
|
379 |
true ->
|
|
|
380 |
%% If there was an unexpected error accepting, log and sleep.
|
|
|
381 |
error_logger:error_report({?MODULE, ?LINE,
|
|
|
382 |
{acceptor_error, Reason}}),
|
|
|
383 |
timer:sleep(100);
|
|
|
384 |
false ->
|
|
|
385 |
ok
|
|
|
386 |
end,
|
|
|
387 |
{noreply, recycle_acceptor(Pid, State)};
|
|
|
388 |
|
|
|
389 |
% this is what release_handler needs to get a list of modules,
|
|
|
390 |
% since our supervisor modules list is set to 'dynamic'
|
|
|
391 |
% see sasl-2.1.9.2/src/release_handler_1.erl get_dynamic_mods
|
|
|
392 |
handle_info({From, Tag, get_modules}, State = #mochiweb_socket_server{name={local,Mod}}) ->
|
|
|
393 |
From ! {element(2,Tag), [Mod]},
|
|
|
394 |
{noreply, State};
|
|
|
395 |
|
|
|
396 |
% If for some reason we can't get the module name, send empty list to avoid release_handler timeout:
|
|
|
397 |
handle_info({From, Tag, get_modules}, State) ->
|
|
|
398 |
error_logger:info_msg("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]),
|
|
|
399 |
From ! {element(2,Tag), []},
|
|
|
400 |
{noreply, State};
|
|
|
401 |
|
|
|
402 |
handle_info(Info, State) ->
|
|
|
403 |
error_logger:info_report([{'INFO', Info}, {'State', State}]),
|
|
|
404 |
{noreply, State}.
|
|
|
405 |
|
|
|
406 |
|
|
|
407 |
|
|
|
408 |
%%
|
|
|
409 |
%% Tests
|
|
|
410 |
%%
|
|
|
411 |
-ifdef(TEST).
|
|
|
412 |
-include_lib("eunit/include/eunit.hrl").
|
|
|
413 |
|
|
|
414 |
upgrade_state_test() ->
|
|
|
415 |
OldState = {mochiweb_socket_server,
|
|
|
416 |
port, loop, name,
|
|
|
417 |
max, ip, listen,
|
|
|
418 |
nodelay, backlog,
|
|
|
419 |
active_sockets,
|
|
|
420 |
acceptor_pool_size,
|
|
|
421 |
ssl, ssl_opts, acceptor_pool},
|
|
|
422 |
State = upgrade_state(OldState),
|
|
|
423 |
CmpState = #mochiweb_socket_server{port=port, loop=loop,
|
|
|
424 |
name=name, max=max, ip=ip,
|
|
|
425 |
listen=listen, nodelay=nodelay,
|
|
|
426 |
backlog=backlog,
|
|
|
427 |
active_sockets=active_sockets,
|
|
|
428 |
acceptor_pool_size=acceptor_pool_size,
|
|
|
429 |
ssl=ssl, ssl_opts=ssl_opts,
|
|
|
430 |
acceptor_pool=acceptor_pool,
|
|
|
431 |
profile_fun=undefined},
|
|
|
432 |
?assertEqual(CmpState, State).
|
|
|
433 |
|
|
|
434 |
|
|
|
435 |
set_buffer_opts_test() ->
|
|
|
436 |
?assertEqual([{buffer, 8192}], set_buffer_opts(undefined, undefined, [])),
|
|
|
437 |
?assertEqual([{recbuf, 5}], set_buffer_opts(5, undefined, [])),
|
|
|
438 |
?assertEqual([{buffer, 6}], set_buffer_opts(undefined, 6, [])),
|
|
|
439 |
?assertEqual([{buffer, 6}, {recbuf, 5}], set_buffer_opts(5, 6, [])).
|
|
|
440 |
|
|
|
441 |
-endif.
|