Browse Source

Add {socket, Socket} transport option, for accepting on existing sockets

Andrew Thompson 12 years ago
parent
commit
036fbd5318
5 changed files with 92 additions and 5 deletions
  1. 18 0
      guide/listeners.md
  2. 1 0
      guide/toc.md
  3. 15 2
      src/ranch.erl
  4. 7 1
      src/ranch_acceptors_sup.erl
  5. 51 2
      test/acceptor_SUITE.erl

+ 18 - 0
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 undelying socket library
+(gen_tcp:connect(), ssl:connect()). accept() will be 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
 ---------------------------------------------
 

+ 1 - 0
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)

+ 15 - 2
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>.

+ 7 - 1
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 = [

+ 51 - 2
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]).
@@ -50,11 +52,13 @@ groups() ->
 		tcp_echo,
 		tcp_max_connections,
 		tcp_max_connections_and_beyond,
-		tcp_upgrade
+		tcp_upgrade,
+		tcp_accept_socket
 	]}, {ssl, [
 		ssl_accept_error,
 		ssl_active_echo,
-		ssl_echo
+		ssl_echo,
+		ssl_accept_socket
 	]}, {misc, [
 		misc_bad_transport
 	]}].
@@ -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, []),