Skip to content

Commit

Permalink
Import the acceptor code from Cowboy
Browse files Browse the repository at this point in the history
Modules were renamed. The 'cowboy_' prefix became 'ranch_'.
At the same time, ranch_ssl_transport became ranch_ssl,
and ranch_tcp_transport became ranch_tcp, because appending
'_transport' felt a bit redundant considering SSL and TCP
clearly are transports.

One test has been added to make sure everything is working.
  • Loading branch information
Loïc Hoguin committed Apr 14, 2012
1 parent f971ec1 commit 4156fa3
Show file tree
Hide file tree
Showing 23 changed files with 1,238 additions and 2 deletions.
10 changes: 10 additions & 0 deletions .gitignore
@@ -0,0 +1,10 @@
.ranch.plt
.eunit
deps
doc/*.css
doc/*.html
doc/*.png
doc/edoc-info
ebin
logs
test/*.beam
11 changes: 11 additions & 0 deletions AUTHORS
@@ -0,0 +1,11 @@
Ranch is available thanks to the work of:

Loïc Hoguin
Ali Sabil
Andrew Thompson
DeadZen
Hunter Morris
Jesper Louis Andersen
Paul Oliver
Roberto Ostinelli
Steven Gravell
13 changes: 13 additions & 0 deletions LICENSE
@@ -0,0 +1,13 @@
Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
50 changes: 50 additions & 0 deletions Makefile
@@ -0,0 +1,50 @@
# See LICENSE for licensing information.

PROJECT = ranch

DIALYZER = dialyzer
REBAR = rebar

all: app

# Application.

deps:
@$(REBAR) get-deps

app: deps
@$(REBAR) compile

clean:
@$(REBAR) clean
rm -f test/*.beam
rm -f erl_crash.dump

docs: clean-docs
@$(REBAR) doc skip_deps=true

clean-docs:
rm -f doc/*.css
rm -f doc/*.html
rm -f doc/*.png
rm -f doc/edoc-info

# Tests.

tests: clean app eunit ct

eunit:
@$(REBAR) -C rebar.tests.config eunit skip_deps=true

ct:
@$(REBAR) -C rebar.tests.config ct skip_deps=true

# Dialyzer.

build-plt:
@$(DIALYZER) --build_plt --output_plt .$(PROJECT).plt \
--apps kernel stdlib sasl tools inets crypto public_key ssl

dialyze:
@$(DIALYZER) --src src --plt .$(PROJECT).plt \
-Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
90 changes: 88 additions & 2 deletions README.md
@@ -1,5 +1,91 @@
Ranch
=====

Ranch is a socket acceptor pool, able to accept connections for any kind
of TCP protocol.
Ranch is a socket acceptor pool for TCP protocols.

Goals
-----

Ranch aims to provide everything you need to accept TCP connections with
a **small** code base and **low latency** while being easy to use directly
as an application or to **embed** into your own.

Ranch provides a **modular** design, letting you choose which transport
and protocol are going to be used for a particular listener. Listeners
accept and manage connections on one port, and include facilities to
limit the number of **concurrent** connections. Connections are sorted
into **pools**, each pool having a different configurable limit.

Ranch also allows you to **upgrade** the acceptor pool without having
to close any of the currently opened sockets.

The project is currently in early development. Comments and suggestions are
more than welcome. To contribute, either open bug reports, or fork the project
and send us pull requests with new or improved functionality. You should
discuss your plans with us before doing any serious work, though, to avoid
duplicating efforts.

Quick start
-----------

* Add Ranch as a rebar or agner dependency to your application.
* Start Ranch and add one or more listeners.
* Write protocol handlers for your application.

Getting Started
---------------

Ranch accepts connections received on a given port and using a given
transport, like TCP or SSL, and forward them to a given protocol
handler. Acceptors and protocol handler processes are of course
supervised automatically.

Ranch does nothing by default. You need to explicitly request Ranch
to listen on a port with your chosen transport and protocol handlers.
To do so, you must start a listener.

A listener is a special kind of supervisor that manages both the
acceptor pool and the protocol processes. It is named and can thus be
started and stopped at will.

An acceptor pool is a pool of processes whose only role is to accept
new connections. It's good practice to have many of these processes
as they are very cheap and allow much quicker response when you get
many connections. Of course, as with everything else, you should
**benchmark** before you decide what's best for you.

Ranch includes both TCP and SSL transport handlers, abstracted through
a single common interface.

You can start and stop listeners by calling `ranch:start_listener/6` and
`ranch:stop_listener/1` respectively.

The following example demonstrates the startup of a very simple listener.

``` erlang
application:start(ranch),
%% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
ranch:start_listener(my_echo_listener, 100,
ranch_tcp, [{port, 1234}],
my_echo_protocol, [{log, "echo.log"}]
).
```

Writing a protocol handler
--------------------------

The only exported function a protocol handler needs is the start_link/4
function, with arguments ListenerPid, Socket, Transport and Opts. ListenerPid
is the pid to the listener's gen_server, managing the connections. Socket is of
course the client socket; Transport is the module name of the chosen transport
handler and Opts is protocol options defined when starting the listener.

After initializing your protocol, it is recommended to call the
function ranch:accept_ack/1 with the ListenerPid as argument,
as it will ensure Ranch has been able to fully initialize the socket.
Anything you do past this point is up to you!

If you need to change some socket options, like enabling raw mode for example,
you can call the <em>Transport:setopts/2</em> function. It is the protocol's
responsability to manage the socket usage, there should be no need for an user
to specify that kind of options while starting a listener.
45 changes: 45 additions & 0 deletions ROADMAP.md
@@ -0,0 +1,45 @@
ROADMAP
=======

This document explains in as much details as possible the
list of planned changes and work to be done on the Ranch
project. It is non-exhaustive and subject to change. Items
are not ordered.

* Write examples.

Ideally we would have one complete example per folder.

Examples should be commented. They may or may not be
used for writing the user guides.

* Write user guides.

We currently have good API documentation, but no step
by step user guides.

* Continuous performance testing.

Initially dubbed the Horse project, Ranch could benefit
from a continuous performance testing tool that would
allow us to easily compare the impact of the changes we
are introducing, similar to what the Phoronix test suite
allows.

* Transport upgrades.

Some protocols allow an upgrade from TCP to SSL without
closing the connection. This is currently not possible
through the Ranch API.

* Resizing the acceptor pool.

We should be able to add more acceptors to a pool but also
to remove some of them as needed.

* Add Transport:secure/0.

Currently Cowboy checks if a connection is secure by
checking if its name is 'ssl'. This isn't a very modular
solution, adding an API function that returns whether
a connection is secure would fix that issue.
1 change: 1 addition & 0 deletions cover.spec
@@ -0,0 +1 @@
{incl_app, ranch, details}.
4 changes: 4 additions & 0 deletions doc/overview.edoc
@@ -0,0 +1,4 @@
@author Lo�c Hoguin <essen@ninenines.eu>
@copyright 2011-2012 Lo�c Hoguin
@version HEAD
@title Socket acceptor pool for TCP protocols.
6 changes: 6 additions & 0 deletions rebar.config
@@ -0,0 +1,6 @@
{erl_opts, [
%% bin_opt_info,
warn_missing_spec,
warnings_as_errors,
warn_export_all
]}.
4 changes: 4 additions & 0 deletions rebar.tests.config
@@ -0,0 +1,4 @@
{cover_enabled, true}.
{deps, []}.
{eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}.
{erl_opts, []}.
26 changes: 26 additions & 0 deletions src/ranch.app.src
@@ -0,0 +1,26 @@
%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

{application, ranch, [
{description, "Socket acceptor pool for TCP protocols."},
{vsn, "0.1.0"},
{modules, []},
{registered, [ranch_sup]},
{applications, [
kernel,
stdlib
]},
{mod, {ranch_app, []}},
{env, []}
]}.
114 changes: 114 additions & 0 deletions src/ranch.erl
@@ -0,0 +1,114 @@
%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

%% @doc Ranch API to start and stop listeners.
-module(ranch).

-export([start_listener/6, stop_listener/1, child_spec/6, accept_ack/1,
get_protocol_options/1, set_protocol_options/2]).

%% @doc Start a listener for the given transport and protocol.
%%
%% A listener is effectively a pool of <em>NbAcceptors</em> acceptors.
%% Acceptors accept connections on the given <em>Transport</em> and forward
%% connections to the given <em>Protocol</em> handler. Both transport and
%% protocol modules can be given options through the <em>TransOpts</em> and
%% the <em>ProtoOpts</em> arguments. Available options are documented in the
%% <em>listen</em> transport function and in the protocol module of your choice.
%%
%% All acceptor and connection processes are supervised by the listener.
%%
%% It is recommended to set a large enough number of acceptors to improve
%% performance. The exact number depends of course on your hardware, on the
%% protocol used and on the number of expected simultaneous connections.
%%
%% The <em>Transport</em> option <em>max_connections</em> allows you to define
%% the maximum number of simultaneous connections for this listener. It defaults
%% to 1024. See <em>ranch_listener</em> for more details on limiting the number
%% of connections.
%%
%% <em>Ref</em> can be used to stop the listener later on.
-spec start_listener(any(), non_neg_integer(), module(), any(), module(), any())
-> {ok, pid()}.
start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
when is_integer(NbAcceptors) andalso is_atom(Transport)
andalso is_atom(Protocol) ->
supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
Transport, TransOpts, Protocol, ProtoOpts)).

%% @doc Stop a listener identified by <em>Ref</em>.
%%
%% Note that stopping the listener will close all currently running
%% connections abruptly.
-spec stop_listener(any()) -> ok | {error, not_found}.
stop_listener(Ref) ->
case supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}) of
ok ->
supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref});
{error, Reason} ->
{error, Reason}
end.

%% @doc Return a child spec suitable for embedding.
%%
%% When you want to embed Ranch in another application, you can use this
%% function to create a <em>ChildSpec</em> suitable for use in a supervisor.
%% The parameters are the same as in <em>start_listener/6</em> but rather
%% than hooking the listener to the Ranch internal supervisor, it just returns
%% the spec.
-spec child_spec(any(), non_neg_integer(), module(), any(), module(), any())
-> supervisor:child_spec().
child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
when is_integer(NbAcceptors) andalso is_atom(Transport)
andalso is_atom(Protocol) ->
{{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
]}, permanent, 5000, supervisor, [ranch_listener_sup]}.

%% @doc Acknowledge the accepted connection.
%%
%% Effectively used to make sure the socket control has been given to
%% the protocol process before starting to use it.
-spec accept_ack(pid()) -> ok.
accept_ack(ListenerPid) ->
receive {shoot, ListenerPid} -> ok end.

%% @doc Return the current protocol options for the given listener.
-spec get_protocol_options(any()) -> any().
get_protocol_options(Ref) ->
ListenerPid = ref_to_listener_pid(Ref),
{ok, ProtoOpts} = ranch_listener:get_protocol_options(ListenerPid),
ProtoOpts.

%% @doc Upgrade the protocol options for the given listener.
%%
%% The upgrade takes place at the acceptor level, meaning that only the
%% newly accepted connections receive the new protocol options. This has
%% no effect on the currently opened connections.
-spec set_protocol_options(any(), any()) -> ok.
set_protocol_options(Ref, ProtoOpts) ->
ListenerPid = ref_to_listener_pid(Ref),
ok = ranch_listener:set_protocol_options(ListenerPid, ProtoOpts).

%% Internal.

-spec ref_to_listener_pid(any()) -> pid().
ref_to_listener_pid(Ref) ->
Children = supervisor:which_children(ranch_sup),
{_, ListenerSupPid, _, _} = lists:keyfind(
{ranch_listener_sup, Ref}, 1, Children),
ListenerSupChildren = supervisor:which_children(ListenerSupPid),
{_, ListenerPid, _, _} = lists:keyfind(
ranch_listener, 1, ListenerSupChildren),
ListenerPid.

0 comments on commit 4156fa3

Please sign in to comment.