%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2001-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(httpd_acceptor).
-include("httpd.hrl").
-include("httpd_internal.hrl").
-include("inets_internal.hrl").
%% Internal application API
-export([start_link/6, start_link/7]).
%% Other exports (for spawn's etc.)
-export([acceptor_init/7, acceptor_init/8, acceptor_loop/6]).
%%
%% External API
%%
%% start_link
start_link(Manager, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout) ->
?hdrd("start link",
[{manager, Manager},
{socket_type, SocketType},
{address, Addr},
{port, Port},
{timeout, AcceptTimeout}]),
Args = [self(), Manager, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout],
proc_lib:start_link(?MODULE, acceptor_init, Args).
start_link(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout) ->
?hdrd("start link",
[{manager, Manager},
{socket_type, SocketType},
{listen_socket, ListenSocket},
{timeout, AcceptTimeout}]),
Args = [self(), Manager, SocketType, ListenSocket, IpFamily,
ConfigDb, AcceptTimeout],
proc_lib:start_link(?MODULE, acceptor_init, Args).
acceptor_init(Parent, Manager, SocketType, {ListenOwner, ListenSocket}, IpFamily,
ConfigDb, AcceptTimeout) ->
?hdrd("acceptor init",
[{parent, Parent},
{manager, Manager},
{socket_type, SocketType},
{listen_owner, ListenOwner},
{listen_socket, ListenSocket},
{timeout, AcceptTimeout}]),
link(ListenOwner),
proc_lib:init_ack(Parent, {ok, self()}),
acceptor_loop(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout).
acceptor_init(Parent, Manager, SocketType, Addr, Port, IpFamily,
ConfigDb, AcceptTimeout) ->
?hdrd("acceptor init",
[{parent, Parent},
{manager, Manager},
{socket_type, SocketType},
{address, Addr},
{port, Port},
{timeout, AcceptTimeout}]),
case (catch do_init(SocketType, Addr, Port, IpFamily)) of
{ok, ListenSocket} ->
proc_lib:init_ack(Parent, {ok, self()}),
acceptor_loop(Manager, SocketType,
ListenSocket, IpFamily,ConfigDb, AcceptTimeout);
Error ->
proc_lib:init_ack(Parent, Error),
error
end.
do_init(SocketType, Addr, Port, IpFamily) ->
?hdrt("do init", []),
do_socket_start(SocketType),
ListenSocket = do_socket_listen(SocketType, Addr, Port, IpFamily),
{ok, ListenSocket}.
do_socket_start(SocketType) ->
?hdrt("do socket start", []),
case http_transport:start(SocketType) of
ok ->
ok;
{error, Reason} ->
?hdrv("failed starting transport", [{reason, Reason}]),
throw({error, {socket_start_failed, Reason}})
end.
do_socket_listen(SocketType, Addr, Port, IpFamily) ->
?hdrt("do socket listen", []),
case http_transport:listen(SocketType, Addr, Port, IpFamily) of
{ok, ListenSocket} ->
ListenSocket;
{error, Reason} ->
?hdrv("listen failed", [{reason, Reason},
{socket_type, SocketType},
{addr, Addr},
{port, Port}]),
throw({error, {listen, Reason}})
end.
%% acceptor
acceptor_loop(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout) ->
?hdrd("awaiting accept",
[{manager, Manager},
{socket_type, SocketType},
{listen_socket, ListenSocket},
{timeout, AcceptTimeout}]),
case (catch http_transport:accept(SocketType, ListenSocket, 50000)) of
{ok, Socket} ->
?hdrv("accepted", [{socket, Socket}]),
handle_connection(Manager, ConfigDb, AcceptTimeout,
SocketType, Socket),
?MODULE:acceptor_loop(Manager, SocketType,
ListenSocket, IpFamily, ConfigDb,AcceptTimeout);
{error, Reason} ->
?hdri("accept failed", [{reason, Reason}]),
handle_error(Reason, ConfigDb),
?MODULE:acceptor_loop(Manager, SocketType, ListenSocket,
IpFamily, ConfigDb, AcceptTimeout);
{'EXIT', Reason} ->
?hdri("accept exited", [{reason, Reason}]),
ReasonString =
lists:flatten(io_lib:format("Accept exit: ~p", [Reason])),
accept_failed(ConfigDb, ReasonString)
end.
handle_connection(Manager, ConfigDb, AcceptTimeout, SocketType, Socket) ->
{ok, Pid} = httpd_request_handler:start(Manager, ConfigDb, AcceptTimeout),
http_transport:controlling_process(SocketType, Socket, Pid),
httpd_request_handler:socket_ownership_transfered(Pid, SocketType, Socket).
handle_error(timeout, _) ->
ok;
handle_error({enfile, _}, _) ->
%% Out of sockets...
sleep(200);
handle_error(emfile, _) ->
%% Too many open files -> Out of sockets...
sleep(200);
handle_error(closed, _) ->
error_logger:info_report("The httpd accept socket was closed by "
"a third party. "
"This will not have an impact on inets "
"that will open a new accept socket and "
"go on as nothing happened. It does however "
"indicate that some other software is behaving "
"badly."),
exit(normal);
%% This will only happen when the client is terminated abnormaly
%% and is not a problem for the server, so we want
%% to terminate normal so that we can restart without any
%% error messages.
handle_error(econnreset,_) ->
exit(normal);
handle_error(econnaborted, _) ->
ok;
handle_error(esslaccept, _) ->
%% The user has selected to cancel the installation of
%% the certifikate, This is not a real error, so we do
%% not write an error message.
ok;
handle_error(Reason, ConfigDb) ->
String = lists:flatten(io_lib:format("Accept error: ~p", [Reason])),
accept_failed(ConfigDb, String).
-spec accept_failed(ConfigDB :: term(),
ReasonString :: string()) -> no_return().
accept_failed(ConfigDb, String) ->
error_logger:error_report(String),
InitData = #init_data{peername = {0, "unknown"}},
Info = #mod{config_db = ConfigDb, init_data = InitData},
mod_log:error_log(Info, String),
mod_disk_log:error_log(Info, String),
exit({accept_failed, String}).
sleep(T) -> receive after T -> ok end.