Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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.