From 036fbd53189f0a43bd8348e517a17da0f00de980 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 24 Sep 2012 13:32:21 -0400 Subject: Add {socket, Socket} transport option, for accepting on existing sockets --- guide/listeners.md | 18 +++++++++++++++ guide/toc.md | 1 + src/ranch.erl | 17 +++++++++++++-- src/ranch_acceptors_sup.erl | 8 ++++++- test/acceptor_SUITE.erl | 53 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/guide/listeners.md b/guide/listeners.md index 1f7c430..48ec622 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 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 --------------------------------------------- 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 Ref. 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..5e8d9cf 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]). @@ -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, []), -- cgit v1.2.3