aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--guide/listeners.md18
-rw-r--r--guide/toc.md1
-rw-r--r--src/ranch.erl17
-rw-r--r--src/ranch_acceptors_sup.erl8
-rw-r--r--test/acceptor_SUITE.erl49
5 files changed, 90 insertions, 3 deletions
diff --git a/guide/listeners.md b/guide/listeners.md
index 1f7c430..32fddde 100644
--- a/guide/listeners.md
+++ b/guide/listeners.md
@@ -113,6 +113,24 @@ We recommend the use of port rewriting for systems with a single server,
and load balancing for systems with multiple servers. Documenting these
solutions is however out of the scope of this guide.
+Accepting connections on an existing socket
+-------------------------------------------
+
+If you want to accept connections on an existing socket, you can use the
+`socket` transport option, which should just be the relevant data returned
+from the connect function for the transport or the underlying socket library
+(`gen_tcp:connect`, `ssl:connect`). The accept function will then be
+called on the passed in socket. You should connect the socket in
+`{active, false}` mode, as well.
+
+Note, however, that because of a bug in SSL, you cannot change ownership of an
+SSL listen socket prior to R16. Ranch will catch the error thrown, but the
+owner of the SSL socket will remain as whatever process created the socket.
+However, this will not affect accept behaviour unless the owner process dies,
+in which case the socket is closed. Therefore, to use this feature with SSL
+with an erlang release prior to R16, ensure that the SSL socket is opened in a
+persistant process.
+
Limiting the number of concurrent connections
---------------------------------------------
diff --git a/guide/toc.md b/guide/toc.md
index f83b6c6..3cbdbcf 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -9,6 +9,7 @@ Ranch User Guide
* Starting and stopping
* Listening on a random port
* Listening on privileged ports
+ * Accepting connections on an existing socket
* Limiting the number of concurrent connections
* Upgrading
* [Transports](transports.md)
diff --git a/src/ranch.erl b/src/ranch.erl
index 6d1d5e8..0924ee7 100644
--- a/src/ranch.erl
+++ b/src/ranch.erl
@@ -59,8 +59,21 @@ start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
false ->
{error, badarg};
true ->
- supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
- Transport, TransOpts, Protocol, ProtoOpts))
+ Res = supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
+ Transport, TransOpts, Protocol, ProtoOpts)),
+ case proplists:get_value(socket, TransOpts) of
+ undefined ->
+ ok;
+ Socket ->
+ %% change the controlling process so the caller dying doesn't
+ %% close the port
+ ListenerPid = ranch_server:lookup_listener(Ref),
+ %%% Note: the catch is here because SSL crashes when you change
+ %%% the controlling process of a listen socket because of a bug.
+ %%% The bug will be fixed in R16.
+ catch(Transport:controlling_process(Socket, ListenerPid))
+ end,
+ Res
end.
%% @doc Stop a listener identified by <em>Ref</em>.
diff --git a/src/ranch_acceptors_sup.erl b/src/ranch_acceptors_sup.erl
index 2e45acf..1d9503c 100644
--- a/src/ranch_acceptors_sup.erl
+++ b/src/ranch_acceptors_sup.erl
@@ -35,7 +35,13 @@ start_link(Ref, NbAcceptors, Transport, TransOpts,
init([Ref, NbAcceptors, Transport, TransOpts,
Protocol, ListenerPid, ConnsPid]) ->
- {ok, LSocket} = Transport:listen(TransOpts),
+ LSocket = case proplists:get_value(socket, TransOpts) of
+ undefined ->
+ {ok, Socket} = Transport:listen(TransOpts),
+ Socket;
+ Socket ->
+ Socket
+ end,
{ok, {_, Port}} = Transport:sockname(LSocket),
ranch_listener:set_port(ListenerPid, Port),
Procs = [
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 0b86a4d..59513ab 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -29,10 +29,12 @@
%% ssl.
-export([ssl_accept_error/1]).
+-export([ssl_accept_socket/1]).
-export([ssl_active_echo/1]).
-export([ssl_echo/1]).
%% tcp.
+-export([tcp_accept_socket/1]).
-export([tcp_active_echo/1]).
-export([tcp_echo/1]).
-export([tcp_max_connections/1]).
@@ -46,6 +48,7 @@ all() ->
groups() ->
[{tcp, [
+ tcp_accept_socket,
tcp_active_echo,
tcp_echo,
tcp_max_connections,
@@ -53,6 +56,7 @@ groups() ->
tcp_upgrade
]}, {ssl, [
ssl_accept_error,
+ ssl_accept_socket,
ssl_active_echo,
ssl_echo
]}, {misc, [
@@ -108,6 +112,26 @@ ssl_accept_error(Config) ->
receive after 500 -> ok end,
true = is_process_alive(AcceptorPid).
+ssl_accept_socket(Config) ->
+ %%% XXX we can't do the spawn to test the controlling process change
+ %%% because of the bug in ssl
+ {ok, S} = ssl:listen(0,
+ [{certfile, ?config(data_dir, Config) ++ "cert.pem"}, binary,
+ {active, false}, {packet, raw}, {reuseaddr, true}]),
+ {ok, _} = ranch:start_listener(ssl_accept_socket, 1,
+ ranch_ssl, [{socket, S}], echo_protocol, []),
+ Port = ranch:get_port(ssl_accept_socket),
+ {ok, Socket} = ssl:connect("localhost", Port,
+ [binary, {active, false}, {packet, raw},
+ {certfile, ?config(data_dir, Config) ++ "cert.pem"}]),
+ ok = ssl:send(Socket, <<"TCP Ranch is working!">>),
+ {ok, <<"TCP Ranch is working!">>} = ssl:recv(Socket, 21, 1000),
+ ok = ranch:stop_listener(ssl_accept_socket),
+ {error, closed} = ssl:recv(Socket, 0, 1000),
+ %% Make sure the listener stopped.
+ {'EXIT', _} = begin catch ranch:get_port(ssl_accept_socket) end,
+ ok.
+
ssl_active_echo(Config) ->
{ok, _} = ranch:start_listener(ssl_active_echo, 1,
ranch_ssl, [{port, 0},
@@ -144,6 +168,31 @@ ssl_echo(Config) ->
%% tcp.
+tcp_accept_socket(_) ->
+ Ref = make_ref(),
+ Parent = self(),
+ spawn(fun() ->
+ {ok, S} = gen_tcp:listen(0, [binary, {active, false}, {packet, raw},
+ {reuseaddr, true}]),
+ {ok, _} = ranch:start_listener(tcp_accept_socket, 1,
+ ranch_tcp, [{socket, S}], echo_protocol, []),
+ Parent ! Ref
+ end),
+ receive
+ Ref -> ok
+ end,
+
+ Port = ranch:get_port(tcp_accept_socket),
+ {ok, Socket} = gen_tcp:connect("localhost", Port,
+ [binary, {active, false}, {packet, raw}]),
+ ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>),
+ {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000),
+ ok = ranch:stop_listener(tcp_accept_socket),
+ {error, closed} = gen_tcp:recv(Socket, 0, 1000),
+ %% Make sure the listener stopped.
+ {'EXIT', _} = begin catch ranch:get_port(tcp_accept_socket) end,
+ ok.
+
tcp_active_echo(_) ->
{ok, _} = ranch:start_listener(tcp_active_echo, 1,
ranch_tcp, [{port, 0}], active_echo_protocol, []),