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
 and load balancing for systems with multiple servers. Documenting these
 solutions is however out of the scope of this guide.
 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
 Limiting the number of concurrent connections
 ---------------------------------------------
 ---------------------------------------------
 
 

+ 1 - 0
guide/toc.md

@@ -9,6 +9,7 @@ Ranch User Guide
    *  Starting and stopping
    *  Starting and stopping
    *  Listening on a random port
    *  Listening on a random port
    *  Listening on privileged ports
    *  Listening on privileged ports
+   *  Accepting connections on an existing socket
    *  Limiting the number of concurrent connections
    *  Limiting the number of concurrent connections
    *  Upgrading
    *  Upgrading
  *  [Transports](transports.md)
  *  [Transports](transports.md)

+ 15 - 2
src/ranch.erl

@@ -59,8 +59,21 @@ start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
 		false ->
 		false ->
 			{error, badarg};
 			{error, badarg};
 		true ->
 		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.
 	end.
 
 
 %% @doc Stop a listener identified by <em>Ref</em>.
 %% @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,
 init([Ref, NbAcceptors, Transport, TransOpts,
 		Protocol, ListenerPid, ConnsPid]) ->
 		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),
 	{ok, {_, Port}} = Transport:sockname(LSocket),
 	ranch_listener:set_port(ListenerPid, Port),
 	ranch_listener:set_port(ListenerPid, Port),
 	Procs = [
 	Procs = [

+ 51 - 2
test/acceptor_SUITE.erl

@@ -29,10 +29,12 @@
 
 
 %% ssl.
 %% ssl.
 -export([ssl_accept_error/1]).
 -export([ssl_accept_error/1]).
+-export([ssl_accept_socket/1]).
 -export([ssl_active_echo/1]).
 -export([ssl_active_echo/1]).
 -export([ssl_echo/1]).
 -export([ssl_echo/1]).
 
 
 %% tcp.
 %% tcp.
+-export([tcp_accept_socket/1]).
 -export([tcp_active_echo/1]).
 -export([tcp_active_echo/1]).
 -export([tcp_echo/1]).
 -export([tcp_echo/1]).
 -export([tcp_max_connections/1]).
 -export([tcp_max_connections/1]).
@@ -50,11 +52,13 @@ groups() ->
 		tcp_echo,
 		tcp_echo,
 		tcp_max_connections,
 		tcp_max_connections,
 		tcp_max_connections_and_beyond,
 		tcp_max_connections_and_beyond,
-		tcp_upgrade
+		tcp_upgrade,
+		tcp_accept_socket
 	]}, {ssl, [
 	]}, {ssl, [
 		ssl_accept_error,
 		ssl_accept_error,
 		ssl_active_echo,
 		ssl_active_echo,
-		ssl_echo
+		ssl_echo,
+		ssl_accept_socket
 	]}, {misc, [
 	]}, {misc, [
 		misc_bad_transport
 		misc_bad_transport
 	]}].
 	]}].
@@ -108,6 +112,26 @@ ssl_accept_error(Config) ->
 	receive after 500 -> ok end,
 	receive after 500 -> ok end,
 	true = is_process_alive(AcceptorPid).
 	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) ->
 ssl_active_echo(Config) ->
 	{ok, _} = ranch:start_listener(ssl_active_echo, 1,
 	{ok, _} = ranch:start_listener(ssl_active_echo, 1,
 		ranch_ssl, [{port, 0},
 		ranch_ssl, [{port, 0},
@@ -144,6 +168,31 @@ ssl_echo(Config) ->
 
 
 %% tcp.
 %% 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(_) ->
 tcp_active_echo(_) ->
 	{ok, _} = ranch:start_listener(tcp_active_echo, 1,
 	{ok, _} = ranch:start_listener(tcp_active_echo, 1,
 		ranch_tcp, [{port, 0}], active_echo_protocol, []),
 		ranch_tcp, [{port, 0}], active_echo_protocol, []),