Skip to content

Commit

Permalink
Introduce Transport:handshake/1,2
Browse files Browse the repository at this point in the history
This commit deprecates Transport:accept_ack/1 in favor of
a new forward-compatible function. Transport:handshake/1,2
will use ssl:handshake/2,3 from Ranch 2.0 onward.
  • Loading branch information
j.uhlig authored and essen committed Jul 2, 2018
1 parent caeadc8 commit 5ada450
Show file tree
Hide file tree
Showing 18 changed files with 181 additions and 76 deletions.
28 changes: 14 additions & 14 deletions doc/src/guide/protocols.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ the call to `ranch:start_listener/5`. This callback must
return `{ok, Pid}`, with `Pid` the pid of the new process.

The newly started process can then freely initialize itself. However,
it must call `ranch:accept_ack/1` before doing any socket operation.
it must call `ranch:handshake/{1,2}` before doing any socket operation.
This will ensure the connection process is the owner of the socket.
It expects the listener's name as argument.

.Acknowledge accepting the socket
.Perform the socket handshake

[source,erlang]
ok = ranch:accept_ack(Ref).
{ok, Socket} = ranch:handshake(Ref).

If your protocol code requires specific socket options, you should
set them while initializing your connection process, after
calling `ranch:accept_ack/1`. You can use `Transport:setopts/2`
calling `ranch:handshake/{1,2}`. You can use `Transport:setopts/2`
for that purpose.

Following is the complete protocol code for the example found
Expand All @@ -39,14 +39,14 @@ in `examples/tcp_echo/`.
-behaviour(ranch_protocol).
-export([start_link/4]).
-export([init/4]).
-export([init/3]).
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
start_link(Ref, _Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Socket, Transport, _Opts = []) ->
ok = ranch:accept_ack(Ref),
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(Ref),
loop(Socket, Transport).
loop(Socket, Transport) ->
Expand All @@ -64,7 +64,7 @@ loop(Socket, Transport) ->
Special processes like the ones that use the `gen_statem` or `gen_server`
behaviours have the particularity of having their `start_link` call not
return until the `init` function returns. This is problematic, because
you won't be able to call `ranch:accept_ack/1` from the `init` callback
you won't be able to call `ranch:handshake/{1,2}` from the `init` callback
as this would cause a deadlock to happen.

Use the `gen_statem:enter_loop/4` function. It allows you to start your process
Expand All @@ -84,12 +84,12 @@ the normal `gen_statem` execution loop.
-export([init/1]).
%% Exports of other gen_statem callbacks here.
start_link(Ref, Socket, Transport, Opts) ->
{ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Socket, Transport, Opts}])}.
start_link(Ref, _Socket, Transport, Opts) ->
{ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Transport, Opts}])}.
init({Ref, Socket, Transport, _Opts = []}) ->
init({Ref, Transport, _Opts = []}) ->
%% Perform any required state initialization here.
ok = ranch:accept_ack(Ref),
{ok, Socket} = ranch:handshake(Ref),
ok = Transport:setopts(Socket, [{active, once}]),
gen_statem:enter_loop(?MODULE, [], state_name, {state_data, Socket, Transport}).
Expand Down
40 changes: 31 additions & 9 deletions doc/src/manual/ranch.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Unique name used to refer to a listener.
None of the options are required.

ack_timeout (5000)::
Maximum allowed time for the `ranch:accept_ack/1` call to finish.
Maximum allowed time for the `ranch:handshake/{1,2}` call to finish.
connection_type (worker)::
Type of process that will handle the connection.
max_connections (1024)::
Expand All @@ -63,13 +63,7 @@ socket::

=== accept_ack(Ref) -> ok

Ref = ref():: Listener name.

Acknowledge that the connection is accepted.

This function MUST be used by a connection process to inform
Ranch that it initialized properly and let it perform any
additional operations before the socket can be safely used.
This function is deprecated in favor of `ranch:handshake/1,2`.

=== child_spec(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> supervisor:child_spec()

Expand Down Expand Up @@ -127,6 +121,34 @@ TransOpts = any():: Current transport options.

Return the transport options set for the given listener.

=== handshake(Ref) -> {ok, Socket}

Ref = ref():: Listener name.
Socket = any():: Initialized socket.

Acknowledge that the connection is accepted.
Returns a socket that is ready to use.

One of the `ranch:handshake/{1,2}` functions MUST be used
by a connection process to inform Ranch that it initialized
properly and let it perform any additional operations before
the socket can be safely used.

=== handshake(Ref, Opts) -> {ok, Socket}

Ref = ref():: Listener name.
Opts = any():: Initialization options.
Socket = any():: Initialized socket.

Acknowledge that the connection is accepted.
Additional options can be provided for socket initialization.
Returns a socket that is ready to use.

One of the `ranch:handshake/{1,2}` functions MUST be used
by a connection process to inform Ranch that it initialized
properly and let it perform any additional operations before
the socket can be safely used.

=== info() -> [{Ref, [{Key, Value}]}]

Ref = ref():: Listener name.
Expand Down Expand Up @@ -272,4 +294,4 @@ Operator = '>' | '>=' | '==' | '=<' | '<':: Comparison operator.
NumConnections = non_neg_integer():: Number of connections to wait for.

Wait until the number of connections on the given listener matches
the given operator and number of connections.
the given operator and number of connections.
28 changes: 15 additions & 13 deletions doc/src/manual/ranch_transport.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,10 @@ Timeout = timeout():: Accept timeout.

Accept a connection on the given listening socket.

The `accept_ack` callback will be used to initialize the socket
The `handshake` callback will be used to initialize the socket
after accepting the connection. This is most useful when the
transport is not raw TCP, like with SSL for example.

=== accept_ack(CSocket, Timeout) -> ok

CSocket = any():: Socket for this connection.
Timeout = timeout():: Ack timeout.

Perform post-accept initialization of the connection.

This function will be called by connection processes
before performing any socket operation. It allows
transports that require extra initialization to perform
their task and make the socket ready to use.

=== close(Socket) -> ok

Socket = any():: Socket opened with listen/1 or accept/2.
Expand All @@ -60,6 +48,20 @@ perform operations on the socket, and that will receive
messages from the socket when active mode is used. When
the controlling process dies, the socket is closed.

=== handshake(CSocket0, Options, Timeout) -> {ok, CSocket1}

CSocket0 = any():: Uninitialized socket for this connection.
Options = any():: Options for initialization.
Timeout = timeout():: Handshake timeout.
CSocket1 = any():: Initialized socket for this connection.

Perform post-accept initialization of the connection.

This function will be called by connection processes
before performing any socket operation. It allows
transports that require extra initialization to perform
their task and return a socket that is ready to use.

=== listen(TransOpts) -> {ok, LSocket} | {error, atom()}

TransOpts = any():: Transport options.
Expand Down
10 changes: 5 additions & 5 deletions examples/tcp_echo/src/echo_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
-behaviour(ranch_protocol).

-export([start_link/4]).
-export([init/4]).
-export([init/3]).

start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
start_link(Ref, _Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.

init(Ref, Socket, Transport, _Opts = []) ->
ok = ranch:accept_ack(Ref),
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(Ref),
loop(Socket, Transport).

loop(Socket, Transport) ->
Expand Down
8 changes: 4 additions & 4 deletions examples/tcp_reverse/src/reverse_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@

%% API.

start_link(Ref, Socket, Transport, Opts) ->
{ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Socket, Transport, Opts}])}.
start_link(Ref, _Socket, Transport, Opts) ->
{ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Transport, Opts}])}.

%% gen_statem.

callback_mode() ->
state_functions.

init({Ref, Socket, Transport, _Opts = []}) ->
ok = ranch:accept_ack(Ref),
init({Ref, Transport, _Opts = []}) ->
{ok, Socket} = ranch:handshake(Ref),
ok = Transport:setopts(Socket, [{active, once}, {packet, line}]),
gen_statem:enter_loop(?MODULE, [], connected,
#state{socket=Socket, transport=Transport},
Expand Down
14 changes: 13 additions & 1 deletion src/ranch.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
-export([child_spec/5]).
-export([child_spec/6]).
-export([accept_ack/1]).
-export([handshake/1]).
-export([handshake/2]).
-export([remove_connection/1]).
-export([get_status/1]).
-export([get_addr/1]).
Expand Down Expand Up @@ -166,10 +168,20 @@ child_spec(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts)

-spec accept_ack(ref()) -> ok.
accept_ack(Ref) ->
receive {shoot, Ref, Transport, Socket, AckTimeout} ->
receive {handshake, Ref, Transport, Socket, AckTimeout} ->
Transport:accept_ack(Socket, AckTimeout)
end.

-spec handshake(ref()) -> {ok, ranch_transport:socket()}.
handshake(Ref) ->
handshake(Ref, []).

-spec handshake(ref(), any()) -> {ok, ranch_transport:socket()}.
handshake(Ref, Opts) ->
receive {handshake, Ref, Transport, Socket, AckTimeout} ->
Transport:handshake(Socket, Opts, AckTimeout)
end.

-spec remove_connection(ref()) -> ok.
remove_connection(Ref) ->
ConnsSup = ranch_server:get_connections_sup(Ref),
Expand Down
8 changes: 4 additions & 4 deletions src/ranch_conns_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
{?MODULE, start_protocol, To, Socket} ->
try Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
shoot(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
{ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
shoot(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
Ret ->
To ! self(),
error_logger:error_msg(
Expand Down Expand Up @@ -219,11 +219,11 @@ loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
loop(State, CurConns, NbChildren, Sleepers)
end.

shoot(State=#state{ref=Ref, transport=Transport, ack_timeout=AckTimeout, max_conns=MaxConns},
handshake(State=#state{ref=Ref, transport=Transport, ack_timeout=AckTimeout, max_conns=MaxConns},
CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
case Transport:controlling_process(Socket, ProtocolPid) of
ok ->
ProtocolPid ! {shoot, Ref, Transport, Socket, AckTimeout},
ProtocolPid ! {handshake, Ref, Transport, Socket, AckTimeout},
put(SupPid, active),
CurConns2 = CurConns + 1,
if CurConns2 < MaxConns ->
Expand Down
14 changes: 12 additions & 2 deletions src/ranch_ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
-module(ranch_ssl).
-behaviour(ranch_transport).

-ifdef(OTP_RELEASE).
-compile({nowarn_deprecated_function, [{ssl, ssl_accept, 3}]}).
-endif.

-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([disallowed_listen_options/0]).
-export([accept/2]).
-export([accept_ack/2]).
-export([handshake/3]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
Expand Down Expand Up @@ -128,9 +133,14 @@ accept(LSocket, Timeout) ->

-spec accept_ack(ssl:sslsocket(), timeout()) -> ok.
accept_ack(CSocket, Timeout) ->
case ssl:ssl_accept(CSocket, Timeout) of
{ok, _} = handshake(CSocket, [], Timeout),
ok.

-spec handshake(ssl:sslsocket(), opts(), timeout()) -> {ok, ssl:sslsocket()}.
handshake(CSocket, Opts, Timeout) ->
case ssl:ssl_accept(CSocket, Opts, Timeout) of
ok ->
ok;
{ok, CSocket};
%% Garbage was most likely sent to the socket, don't error out.
{error, {tls_alert, _}} ->
ok = close(CSocket),
Expand Down
8 changes: 7 additions & 1 deletion src/ranch_tcp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
-export([disallowed_listen_options/0]).
-export([accept/2]).
-export([accept_ack/2]).
-export([handshake/3]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
Expand Down Expand Up @@ -100,9 +101,14 @@ accept(LSocket, Timeout) ->
gen_tcp:accept(LSocket, Timeout).

-spec accept_ack(inet:socket(), timeout()) -> ok.
accept_ack(_, _) ->
accept_ack(CSocket, Timeout) ->
{ok, _} = handshake(CSocket, [], Timeout),
ok.

-spec handshake(inet:socket(), opts(), timeout()) -> {ok, inet:socket()}.
handshake(CSocket, _, _) ->
{ok, CSocket}.

%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
Expand Down
4 changes: 3 additions & 1 deletion src/ranch_transport.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
-export([sendfile/6]).

-type socket() :: any().
-export_type([socket/0]).

-type opts() :: any().
-type stats() :: any().
-type sendfile_opts() :: [{chunk_size, non_neg_integer()}].
Expand All @@ -28,7 +30,7 @@
-callback listen(opts()) -> {ok, socket()} | {error, atom()}.
-callback accept(socket(), timeout())
-> {ok, socket()} | {error, closed | timeout | atom()}.
-callback accept_ack(socket(), timeout()) -> ok.
-callback handshake(socket(), opts(), timeout()) -> {ok, socket()}.
-callback connect(string(), inet:port_number(), opts())
-> {ok, socket()} | {error, atom()}.
-callback connect(string(), inet:port_number(), opts(), timeout())
Expand Down
22 changes: 22 additions & 0 deletions test/accept_ack_protocol.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-module(accept_ack_protocol).
-behaviour(ranch_protocol).

-export([start_link/4]).
-export([init/4]).

start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.

init(Ref, Socket, Transport, _Opts = []) ->
ok = ranch:accept_ack(Ref),
loop(Socket, Transport).

loop(Socket, Transport) ->
case Transport:recv(Socket, 0, 5000) of
{ok, Data} ->
Transport:send(Socket, Data),
loop(Socket, Transport);
_ ->
ok = Transport:close(Socket)
end.
Loading

0 comments on commit 5ada450

Please sign in to comment.