Import ranch.

Ranch is the listener used by cowboy to handle HTTP connections.

URL: https://github.com/ninenines/ranch
main
absc 2024-07-23 22:07:38 +02:00
parent 9fdf706bcb
commit 4aa09346d7
107 changed files with 12791 additions and 0 deletions

13
ranch/LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) 2011-2021, 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.

7
ranch/Makefile Normal file
View File

@ -0,0 +1,7 @@
.PHONY: all clean
all:
${MAKE} -C src
clean:
${MAKE} -C src clean

37
ranch/README.asciidoc Normal file
View File

@ -0,0 +1,37 @@
= Ranch
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.
== Online documentation
* https://ninenines.eu/docs/en/ranch/2.1/guide[User guide]
* https://ninenines.eu/docs/en/ranch/2.1/manual[Function reference]
== Offline documentation
* While still online, run `make docs`
* User guide available in `doc/` in PDF and HTML formats
* Function reference man pages available in `doc/man3/` and `doc/man7/`
* Run `make install-docs` to install man pages on your system
* Full documentation in Asciidoc available in `doc/src/`
* Examples available in `examples/`
== Getting help
* https://github.com/ninenines/ranch/issues[Issues tracker]
* https://ninenines.eu/services[Commercial Support]

View File

@ -0,0 +1,40 @@
// a2x: --dblatex-opts "-P latex.output.revhistory=0 -P doc.publisher.show=0 -P index.numbered=0"
// a2x: -d book --attribute tabsize=4
= Ranch User Guide
= Interface
include::introduction.asciidoc[Introduction]
include::listeners.asciidoc[Listeners]
include::transports.asciidoc[Transports]
include::protocols.asciidoc[Protocols]
include::embedded.asciidoc[Embedded mode]
= How to
include::parsers.asciidoc[Writing parsers]
include::ssl_auth.asciidoc[SSL client authentication]
include::connection_draining.asciidoc[Connection draining]
= Advanced
include::internals.asciidoc[Internals]
= Additional information
include::migrating_from_2.0.asciidoc[Migrating from Ranch 2.0 to 2.1]
include::migrating_from_1.7.asciidoc[Migrating from Ranch 1.7 to 2.0]
include::migrating_from_1.6.asciidoc[Migrating from Ranch 1.6 to 1.7]
include::migrating_from_1.5.asciidoc[Migrating from Ranch 1.5 to 1.6]
include::migrating_from_1.x.asciidoc[Migrating from Ranch 1.x]

View File

@ -0,0 +1,98 @@
== Connection draining
Stopping a Ranch listener via `ranch:stop_listener/1` will invariably kill
all connection processes the listener hosts. However, you may want to stop
a listener in a graceful fashion, ie by not accepting any new connections,
but allowing the existing connection processes to exit by themselves instead
of being killed.
For this purpose, you should first suspend the listener you wish to
stop gracefully, and then wait for its connection count to drop to
zero.
.Draining a single listener
[source,erlang]
----
ok = ranch:suspend_listener(Ref),
ok = ranch:wait_for_connections(Ref, '==', 0),
ok = ranch:stop_listener(Ref).
----
If you want to drain more than just one listener, it may be important to first suspend
them all before beginning to wait for their connection counts to reach zero. Otherwise,
the not yet suspended listeners will still be accepting connections while you wait for
the suspended ones to be drained.
.Draining multiple listeners
[source,erlang]
----
lists:foreach(
fun (Ref) ->
ok = ranch:suspend_listener(Ref)
end,
Refs
),
lists:foreach(
fun (Ref) ->
ok = ranch:wait_for_connections(Ref, '==', 0),
ok = ranch:stop_listener(Ref)
end,
Refs
).
----
If you have long-running connection processes hosted by the listener you want to stop
gracefully, draining may take a long time, possibly forever. If you just want to give
the connection processes a chance to finish, but are not willing to wait for infinity,
the waiting part could be handled in a separate process.
.Draining a listener with a timeout
[source,erlang]
----
ok = ranch:suspend_listener(Ref),
{DrainPid, DrainRef} = spawn_monitor(
fun () ->
ok = ranch:wait_for_connections(Ref, '==', 0)
end
),
receive
{'DOWN', DrainRef, process, DrainPid, _} ->
ok
after DrainTimeout ->
exit(DrainPid, kill),
ok
end,
ok = ranch:stop_listener(Ref).
----
To drain listeners automatically as part of your application shutdown routine,
use the `prep_stop/1` function of your application module.
.Draining listeners automatically on application shutdown
[source,erlang]
----
-module(my_app).
-behavior(application).
-export([start/2]).
-export([prep_stop/1]).
-export([stop/1]).
start(_StartType, _StartArgs) ->
{ok, _} = ranch:start_listener(my_listener, ranch_tcp, #{}, my_protocol, []),
my_app_sup:start_link().
prep_stop(State) ->
ok = ranch:suspend_listener(my_listener),
ok = ranch:wait_for_connections(my_listener, '==', 0),
ok = ranch:stop_listener(my_listener),
State.
stop(_State) ->
ok.
----

View File

@ -0,0 +1,47 @@
== Embedded mode
Embedded mode allows you to insert Ranch listeners directly
in your supervision tree. This allows for greater fault tolerance
control by permitting the shutdown of a listener due to the
failure of another part of the application and vice versa.
However, just as for non-embedded listeners that were started via
`ranch:start_listener/5`, it is required that the `ranch` application
is running before you can start embedded listeners. Furthermore,
this also means that embedded listeners will restart when `ranch_sup` fails.
WARNING: By using embedded mode, it is possible to start a listener with the same name
as an already existing listener. This will corrupt the information Ranch
keeps for that listener, so you should take care to ensure unique listener
names yourself. A good way to achieve this is by combining the embedded
listener's name with `?MODULE`, or the name of the application it is used
in.
=== Embedding
To embed Ranch in your application you can simply add the child specs
to your supervision tree. This can all be done in the `init/1` function
of one of your application supervisors.
Ranch has a convenience function for getting the listeners child specs
called `ranch:child_spec/5`, that works like `ranch:start_listener/5`,
except that it doesn't start anything, it only returns child specs.
The following example adds one listener to another application's
supervision tree.
.Embed Ranch directly in your supervision tree
[source,erlang]
----
init([]) ->
ListenerSpec = ranch:child_spec({?MODULE, echo},
ranch_tcp, #{socket_opts => [{port, 5555}]},
echo_protocol, []
),
{ok, {#{}, [ListenerSpec]}}.
----
Embedded listeners cannot be stopped via `ranch:stop_listener/1`. Instead,
are to be stopped as part of the shutdown of your application's supervison
tree.

View File

@ -0,0 +1,99 @@
== Internals
This chapter may not apply to embedded Ranch as embedding allows you
to use an architecture specific to your application, which may or may
not be compatible with the description of the Ranch application.
Note that for everything related to efficiency and performance,
you should perform the benchmarks yourself to get the numbers that
matter to you. Generic benchmarks found on the web may or may not
be of use to you, you can never know until you benchmark your own
system.
A third party dive into the internals of Ranch is available should
you be interested: https://baozi.technology/ranch-under-the-hood/[Ranch: what's under the hood?]
We make no claims with regard to its freshness or accuracy but this
is a nice document to read along this section.
=== Architecture
Ranch is an OTP application.
Like all OTP applications, Ranch has a top supervisor. It is responsible
for supervising the `ranch_server` process and all the listeners that
will be started.
The `ranch_server` gen_server is a central process keeping track of the
listeners and their acceptors. It does so through the use of a public ets
table called `ranch_server`. The table is owned by the top supervisor
to improve fault tolerance. This way if the `ranch_server` gen_server
fails, it doesn't lose any information and the restarted process can
continue as if nothing happened.
Ranch uses a custom supervisor for managing connections. This supervisor
keeps track of the number of connections and handles connection limits
directly. While it is heavily optimized to perform the task of creating
connection processes for accepted connections, it is still following the
OTP principles and the usual `sys` and `supervisor` calls will work on
it as expected.
Listeners are grouped into the `ranch_listener_sup` supervisor and
consist of three kinds of processes: the listener gen_server, the
acceptor processes and the connection processes, both grouped under
their own supervisor. All of these processes are registered to the
`ranch_server` gen_server with varying amount of information.
All socket operations, including listening for connections, go through
transport handlers. Accepted connections are given to the protocol handler.
Transport handlers are simple callback modules for performing operations on
sockets. Protocol handlers start a new process, which receives socket
ownership, with no requirements on how the code should be written inside
that new process.
=== Number of acceptors
The second argument to `ranch:start_listener/5` is the number of
processes that will be accepting connections. Care should be taken
when choosing this number.
First of all, it should not be confused with the maximum number
of connections. Acceptor processes are only used for accepting and
have nothing else in common with connection processes. Therefore
there is nothing to be gained from setting this number too high,
in fact it can slow everything else down.
Second, this number should be high enough to allow Ranch to accept
connections concurrently. But the number of cores available doesn't
seem to be the only factor for choosing this number, as we can
observe faster accepts if we have more acceptors than cores. It
might be entirely dependent on the protocol, however.
Our observations suggest that using 100 acceptors on modern hardware
is a good solution, as it's big enough to always have acceptors ready
and it's low enough that it doesn't have a negative impact on the
system's performances.
=== Platform-specific TCP features
Some socket options are platform-specific and not supported by `inet`.
They can be of interest because they generally are related to
optimizations provided by the underlying OS. They can still be enabled
thanks to the `raw` option, for which we will see an example.
One of these features is `TCP_DEFER_ACCEPT` on Linux. It is a simplified
accept mechanism which will wait for application data to come in before
handing out the connection to the Erlang process.
This is especially useful if you expect many connections to be mostly
idle, perhaps part of a connection pool. They can be handled by the
kernel directly until they send any real data, instead of allocating
resources to idle connections.
To enable this mechanism, the following option can be used.
.Using raw transport options
[source,erlang]
{raw, 6, 9, << 30:32/native >>}
It means go on layer 6, turn on option 9 with the given integer parameter.

View File

@ -0,0 +1,25 @@
== Introduction
Ranch is a socket acceptor pool for TCP protocols.
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.
=== Prerequisites
It is assumed the developer already knows Erlang and has some experience
with socket programming and TCP protocols.
=== Supported platforms
Ranch is tested and supported on Linux, FreeBSD, macOS and Windows.
Ranch is developed for Erlang/OTP 22+.
Ranch may be compiled on earlier Erlang versions with small source code
modifications but there is no guarantee that it will work as expected.
=== Versioning
Ranch uses http://semver.org/[Semantic Versioning 2.0.0]

View File

@ -0,0 +1,479 @@
== Listeners
A listener is a set of processes whose role is to listen on a port
for new connections. It manages a pool of acceptor processes, each
of them indefinitely accepting connections. When it does, it starts
a new process executing the protocol handler code. All the socket
programming is abstracted through the use of transport handlers.
The listener takes care of supervising all the acceptor and connection
processes, allowing developers to focus on building their application.
=== Starting a listener
Ranch does nothing by default. It is up to the application developer
to request that Ranch listens for connections.
A listener can be started and stopped at will.
When starting a listener, a number of different settings are required:
* A name to identify it locally and be able to interact with it.
* A transport handler and its associated options.
* A protocol handler and its associated options.
Ranch includes both TCP and SSL transport handlers, respectively
`ranch_tcp` and `ranch_ssl`.
A listener can be started by calling the `ranch:start_listener/5`
function. Before doing so however, you must ensure that the `ranch`
application is started.
.Starting the Ranch application
[source,erlang]
ok = application:start(ranch).
You are then ready to start a listener. Let's call it `tcp_echo`. It will
have a pool of 100 acceptors, use a TCP transport and forward connections
to the `echo_protocol` handler.
.Starting a listener for TCP connections on port 5555
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}]},
echo_protocol, []
).
You can try this out by compiling and running the `tcp_echo` example in the
examples directory. To do so, open a shell in the 'examples/tcp_echo/'
directory and run the following command:
.Building and starting a Ranch example
[source,bash]
$ make run
You can then connect to it using telnet and see the echo server reply
everything you send to it. Then when you're done testing, you can use
the `Ctrl+]` key to escape to the telnet command line and type
`quit` to exit.
.Connecting to the example listener with telnet
[source,bash]
----
$ telnet localhost 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
Hello!
It works!
It works!
^]
telnet> quit
Connection closed.
----
=== Stopping a listener
All you need to stop a Ranch listener is to call the
`ranch:stop_listener/1` function with the listener's name
as argument. In the previous section we started the listener
named `tcp_echo`. We can now stop it.
.Stopping a listener
[source,erlang]
ranch:stop_listener(tcp_echo).
=== Suspending and resuming a listener
Listeners can be suspended and resumed by calling
`ranch:suspend_listener/1` and `ranch:resume_listener/1`,
respectively, with the name of the listener as argument.
Suspending a listener will cause it to stop listening and not accept
new connections, but existing connection processes will not be stopped.
.Suspending a listener
[source,erlang]
ranch:suspend_listener(tcp_echo).
Resuming a listener will cause it to start listening and accept new
connections again.
It is worth mentioning, however, that if the listener is configured
to listen on a random port, it will listen on a different port than
before it was suspended.
.Resuming a listener
[source,erlang]
ranch:resume_listener(tcp_echo).
Whether a listener is currently running or suspended can be queried
by calling `ranch:get_status/1` with the listener name as argument.
=== Default transport options
By default the socket will be set to return `binary` data, with the
options `{active, false}`, `{packet, raw}`, `{reuseaddr, true}` set.
These values can't be overridden when starting the listener, but
they can be overridden using `Transport:setopts/2` in the protocol.
It will also set `{backlog, 1024}` and `{nodelay, true}`, which
can be overridden at listener startup.
=== Listening on a random port
You do not have to specify a specific port to listen on. If you give
the port number 0, or if you omit the port number entirely, Ranch will
start listening on a random port.
You can retrieve this port number by calling `ranch:get_port/1`. The
argument is the name of the listener you gave in `ranch:start_listener/5`.
.Starting a listener for TCP connections on a random port
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 0}]},
echo_protocol, []
).
Port = ranch:get_port(tcp_echo).
=== Listening on privileged ports
Some systems limit access to ports below 1024 for security reasons.
This can easily be identified by an `{error, eacces}` error when trying
to open a listening socket on such a port.
The methods for listening on privileged ports vary between systems,
please refer to your system's documentation for more information.
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.
=== Listening on a UNIX Domain socket
On UNIX systems, it is also possible to use Ranch to listen on a UNIX
domain socket by specifying `{local, SocketFile}` for the `ip` socket
option. In this case, the port must be set to 0 or omitted. The given
file must not exist: Ranch must be able to create it.
.Starting a listener for TCP connections on a UNIX Domain socket
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [
{ip, {local, "/tmp/ranch_echo.sock"}},
{port, 0}
]}, echo_protocol, []
).
=== Performing additional setup steps on a listening socket
If it is necessary to perform additional setup steps on the listening
socket, it is possible to specify a function with the transport option
`post_listen_callback`. This function will be called after the listening
socket has been created but before accepting connections on it,
with the socket as the single argument.
The function must return either the atom `ok`, after which the listener
will start accepting connections on the socket, or a tuple
`{error, Reason}` which will cause the listener to fail starting with
`Reason`.
.Setting permissions on a UNIX Domain socket file
[source,erlang]
----
PostListenCb = fun (Sock) ->
case ranch_tcp:sockname(Sock) of
{ok, {local, SockFile}} ->
file:change_mode(SockFile, 8#777);
{ok, _} ->
ok;
Error = {error, _} ->
Error
end
end,
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{
socket_opts => [
{ip, {local, "/tmp/ranch_echo.sock"}},
{port, 0}],
post_listen_callback => PostListenCb},
echo_protocol, []
).
----
=== Accepting connections on an existing socket
If you want to accept connections on an existing socket, you can write
a custom `ranch_transport` implementation that fetches or otherwise
acquires a listening socket in the `listen/1` callback and returns it
in the form of `{ok, ListenSocket}`.
The custom `listen/1` function must ensure that the listener process
(usually the process calling it) is also made the controlling process
of the socket it returns. Failing to do so will result in stop/start
and suspend/resume not working properly for that listener.
=== Limiting the number of concurrent connections
The `max_connections` transport option allows you to limit the number
of concurrent connections per connection supervisor (see below).
It defaults to 1024. Its purpose is to prevent your system from being
overloaded and ensuring all the connections are handled optimally.
.Customizing the maximum number of concurrent connections
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}], max_connections => 100},
echo_protocol, []
).
You can disable this limit by setting its value to the atom `infinity`.
.Disabling the limit for the number of connections
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}], max_connections => infinity},
echo_protocol, []
).
The maximum number of connections is a soft limit. In practice, it
can reach `max_connections` + the number of acceptors.
When the maximum number of connections is reached, Ranch will stop
accepting connections. This will not result in further connections
being rejected, as the kernel option allows queueing incoming
connections. The size of this queue is determined by the `backlog`
option and defaults to 1024. Ranch does not know about the number
of connections that are in the backlog.
You may not always want connections to be counted when checking for
`max_connections`. For example you might have a protocol where both
short-lived and long-lived connections are possible. If the long-lived
connections are mostly waiting for messages, then they don't consume
much resources and can safely be removed from the count.
To remove the connection from the count, you must call the
`ranch:remove_connection/1` from within the connection process,
with the name of the listener as the only argument.
.Removing a connection from the count of connections
[source,erlang]
ranch:remove_connection(Ref).
As seen in the chapter covering protocols, this reference is received
as the first argument of the protocol's `start_link/3` callback.
You can modify the `max_connections` value on a running listener by
using the `ranch:set_max_connections/2` function, with the name of the
listener as first argument and the new value as the second.
.Upgrading the maximum number of connections
[source,erlang]
ranch:set_max_connections(tcp_echo, MaxConns).
The change will occur immediately.
=== Customizing the number of acceptor processes
By default Ranch will use 10 acceptor processes. Their role is
to accept connections and spawn a connection process for every
new connection.
This number can be tweaked to improve performance. A good
number is typically between 10 or 100 acceptors. You must
measure to find the best value for your application.
.Specifying a custom number of acceptor processes
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}], num_acceptors => 42},
echo_protocol, []
).
=== Customizing the number of connection supervisors
By default Ranch will use one connection supervisor for each
acceptor process (but not vice versa). Their task is to
supervise the connection processes started by an acceptor.
The number of connection supervisors can be tweaked.
Note that the association between the individual acceptors and
connection supervisors is fixed, meaning that acceptors will
always use the same connection supervisor to start connection
processes.
.Specifying a custom number of connection supervisors
[source,erlang]
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}], num_conns_sups => 42},
echo_protocol, []
).
=== Setting connection count alarms
The `alarms` transport option allows you to configure alarms
which will be triggered when the number of connections tracked
by one connection supervisor reaches or exceeds the defined treshold.
The `alarms` transport option takes a map with alarm names as keys and alarm
options as values.
Any term is allowed as an alarm name.
Alarm options include the alarm type and a treshold that, when reached,
triggers the given callback. A cooldown prevents the alarm from being
triggered too often.
.Log warnings when the number of connections exceeds 100
[source,erlang]
----
Alarms = #{
my_alarm => #{
type => num_connections,
treshold => 100,
callback => fun(Ref, Name, ConnSup, ConnPids]) ->
logger:warning("Warning (~s): "
"Supervisor ~s of listener ~s "
"has ~b connections",
[Name, Ref, ConnSup, length(ConnPids)])
end
}
},
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{alarms => Alarms, socket_opts => [{port, 5555}]},
echo_protocol, []
).
----
In the example code, an alarm named `my_alarm` is defined, which will
call the given function when the number of connections tracked
by the connection supervisor reaches or exceeds 100. When the number of
connections is still (or again) above 100 after the default cooldown
period of 5 seconds, the alarm will trigger again.
=== When running out of file descriptors
Operating systems have limits on the number of sockets
which can be opened by applications. When this maximum is
reached the listener can no longer accept new connections. The
accept rate of the listener will be automatically reduced, and a
warning message will be logged.
----
=ERROR REPORT==== 13-Jan-2016::12:24:38 ===
Ranch acceptor reducing accept rate: out of file descriptors
----
If you notice messages like this you should increase the number
of file-descriptors which can be opened by your application. How
this should be done is operating-system dependent. Please consult
the documentation of your operating system.
=== Using a supervisor for connection processes
Ranch allows you to define the type of process that will be used
for the connection processes. By default it expects a `worker`.
When the `connection_type` configuration value is set to `supervisor`,
Ranch will consider that the connection process it manages is a
supervisor and will reflect that in its supervision tree.
Connection processes of type `supervisor` can either handle the
socket directly or through one of their children. In the latter
case the start function for the connection process must return
two pids: the pid of the supervisor you created (that will be
supervised) and the pid of the protocol handling process (that
will receive the socket).
Instead of returning `{ok, ConnPid}`, simply return
`{ok, SupPid, ConnPid}`.
It is very important that the connection process be created
under the supervisor process so that everything works as intended.
If not, you will most likely experience issues when the supervised
process is stopped.
=== Upgrading
Ranch allows you to upgrade the protocol options. This takes effect
immediately and for all subsequent connections.
To upgrade the protocol options, call `ranch:set_protocol_options/2`
with the name of the listener as first argument and the new options
as the second.
.Upgrading the protocol options
[source,erlang]
ranch:set_protocol_options(tcp_echo, NewOpts).
All future connections will use the new options.
You can also retrieve the current options similarly by
calling `ranch:get_protocol_options/1`.
.Retrieving the current protocol options
[source,erlang]
Opts = ranch:get_protocol_options(tcp_echo).
=== Changing transport options
Ranch allows you to change the transport options of a listener with
the `ranch:set_transport_options/2` function, for example to change the
number of acceptors or to make it listen on a different port.
.Changing the transport options
[source,erlang]
ranch:set_transport_options(tcp_echo, NewOpts).
You can retrieve the current transport options by calling
`ranch:get_transport_options/1`.
.Retrieving the current transport options
[source,erlang]
Opts = ranch:get_transport_options(tcp_echo).
=== Obtaining information about listeners
Ranch provides two functions for retrieving information about the
listeners, for reporting and diagnostic purposes.
The `ranch:info/0` function will return detailed information
about all listeners.
.Retrieving detailed information
[source,erlang]
ranch:info().
The `ranch:procs/2` function will return all acceptor or listener
processes for a given listener.
.Get all acceptor processes
[source,erlang]
ranch:procs(tcp_echo, acceptors).
.Get all connection processes
[source,erlang]
ranch:procs(tcp_echo, connections).

View File

@ -0,0 +1,76 @@
[appendix]
== Migrating from Ranch 1.5 to 1.6
Ranch 1.6 added the ability to suspend and resume listeners.
It also deprecates a number of features and add interfaces
that will be used in Ranch 2.0.
Ranch 1.6 is compatible with Erlang/OTP 18.0 onward. Support
for older releases has been removed.
=== Features added
* Listeners can now be suspended/resumed without stopping existing
connection processes. This effectively closes the listening socket
and stops the acceptor processes.
* Transport options can now be updated for suspended listeners.
* The `Socket` argument given when the protocol starts has been
deprecated. In Ranch 2.0 the socket will be obtainable only
by calling `ranch:handshake/1,2`.
* Ranch-specific transport options and socket options are now
better separated. When passing Ranch-specific transport options,
Ranch now expects to receive a map, in which case socket
options are passed in the `socket_opts` value. When there
are only socket options they can be passed to Ranch directly
as a convenience.
* Any future transport option will only be added to the map
type. This includes transport options added in this release.
* The transport option `ack_timeout` was renamed to `handshake_timeout`
in the map type.
* The `cacerts` socket option is now silenced in error logs
just like the `certs` and `key` options.
* The manual has been heavily updated and now features one
manual page per function and module, complete with a per-function
changelog, examples and more.
=== Experimental features added
* It is now possible to configure the restart intensity for
`ranch_sup` using the OTP application environment. This
feature will remain undocumented unless there is popular
demand for it.
* Add the transport option `logger` that allows configuring
which logger module will be used. The logger module must
follow the interface of the new `logger` module in Erlang/OTP 21,
or be set to `error_logger` to keep the old behavior.
=== Changed behaviors
* Transport modules must now implement `Transport:handshake/2,3`
which deprecates and will replace `Transport:accept_ack/1` in
Ranch 2.0. It returns a new socket and can therefore be used
for implementing TLS upgrade mechanisms.
=== New functions
* The functions `ranch:suspend_listener/1` and `ranch:resume_listener/1`
were added. In addition the function `ranch:get_state/1` can be used
to obtain the running state of a listener.
* A function `ranch:wait_for_connections/3` was added.
* A function `ranch:handshake/1,2` was added to replace the
function `ranch:accept_ack/1`.
=== Bugs fixed
* The specs for the function `Transport:sendfile/2,4,5` have been
corrected. The type used for the filename was too restricting.

View File

@ -0,0 +1,46 @@
[appendix]
== Migrating from Ranch 1.6 to 1.7
Ranch 1.7 adds built-in support for the PROXY protocol.
The PROXY protocol is a simple and efficient way for proxies
to transmit information about the client.
While a third-party library already existed, it was not
entirely compatible with the Ranch interface, in particular
when socket active mode was involved. This new implementation
fixes that and supports the full protocol with as little
overhead as possible compared to normal operations: just one
extra function call.
Ranch 1.7 is compatible with Erlang/OTP 19.0 onward. Support
for Erlang/OTP 18 has been removed.
=== Features added
* Full support for the PROXY protocol was added.
=== New functions
* Add the function `ranch:recv_proxy_header/2` to receive
the PROXY protocol header and parse it. It must be called
before `ranch:handshake/1,2`.
* Add the functions `ranch_proxy_header:parse/1` and
`ranch_proxy_header:header/1,2` to parse and build a
PROXY protocol header, respectively.
=== Bugs fixed
* Fix a race condition when the listener is restarted
after `ranch_listener_sup` crashes. This resulted in
the original options being used even if the options
were updated at runtime.
* Make the acceptors exit instead of crash when the
listening socket has been closed to prevent
unnecessary logs.
* Fix an issue where listener information would not get
cleaned up when an embedded listener was stopped. This
was fixed in Ranch 1.6.2.

View File

@ -0,0 +1,163 @@
[appendix]
== Migrating from Ranch 1.7+ to Ranch 2.0
Ranch 2.0 adds support for multiple connection supervisors.
Ranch 1.x had a bottleneck because it used only a single
connection supervisor. This was more evident when many
connections were dropped at once as the supervisor couldn't
keep up and failed to accept new connections while cleaning
up the old ones. Ranch 2.0 behaves much better in this scenario
by default. Multiple connection supervisors also helps with
concurrently accepting new connections.
Ranch 2.0 also adds experimental support for opening more
than one listening socket on a single port.
Starting with Ranch 2.0 we are also providing a
https://github.com/juhlig/prometheus_ranch[Prometheus collector]
as a separate project as well as a
https://github.com/juhlig/prometheus_ranch/blob/master/dashboards/ranch-dashboard.json[Grafana dashboard].
Ranch 2.0 is compatible with Erlang/OTP 21.0 onward. Support
for Erlang/OTP 19 and 20 has been removed.
=== Features added
* Ranch now comes with a `ranch.appup` file necessary for
performing release upgrades. A test suite has been added
to confirm release upgrades work from one tag to the next.
Numerous fixes were made that will also improve error recovery.
Release upgrades will only be supported from Ranch 2.0
onward.
* The `num_conns_sups` option has been added. It allows
configuring the number of connection supervisors. It
now defaults to `num_accceptors`. The old behavior can
be obtained by setting this value to 1.
* The `logger` option is no longer experimental. It now
defaults to `logger` instead of `error_logger`.
* UNIX domain sockets are now supported.
* The active N socket option is now supported. It requires
Erlang/OTP 21.3 or above for TLS, however.
* Embedded listeners are now failing in a predictable
manner when `ranch_server` goes down. It is no longer
necessary to embed `ranch_sup` and the recommendation
is now to just start Ranch normally when using embedded
listeners.
* Two steps handshake is now supported. This allows
obtaining TLS extensions and updating options before
resuming the handshake. The handshake can also be
canceled.
=== Experimental features added
* The experimental `num_listen_sockets` option has been
added. It allows opening more than one listening socket
per listener. It can only be used alongside the Linux
`SO_REUSEPORT` socket option or equivalent. It allows
working around a bottleneck in the kernel and maximizes
resource usage, leading to increased rates for accepting
new connections.
=== Features removed
* The `socket` option was removed. A more viable solution
is to define a custom transport module that returns a fresh
socket when `Transport:listen/1` is called.
=== Changed behaviors
* The callback function `Transport:listen/1` and its
implementations in `ranch_tcp` and `ranch_ssl` have changed
to accept a map of transport options instead of only
socket options.
* The callback function `Transport:messages/0` return value
now includes the tag used for passive messages.
* The `Socket` argument was removed from `Protocol:start_link/3`.
The socket must now be obtained by calling `ranch:handshake/1,2`.
=== Added functions
* The functions `ranch:handshake_continue/1,2` and
`ranch:handshake_cancel/1` can be used to perform
a two steps handshake. These functions may not be
supported by all transports.
=== Changed functions
* The `NumAcceptors` argument was removed from `ranch:start_listener/5`
and `ranch:child_spec/5` and moved to the transport options.
* Ranch options can no longer be passed along with socket options
as a proplist. The only forms allowed are now the `ranch:opts()`
map or only socket options as-is. Individual transport options
are now validated as well. The `ranch:opts()` map must
be used when socket options also use a map. This applies to the
`ranch:start_listener/5`, `ranch:child_spec/5` and
`ranch:set_transport_options/2` functions.
* The function `ranch:info/1,2` now returns a map containing
each listener's information rather than a list of key/values.
The key `num_acceptors` was removed as it can be found in the
transport options.
* The function `ranch:set_transport_options/2` no longer requires
the listener to be suspended. Which options apply immediately,
on suspend/resume or on restart has been documented. Some work
has also been done to make these option changes more predictable.
=== Removed functions
* The function `ranch:accept_ack/1` has been removed in favor
of `ranch:handshake/1,2`.
=== Bugs fixed
* Calling `ranch:remove_connection/1` will now resume a sleeping
acceptor process when applicable.
* Repeatedly calling `ranch:remove_connection/1` from a connection
process would crash the respective connection supervisor. This has
now been fixed.
* When a connection process was failing to start, the socket was
not closed and this lead to leaking sockets. This is now corrected.
=== Other changes
* Connection draining has now been documented in the guide
following user feedback and discussions.
* Ranch is now tested against https://concuerror.com/[Concuerror],
a model checking tool for debugging, testing and verifying
concurrent Erlang programs. Two tests have been added in this
release and more will follow in the future.
* Ranch is now tested against `stampede`, a chaos monkey style
testing tool. Currently includes three scenarios: normal
TCP and TLS listeners and embedded TCP listener. This new
test suite helped uncover a misplaced `monitor/2` call
added during the development of Ranch 2.0 (we were using a
similar tool, `havoc`, at the time of finding that issue).
* The supervisor for acceptors and the parent supervisor for
connection supervisors now have an adaptive restart
intensity limit set to `1 + ceil(math:log2(NumChildren))`
to allow room for errors when they have many children.
* Ranch now uses stricter compiler options. Missing function
specs were added to internal modules.
* Ranch now calls `ssl:handshake/1,2,3` instead of
`ssl:ssl_accept/1,2`.
* The `ranch_ssl:ssl_opt()` type has been updated to conform
with Erlang/OTP 23.0.

View File

@ -0,0 +1,70 @@
[appendix]
== Migrating from Ranch 1.x
The changelog for Ranch releases before 1.6 can be found
in this section.
=== 1.5.0
* Add transport functions getopts/2, getstat/1 and getstat/2
* Fix ranch:info/0 and ranch:procs/2 in embedded mode
* Prevent ranch_conns_sup from stopping on unexpected messages
=== 1.4.0
* Add new transport option num_acceptor
* Deprecate ranch:start_listener/6 in favor of start_listener/5
* Deprecate ranch:child_spec/6 in favor of child_spec/5
=== 1.3.0
The version numbers 1.3.1 and 1.3.2 were later made to fix
small mistakes made during the 1.3.0 release process. They
do not include code changes.
* Tested with OTP R16B+ on Linux, FreeBSD, OSX and Windows
* Add ssl to the list of dependencies
* Add ranch:info/0 and ranch:procs/2 to retrieve Ranch state information
* Allow configuring a listener with only SNI, without a default certificate
* Blacklist transport options instead of whitelist
** Unknown options are now allowed, but will result in a Dialyzer warning
* Add many transport options typespecs and documentation
* Don't silently drop the accept rate when running out of fds
* Prevent a race condition when stopping listeners
* Improve reporting for common errors, for example eaddrinuse
* Fix double removal of connections bug
** The number of active connections should now be exact
* Fix stuck acceptor bug when controlling_socket returned errors
* Numerous documentation and examples improvements
=== 1.2.1
* Fix bug preventing node shutdown when SSL is used with OTP 17.1+
* Tune restart intensity in all supervisors
=== 1.2.0
* Allow the supervised process and the process owning the socket to be different
* Add many transport options (please refer to the documentation)
* Add function ranch:get_addr/1 to retrieve both IP and port of listener
* Don't pass Ranch-specific options down to transports
** Should make Dialyzer happy in user projects
** New types ranch:opt(), ranch_tcp:opt(), ranch_ssl:ssl_opt() and ranch_ssl:opt()
* Fix crash when filtering unknown options out
* Print a warning for each option filtered out
* Handle Transport:controlling_socket/2 errors and close the socket
* Handle Protocol:start_link/4 crashes to avoid killing all active connections
* Use Asciidoc for documentation
* Test Ranch across 14 Erlang versions on CircleCI
* Improve and document test suites with recent ct_helper improvements
* Fix a number of intermittent test issues
=== 1.1.0
* Add Transport:secure/0
* Add SSL partial_chain option
* Stop reporting errors on {error, closed} in accept_ack
=== 1.0.0
* Initial release

View File

@ -0,0 +1,70 @@
[appendix]
== Migrating from Ranch 2.0 to Ranch 2.1
Ranch 2.1 adds counters and alarms.
The https://github.com/juhlig/prometheus_ranch[Prometheus collector]
was updated to include accepted/terminated connections
metrics.
Ranch 2.1 is compatible with Erlang/OTP 22.0 onward. Support
for Erlang/OTP 21 has been removed.
=== Features added
* Metrics are now provided by `ranch:info/0,1`. Currently
includes accepted/terminated connection counts per
connection supervisor.
* Alarms can now be configured. The only alarm currently
available is `num_connections`. When the number of
connections goes over a configurable treshold Ranch
will call the given callback. This can be used to
programmatically shut down idle connections to
make up space for new connections, for example.
* A `post_listen` callback option has been added. It
receives sockets immediately after the `Transport:listen/1`
call. It can be used for some additional initialization
of the socket, such as setting file permissions on
Unix domain sockets.
* It is now possible to use TLS-PSK authentication
without having to specify a default certificate
for TLS < 1.3.
=== Experimental features added
* The `inet_backend` option is now properly handled
and tested for TCP listeners. This allows using
the experimental `socket` backend. The `socket`
backend is now tested with Ranch. Note that
there are known issues and Windows support is not
currently implemented.
=== Changed behaviors
* Ranch will now remove unsupported SSL/TLS options
where applicable. A warning will be logged when
this happens. Options are only removed when they
are not compatible with the selected TLS version
and leaving them would prevent the listener from
starting.
+
The following options are removed when using TLS
1.1, 1.2 or 1.3: `beast_mitigation` and `padding_check`.
+
The following options are removed when using TLS
1.3 exclusively: `client_renegotiation`,
`next_protocols_advertised`, `psk_identity`,
`reuse_session`, `reuse_sessions`,
`secure_renegotiate` and `user_lookup_fun`.
=== Added functions
* The function `ranch_proxy_header:to_connection_info/1`
converts PROXY protocol information to the same
format as `ssl:connection_information/1`. Because
there is little overlap only the `protocol`,
`selected_cipher_suite` and `sni_hostname` will
be available, however.

View File

@ -0,0 +1,92 @@
== Writing parsers
There are three kinds of protocols:
* Text protocols
* Schema-less binary protocols
* Schema-based binary protocols
This chapter introduces the first two kinds. It will not cover
more advanced topics such as continuations or parser generators.
This chapter isn't specifically about Ranch, we assume here that
you know how to read data from the socket. The data you read and
the data that hasn't been parsed is saved in a buffer. Every
time you read from the socket, the data read is appended to the
buffer. What happens next depends on the kind of protocol. We
will only cover the first two.
=== Parsing text
Text protocols are generally line based. This means that we can't
do anything with them until we receive the full line.
A simple way to get a full line is to use `binary:split/2,3`.
.Using binary:split/2 to get a line of input
[source,erlang]
case binary:split(Buffer, <<"\n">>) of
[_] ->
get_more_data(Buffer);
[Line, Rest] ->
handle_line(Line, Rest)
end.
In the above example, we can have two results. Either there was
a line break in the buffer and we get it split into two parts,
the line and the rest of the buffer; or there was no line break
in the buffer and we need to get more data from the socket.
Next, we need to parse the line. The simplest way is to again
split, here on space. The difference is that we want to split
on all spaces character, as we want to tokenize the whole string.
.Using binary:split/3 to split text
[source,erlang]
case binary:split(Line, <<" ">>, [global]) of
[<<"HELLO">>] ->
be_polite();
[<<"AUTH">>, User, Password] ->
authenticate_user(User, Password);
[<<"QUIT">>, Reason] ->
quit(Reason)
%% ...
end.
Pretty simple, right? Match on the command name, get the rest
of the tokens in variables and call the respective functions.
After doing this, you will want to check if there is another
line in the buffer, and handle it immediately if any.
Otherwise wait for more data.
=== Parsing binary
Binary protocols can be more varied, although most of them are
pretty similar. The first four bytes of a frame tend to be
the size of the frame, which is followed by a certain number
of bytes for the type of frame and then various parameters.
Sometimes the size of the frame includes the first four bytes,
sometimes not. Other times this size is encoded over two bytes.
And even other times little-endian is used instead of big-endian.
The general idea stays the same though.
.Using binary pattern matching to split frames
[source,erlang]
<< Size:32, _/bits >> = Buffer,
case Buffer of
<< Frame:Size/binary, Rest/bits >> ->
handle_frame(Frame, Rest);
_ ->
get_more_data(Buffer)
end.
You will then need to parse this frame using binary pattern
matching, and handle it. Then you will want to check if there
is another frame fully received in the buffer, and handle it
immediately if any. Otherwise wait for more data.

View File

@ -0,0 +1,113 @@
== Protocols
A protocol handler starts a connection process and defines the
protocol logic executed in this process.
=== Writing a protocol handler
All protocol handlers must implement the `ranch_protocol` behavior
which defines a single callback, `start_link/3`. This callback is
responsible for spawning a new process for handling the connection.
It receives three arguments: the name of the listener, the
transport handler being used and the protocol options defined in
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: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.
.Perform the socket handshake
[source,erlang]
{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:handshake/1,2`. You can use `Transport:setopts/2`
for that purpose.
Following is the complete protocol code for the example found
in `examples/tcp_echo/`.
.Protocol module that echoes everything it receives
[source,erlang]
----
-module(echo_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(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.
----
=== Using gen_statem and gen_server
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:handshake/1,2` from the `init` callback
as this would cause a deadlock to happen.
This problem can be addressed in several ways.
==== gen_statem
* Use state enter calls and place the `ranch:handshake/1,2` call in the enter
clause of the initial state. Check the `tcp_reverse` example for a complete
example.
* Use a `next_event` action in the return from `init/1` and place the
`ranch:handshake/1,2` call in the clause handling the event in the initial
state.
* Use the `gen_statem:enter_loop/4` function and start your process with
`proc_lib:spawn_link/3` or `proc_lib:start_link/3,4,5`. See below for an
example.
.Using gen_statem:enter_loop/4 to start a protocol
[source,erlang]
----
-module(my_protocol).
-behaviour(gen_statem).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/1]).
%% Exports of other gen_statem callbacks here.
start_link(Ref, Transport, Opts) ->
{ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Transport, Opts}])}.
init({Ref, Transport, _Opts}) ->
%% Perform any required state initialization here.
{ok, Socket} = ranch:handshake(Ref),
ok = Transport:setopts(Socket, [{active, once}]),
gen_statem:enter_loop(?MODULE, [], state_name, {state_data, Socket, Transport}).
%% Other gen_statem callbacks here.
----
==== gen_server
* Use `{continue, Continue}` in the return from `init/1` and place the
`ranch:handshake/1,2` call in a corresponding `handle_continue/2` clause.
* Use the `gen_server:enter_loop/3` function and start your process with
`proc_lib:spawn_link/3` or `proc_lib:start_link/3,4,5`.

View File

@ -0,0 +1,120 @@
== SSL client authentication
=== Purpose
SSL client authentication is a mechanism allowing applications to
identify certificates. This allows your application to make sure that
the client is an authorized certificate, but makes no claim about
whether the user can be trusted. This can be combined with a password
based authentication to attain greater security.
The server only needs to retain the certificate serial number and
the certificate issuer to authenticate the certificate. Together,
they can be used to uniquely identify a certificate.
As Ranch allows the same protocol code to be used for both SSL and
non-SSL transports, you need to make sure you are in an SSL context
before attempting to perform an SSL client authentication. This
can be done by checking the return value of `Transport:name/0`.
=== Obtaining client certificates
You can obtain client certificates from various sources. You can
generate them yourself, or you can use a service like CAcert.org
which allows you to generate client and server certificates for
free.
Following are the steps you need to take to create a CAcert.org
account, generate a certificate and install it in your favorite
browser.
* Open http://cacert.org in your favorite browser
* Root Certificate link: install both certificates
* Join (Register an account)
* Verify your account (check your email inbox!)
* Log in
* Client Certificates: New
* Follow instructions to create the certificate
* Install the certificate in your browser
You can optionally save the certificate for later use, for example
to extract the `IssuerID` information as will be detailed later on.
=== Transport configuration
The SSL transport does not request a client certificate by default.
You need to specify the `{verify, verify_peer}` option when starting
the listener to enable this behavior.
.Configure a listener for SSL authentication
[source,erlang]
{ok, _} = ranch:start_listener(my_ssl,
ranch_ssl, #{socket_opts => [
{port, SSLPort},
{certfile, PathToCertfile},
{cacertfile, PathToCACertfile},
{verify, verify_peer}
]},
my_protocol, []
).
In this example we set the required `port` and `certfile`, but also
the `cacertfile` containing the CACert.org root certificate, and
the option to request the client certificate.
If you enable the `{verify, verify_peer}` option and the client does
not have a client certificate configured for your domain, then no
certificate will be sent. This allows you to use SSL for more than
just authenticated clients.
=== Authentication
To authenticate users, you must first save the certificate information
required. If you have your users' certificate files, you can simply
load the certificate and retrieve the information directly.
.Retrieve the issuer ID from a certificate
[source,erlang]
----
certfile_to_issuer_id(Filename) ->
{ok, Data} = file:read_file(Filename),
[{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(Data),
{ok, IssuerID} = public_key:pkix_issuer_id(Cert, self),
IssuerID.
----
The `IssuerID` variable contains both the certificate serial number
and the certificate issuer stored in a tuple, so this value alone can
be used to uniquely identify the user certificate. You can save this
value in a database, a configuration file or any other place where an
Erlang term can be stored and retrieved.
To retrieve the `IssuerID` from a running connection, you need to first
retrieve the client certificate and then extract this information from
it. Ranch does not provide a function to retrieve the client certificate.
Instead you can use the `ssl:peercert/1` function. Once you have the
certificate, you can again use the `public_key:pkix_issuer_id/2` to
extract the `IssuerID` value.
The following function returns the `IssuerID` or `false` if no client
certificate was found. This snippet is intended to be used from your
protocol code.
.Retrieve the issuer ID from the certificate for the current connection
[source,erlang]
----
socket_to_issuer_id(Socket) ->
case ssl:peercert(Socket) of
{error, no_peercert} ->
false;
{ok, Cert} ->
{ok, IssuerID} = public_key:pkix_issuer_id(Cert, self),
IssuerID
end.
----
You then only need to match the `IssuerID` value to authenticate the
user.

View File

@ -0,0 +1,177 @@
== Transports
A transport defines the interface to interact with a socket.
Transports can be used for connecting, listening and accepting
connections, but also for receiving and sending data. Both
passive and active mode are supported, although all sockets
are initialized as passive.
=== TCP transport
The TCP transport is a thin wrapper around `gen_tcp`.
=== SSL transport
The SSL transport is a thin wrapper around `ssl`.
Ranch depends on `ssl` by default so any necessary
dependencies will start when Ranch is started. It is
possible to remove the dependency when the SSL transport
will not be used. Refer to your release build tool's
documentation for more information.
When embedding Ranch listeners that have an SSL transport,
your application must depend on the `ssl` application for
proper behavior.
=== Sending and receiving data
This section assumes that `Transport` is a valid transport handler
(like `ranch_tcp` or `ranch_ssl`) and `Socket` is a connected
socket obtained through the listener.
You can send data to a socket by calling the `Transport:send/2`
function. The data can be given as `iodata()`, which is defined as
`binary() | iolist()`. All the following calls will work:
.Sending data to the socket
[source,erlang]
----
Transport:send(Socket, <<"Ranch is cool!">>).
Transport:send(Socket, "Ranch is cool!").
Transport:send(Socket, ["Ranch", ["is", "cool!"]]).
Transport:send(Socket, ["Ranch", [<<"is">>, "cool!"]]).
----
You can receive data either in passive or in active mode. Passive mode
means that you will perform a blocking `Transport:recv/3` call, while
active mode means that you will receive the data as a message.
By default, all data will be received as binary. It is possible to
receive data as strings, although this is not recommended as binaries
are a more efficient construct, especially for binary protocols.
Receiving data using passive mode requires a single function call. The
first argument is the socket, and the third argument is a timeout duration
before the call returns with `{error, timeout}`.
The second argument is the amount of data in bytes that we want to receive.
The function will wait for data until it has received exactly this amount.
If you are not expecting a precise size, you can specify 0 which will make
this call return as soon as data was read, regardless of its size.
.Receiving data from the socket in passive mode
[source,erlang]
{ok, Data} = Transport:recv(Socket, 0, 5000).
Active mode requires you to inform the socket that you want to receive
data as a message and to write the code to actually receive it.
There are three kinds of active modes: `{active, once}`, `{active, N}`
and `{active, true}`. The first will send a single message before going
back to passive mode; the second will send `N` messages followed by
a `Passive` message when switching back to passive mode; the third
will send messages indefinitely. We recommend not using the `{active, true}`
mode as it could quickly flood your process mailbox. It's better to keep
the data in the socket and read it only when required.
Four different messages can be received:
* Incoming data: `{OK, Socket, Data}`
* Socket closed: `{Closed, Socket}`
* Socket error: `{Error, Socket, Reason}`
* Switch to passive mode: `{Passive, Socket}`
The value of `OK`, `Closed`, `Error` and `Passive` can be different
depending on the transport being used. To be able to properly match
on them you must first call the `Transport:messages/0` function.
.Retrieving the transport's active message identifiers
[source,erlang]
{OK, Closed, Error, Passive} = Transport:messages().
To start receiving messages you will need to call the `Transport:setopts/2`
function, and do so every time you want to receive data.
.Receiving messages from the socket in active mode
[source,erlang]
----
{OK, Closed, Error, Passive} = Transport:messages(),
Transport:setopts(Socket, [{active, once}]),
receive
{OK, Socket, Data} ->
io:format("data received: ~p~n", [Data]);
{Closed, Socket} ->
io:format("socket got closed!~n");
{Error, Socket, Reason} ->
io:format("error happened: ~p~n", [Reason])
end.
----
You can easily integrate active sockets with existing Erlang code as all
you really need is just a few more clauses when receiving messages.
=== Sending files
As in the previous section it is assumed `Transport` is a valid transport
handler and `Socket` is a connected socket obtained through the listener.
To send a whole file, with name `Filename`, over a socket:
.Sending a file by filename
[source,erlang]
{ok, SentBytes} = Transport:sendfile(Socket, Filename).
Or part of a file, with `Offset` greater than or equal to 0, `Bytes` number of
bytes and chunks of size `ChunkSize`:
.Sending part of a file by filename in chunks
[source,erlang]
Opts = [{chunk_size, ChunkSize}],
{ok, SentBytes} = Transport:sendfile(Socket, Filename, Offset, Bytes, Opts).
To improve efficiency when sending multiple parts of the same file it is also
possible to use a file descriptor opened in raw mode:
.Sending a file opened in raw mode
[source,erlang]
{ok, RawFile} = file:open(Filename, [raw, read, binary]),
{ok, SentBytes} = Transport:sendfile(Socket, RawFile, Offset, Bytes, Opts).
=== Upgrading a TCP socket to SSL
A connected TCP socket can be upgraded to a SSL socket via the function
`ranch_ssl:handshake/3`. The socket *must* be in `{active, false}` mode
before telling the client that the server is ready to upgrade in order
to avoid race conditions.
IMPORTANT: The new socket received from `ranch_ssl:handshake/3` must be
used via the `ranch_ssl` transport.
.Performing a TLS handshake on a TCP socket
[source,erlang]
{ok, SslSocket} = ranch_ssl:handshake(TcpSocket, SslOpts, 5000).
=== Writing a transport handler
A transport handler is a module implementing the `ranch_transport` behavior.
It defines a certain number of callbacks that must be written in order to
allow transparent usage of the transport handler.
The behavior doesn't define the socket options available when opening a
socket. These do not need to be common to all transports as it's easy enough
to write different initialization functions for the different transports that
will be used. With one exception though. The `setopts/2` function *must*
implement the `{active, once}` and the `{active, true}` options.
If the transport handler doesn't have a native implementation of `sendfile/5` a
fallback is available, `ranch_transport:sendfile/6`. The extra first argument
is the transport's module. See `ranch_ssl` for an example.

View File

@ -0,0 +1,211 @@
= ranch(3)
== Name
ranch - Socket acceptor pool
== Description
The module `ranch` provides functions for starting and
manipulating Ranch listeners.
== Exports
Start/stop:
* link:man:ranch:start_listener(3)[ranch:start_listener(3)] - Start a listener
* link:man:ranch:stop_listener(3)[ranch:stop_listener(3)] - Stop a listener
* link:man:ranch:child_spec(3)[ranch:child_spec(3)] - Build child specifications for a new listener
Suspend/resume:
* link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)] - Suspend a running listener
* link:man:ranch:resume_listener(3)[ranch:resume_listener(3)] - Resume a suspended listener
* link:man:ranch:get_status(3)[ranch:get_status(3)] - Get a listener's running state
Connections:
* link:man:ranch:handshake(3)[ranch:handshake(3)] - Perform the transport handshake
* link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)] - Resume the paused transport handshake
* link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)] - Cancel the paused transport handshake
* link:man:ranch:recv_proxy_header(3)[ranch:recv_proxy_header(3)] - Receive the PROXY protocol header
* link:man:ranch:remove_connection(3)[ranch:remove_connection(3)] - Remove connection from the count
Options:
* link:man:ranch:get_max_connections(3)[ranch:get_max_connections(3)] - Get the max number of connections per connection supervisor
* link:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)] - Get the current protocol options
* link:man:ranch:get_transport_options(3)[ranch:get_transport_options(3)] - Get the current transport options
* link:man:ranch:set_max_connections(3)[ranch:set_max_connections(3)] - Set the max number of connections per connection supervisor
* link:man:ranch:set_protocol_options(3)[ranch:set_protocol_options(3)] - Set the protocol options
* link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)] - Set the transport options
Introspection:
* link:man:ranch:get_addr(3)[ranch:get_addr(3)] - Get the listening address
* link:man:ranch:get_port(3)[ranch:get_port(3)] - Get the listening port
* link:man:ranch:info(3)[ranch:info(3)] - Overview of Ranch listeners
* link:man:ranch:procs(3)[ranch:procs(3)] - Retrieve pids from a listener
* link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)] - Wait for a specific number of connections
== Types
=== max_conns()
[source,erlang]
----
max_conns() = non_neg_integer() | infinity
----
Maximum number of connections allowed per connection supervisor.
This is a soft limit. The actual number of connections
might be slightly above the limit due to concurrency
when accepting new connections. Some connections may
also be removed from this count explicitly by the user
code.
=== opts()
[source,erlang]
----
opts() = any() | transport_opts(any())
----
Transport or socket options.
=== ref()
[source,erlang]
----
ref() = any()
----
Unique name used to refer to a listener.
=== transport_opts(SocketOpts)
[source,erlang]
----
transport_opts(SocketOpts) = #{
alarms => #{
term() => #{
type := num_connections,
treshold := non_neg_integer(),
callback := fun((ref(), term(), pid(), [pid()]) -> any()),
cooldown => non_neg_integer()
}
},
connection_type => worker | supervisor,
handshake_timeout => timeout(),
max_connections => max_conns(),
logger => module(),
num_acceptors => pos_integer(),
num_conns_sups => pos_integer(),
post_listen_callback => fun((term()) -> ok | {error, term()}),
shutdown => timeout() | brutal_kill,
socket_opts => SocketOpts
}
----
Transport options.
The transport options are a combination of Ranch-specific
options and transport-specific socket options.
None of the options are required.
alarms (#{})::
Alarms to call a function when the number of connections tracked
by one connection supervisor reaches or exceeds a defined treshold.
+
The map keys are the alarm names, which can be any `term`. The
associated values are the respective alarm options, again in a map
with the following keys:
type:::
Must be set to `num_connections`.
treshold:::
Treshold value, which must be a `non_neg_integer`. When the
number of connections tracked by a single connection supervisor
reaches or exceeds this value, The alarm will trigger and call
the function defined in the `callback` key (see below).
callback:::
The alarm function, which takes the listener name, the alarm
name, the pid of the connection supervisor and a list of the pids
of all connection processes under that supervisor as arguments.
The return value is ignored.
cooldown (5000):::
The minimum time after which the alarm can be triggered again,
in milliseconds.
connection_type (worker)::
Type of process that will handle the connection.
handshake_timeout (5000)::
Maximum allowed time for the `ranch:handshake/1,2` call to finish.
logger (logger)::
The module that will be used to write log messages.
max_connections (1024)::
Maximum number of active connections per connection supervisor.
Soft limit. Use `infinity` to disable the limit entirely.
num_acceptors (10)::
Number of processes that accept connections.
num_conns_sups - see below::
Number of processes that supervise connection processes.
If not specified, defaults to be equal to `num_acceptors`.
post_listen_callback (fun(_ListenSock) -> ok end)::
A function which will be called after a listen socket has been successfully
created, with the socket as argument. It can be used to perform any
necessary setup steps on the socket.
+
If the callback function returns `ok`, the listener will start accepting
connections on the socket. If it returns `{error, Reason}`, the listener
will fail to start.
shutdown (5000)::
Maximum allowed time for children to stop on listener shutdown.
socket_opts::
Socket options to be used by `Transport:listen/1`. Please refer to the
documentation of the transport module you are using for more details.
== Changelog
* *2.0*: The type `transport_opts(SocketOpts)` was added.
* *2.0*: The function `ranch:accept_ack/1` was removed in favor of
link:man:ranch:handshake(3)[ranch:handshake(3)].
* *2.0*: The option `max_connections` is now per connection supervisor.
* *2.0*: The `num_conns_sup` option was added.
* *2.0*: The `socket` option was removed.
* *2.0*: The `logger` option is no longer experimental. It now defaults
to `logger` instead of `error_logger`.
* *2.0*: The `opt()` type was removed.
* *1.6*: The experimental `logger` option was added.
* *1.6*: The `opt()` type was deprecated in favor of the new `opts()` type.
== See also
link:man:ranch(7)[ranch(7)]

View File

@ -0,0 +1,106 @@
= ranch:child_spec(3)
== Name
ranch:child_spec - Build child specifications for a new listener
== Description
[source,erlang]
----
child_spec(Ref :: ranch_ref(),
Transport :: module(),
TransOpts :: ranch:opts(),
Protocol :: module(),
ProtoOpts :: any())
-> supervisor:child_spec()
----
Build child specifications for a new listener which can
be embedded directly in an application's supervision
tree.
The actual listener is placed under a supervisor which
monitors `ranch_server` via a proxy process and will
restart the listener if `ranch_server` crashes.
== Arguments
Ref::
The listener name is used to refer to this listener in
future calls, for example when updating the configuration.
+
It can be any Erlang term. An atom is generally good enough,
for example `api`, `my_app_clear` or `my_app_tls`.
Transport::
The transport module that will be used by Ranch to accept
connections and that will be passed to the protocol module
along with the socket.
+
The interface of the transport module is documented in the
link:man:ranch_transport(3)[ranch_transport(3)] manual.
TransportOpts::
Transport options include the Ranch-specific options
and the socket options. The listener's port number must
be defined in the socket options.
+
The available options for the built-in Ranch transports
are documented in the link:man:ranch_tcp(3)[ranch_tcp(3)]
and link:man:ranch_ssl(3)[ranch_ssl(3)] manuals.
Protocol::
The protocol module that will be used by Ranch after
the connection has been accepted.
+
The interface of the protocol module is documented in the
link:man:ranch_protocol(3)[ranch_protocol(3)] manual.
ProtocolOpts::
The protocol options given when calling the protocol
module. Please consult the documentation of the protocol
module you are using for more details.
== Return value
Child specifications are returned.
== Changelog
* *2.0*: The actual listener is placed under a supervisor in order to
restart the listener if `ranch_server` crashes.
* *2.0*: The `TransOpts` argument must no longer contain
Ranch-specific options if given as a list. Use a map.
* *1.4*: The `NumAcceptors` argument was moved to the transport options.
== Examples
.Embed a listener
[source,erlang]
----
-behavior(supervisor).
init(_) ->
{ok, {#{strategy => one_for_one}, [
ranch:child_spec(echo,
ranch_tcp, [{port, 5555}],
echo_protocol, []
)
]}}.
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch(3)[ranch(3)],
link:man:ranch_tcp(3)[ranch_tcp(3)],
link:man:ranch_ssl(3)[ranch_ssl(3)],
link:man:ranch_transport(3)[ranch_transport(3)],
link:man:ranch_protocol(3)[ranch_protocol(3)]

View File

@ -0,0 +1,59 @@
= ranch:get_addr(3)
== Name
ranch:get_addr - Get the listening address
== Description
[source,erlang]
----
get_addr(Ref :: ranch:ref())
-> {IP :: inet:ip_address(),
Port :: inet:port_number()}
| {local, SocketFile :: binary()}
| {undefined, undefined}
----
Get the listening address.
== Arguments
Ref::
The listener name.
== Return value
The address of the listener is returned as a tuple of the form
`{IP, Port}` when listening on a network interface, or
`{local, SocketFile}` when listening on a UNIX Domain socket.
When the listener is suspended, `{undefined, undefined}` will
be returned.
The IP address is the IP of the network interface the
socket is bound to.
The socket file is the path of a file on your system the
socket is bound to.
== Examples
.Get the listening port and IP
[source,erlang]
----
{IP, Port} = ranch:get_addr(example).
----
.Get the listening UNIX Domain socket file
[source,erlang]
----
{local, SocketFile} = ranch:get_addr(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:get_port(3)[ranch:get_port(3)],
link:man:ranch:info(3)[ranch:info(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,45 @@
= ranch:get_max_connections(3)
== Name
ranch:get_max_connections - Get the max number of connections per connection supervisor
== Description
[source,erlang]
----
get_max_connections(Ref :: ranch:ref())
-> MaxConns :: ranch:max_conns()
----
Get the max number of connections per connection supervisor.
== Arguments
Ref::
The listener name.
== Return value
The maximum number of connections per connection supervisor
is returned.
== Changelog
* *2.0*: The maximum number of connections is now per connection supervisor.
== Examples
.Get the max number of connections per connection supervisor
[source,erlang]
----
MaxConns = ranch:get_max_connections(example).
----
== See also
link:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)],
link:man:ranch:get_transport_options(3)[ranch:get_transport_options(3)],
link:man:ranch:set_max_connections(3)[ranch:set_max_connections(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,48 @@
= ranch:get_port(3)
== Name
ranch:get_port - Get the listening port
== Description
[source,erlang]
----
get_port(Ref :: ranch:ref())
-> Port :: inet:port_number() | undefined
----
Get the listening port.
This function is particularly useful to retrieve the
listening port number when it was not provided in the
options and was chosen randomly instead.
== Arguments
Ref::
The listener name.
== Return value
The listening port is returned.
When the listener is suspended or using a UNIX Domain
socket instead of a network interface, `undefined`
will be returned.
== Examples
.Get the listening port
[source,erlang]
----
Port = ranch:get_port(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:get_addr(3)[ranch:get_addr(3)],
link:man:ranch:info(3)[ranch:info(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,40 @@
= ranch:get_protocol_options(3)
== Name
ranch:get_protocol_options - Get the current protocol options
== Description
[source,erlang]
----
get_protocol_options(Ref :: ranch:ref())
-> ProtoOpts :: any()
----
Get the current protocol options.
== Arguments
Ref::
The listener name.
== Return value
The current protocol options are returned.
== Examples
.Get the current protocol options
[source,erlang]
----
ProtoOpts = ranch:get_protocol_options(example).
----
== See also
link:man:ranch:get_max_connections(3)[ranch:get_max_connections(3)],
link:man:ranch:get_transport_options(3)[ranch:get_transport_options(3)],
link:man:ranch:set_protocol_options(3)[ranch:set_protocol_options(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,46 @@
= ranch:get_status(3)
== Name
ranch:get_status - Get a listener's running state
== Description
[source,erlang]
----
get_status(Ref :: ranch_ref()) -> running | suspended
----
Get a listener's running state.
== Arguments
Ref::
The listener name.
== Return value
An atom is returned indicating the running status of the listener.
== Changelog
* *1.6*: Function introduced.
== Examples
.Get a listener's running state
[source,erlang]
----
ranch:get_status(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)],
link:man:ranch:resume_listener(3)[ranch:resume_listener(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,40 @@
= ranch:get_transport_options(3)
== Name
ranch:get_transport_options - Get the current transport options
== Description
[source,erlang]
----
get_transport_options(Ref :: ranch:ref())
-> TransOpts :: ranch:transport_opts(any())
----
Get the current transport options.
== Arguments
Ref::
The listener name.
== Return value
The current transport options are returned.
== Examples
.Get the current transport options
[source,erlang]
----
TransOpts = ranch:get_transport_options(example).
----
== See also
link:man:ranch:get_max_connections(3)[ranch:get_max_connections(3)],
link:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,84 @@
= ranch:handshake(3)
== Name
ranch:handshake - Perform the transport handshake
== Description
[source,erlang]
----
handshake(Ref) -> {ok, Socket} | {continue, Info}
handshake(Ref, Opts) -> {ok, Socket} | {continue, Info}
Ref :: ranch:ref()
Opts :: any()
Socket :: any()
Info :: any()
----
Perform the transport handshake.
This function must be called by the protocol process in order
to retrieve the socket for the connection. Ranch performs the
handshake necessary to give control of the socket to this
process and also does the transport handshake, for example
setting up the TLS connection.
== Arguments
Ref::
The listener name.
Opts::
Transport handshake options.
+
Allowed options depend on the transport module.
== Return value
An `ok` tuple is returned containing the socket for the connection
by default.
Depending on configuration, a `continue` tuple can otherwise
be returned when the handshake operation is paused. It contains
data provided by the transport that can be used to inform further
decisions before resuming the handshake, for example to provide
new transport options. The handshake can be resumed using
link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)]
or canceled using
link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)].
This function will trigger an exception when an error occurs.
== Changelog
* *2.0*: The `continue` tuple can now be returned.
* *1.6*: Function introduced. Replaces `ranch:accept_ack/1`.
== Examples
.Initialize the connection process
[source,erlang]
----
start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, Opts) ->
{ok, Socket} = ranch:handshake(Ref),
loop(#state{ref=Ref, socket=Socket,
transport=Transport, opts=Opts}).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)],
link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)],
link:man:ranch:recv_proxy_header(3)[ranch:recv_proxy_header(3)],
link:man:ranch:remove_connection(3)[ranch:remove_connection(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,55 @@
= ranch:handshake_cancel(3)
== Name
ranch:handshake_cancel - Cancel the paused transport handshake
== Description
[source,erlang]
----
handshake_cancel(Ref :: ranch:ref()) -> ok
----
Cancel the paused transport handshake.
This function may be called by the protocol process
to cancel a paused handshake.
== Arguments
Ref::
The listener name.
+
Allowed options depend on the transport module.
== Return value
The return value depends on the transport module.
== Changelog
* *2.0*: Function introduced.
== Examples
.Cancel a paused transport handshake
[source,erlang]
----
start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, Opts) ->
{continue, _Info} = ranch:handshake(Ref),
ranch:handshake_cancel(Ref),
exit(handshake_cancelled).
----
== See also
link:man:ranch:handshake(3)[ranch:handshake(3)],
link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,67 @@
= ranch:handshake_continue(3)
== Name
ranch:handshake_continue - Resume the paused transport handshake
== Description
[source,erlang]
----
handshake_continue(Ref) -> {ok, Socket}
handshake_continue(Ref, Opts) -> {ok, Socket}
Ref :: ranch:ref()
Opts :: any()
Socket :: any()
----
Resume the paused transport handshake.
This function must be called by the protocol process in order
to resume a paused handshake.
== Arguments
Ref::
The listener name.
Opts::
Transport handshake options.
+
Allowed options depend on the transport module.
== Return value
An `ok` tuple is returned containing the socket for the connection.
This function will trigger an exception when an error occurs.
== Changelog
* *2.0*: Function introduced.
== Examples
.Continue a paused transport handshake
[source,erlang]
----
start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, Opts) ->
{continue, _Info} = ranch:handshake(Ref),
{ok, Socket} = ranch:handshake_continue(Ref),
loop(#state{ref=Ref, socket=Socket,
transport=Transport, opts=Opts}).
----
== See also
link:man:ranch:handshake(3)[ranch:handshake(3)],
link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,75 @@
= ranch:info(3)
== Name
ranch:info - Overview of Ranch listeners
== Description
[source,erlang]
----
info() -> #{Ref := Info}
info(Ref) -> Info
Info :: #{Key :: atom() := Value :: any()}
----
Overview of Ranch listeners.
== Arguments
Ref::
The listener name.
== Return value
Returns detailed information about one or all
Ranch listeners. The following keys are returned:
pid:: Pid of the listener's top-level supervisor.
status:: Listener status, either running or suspended.
ip:: Interface Ranch listens on.
port:: Port number Ranch listens on.
max_connections:: Maximum number of connections per connection supervisor.
active_connections:: Number of active connections.
all_connections:: Number of connections, including those removed from the count.
transport:: Transport module.
transport_options:: Transport options.
protocol:: Protocol module.
protocol_options:: Protocol options.
metrics:: Listener metrics.
== Metrics
Listener metrics are provided as a map, with the following keys:
{conns_sup, Index, accept}:: Number of accepted connections, per connection supervisor.
{conns_sup, Index, terminate}:: Number of terminated connection processes, per connection supervisor.
== Changelog
* *2.1*: Added accept/terminate metrics to the output of `ranch:info/0,1`.
* *2.0*: The listener info is now returned as a map.
* *2.0*: The `num_acceptors` key has been removed.
== Examples
.Get information about all listeners
[source,erlang]
----
AllInfo = ranch:info().
----
.Get information about a specific listener
[source,erlang]
----
Info = ranch:info(example).
----
== See also
link:man:ranch:get_addr(3)[ranch:get_addr(3)],
link:man:ranch:get_port(3)[ranch:get_port(3)],
link:man:ranch:procs(3)[ranch:procs(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,51 @@
= ranch:procs(3)
== Name
ranch:procs - Retrieve pids from a listener
== Description
[source,erlang]
----
procs(Ref :: ranch:ref(),
Type :: acceptors | connections)
-> Pids :: [pid()]
----
Retrieve pids from a listener.
== Arguments
Ref::
The listener name.
Type::
The type of process that will be returned.
== Return value
A list of pids is returned.
== Examples
.Get the pids of the acceptor processes
[source,erlang]
----
Pids = ranch:procs(acceptors).
----
.Get the pids of the connection processes
[source,erlang]
----
Pids = ranch:procs(connections).
----
== See also
link:man:ranch:get_addr(3)[ranch:get_addr(3)],
link:man:ranch:get_port(3)[ranch:get_port(3)],
link:man:ranch:info(3)[ranch:info(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,71 @@
= ranch:recv_proxy_header(3)
== Name
ranch:recv_proxy_header - Receive the PROXY protocol header
== Description
[source,erlang]
----
recv_proxy_header(ranch:ref(), timeout())
-> {ok, ranch_proxy_header:proxy_info()}
| {error, Reason :: atom()}
| {error, protocol_error, HumanReadable :: atom()}
----
Receive the PROXY protocol header.
This function must be called before `ranch:handshake/1,2`
on newly accepted connections to read and parse the PROXY
protocol header, if any.
== Arguments
Ref::
The listener name.
Timeout::
Receive timeout in milliseconds.
== Return value
An `ok` tuple is returned containing PROXY header information
on success.
An `error` 2-tuple is returned when a socket error occurs.
An `error` 3-tuple is returned when a protocol error occurs
and Ranch was not able to parse the PROXY header information.
The third element contains a human-readable description of
the error.
== Changelog
* *1.7*: Function introduced.
== Examples
.Receive the PROXY protocol header
[source,erlang]
----
start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, Opts) ->
{ok, ProxyInfo} = ranch:recv_proxy_header(Ref, 1000),
{ok, Socket} = ranch:handshake(Ref),
loop(#state{ref=Ref, socket=Socket, transport=Transport,
proxy_info=ProxyInfo, opts=Opts}).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:handshake(3)[ranch:handshake(3)],
link:man:ranch:remove_connection(3)[ranch:remove_connection(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,46 @@
= ranch:remove_connection(3)
== Name
ranch:remove_connection - Remove connection from the count
== Description
[source,erlang]
----
remove_connection(Ref :: ranch:ref()) -> ok
----
Remove connection from the count.
This connection will no longer be included in the count when
limiting the number of connections. This can be useful in a
mixed environment where some connections are active and others
are passive. Passive connections spend most of their time idling
and are not consuming much resources.
This function may only be called from a connection process.
== Arguments
Ref::
The listener name.
== Return value
The atom `ok` is always returned. It can be safely ignored.
== Examples
.Remove the connection process from the count
[source,erlang]
----
ranch:remove_connection(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:handshake(3)[ranch:handshake(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,57 @@
= ranch:resume_listener(3)
== Name
ranch:resume_listener - Resume a suspended listener
== Description
[source,erlang]
----
resume_listener(Ref :: ranch_ref())
-> ok | {error, any()}
----
Resume a suspended listener.
Ranch will start listening for and accepting connections
again. The function
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)]
can be used to change the transport options before resuming
the listener.
Nothing is done when the listener is already running.
== Arguments
Ref::
The listener name.
== Return value
The atom `ok` is returned on success.
An error tuple is returned when the listener could not be restarted.
== Changelog
* *1.6*: Function introduced.
== Examples
.Resume a listener
[source,erlang]
----
ok = ranch:resume_listener(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)],
link:man:ranch:get_status(3)[ranch:get_status(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,54 @@
= ranch:set_max_connections(3)
== Name
ranch:set_max_connections - Set the max number of connections per connection supervisor
== Description
[source,erlang]
----
set_max_connections(Ref :: ranch:ref(),
MaxConns :: ranch:max_conns())
-> ok
----
Set the max number of connections per connection supervisor.
The change will be applied immediately. If the new value is
smaller than the previous one, Ranch will wait for the extra
connections to terminate and will not accept new connections
until the number of connections goes below the limit.
== Arguments
Ref::
The listener name.
MaxConns::
The new maximum number of connections per connection supervisor.
== Return value
The atom `ok` is always returned. It can be safely ignored.
== Changelog
* *2.0*: The maximum number of connections is now per connection supervisor.
== Examples
.Set the max number of connections per connection supervisor
[source,erlang]
----
ranch:set_max_connections(example, 10000).
----
== See also
link:man:ranch:get_max_connections(3)[ranch:get_max_connections(3)],
link:man:ranch:set_protocol_options(3)[ranch:set_protocol_options(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,61 @@
= ranch:set_protocol_options(3)
== Name
ranch:set_protocol_options - Set the protocol options
== Description
[source,erlang]
----
set_protocol_options(Ref :: ranch:ref(),
ProtoOpts :: any())
-> ok
----
Set the protocol options.
The change will be applied immediately for all new connections.
Old connections will not receive the new options.
Note that the complete set of protocol options is replaced. To update a subset
of the options, it is recommended to get the current protocol options using
link:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)], update
them and then set them back using this function.
== Arguments
Ref::
The listener name.
ProtoOpts::
The new protocol options.
== Return value
The atom `ok` is always returned. It can be safely ignored.
== Examples
.Set the protocol options
[source,erlang]
----
ranch:set_protocol_options(example, ProtoOpts).
----
.Update some of the protocol options
[source,erlang]
----
ProtoOpts0 = ranch:get_protocol_options(example),
ProtoOpts = ProtoOpts0#{request_timeout => 2000},
ranch:set_protocol_options(example, ProtoOpts).
----
== See also
link:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)],
link:man:ranch:set_max_connections(3)[ranch:set_max_connections(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,97 @@
= ranch:set_transport_options(3)
== Name
ranch:set_transport_options - Set the transport options
== Description
[source,erlang]
----
set_transport_options(Ref :: ranch:ref(),
TransOpts :: ranch:opts())
-> ok | {error, Reason :: term()}
----
Set the transport options.
The complete set of transport options is replaced. To update a subset of the
transport options, it is recommended to get the current transport options using
link:man:ranch:get_transport_options(3)[ranch:get_transport_options(3)], update
them and then set them back using this function.
Changes to the following options will take effect...
* immediately:
** `max_connections`
** `handshake_timeout`
** `shutdown`
* only after the listener has been suspended and resumed:
** `num_acceptors`
** `num_listen_sockets`
** `post_listen_callback`
** `socket_opts`
* only when the entire listener is restarted:
** `connection_type`
** `num_conns_sups`
** `logger`
== Arguments
Ref::
The listener name.
TransOpts::
The new transport options.
== Return value
The atom `ok` is returned on success.
An error tuple is returned on failure, for example if the given
transport options contain invalid values.
== Changelog
* *2.0*: The restriction that the listener must be suspended
has been removed.
* *2.0*: The `TransOpts` argument must no longer contain
Ranch-specific options if given as a list. Use a map.
== Examples
.Set the transport options
[source,erlang]
----
Ref = example,
ok = ranch:suspend_listener(Ref),
ok = ranch:set_transport_options(Ref, TransOpts),
ok = ranch:resume_listener(Ref).
----
.Update the listener TCP port within the `socket_opts` transport option
[source,erlang]
----
Ref = example,
TransOpts0 = ranch:get_transport_options(Ref),
#{socket_opts = SocketOpts0} = TransOpts0,
SocketOpts = [{port, 12345}|proplists:delete(port, SocketOpts0)],
TransOpts = TransOpts0#{socket_opts = SocketOpts},
ok = ranch:suspend_listener(Ref),
ok = ranch:set_transport_options(Ref, TransOpts),
ok = ranch:resume_listener(Ref).
----
== See also
link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)],
link:man:ranch:resume_listener(3)[ranch:resume_listener(3)],
link:man:ranch:get_transport_options(3)[ranch:get_transport_options(3)],
link:man:ranch:set_max_connections(3)[ranch:set_max_connections(3)],
link:man:ranch:set_protocol_options(3)[ranch:set_protocol_options(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,133 @@
= ranch:start_listener(3)
== Name
ranch:start_listener - Start a listener
== Description
[source,erlang]
----
start_listener(Ref :: ranch_ref(),
Transport :: module(),
TransOpts :: ranch:opts(),
Protocol :: module(),
ProtoOpts :: any())
-> {ok, ListenerPid :: pid()}
| {error, any()}
----
Start a listener.
A listener is a set of processes that accepts and manages
connections using the given transport and protocol modules.
== Arguments
Ref::
The listener name is used to refer to this listener in
future calls, for example when stopping it or when
updating the configuration.
+
It can be any Erlang term. An atom is generally good enough,
for example `api`, `my_app_clear` or `my_app_tls`.
Transport::
The transport module that will be used by Ranch to accept
connections and that will be passed to the protocol module
along with the socket.
+
The interface of the transport module is documented in the
link:man:ranch_transport(3)[ranch_transport(3)] manual.
TransportOpts::
Transport options include the Ranch-specific options
and the socket options. The listener's port number must
be defined in the socket options.
+
Socket options may be given directly if there are no
Ranch-specific options.
+
The available options for the built-in Ranch transports
are documented in the link:man:ranch_tcp(3)[ranch_tcp(3)]
and link:man:ranch_ssl(3)[ranch_ssl(3)] manuals.
Protocol::
The protocol module that will be used by Ranch after
the connection has been accepted.
+
The interface of the protocol module is documented in the
link:man:ranch_protocol(3)[ranch_protocol(3)] manual.
ProtocolOpts::
The protocol options given when calling the protocol
module. Please consult the documentation of the protocol
module you are using for more details.
== Return value
An ok tuple is returned on success. It contains the pid of
the top-level supervisor for the listener.
An error tuple is returned on error. The error reason may
be any Erlang term.
A common error is `eaddrinuse`. It indicates that the port
configured for Ranch is already in use.
== Changelog
* *2.0*: The `TransOpts` argument must no longer contain
Ranch-specific options if given as a list. Use a map.
* *1.4*: The `NumAcceptors` argument was moved to the transport options.
== Examples
.Start a listener
[source,erlang]
----
{ok, _} = ranch:start_listener(example,
ranch_tcp, [{port, 8080}],
cowboy_http2, #{}
).
----
.Start a listener with Ranch-specific options
[source,erlang]
----
{ok, _} = ranch:start_listener(example,
ranch_tcp, #{
num_acceptors => 75,
socket_opts => [{port, 8080}]
},
cowboy_http2, #{}
).
----
.Start a listener on a random port
[source,erlang]
----
Ref = example,
{ok, _} = ranch:start_listener(Ref,
ranch_tcp, #{},
cowboy_http2, #{}
),
Port = ranch:get_port(Ref).
----
== See also
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch:child_spec(3)[ranch:child_spec(3)],
link:man:ranch(3)[ranch(3)],
link:man:ranch_tcp(3)[ranch_tcp(3)],
link:man:ranch_ssl(3)[ranch_ssl(3)],
link:man:ranch_transport(3)[ranch_transport(3)],
link:man:ranch_protocol(3)[ranch_protocol(3)]

View File

@ -0,0 +1,58 @@
= ranch:stop_listener(3)
== Name
ranch:stop_listener - Stop a listener
== Description
[source,erlang]
----
stop_listener(Ref :: ranch_ref())
-> ok | {error, not_found}
----
Stop a listener.
The listener is stopped gracefully, first by closing the
listening port, then by stopping the connection processes.
These processes are stopped according to the `shutdown`
transport option, which may be set to brutally kill all
connection processes or give them some time to stop properly.
In order for the connection processes to exit gracefully,
they need to trap exit signals and stop before the configured
shutdown timeout. If greater control over the shutdown is
required the functions link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)]
and link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)]
can be used.
This function does not return until the listener is
completely stopped.
== Arguments
Ref::
The listener name.
== Return value
The atom `ok` is returned on success.
An error tuple is returned when the listener is not found.
== Examples
.Stop a listener
[source,erlang]
----
ok = ranch:stop_listener(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:child_spec(3)[ranch:child_spec(3)],
link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,62 @@
= ranch:suspend_listener(3)
== Name
ranch:suspend_listener - Suspend a running listener
== Description
[source,erlang]
----
suspend_listener(Ref :: ranch_ref())
-> ok | {error, any()}
----
Suspend a running listener.
Ranch will stop listening for and accepting connections and
the listening socket will be closed. Existing connections
will continue undisturbed. The function
link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)]
can be used to wait for connections to be closed if necessary.
Some transport options can only be changed when the listener is
suspended. Please consult the
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)]
manual for more information.
Nothing is done when the listener is already suspended.
== Arguments
Ref::
The listener name.
== Return value
The atom `ok` is returned on success.
An error tuple is returned when the listener could not be suspended.
== Changelog
* *1.6*: Function introduced.
== Examples
.Suspend a listener
[source,erlang]
----
ok = ranch:suspend_listener(example).
----
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch:resume_listener(3)[ranch:resume_listener(3)],
link:man:ranch:get_status(3)[ranch:get_status(3)],
link:man:ranch:set_transport_options(3)[ranch:set_transport_options(3)],
link:man:ranch:wait_for_connections(3)[ranch:wait_for_connections(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,76 @@
= ranch:wait_for_connections(3)
== Name
ranch:wait_for_connections - Wait for a specific number of connections
== Description
[source,erlang]
----
wait_for_connections(Ref :: ranch:ref(),
Operator,
NumConns :: non_neg_integer())
-> ok
Operator :: '>' | '>=' | '==' | '=<' | '<'
----
Wait for a specific number of connections.
This function waits until the number of connections on the
given listener becomes higher than, equal to or lower than
the given number. It never returns otherwise.
This function can be used to gracefully shutdown a listener
by first suspending the listener and then waiting for
connections to terminate before finally stopping the listener.
// @todo The suspend/wait/stop pattern should be tested.
== Arguments
Ref::
The listener name.
Operator::
The operator to use for the comparison.
NumConns::
The number of connections to reach.
== Return value
The atom `ok` is always returned. It can be safely ignored.
== Changelog
* *1.6*: Function introduced.
== Examples
.Wait for at least 100 connections
[source,erlang]
----
ranch:wait_for_connections(example, '>=', 100).
----
.Gracefully shutdown a listener
[source,erlang]
----
Ref = example,
ok = ranch:suspend_listener(Ref),
ranch:wait_for_connections(Ref, '==', 0),
ok = ranch:stop_listener(Ref).
----
== See also
link:man:ranch:stop_listener(3)[ranch:stop_listener(3)],
link:man:ranch:suspend_listener(3)[ranch:suspend_listener(3)],
link:man:ranch:resume_listener(3)[ranch:resume_listener(3)],
link:man:ranch(3)[ranch(3)]

View File

@ -0,0 +1,61 @@
= ranch(7)
== Name
ranch - Socket acceptor pool for TCP protocols
== Description
Ranch is a socket acceptor pool for TCP protocols.
Ranch manages listeners which are a set of processes that
accept and manage connections. The connection's transport
and protocol modules are configured per listener. Listeners
can be inspected and reconfigured without interruptions in
service.
== Modules
Functions:
* link:man:ranch(3)[ranch(3)] - Socket acceptor pool
* link:man:ranch_proxy_header(3)[ranch_proxy_header(3)] - PROXY protocol
Transports:
* link:man:ranch_ssl(3)[ranch_ssl(3)] - SSL transport
* link:man:ranch_tcp(3)[ranch_tcp(3)] - TCP transport
Behaviors:
* link:man:ranch_protocol(3)[ranch_protocol(3)] - Protocol modules
* link:man:ranch_transport(3)[ranch_transport(3)] - Transport modules
== Dependencies
* ssl - Secure communication over sockets
All these applications must be started before the `ranch`
application. To start Ranch and all dependencies at once:
[source,erlang]
----
{ok, _} = application:ensure_all_started(ranch).
----
== Environment
The `ranch` application defines one application environment
configuration parameter.
profile (false)::
When enabled, Ranch will start `eprof` profiling automatically.
+
You can use the `ranch_app:profile_output/0` function to stop
profiling and output the results to the files 'procs.profile'
and 'total.profile'. Do not use in production.
== See also
ssl(7)

View File

@ -0,0 +1,56 @@
= ranch_protocol(3)
== Name
ranch_protocol - Protocol modules
== Description
The module `ranch_protocol` defines the interface used
by Ranch protocols.
== Callbacks
Ranch protocols implement the following interface:
[source,erlang]
----
start_link(Ref :: ranch:ref(),
Transport :: module(),
ProtoOpts :: any())
-> {ok, ConnPid :: pid()}
| {ok, SupPid :: pid(), ConnPid :: pid()}
----
Start a new connection process.
The only purpose of this callback is to start a process that
will handle the socket. It must spawn the process, link and
then return the new pid. This function will always be called
from inside a supervisor.
This callback can also return two pids. The first pid is the
pid of the process that will be supervised. The second pid is
the pid of the process that will receive ownership of the
socket. This second process must be a child of the first. This
form is only available when `connection_type` is set to
`supervisor`.
If any other value is returned, the supervisor will close the
socket and assume no process has been started.
Do not perform any operations in this callback, as this would
block the supervisor responsible for starting connection
processes and degrade performance severely.
== Changelog
* *2.0*: The second argument `Socket` was removed.
* *1.6*: The second argument `Socket` was deprecated. Call
link:man:ranch:handshake(3)[ranch:handshake(3)]
to obtain the socket.
== See also
link:man:ranch:handshake(3)[ranch:handshake(3)],
link:man:ranch(7)[ranch(7)]

View File

@ -0,0 +1,165 @@
= ranch_proxy_header(3)
== Name
ranch_proxy_header - PROXY protocol
== Description
The module `ranch_proxy_header` provides functions
for parsing and building the PROXY protocol header.
== Exports
* link:man:ranch_proxy_header:parse(3)[ranch_proxy_header:parse(3)] - Parse a PROXY protocol header
* link:man:ranch_proxy_header:header(3)[ranch_proxy_header:header(3)] - Build a PROXY protocol header
* link:man:ranch_proxy_header:to_connection_info(3)[ranch_proxy_header:to_connection_info(3)] - Convert proxy_info() to ssl:connection_info()
== Types
=== proxy_info()
[source,erlang]
----
proxy_info() = #{
%% Mandatory part.
version := 1 | 2,
command := local | proxy,
transport_family => undefined | ipv4 | ipv6 | unix,
transport_protocol => undefined | stream | dgram,
%% Addresses.
src_address => inet:ip_address() | binary(),
src_port => inet:port_number(),
dest_address => inet:ip_address() | binary(),
dest_port => inet:port_number(),
%% Extra TLV-encoded data.
alpn => binary(), %% US-ASCII.
authority => binary(), %% UTF-8.
netns => binary(), %% US-ASCII.
ssl => #{
client := [ssl | cert_conn | cert_sess],
verified := boolean(),
version => binary(), %% US-ASCII.
cipher => binary(), %% US-ASCII.
sig_alg => binary(), %% US-ASCII.
key_alg => binary(), %% US-ASCII.
cn => binary() %% UTF-8.
},
%% Unknown TLVs can't be parsed so the raw data is given.
raw_tlvs => [{0..255, binary()}]
}.
----
The PROXY protocol information.
The following fields may be found, although most of them are
optional:
version::
The PROXY protocol version used.
command::
`proxy` is used for proxied connections. `local` for non-proxied
connections. Those do not have any additional information.
transport_family::
The transport family of the original connection.
transport_protocol::
The transport protocol of the original connection.
src_address::
The source address of the original connection. This is the
original address of the client.
src_port::
The source port of the original connection. This is the
port the client opened on its end for the connection. It
is not defined for UNIX domain sockets.
dest_address::
The destination address of the original connection.
dest_port::
The destination port of the original connection. It
is not defined for UNIX domain sockets.
alpn::
The upper layer protocol in use over the connection. This
is typically negotiated via the ALPN extension for TLS.
authority::
The host name serving as authority for the connection.
This is typically passed using the SNI extension for TLS.
netns::
The namespace's name for the original connection.
ssl::
Various information pertaining to the original SSL/TLS
connection.
client:::
A list containing a number of flags. `ssl` indicates
that the client connected over SSL/TLS. `cert_conn`
indicates that the client provided a certificate over
the original connection. `cert_sess` indicates that
the client provided a certificate at least once over
the TLS session this connection belongs to.
verified:::
Whether the client presented a certificate and it was
successfully verified.
version:::
The US-ASCII string containing the SSL/TLS version
used for the original connection.
cipher:::
The US-ASCII string name of the cipher used.
sig_alg:::
The US-ASCII string name of the algorithm used to sign
the certificate provided by the client.
key_alg:::
The US-ASCII string name of the algorithm used to generate
the key of the certificate provided by the client.
cn:::
The UTF-8 string representation of the Common Name field
of the client certificate's Distinguished Name.
raw_tlvs::
The non-standard TLVs that Ranch was not able to parse.
== Changelog
* *1.7*: Module introduced.
== See also
link:man:ranch(7)[ranch(7)]

View File

@ -0,0 +1,75 @@
= ranch_proxy_header:header(3)
== Name
ranch_proxy_header:header - Build a PROXY protocol header
== Description
[source,erlang]
----
header(ProxyInfo) -> header(ProxyInfo, #{})
header(ProxyInfo, BuildOpts) -> iodata()
ProxyInfo :: ranch_proxy_header:proxy_info()
BuildOpts :: #{
checksum => crc32c,
padding => pos_integer() %% >= 3
}
----
Build a PROXY protocol header.
== Arguments
ProxyInfo::
The proxy information to encode.
BuildOpts::
Options to control whether to add a checksum or padding
should be included in the encoded PROXY protocol header.
== Return value
The PROXY protocol header is returned.
== Changelog
* *1.7*: Function introduced.
== Examples
.Build a PROXY protocol header
[source,erlang]
----
ProxyInfo = #{
version => 2,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {192, 168, 1, 11},
src_port => 54321,
dest_address => {192, 168, 1, 42},
dest_port => 443
},
Data = ranch_proxy_header:parse(ProxyInfo).
----
.Build a PROXY protocol header with checksum and padding
[source,erlang]
----
Data = ranch_proxy_header:parse(ProxyInfo, #{
checksum => crc32c,
padding => 7
}).
----
== See also
link:man:ranch_proxy_header:header(3)[ranch_proxy_header:header(3)],
link:man:ranch_proxy_header(3)[ranch_proxy_header(3)]

View File

@ -0,0 +1,50 @@
= ranch_proxy_header:parse(3)
== Name
ranch_proxy_header:parse - Parse a PROXY protocol header
== Description
[source,erlang]
----
parse(Data :: binary())
-> {ok, ranch_proxy_header:proxy_info(), Rest :: binary()}
| {error, HumanReadable :: atom()}
----
Parse a PROXY protocol header.
== Arguments
Data::
The PROXY protocol header optionally followed by more data.
== Return value
An `ok` tuple is returned on success, containing the proxy
information found in the header and the rest of the data
if more was provided.
An `error` tuple is returned when a protocol error is
detected. It contains a human readable message about the
error.
== Changelog
* *1.7*: Function introduced.
== Examples
.Parse the PROXY protocol header
[source,erlang]
----
{ok ProxyInfo, Rest} = ranch_proxy_header:parse(Data).
----
== See also
link:man:ranch_proxy_header:header(3)[ranch_proxy_header:header(3)],
link:man:ranch_proxy_header:to_connection_info(3)[ranch_proxy_header:to_connection_info(3)],
link:man:ranch_proxy_header(3)[ranch_proxy_header(3)]

View File

@ -0,0 +1,49 @@
= ranch_proxy_header:to_connection_info(3)
== Name
ranch_proxy_header:to_connection_info - Convert proxy_info() to ssl:connection_info()
== Description
[source,erlang]
----
to_connection_info(ProxyInfo :: proxy_info())
-> ssl:connection_info()
----
Convert `ranch_proxy_header:proxy_info()` information
to the `ssl:connection_info()` format returned by
`ssl:connection_information/1,2`.
== Arguments
ProxyInfo::
The PROXY protocol information.
== Return value
Connection information is returned as a proplist.
Because the PROXY protocol header includes limited
information, only the keys `protocol`, `selected_cipher_suite`
and `sni_hostname` will be returned, at most. All keys
are optional.
== Changelog
* *2.1*: Function introduced.
== Examples
.Convert the PROXY protocol information
[source,erlang]
----
ConnInfo = ranch_proxy_header:to_connection_info(ProxyInfo).
----
== See also
link:man:ranch_proxy_header:parse(3)[ranch_proxy_header:parse(3)],
link:man:ranch_proxy_header(3)[ranch_proxy_header(3)]

View File

@ -0,0 +1,333 @@
= ranch_ssl(3)
== Name
ranch_ssl - SSL transport
== Description
The module `ranch_ssl` implements an SSL Ranch transport.
== Exports
The module `ranch_ssl` implements the interface defined
by link:man:ranch_transport(3)[ranch_transport(3)].
== Types
=== opt()
[source,erlang]
----
opt() :: ranch_tcp:opt() | ssl_opt()
----
Listen options.
The TCP options are defined in link:man:ranch_tcp(3)[ranch_tcp(3)].
=== opts()
[source,erlang]
----
opts() :: [opt()]
----
List of listen options.
=== ssl_opt()
[source,erlang]
----
ssl_opt() = {alpn_preferred_protocols, [binary()]}
| {anti_replay, '10k' | '100k' | {integer(), integer(), integer()}}
| {beast_mitigation, one_n_minus_one | zero_n | disabled}
| {cacertfile, file:filename()}
| {cacerts, [public_key:der_encoded()]}
| {cert, public_key:der_encoded()}
| {certfile, file:filename()}
| {ciphers, ssl:ciphers()}
| {client_renegotiation, boolean()}
| {crl_cache, [any()]}
| {crl_check, boolean() | peer | best_effort}
| {depth, integer()}
| {dh, binary()}
| {dhfile, file:filename()}
| {eccs, [ssl:named_curve()]}
| {fail_if_no_peer_cert, boolean()}
| {handshake, hello | full}
| {hibernate_after, timeout()}
| {honor_cipher_order, boolean()}
| {honor_ecc_order, boolean()}
| {key, ssl:key()}
| {key_update_at, pos_integer()}
| {keyfile, file:filename()}
| {log_alert, boolean()}
| {log_level, logger:level()}
| {max_handshake_size, integer()}
| {middlebox_comp_mode, boolean()}
| {next_protocols_advertised, [binary()]}
| {padding_check, boolean()}
| {partial_chain, fun()}
| {password, string()}
| {protocol, tls | dtls}
| {psk_identity, string()}
| {reuse_session, fun()}
| {reuse_sessions, boolean()}
| {secure_renegotiate, boolean()}
| {session_tickets, disabled | stateful | stateless}
| {signature_algs, [{ssl:hash(), ssl:sign_algo()}]}
| {signature_algs_cert, [ssl:sign_scheme()]}
| {sni_fun, fun()}
| {sni_hosts, [{string(), ssl_opt()}]}
| {supported_groups, [ssl:group()]}
| {user_lookup_fun, {fun(), any()}}
| {verify, verify_none | verify_peer}
| {verify_fun, {fun(), any()}}
| {versions, [ssl:protocol_version()]}
----
SSL-specific listen options.
Specifying a certificate is mandatory, either through the `cert`
or `certfile` option, or by configuring SNI. None of the other
options are required.
The default value is given next to the option name:
alpn_preferred_protocols::
Perform Application-Layer Protocol Negotiation
with the given list of preferred protocols.
anti_replay::
Configures the server's built-in anti replay feature based on
Bloom filters.
beast_mitigation (one_n_minus_one)::
Change the BEAST mitigation strategy for SSL-3.0 and TLS-1.0
to interoperate with legacy software.
cacertfile::
Path to PEM encoded trusted certificates file used to verify
peer certificates.
cacerts::
List of DER encoded trusted certificates.
cert::
DER encoded user certificate.
certfile::
Path to the PEM encoded user certificate file. May also
contain the private key.
ciphers::
List of ciphers that clients are allowed to use.
client_renegotiation (true)::
Whether to allow client-initiated renegotiation.
crl_cache ({ssl_crl_cache, {internal, []}})::
Customize the module used to cache Certificate Revocation Lists.
crl_check (false)::
Whether to perform CRL check on all certificates in the chain
during validation.
depth (1)::
Maximum of intermediate certificates allowed in the
certification path.
dh::
DER encoded Diffie-Hellman parameters.
dhfile::
Path to the PEM encoded Diffie-Hellman parameters file.
eccs::
List of named ECC curves.
fail_if_no_peer_cert (false)::
Whether to refuse the connection if the client sends an
empty certificate.
handshake (full)::
If `hello` is specified for this option, the handshake is
paused after receiving the client hello message. The handshake
can then be resumed via `handshake_continue/3`, or cancelled
via `handshake_cancel/1`.
+
This option cannot be given to `ranch:handshake/1,2`.
hibernate_after (undefined)::
Time in ms after which SSL socket processes go into
hibernation to reduce memory usage.
honor_cipher_order (false)::
If true, use the server's preference for cipher selection.
If false, use the client's preference.
honor_ecc_order (false)::
If true, use the server's preference for ECC curve selection.
If false, use the client's preference.
key::
DER encoded user private key.
key_update_at::
Configures the maximum amount of bytes that can be sent on a
TLS 1.3 connection before an automatic key update is performed.
keyfile::
Path to the PEM encoded private key file, if different from
the certfile.
log_alert (true)::
If false, error reports will not be displayed.
log_level::
Specifies the log level for TLS/DTLS.
max_handshake_size (256*1024)::
Used to limit the size of valid TLS handshake packets to
avoid DoS attacks.
middlebox_comp_mode (true)::
Configures the middlebox compatibility mode on a TLS 1.3
connection.
next_protocols_advertised::
List of protocols to send to the client if it supports the
Next Protocol extension.
padding_check::
Allow disabling the block cipher padding check for TLS-1.0
to be able to interoperate with legacy software.
partial_chain::
Claim an intermediate CA in the chain as trusted.
password::
Password to the private key file, if password protected.
protocol (tls)::
Choose TLS or DTLS protocol for the transport layer security.
psk_identity::
Provide the given PSK identity hint to the client during the
handshake.
reuse_session::
Custom policy to decide whether a session should be reused.
reuse_sessions (false)::
Whether to allow session reuse.
secure_renegotiate (false)::
Whether to reject renegotiation attempts that do not conform
to RFC5746.
session_tickets::
Configures the session ticket functionality.
signature_algs::
The TLS signature algorithm extension may be used, from TLS 1.2,
to negotiate which signature algorithm to use during the TLS
handshake.
signature_algs_cert::
List of signature schemes for the signature_algs_cert extension
introduced in TLS 1.3, in order to make special requirements
on signatures used in certificates.
sni_fun::
Function called when the client requests a host using Server
Name Indication. Returns options to apply.
sni_hosts::
Options to apply for the host that matches what the client
requested with Server Name Indication.
supported_groups([x25519, x448, secp256r1, secp384r1])::
TLS 1.3 introduces the `supported_groups` extension that is
used for negotiating the Diffie-Hellman parameters in a
TLS 1.3 handshake. Both client and server can specify a list
of parameters that they are willing to use.
user_lookup_fun::
Function called to determine the shared secret when using PSK,
or provide parameters when using SRP.
verify (verify_none)::
Use `verify_peer` to request a certificate from the client.
verify_fun::
Custom policy to decide whether a client certificate is valid.
versions::
TLS protocol versions that will be supported.
Note that the client will not send a certificate unless the
value for the `verify` option is set to `verify_peer`. This
means that `fail_if_no_peer_cert` only applies when combined
with the `verify` option. The `verify_fun` option allows
greater control over the client certificate validation.
The options `sni_fun` and `sni_hosts` are mutually exclusive.
== Changelog
* *2.0*: The `ssl_opt()` type was updated for OTP-23.0.
== See also
link:man:ranch(7)[ranch(7)],
link:man:ranch_transport(3)[ranch_transport(3)],
link:man:ranch_tcp(3)[ranch_tcp(3)],
ssl(3)

View File

@ -0,0 +1,194 @@
= ranch_tcp(3)
== Name
ranch_tcp - TCP transport
== Description
The module `ranch_tcp` implements a TCP Ranch transport.
The function `sendfile` may not work correctly when used
against files stored in a VirtualBox shared folder.
== Exports
The module `ranch_tcp` implements the interface defined
by link:man:ranch_transport(3)[ranch_transport(3)].
== Types
=== opt()
[source,erlang]
----
opt() = {backlog, non_neg_integer()}
| {buffer, non_neg_integer()}
| {delay_send, boolean()}
| {dontroute, boolean()}
| {exit_on_close, boolean()}
| {fd, non_neg_integer()}
| {high_msgq_watermark, non_neg_integer()}
| {high_watermark, non_neg_integer()}
| inet
| inet6
| {ip, inet:ip_address() | inet:local_address()}
| {ipv6_v6only, boolean()}
| {keepalive, boolean()}
| {linger, {boolean(), non_neg_integer()}}
| {low_msgq_watermark, non_neg_integer()}
| {low_watermark, non_neg_integer()}
| {nodelay, boolean()}
| {port, inet:port_number()}
| {priority, integer()}
| {raw, non_neg_integer(), non_neg_integer(), binary()}
| {recbuf, non_neg_integer()}
| {send_timeout, timeout()}
| {send_timeout_close, boolean()}
| {sndbuf, non_neg_integer()}
| {tos, integer()}
----
Listen options.
Note that additional options may be set by the protocol
module using `Transport:setopts/2`.
None of the options are required.
Please consult the `gen_tcp` and `inet` manuals for a more
thorough description of these options. This manual only aims
to provide a short description along with what the defaults
are. Defaults may be different in Ranch compared to `gen_tcp`.
Defaults are given next to the option name:
backlog (1024)::
Max length of the queue of pending connections.
buffer::
Size of the buffer used by the Erlang driver. Default
is system-dependent.
delay_send (false)::
Always queue data before sending, to send fewer, larger
packets over the network.
dontroute (false)::
Don't send via a gateway, only send to directly connected hosts.
exit_on_close (true)::
Disable to allow sending data after a close has been detected.
fd::
File descriptor of the socket, if it was opened externally.
high_msgq_watermark (8192)::
Limit in the amount of data in the socket message queue before
the queue becomes busy.
high_watermark (8192)::
Limit in the amount of data in the ERTS socket implementation's
queue before the socket becomes busy.
inet::
Set up the socket for IPv4.
inet6::
Set up the socket for IPv6.
ip::
Interface to listen on. Listen on all network interfaces by default.
On UNIX systems, it is also possible to use a UNIX Domain
socket file by specifying `{local, SocketFile}`.
ipv6_v6only (false)::
Listen on IPv4 and IPv6 (false) or only on IPv6 (true).
Use with inet6.
keepalive (false)::
Enable sending of keep-alive messages.
linger ({false, 0})::
Whether to wait and how long to flush data sent before closing
the socket.
low_msgq_watermark (4096)::
Amount of data in the socket message queue before the queue
leaves busy state.
low_watermark (4096)::
Amount of data in the ERTS socket implementation's queue
before the socket leaves busy state.
nodelay (true)::
Whether to enable TCP_NODELAY.
port (0)::
TCP port number to listen on. 0 means a random port will be used.
priority (0)::
Priority value for all packets to be sent on this socket.
recbuf::
Minimum size of the socket's receive buffer.
Default is system-dependent.
send_timeout (30000)::
How long the send call may wait for confirmation before returning.
send_timeout_close (true)::
Whether to close the socket when the confirmation wasn't received.
sndbuf::
Minimum size of the socket's send buffer.
Default is system-dependent.
tos::
Value for the IP_TOS IP level option. Use with caution.
In addition, the `raw` option can be used to set system-specific
options by specifying the protocol level, the option number and
the actual option value specified as a binary. This option is not
portable. Use with caution.
=== opts()
[source,erlang]
----
opts() :: [opt()]
----
List of listen options.
== See also
link:man:ranch(7)[ranch(7)],
link:man:ranch_transport(3)[ranch_transport(3)],
link:man:ranch_ssl(3)[ranch_ssl(3)],
gen_tcp(3),
inet(3)

View File

@ -0,0 +1,355 @@
= ranch_transport(3)
== Name
ranch_transport - Transport modules
== Description
The module `ranch_transport` defines the interface used
by Ranch transports.
== Callbacks
Ranch transports implement the following interface:
=== accept
[source,erlang]
----
accept(LSocket :: socket(), Timeout :: timeout())
-> {ok, Socket :: socket()}
| {error, closed | timeout | atom()}
----
Use the listening socket returned by `listen/1`
to accept a new connection. The timeout is specified
in milliseconds.
=== close
[source,erlang]
----
close(Socket :: socket()) -> ok
----
Close the socket.
=== controlling_process
[source,erlang]
----
controlling_process(Socket :: socket(), Pid :: pid())
-> ok | {error, closed | not_owner | atom()}
----
Assign a new controlling process to the socket. The
controlling process is the process that is linked to
and receives messages from the socket.
=== getopts
[source,erlang]
----
getopts(Socket :: socket(), SockOpts :: [atom()])
-> {ok, any()} | {error, atom()}
----
Get one or more options for the socket.
=== getstat
[source,erlang]
----
getstat(Socket :: socket())
-> {ok, SockStatValues :: any()} | {error, atom()}
----
Get all statistics for the socket.
[source,erlang]
----
getstat(Socket :: socket(), SockStats :: [atom()])
-> {ok, SockStatValues :: any()} | {error, atom()}
----
Get one or more statistic options for the socket.
=== handshake
[source,erlang]
----
handshake(Socket0 :: socket(),
Timeout :: timeout())
-> {ok, Socket :: socket()}
| {ok, Socket :: socket(), Info :: any()}
| {error, any()}
handshake(Socket0 :: socket(),
SockOpts :: opts(),
Timeout :: timeout())
-> {ok, Socket :: socket()}
| {ok, Socket :: socket(), Info :: any()}
| {error, any()}
----
Perform the transport-level handshake.
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.
If the handshake is completed by this call, the function will
return `{ok, Socket}`. However, some transports (notably,
`ranch_ssl` if `{handshake, hello}` is specified in the socket
options) may pause the handshake at a certain point and return
`{ok, Socket, Info}` instead, in order to allow for
additional decisions to be made before resuming the handshake
with `handshake_continue/3` or cancelling it with
`handshake_cancel/1`.
This function may also be used to upgrade a connection
from a transport to another depending on the capabilities
of the transports. For example a `ranch_tcp` socket may
be upgraded to a `ranch_ssl` one using this function.
=== handshake_continue
[source,erlang]
----
handshake_continue(Socket0 :: socket(),
Timeout :: timeout())
-> {ok, Socket :: socket()}
| {error, any()}
handshake_continue(Socket0 :: socket(),
SockOpts :: opts(),
Timeout :: timeout())
-> {ok, Socket :: socket()}
| {error, any()}
----
Resume the paused transport-level handshake and return a socket
that is ready to use.
This function will be called by connection processes
to resume a paused handshake.
=== handshake_cancel
[source,erlang]
----
handshake_cancel(Socket :: socket()) -> ok
----
Cancel the paused transport-level handshake.
=== listen
[source,erlang]
----
listen(TransportOpts :: ranch:transport_opts(any()))
-> {ok, LSocket :: socket()} | {error, atom()}
----
Create a socket that listens on the port given in the
socket options.
The port may not be specified or may be set to 0, which
means a random available port number will be chosen.
=== messages
[source,erlang]
----
messages()
-> {OK :: atom(),
Closed :: atom(),
Error :: atom(),
Passive :: atom()}
----
Return the tuple keys for the messages sent by the socket.
=== name
[source,erlang]
----
name() -> Name :: atom()
----
Return the name of the transport.
=== peername
[source,erlang]
----
peername(Socket :: socket())
-> {ok, {inet:ip_address(), inet:port_number()}}
| {local, binary()} | {error, atom()}.
----
Return the address and port number for the other end of
the connection.
For UNIX Domain sockets the return value will be
`{local, PeerSocket}`, with `PeerSocket` typically
an empty binary.
=== recv
[source,erlang]
----
recv(Socket :: socket(),
Length :: non_neg_integer(),
Timeout :: timeout())
-> {ok, Packet :: any()}
| {error, closed | timeout | atom()}
----
Receive a packet from the socket in passive mode.
Attempting to receive data from a socket that is
in active mode will return an error.
A length of 0 will return the data available on
the socket as soon as possible, regardless of length.
While it is possible to use the timeout value `infinity`,
it is highly discouraged as it could cause your process
to get stuck waiting for data that will never come. This may
happen when a socket becomes half-open due to a crash of the
remote endpoint. Wi-Fi going down is another common culprit.
=== secure
[source,erlang]
----
secure() -> boolean()
----
Return whether the transport can be used for secure connections.
=== send
[source,erlang]
----
send(Socket :: socket(), Packet :: iodata())
-> ok | {error, atom()}
----
Send a packet on the socket.
=== sendfile
[source,erlang]
----
sendfile(Socket, File)
-> sendfile(Socket, File, 0, 0, [])
sendfile(Socket, File, Offset, Bytes)
-> sendfile(Socket, File, Offset, Bytes, [])
sendfile(Socket :: socket(),
File :: file:name_all() | file:fd(),
Offset :: non_neg_integer(),
Bytes :: non_neg_integer(),
Opts :: sendfile_opts())
-> {ok, SentBytes :: non_neg_integer()} | {error, atom()}
----
Send a file on the socket.
The file may be sent full or in parts, and may be specified
by its filename or by an already open file descriptor.
Transports that manipulate TCP directly may use the
`file:sendfile/2,4,5` function, which calls the `sendfile`
syscall where applicable (on Linux, for example). Other
transports can use the `sendfile/6` function exported from
this module.
=== setopts
[source,erlang]
----
setopts(Socket :: socket(), SockOpts :: any())
-> ok | {error, atom()}
----
Set one or more options for the socket.
=== shutdown
[source,erlang]
----
shutdown(Socket :: socket(),
How :: read | write | read_write)
-> ok | {error, atom()}
----
Close the socket for reading and/or writing.
=== sockname
[source,erlang]
----
sockname(Socket :: socket())
-> {ok, {inet:ip_address(), inet:port_number()}}
| {error, atom()}.
----
Return the address and port number for the local end
of the connection.
For UNIX Domain sockets the return value will be
`{local, SocketFile}`.
== Exports
The following function can be used when implementing
transport modules:
* link:man:ranch_transport:sendfile(3)[ranch_transport:sendfile(3)] - Send a file on the socket
== Types
=== sendfile_opts()
[source,erlang]
----
sendfile_opts() :: [{chunk_size, non_neg_integer()}]
----
Options accepted by the sendfile function and callbacks:
chunk_size (8191)::
The chunk size, in bytes.
=== socket()
[source,erlang]
----
socket() :: any()
----
The socket.
The exact type will vary depending on the transport module.
== Changelog
* *2.0*: The callback `listen/1` has changed to accept a map of
transport options instead of socket options.
* *2.0*: The callback `messages/0` return value was updated to
include the passive message for `{active, N}`.
* *1.6*: The `socket()` type was added for documentation purposes.
* *1.6*: The type of the sendfile filename was extended.
== See also
link:man:ranch(7)[ranch(7)],
link:man:ranch_tcp(3)[ranch_tcp(3)],
link:man:ranch_ssl(3)[ranch_ssl(3)]

View File

@ -0,0 +1,84 @@
= ranch_transport:sendfile(3)
== Name
ranch_transport:sendfile - Send a file on the socket
== Description
[source,erlang]
----
sendfile(Transport :: module(),
Socket :: ranch_transport:socket(),
File :: file:name_all() | file:fd(),
Offset :: non_neg_integer(),
Bytes :: non_neg_integer(),
Opts :: ranch_transport:sendfile_opts())
-> {ok, SentBytes :: non_neg_integer()} | {error, atom()}
----
Send a file on the socket.
The file may be sent full or in parts, and may be specified
by its filename or by an already open file descriptor.
This function emulates the function `file:sendfile/2,4,5`
and may be used when transports are not manipulating TCP
directly.
== Arguments
Transport::
The transport module.
Socket::
The socket.
File::
The filename or file descriptor for the file to be sent.
Offset::
Start position in the file, in bytes.
Bytes::
Length in bytes.
Opts::
Additional options.
== Return value
The number of bytes actually sent is returned on success
inside an `ok` tuple.
An `error` tuple is returned otherwise.
== Changelog
* *1.6*: The type of the `File` argument was extended.
== Examples
.Implement Transport:sendfile using the fallback
[source,erlang]
----
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
sendfile(Socket, File, Offset, Bytes, Opts) ->
ranch_transport:sendfile(?MODULE, Socket,
File, Offset, Bytes, Opts).
----
== See also
link:man:ranch_transport(3)[ranch_transport(3)]

9
ranch/ebin/ranch.app Normal file
View File

@ -0,0 +1,9 @@
{application, 'ranch', [
{description, "Socket acceptor pool for TCP protocols."},
{vsn, "2.1.0"},
{modules, ['ranch','ranch_acceptor','ranch_acceptors_sup','ranch_app','ranch_conns_sup','ranch_conns_sup_sup','ranch_crc32c','ranch_embedded_sup','ranch_listener_sup','ranch_protocol','ranch_proxy_header','ranch_server','ranch_server_proxy','ranch_ssl','ranch_sup','ranch_tcp','ranch_transport']},
{registered, [ranch_sup,ranch_server]},
{applications, [kernel,stdlib,ssl]},
{mod, {ranch_app, []}},
{env, []}
]}.

View File

@ -0,0 +1,4 @@
PROJECT = tcp_echo
DEPS = ranch
dep_ranch_commit = master
include ../../erlang.mk

View File

@ -0,0 +1,27 @@
Ranch TCP echo example
======================
To try this example, you need GNU `make` and `git` in your PATH.
To build the example, run the following command:
``` bash
$ make
```
To start the release in the foreground:
``` bash
$ ./_rel/tcp_echo_example/bin/tcp_echo_example console
```
Then start a telnet session to port 5555:
``` bash
$ telnet localhost 5555
```
Type in a few words and see them echoed back.
Be aware that there is a timeout of 5 seconds without receiving
data before the example server disconnects your session.

View File

@ -0,0 +1,2 @@
{release, {tcp_echo_example, "1"}, [tcp_echo, sasl]}.
{extended_start_script, true}.

View File

@ -0,0 +1,24 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(echo_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(Ref),
loop(Socket, Transport).
loop(Socket, Transport) ->
case Transport:recv(Socket, 0, 60000) of
{ok, Data} when Data =/= <<4>> ->
Transport:send(Socket, Data),
loop(Socket, Transport);
_ ->
ok = Transport:close(Socket)
end.

View File

@ -0,0 +1,20 @@
%% Feel free to use, reuse and abuse the code in this file.
%% @private
-module(tcp_echo_app).
-behaviour(application).
%% API.
-export([start/2]).
-export([stop/1]).
%% API.
start(_Type, _Args) ->
{ok, _} = ranch:start_listener(tcp_echo,
ranch_tcp, #{socket_opts => [{port, 5555}]},
echo_protocol, []),
tcp_echo_sup:start_link().
stop(_State) ->
ok.

View File

@ -0,0 +1,22 @@
%% Feel free to use, reuse and abuse the code in this file.
%% @private
-module(tcp_echo_sup).
-behaviour(supervisor).
%% API.
-export([start_link/0]).
%% supervisor.
-export([init/1]).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% supervisor.
init([]) ->
{ok, {{one_for_one, 10, 10}, []}}.

View File

@ -0,0 +1,4 @@
PROJECT = tcp_reverse
DEPS = ranch
dep_ranch_commit = master
include ../../erlang.mk

View File

@ -0,0 +1,33 @@
Ranch TCP reverse example
=========================
This example uses a `gen_statem` to handle a protocol to revese input.
See `reverse_protocol.erl` for the implementation. Documentation about
this topic can be found in the guide:
http://ninenines.eu/docs/en/ranch/HEAD/guide/protocols/#using_gen_statem
To try this example, you need GNU `make` and `git` in your PATH.
To build the example, run the following command:
``` bash
$ make
```
To start the release in the foreground:
``` bash
$ ./_rel/tcp_reverse_example/bin/tcp_reverse_example console
```
Then start a telnet session to port 5555:
``` bash
$ telnet localhost 5555
```
Type in a few words and see them reversed! Amazing!
Be aware that there is a timeout of 5 seconds without receiving
data before the example server disconnects your session.

View File

@ -0,0 +1,2 @@
{release, {tcp_reverse_example, "1"}, [tcp_reverse, sasl]}.
{extended_start_script, true}.

View File

@ -0,0 +1,87 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(reverse_protocol).
-behaviour(gen_statem).
-behaviour(ranch_protocol).
%% API.
-export([start_link/3]).
%% gen_statem.
-export([callback_mode/0]).
-export([init/1]).
-export([connected/3]).
-export([terminate/3]).
-export([code_change/4]).
-define(TIMEOUT, 60000).
-record(state, {ref, transport, socket}).
%% API.
start_link(Ref, Transport, Opts) ->
gen_statem:start_link(?MODULE, {Ref, Transport, Opts}, []).
%% gen_statem.
callback_mode() ->
[state_functions, state_enter].
init({Ref, Transport, _Opts = []}) ->
{ok, connected, #state{ref=Ref, transport=Transport}, ?TIMEOUT}.
connected(enter, connected, StateData=#state{
ref=Ref, transport=Transport}) ->
{ok, Socket} = ranch:handshake(Ref),
ok = Transport:setopts(Socket, [{active, once}, {packet, line}]),
{keep_state, StateData#state{socket=Socket}};
connected(info, {tcp, Socket, Data}, _StateData=#state{
socket=Socket, transport=Transport})
when byte_size(Data) >= 1 ->
Transport:setopts(Socket, [{active, once}]),
Transport:send(Socket, reverse_binary(Data)),
{keep_state_and_data, ?TIMEOUT};
connected(info, {tcp_closed, _Socket}, _StateData) ->
{stop, normal};
connected(info, {tcp_error, _, Reason}, _StateData) ->
{stop, Reason};
connected({call, From}, _Request, _StateData) ->
gen_statem:reply(From, ok),
keep_state_and_data;
connected(cast, _Msg, _StateData) ->
keep_state_and_data;
connected(timeout, _Msg, _StateData) ->
{stop, normal};
connected(_EventType, _Msg, _StateData) ->
{stop, normal}.
terminate(Reason, StateName, StateData=#state{
socket=Socket, transport=Transport})
when Socket=/=undefined, Transport=/=undefined ->
catch Transport:close(Socket),
terminate(Reason, StateName,
StateData#state{socket=undefined, transport=undefined});
terminate(_Reason, _StateName, _StateData) ->
ok.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%% Internal.
reverse_binary(B0) when is_binary(B0) ->
Size = bit_size(B0),
<<B1:Size/integer-little>> = B0,
case <<B1:Size/integer-big>> of
%% Take care of different possible line terminators.
<<$\n, $\r, B2/binary>> ->
%% CR/LF (Windows)
<<B2/binary, $\r, $\n>>;
<<$\n, B2/binary>> ->
%% LF (Linux, Mac OS X and later)
<<B2/binary, $\n>>;
<<$\r, B2/binary>> ->
%% CR (Mac Classic, ie prior to Mac OS X)
<<B2/binary, $\r>>
end.

View File

@ -0,0 +1,20 @@
%% Feel free to use, reuse and abuse the code in this file.
%% @private
-module(tcp_reverse_app).
-behaviour(application).
%% API.
-export([start/2]).
-export([stop/1]).
%% API.
start(_Type, _Args) ->
{ok, _} = ranch:start_listener(tcp_reverse,
ranch_tcp, #{socket_opts => [{port, 5555}]},
reverse_protocol, []),
tcp_reverse_sup:start_link().
stop(_State) ->
ok.

View File

@ -0,0 +1,22 @@
%% Feel free to use, reuse and abuse the code in this file.
%% @private
-module(tcp_reverse_sup).
-behaviour(supervisor).
%% API.
-export([start_link/0]).
%% supervisor.
-export([init/1]).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% supervisor.
init([]) ->
{ok, {{one_for_one, 10, 10}, []}}.

20
ranch/src/Makefile Normal file
View File

@ -0,0 +1,20 @@
.PHONY: all
.SUFFIXES: .erl .beam
ERLC?= erlc -server -pa ${PWD}
ERLFLAGS+= +warn_missing_spec +warn_untyped_record
OBJS= ranch_transport.beam ranch.beam ranch_acceptor.beam ranch_acceptors_sup.beam
OBJS+= ranch_conns_sup.beam ranch_conns_sup_sup.beam ranch_crc32c.beam
OBJS+= ranch_embedded_sup.beam ranch_listener_sup.beam ranch_protocol.beam
OBJS+= ranch_proxy_header.beam ranch_server.beam ranch_server_proxy.beam
OBJS+= ranch_ssl.beam ranch_sup.beam ranch_tcp.beam ranch_app.beam
all: ${OBJS}
.erl.beam:
${ERLC} ${ERLOPTS} ${ERLFLAGS} $<
clean:
rm -f *.beam

104
ranch/src/ranch.appup Normal file
View File

@ -0,0 +1,104 @@
%% ranch_conns_sup is a custom supervisor (special process),
%% not an OTP supervisor. When "supervisor" is used, the
%% module is always loaded before calling system_code_change,
%% for both upgrade and downgrade operations. This is so that
%% the supervisor's init callback of the upgraded/downgraded
%% module gets called. But the custom supervisors in Ranch
%% do not have an init callback and therefore can function
%% like other special processes: when upgrading, load the
%% module then call system_code_change; and when downgrading,
%% call system_code_change and then load the module.
{"2.1.0",
[{<<"2\\.0\\.[0-9]+.*">>, [
{apply, {ranch, stop_all_acceptors, []}},
{load_module, ranch},
{load_module, ranch_acceptor},
{update, ranch_acceptors_sup, supervisor},
{load_module, ranch_app},
{update, ranch_server, {advanced, []}},
{update, ranch_conns_sup_sup, supervisor},
%% See comments at the top of the file about ranch_conns_sup.
{update, ranch_conns_sup, {advanced, []}},
{load_module, ranch_crc32c},
{update, ranch_embedded_sup, supervisor},
{update, ranch_listener_sup, supervisor},
{load_module, ranch_protocol},
{load_module, ranch_proxy_header},
{update, ranch_server_proxy, {advanced, []}},
{load_module, ranch_ssl},
{update, ranch_sup, supervisor},
{load_module, ranch_tcp},
{load_module, ranch_transport},
{apply, {ranch, restart_all_acceptors, []}}
]}],
[{<<"2\\.0\\.[0-9]+.*">>, [
{apply, {ranch, stop_all_acceptors, []}},
{load_module, ranch},
{load_module, ranch_acceptor},
{update, ranch_acceptors_sup, supervisor},
{load_module, ranch_app},
%% See comments at the top of the file about ranch_conns_sup.
{update, ranch_conns_sup, {advanced, []}},
{update, ranch_conns_sup_sup, supervisor},
{load_module, ranch_crc32c},
{update, ranch_embedded_sup, supervisor},
{update, ranch_listener_sup, supervisor},
{load_module, ranch_protocol},
{load_module, ranch_proxy_header},
{update, ranch_server, {advanced, []}},
{update, ranch_server_proxy, {advanced, []}},
{load_module, ranch_ssl},
{update, ranch_sup, supervisor},
{load_module, ranch_tcp},
{load_module, ranch_transport},
{apply, {ranch, restart_all_acceptors, []}}
]}]
}.
{"2.0.0",
[{<<"2\\.0\\.[0-9]+.*">>, [
{apply, {ranch, stop_all_acceptors, []}},
{load_module, ranch},
{load_module, ranch_acceptor},
{update, ranch_acceptors_sup, supervisor},
{load_module, ranch_app},
{update, ranch_server, {advanced, []}},
{update, ranch_conns_sup_sup, supervisor},
%% See comments at the top of the file about ranch_conns_sup.
{update, ranch_conns_sup, {advanced, []}},
{load_module, ranch_crc32c},
{update, ranch_embedded_sup, supervisor},
{update, ranch_listener_sup, supervisor},
{load_module, ranch_protocol},
{load_module, ranch_proxy_header},
{update, ranch_server_proxy, {advanced, []}},
{load_module, ranch_ssl},
{update, ranch_sup, supervisor},
{load_module, ranch_tcp},
{load_module, ranch_transport},
{apply, {ranch, restart_all_acceptors, []}}
]}],
[{<<"2\\.0\\.[0-9]+.*">>, [
{apply, {ranch, stop_all_acceptors, []}},
{load_module, ranch},
{load_module, ranch_acceptor},
{update, ranch_acceptors_sup, supervisor},
{load_module, ranch_app},
%% See comments at the top of the file about ranch_conns_sup.
{update, ranch_conns_sup, {advanced, []}},
{update, ranch_conns_sup_sup, supervisor},
{load_module, ranch_crc32c},
{update, ranch_embedded_sup, supervisor},
{update, ranch_listener_sup, supervisor},
{load_module, ranch_protocol},
{load_module, ranch_proxy_header},
{update, ranch_server, {advanced, []}},
{update, ranch_server_proxy, {advanced, []}},
{load_module, ranch_ssl},
{update, ranch_sup, supervisor},
{load_module, ranch_tcp},
{load_module, ranch_transport},
{apply, {ranch, restart_all_acceptors, []}}
]}]
}.

625
ranch/src/ranch.erl Normal file
View File

@ -0,0 +1,625 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
%%
%% 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.
-module(ranch).
-export([start_listener/5]).
-export([normalize_opts/1]).
-export([stop_listener/1]).
-export([suspend_listener/1]).
-export([resume_listener/1]).
-export([stop_all_acceptors/0]).
-export([restart_all_acceptors/0]).
-export([child_spec/5]).
-export([handshake/1]).
-export([handshake/2]).
-export([handshake_continue/1]).
-export([handshake_continue/2]).
-export([handshake_cancel/1]).
-export([recv_proxy_header/2]).
-export([remove_connection/1]).
-export([get_status/1]).
-export([get_addr/1]).
-export([get_port/1]).
-export([get_max_connections/1]).
-export([set_max_connections/2]).
-export([get_transport_options/1]).
-export([set_transport_options/2]).
-export([get_protocol_options/1]).
-export([set_protocol_options/2]).
-export([info/0]).
-export([info/1]).
-export([procs/2]).
-export([wait_for_connections/3]).
-export([wait_for_connections/4]).
-export([filter_options/4]).
-export([set_option_default/3]).
-export([require/1]).
-export([log/4]).
-type max_conns() :: non_neg_integer() | infinity.
-export_type([max_conns/0]).
-type opts() :: any() | transport_opts(any()).
-export_type([opts/0]).
-type alarm(Type, Callback) :: #{
type := Type,
callback := Callback,
treshold := non_neg_integer(),
cooldown => non_neg_integer()
}.
-type alarm_num_connections() :: alarm(num_connections, fun((ref(), term(), pid(), [pid()]) -> any())).
-type transport_opts(SocketOpts) :: #{
alarms => #{term() => alarm_num_connections()},
connection_type => worker | supervisor,
handshake_timeout => timeout(),
logger => module(),
max_connections => max_conns(),
num_acceptors => pos_integer(),
num_conns_sups => pos_integer(),
num_listen_sockets => pos_integer(),
post_listen_callback => fun((term()) -> ok | {error, term()}),
shutdown => timeout() | brutal_kill,
socket_opts => SocketOpts
}.
-export_type([transport_opts/1]).
-type ref() :: any().
-export_type([ref/0]).
-spec start_listener(ref(), module(), opts(), module(), any())
-> supervisor:startchild_ret().
start_listener(Ref, Transport, TransOpts0, Protocol, ProtoOpts)
when is_atom(Transport), is_atom(Protocol) ->
TransOpts = normalize_opts(TransOpts0),
_ = code:ensure_loaded(Transport),
case {erlang:function_exported(Transport, name, 0), validate_transport_opts(TransOpts)} of
{true, ok} ->
ChildSpec = #{id => {ranch_listener_sup, Ref}, start => {ranch_listener_sup, start_link, [
Ref, Transport, TransOpts, Protocol, ProtoOpts
]}, type => supervisor},
maybe_started(supervisor:start_child(ranch_sup, ChildSpec));
{false, _} ->
{error, {bad_transport, Transport}};
{_, TransOptsError} ->
TransOptsError
end.
-spec normalize_opts(opts()) -> transport_opts(any()).
normalize_opts(Map) when is_map(Map) ->
Map;
normalize_opts(Any) ->
#{socket_opts => Any}.
-spec validate_transport_opts(transport_opts(any())) -> ok | {error, any()}.
validate_transport_opts(Opts) ->
maps:fold(fun
(Key, Value, ok) ->
case validate_transport_opt(Key, Value, Opts) of
true ->
ok;
false ->
{error, {bad_option, Key}}
end;
(_, _, Acc) ->
Acc
end, ok, Opts).
-spec validate_transport_opt(any(), any(), transport_opts(any())) -> boolean().
validate_transport_opt(connection_type, worker, _) ->
true;
validate_transport_opt(connection_type, supervisor, _) ->
true;
validate_transport_opt(handshake_timeout, infinity, _) ->
true;
validate_transport_opt(handshake_timeout, Value, _) ->
is_integer(Value) andalso Value >= 0;
validate_transport_opt(max_connections, infinity, _) ->
true;
validate_transport_opt(max_connections, Value, _) ->
is_integer(Value) andalso Value >= 0;
validate_transport_opt(alarms, Alarms, _) ->
maps:fold(
fun
(_, Opts, true) ->
validate_alarm(Opts);
(_, _, false) ->
false
end,
true,
Alarms);
validate_transport_opt(logger, Value, _) ->
is_atom(Value);
validate_transport_opt(num_acceptors, Value, _) ->
is_integer(Value) andalso Value > 0;
validate_transport_opt(num_conns_sups, Value, _) ->
is_integer(Value) andalso Value > 0;
validate_transport_opt(num_listen_sockets, Value, Opts) ->
is_integer(Value) andalso Value > 0
andalso Value =< maps:get(num_acceptors, Opts, 10);
validate_transport_opt(post_listen_callback, Value, _) ->
is_function(Value, 1);
validate_transport_opt(shutdown, brutal_kill, _) ->
true;
validate_transport_opt(shutdown, infinity, _) ->
true;
validate_transport_opt(shutdown, Value, _) ->
is_integer(Value) andalso Value >= 0;
validate_transport_opt(socket_opts, _, _) ->
true;
validate_transport_opt(_, _, _) ->
false.
validate_alarm(Alarm = #{type := num_connections, treshold := Treshold,
callback := Callback}) ->
is_integer(Treshold) andalso Treshold >= 0
andalso is_function(Callback, 4)
andalso case Alarm of
#{cooldown := Cooldown} ->
is_integer(Cooldown) andalso Cooldown >= 0;
_ ->
true
end;
validate_alarm(_) ->
false.
maybe_started({error, {{shutdown,
{failed_to_start_child, ranch_acceptors_sup,
{listen_error, _, Reason}}}, _}} = Error) ->
start_error(Reason, Error);
maybe_started(Res) ->
Res.
start_error(E=eaddrinuse, _) -> {error, E};
start_error(E=eacces, _) -> {error, E};
start_error(E=no_cert, _) -> {error, E};
start_error(_, Error) -> Error.
-spec stop_listener(ref()) -> ok | {error, not_found}.
stop_listener(Ref) ->
[_, Transport, _, _, _] = ranch_server:get_listener_start_args(Ref),
TransOpts = get_transport_options(Ref),
case supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}) of
ok ->
_ = supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref}),
ranch_server:cleanup_listener_opts(Ref),
Transport:cleanup(TransOpts);
{error, Reason} ->
{error, Reason}
end.
-spec suspend_listener(ref()) -> ok | {error, any()}.
suspend_listener(Ref) ->
case get_status(Ref) of
running ->
ListenerSup = ranch_server:get_listener_sup(Ref),
ok = ranch_server:set_addr(Ref, {undefined, undefined}),
supervisor:terminate_child(ListenerSup, ranch_acceptors_sup);
suspended ->
ok
end.
-spec resume_listener(ref()) -> ok | {error, any()}.
resume_listener(Ref) ->
case get_status(Ref) of
running ->
ok;
suspended ->
ListenerSup = ranch_server:get_listener_sup(Ref),
Res = supervisor:restart_child(ListenerSup, ranch_acceptors_sup),
maybe_resumed(Res)
end.
maybe_resumed(Error={error, {listen_error, _, Reason}}) ->
start_error(Reason, Error);
maybe_resumed({ok, _}) ->
ok;
maybe_resumed({ok, _, _}) ->
ok;
maybe_resumed(Res) ->
Res.
-spec stop_all_acceptors() -> ok.
stop_all_acceptors() ->
_ = [ok = do_acceptors(Pid, terminate_child)
|| {_, Pid} <- ranch_server:get_listener_sups()],
ok.
-spec restart_all_acceptors() -> ok.
restart_all_acceptors() ->
_ = [ok = do_acceptors(Pid, restart_child)
|| {_, Pid} <- ranch_server:get_listener_sups()],
ok.
do_acceptors(ListenerSup, F) ->
ListenerChildren = supervisor:which_children(ListenerSup),
case lists:keyfind(ranch_acceptors_sup, 1, ListenerChildren) of
{_, AcceptorsSup, _, _} when is_pid(AcceptorsSup) ->
AcceptorChildren = supervisor:which_children(AcceptorsSup),
%% @todo What about errors?
_ = [supervisor:F(AcceptorsSup, AcceptorId)
|| {AcceptorId, _, _, _} <- AcceptorChildren],
ok;
{_, Atom, _, _} ->
{error, Atom}
end.
-spec child_spec(ref(), module(), opts(), module(), any())
-> supervisor:child_spec().
child_spec(Ref, Transport, TransOpts0, Protocol, ProtoOpts) ->
TransOpts = normalize_opts(TransOpts0),
#{id => {ranch_embedded_sup, Ref}, start => {ranch_embedded_sup, start_link, [
Ref, Transport, TransOpts, Protocol, ProtoOpts
]}, type => supervisor}.
-spec handshake(ref()) -> {ok, ranch_transport:socket()} | {continue, any()}.
handshake(Ref) ->
handshake1(Ref, undefined).
-spec handshake(ref(), any()) -> {ok, ranch_transport:socket()} | {continue, any()}.
handshake(Ref, Opts) ->
handshake1(Ref, {opts, Opts}).
handshake1(Ref, Opts) ->
receive {handshake, Ref, Transport, CSocket, Timeout} ->
Handshake = handshake_transport(Transport, handshake, CSocket, Opts, Timeout),
handshake_result(Handshake, Ref, Transport, CSocket, Timeout)
end.
-spec handshake_continue(ref()) -> {ok, ranch_transport:socket()}.
handshake_continue(Ref) ->
handshake_continue1(Ref, undefined).
-spec handshake_continue(ref(), any()) -> {ok, ranch_transport:socket()}.
handshake_continue(Ref, Opts) ->
handshake_continue1(Ref, {opts, Opts}).
handshake_continue1(Ref, Opts) ->
receive {handshake_continue, Ref, Transport, CSocket, Timeout} ->
Handshake = handshake_transport(Transport, handshake_continue, CSocket, Opts, Timeout),
handshake_result(Handshake, Ref, Transport, CSocket, Timeout)
end.
handshake_transport(Transport, Fun, CSocket, undefined, Timeout) ->
Transport:Fun(CSocket, Timeout);
handshake_transport(Transport, Fun, CSocket, {opts, Opts}, Timeout) ->
Transport:Fun(CSocket, Opts, Timeout).
handshake_result(Result, Ref, Transport, CSocket, Timeout) ->
case Result of
OK = {ok, _} ->
OK;
{ok, CSocket2, Info} ->
self() ! {handshake_continue, Ref, Transport, CSocket2, Timeout},
{continue, Info};
{error, {tls_alert, _}} ->
ok = Transport:close(CSocket),
exit(normal);
{error, Reason} when Reason =:= timeout; Reason =:= closed ->
ok = Transport:close(CSocket),
exit(normal);
{error, Reason} ->
ok = Transport:close(CSocket),
error(Reason)
end.
-spec handshake_cancel(ref()) -> ok.
handshake_cancel(Ref) ->
receive {handshake_continue, Ref, Transport, CSocket, _} ->
Transport:handshake_cancel(CSocket)
end.
%% Unlike handshake/2 this function always return errors because
%% the communication between the proxy and the server are expected
%% to be reliable. If there is a problem while receiving the proxy
%% header, we probably want to know about it.
-spec recv_proxy_header(ref(), timeout())
-> {ok, ranch_proxy_header:proxy_info()}
| {error, closed | atom()}
| {error, protocol_error, atom()}.
recv_proxy_header(Ref, Timeout) ->
receive HandshakeState={handshake, Ref, Transport, CSocket, _} ->
self() ! HandshakeState,
Transport:recv_proxy_header(CSocket, Timeout)
end.
-spec remove_connection(ref()) -> ok.
remove_connection(Ref) ->
ListenerSup = ranch_server:get_listener_sup(Ref),
{_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1,
supervisor:which_children(ListenerSup)),
_ = [ConnsSup ! {remove_connection, Ref, self()} ||
{_, ConnsSup, _, _} <- supervisor:which_children(ConnsSupSup)],
ok.
-spec get_status(ref()) -> running | suspended.
get_status(Ref) ->
ListenerSup = ranch_server:get_listener_sup(Ref),
Children = supervisor:which_children(ListenerSup),
case lists:keyfind(ranch_acceptors_sup, 1, Children) of
{_, undefined, _, _} ->
suspended;
_ ->
running
end.
-spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()} |
{local, binary()} | {undefined, undefined}.
get_addr(Ref) ->
ranch_server:get_addr(Ref).
-spec get_port(ref()) -> inet:port_number() | undefined.
get_port(Ref) ->
case get_addr(Ref) of
{local, _} ->
undefined;
{_, Port} ->
Port
end.
-spec get_connections(ref(), active|all) -> non_neg_integer().
get_connections(Ref, active) ->
SupCounts = [ranch_conns_sup:active_connections(ConnsSup) ||
{_, ConnsSup} <- ranch_server:get_connections_sups(Ref)],
lists:sum(SupCounts);
get_connections(Ref, all) ->
SupCounts = [proplists:get_value(active, supervisor:count_children(ConnsSup)) ||
{_, ConnsSup} <- ranch_server:get_connections_sups(Ref)],
lists:sum(SupCounts).
-spec get_max_connections(ref()) -> max_conns().
get_max_connections(Ref) ->
ranch_server:get_max_connections(Ref).
-spec set_max_connections(ref(), max_conns()) -> ok.
set_max_connections(Ref, MaxConnections) ->
ranch_server:set_max_connections(Ref, MaxConnections).
-spec get_transport_options(ref()) -> transport_opts(any()).
get_transport_options(Ref) ->
ranch_server:get_transport_options(Ref).
-spec set_transport_options(ref(), opts()) -> ok | {error, term()}.
set_transport_options(Ref, TransOpts0) ->
TransOpts = normalize_opts(TransOpts0),
case validate_transport_opts(TransOpts) of
ok ->
ok = ranch_server:set_transport_options(Ref, TransOpts),
ok = apply_transport_options(Ref, TransOpts);
TransOptsError ->
TransOptsError
end.
apply_transport_options(Ref, TransOpts) ->
_ = [ConnsSup ! {set_transport_options, TransOpts}
|| {_, ConnsSup} <- ranch_server:get_connections_sups(Ref)],
ok.
-spec get_protocol_options(ref()) -> any().
get_protocol_options(Ref) ->
ranch_server:get_protocol_options(Ref).
-spec set_protocol_options(ref(), any()) -> ok.
set_protocol_options(Ref, Opts) ->
ranch_server:set_protocol_options(Ref, Opts).
-spec info() -> #{ref() := #{atom() := term()}}.
info() ->
lists:foldl(
fun ({Ref, Pid}, Acc) ->
Acc#{Ref => listener_info(Ref, Pid)}
end,
#{},
ranch_server:get_listener_sups()
).
-spec info(ref()) -> #{atom() := term()}.
info(Ref) ->
Pid = ranch_server:get_listener_sup(Ref),
listener_info(Ref, Pid).
listener_info(Ref, Pid) ->
[_, Transport, _, Protocol, _] = ranch_server:get_listener_start_args(Ref),
Status = get_status(Ref),
{IP, Port} = case get_addr(Ref) of
Addr = {local, _} ->
{Addr, undefined};
Addr ->
Addr
end,
MaxConns = get_max_connections(Ref),
TransOpts = ranch_server:get_transport_options(Ref),
ProtoOpts = get_protocol_options(Ref),
#{
pid => Pid,
status => Status,
ip => IP,
port => Port,
max_connections => MaxConns,
active_connections => get_connections(Ref, active),
all_connections => get_connections(Ref, all),
transport => Transport,
transport_options => TransOpts,
protocol => Protocol,
protocol_options => ProtoOpts,
metrics => metrics(Ref)
}.
-spec procs(ref(), acceptors | connections) -> [pid()].
procs(Ref, Type) ->
ListenerSup = ranch_server:get_listener_sup(Ref),
procs1(ListenerSup, Type).
procs1(ListenerSup, acceptors) ->
{_, SupPid, _, _} = lists:keyfind(ranch_acceptors_sup, 1,
supervisor:which_children(ListenerSup)),
try
[Pid || {_, Pid, _, _} <- supervisor:which_children(SupPid)]
catch exit:{noproc, _} ->
[]
end;
procs1(ListenerSup, connections) ->
{_, SupSupPid, _, _} = lists:keyfind(ranch_conns_sup_sup, 1,
supervisor:which_children(ListenerSup)),
Conns=
lists:map(fun ({_, SupPid, _, _}) ->
[Pid || {_, Pid, _, _} <- supervisor:which_children(SupPid)]
end,
supervisor:which_children(SupSupPid)
),
lists:flatten(Conns).
-spec metrics(ref()) -> #{}.
metrics(Ref) ->
Counters = ranch_server:get_stats_counters(Ref),
CounterInfo = counters:info(Counters),
NumCounters = maps:get(size, CounterInfo),
NumConnsSups = NumCounters div 2,
lists:foldl(
fun (Id, Acc) ->
Acc#{
{conns_sup, Id, accept} => counters:get(Counters, 2*Id-1),
{conns_sup, Id, terminate} => counters:get(Counters, 2*Id)
}
end,
#{},
lists:seq(1, NumConnsSups)
).
-spec wait_for_connections
(ref(), '>' | '>=' | '==' | '=<', non_neg_integer()) -> ok;
(ref(), '<', pos_integer()) -> ok.
wait_for_connections(Ref, Op, NumConns) ->
wait_for_connections(Ref, Op, NumConns, 1000).
-spec wait_for_connections
(ref(), '>' | '>=' | '==' | '=<', non_neg_integer(), non_neg_integer()) -> ok;
(ref(), '<', pos_integer(), non_neg_integer()) -> ok.
wait_for_connections(Ref, Op, NumConns, Interval) ->
validate_op(Op, NumConns),
validate_num_conns(NumConns),
validate_interval(Interval),
wait_for_connections_loop(Ref, Op, NumConns, Interval).
validate_op('>', _) -> ok;
validate_op('>=', _) -> ok;
validate_op('==', _) -> ok;
validate_op('=<', _) -> ok;
validate_op('<', NumConns) when NumConns > 0 -> ok;
validate_op(_, _) -> error(badarg).
validate_num_conns(NumConns) when is_integer(NumConns), NumConns >= 0 -> ok;
validate_num_conns(_) -> error(badarg).
validate_interval(Interval) when is_integer(Interval), Interval >= 0 -> ok;
validate_interval(_) -> error(badarg).
wait_for_connections_loop(Ref, Op, NumConns, Interval) ->
CurConns = try
get_connections(Ref, all)
catch _:_ ->
0
end,
case erlang:Op(CurConns, NumConns) of
true ->
ok;
false when Interval =:= 0 ->
wait_for_connections_loop(Ref, Op, NumConns, Interval);
false ->
timer:sleep(Interval),
wait_for_connections_loop(Ref, Op, NumConns, Interval)
end.
-spec filter_options([inet | inet6 | {atom(), any()} | {raw, any(), any(), any()}],
[atom()], Acc, module()) -> Acc when Acc :: [any()].
filter_options(UserOptions, DisallowedKeys, DefaultOptions, Logger) ->
AllowedOptions = filter_user_options(UserOptions, DisallowedKeys, Logger),
lists:foldl(fun merge_options/2, DefaultOptions, AllowedOptions).
%% 2-tuple options.
filter_user_options([Opt = {Key, _}|Tail], DisallowedKeys, Logger) ->
case lists:member(Key, DisallowedKeys) of
false ->
[Opt|filter_user_options(Tail, DisallowedKeys, Logger)];
true ->
filter_options_warning(Opt, Logger),
filter_user_options(Tail, DisallowedKeys, Logger)
end;
%% Special option forms.
filter_user_options([inet|Tail], DisallowedKeys, Logger) ->
[inet|filter_user_options(Tail, DisallowedKeys, Logger)];
filter_user_options([inet6|Tail], DisallowedKeys, Logger) ->
[inet6|filter_user_options(Tail, DisallowedKeys, Logger)];
filter_user_options([Opt = {raw, _, _, _}|Tail], DisallowedKeys, Logger) ->
[Opt|filter_user_options(Tail, DisallowedKeys, Logger)];
filter_user_options([Opt|Tail], DisallowedKeys, Logger) ->
filter_options_warning(Opt, Logger),
filter_user_options(Tail, DisallowedKeys, Logger);
filter_user_options([], _, _) ->
[].
filter_options_warning(Opt, Logger) ->
log(warning,
"Transport option ~p unknown or invalid.~n",
[Opt], Logger).
merge_options({Key, _} = Option, OptionList) ->
lists:keystore(Key, 1, OptionList, Option);
merge_options(Option, OptionList) ->
[Option|OptionList].
-spec set_option_default(Opts, atom(), any())
-> Opts when Opts :: [{atom(), any()}].
set_option_default(Opts, Key, Value) ->
case lists:keymember(Key, 1, Opts) of
true -> Opts;
false -> [{Key, Value}|Opts]
end.
-spec require([atom()]) -> ok.
require([]) ->
ok;
require([App|Tail]) ->
case application:start(App) of
ok -> ok;
{error, {already_started, App}} -> ok
end,
require(Tail).
-spec log(logger:level(), io:format(), list(), module() | #{logger => module()}) -> ok.
log(Level, Format, Args, Logger) when is_atom(Logger) ->
log(Level, Format, Args, #{logger => Logger});
log(Level, Format, Args, #{logger := Logger})
when Logger =/= error_logger ->
_ = Logger:Level(Format, Args),
ok;
%% Because error_logger does not have all the levels
%% we accept we have to do some mapping to error_logger functions.
log(Level, Format, Args, _) ->
Function = case Level of
emergency -> error_msg;
alert -> error_msg;
critical -> error_msg;
error -> error_msg;
warning -> warning_msg;
notice -> warning_msg;
info -> info_msg;
debug -> info_msg
end,
error_logger:Function(Format, Args).

View File

@ -0,0 +1,72 @@
%% Copyright (c) 2011-2021, 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.
-module(ranch_acceptor).
-export([start_link/5]).
-export([init/4]).
-export([loop/5]).
-spec start_link(ranch:ref(), pos_integer(), inet:socket(), module(), module())
-> {ok, pid()}.
start_link(Ref, AcceptorId, LSocket, Transport, Logger) ->
ConnsSup = ranch_server:get_connections_sup(Ref, AcceptorId),
Pid = spawn_link(?MODULE, init, [LSocket, Transport, Logger, ConnsSup]),
{ok, Pid}.
-spec init(inet:socket(), module(), module(), pid()) -> no_return().
init(LSocket, Transport, Logger, ConnsSup) ->
MonitorRef = monitor(process, ConnsSup),
loop(LSocket, Transport, Logger, ConnsSup, MonitorRef).
-spec loop(inet:socket(), module(), module(), pid(), reference()) -> no_return().
loop(LSocket, Transport, Logger, ConnsSup, MonitorRef) ->
_ = case Transport:accept(LSocket, infinity) of
{ok, CSocket} ->
case Transport:controlling_process(CSocket, ConnsSup) of
ok ->
%% This call will not return until process has been started
%% AND we are below the maximum number of connections.
ranch_conns_sup:start_protocol(ConnsSup, MonitorRef,
CSocket);
{error, _} ->
Transport:close(CSocket)
end;
%% Reduce the accept rate if we run out of file descriptors.
%% We can't accept anymore anyway, so we might as well wait
%% a little for the situation to resolve itself.
{error, emfile} ->
ranch:log(warning,
"Ranch acceptor reducing accept rate: out of file descriptors~n",
[], Logger),
receive after 100 -> ok end;
%% Exit if the listening socket got closed.
{error, closed} ->
exit(closed);
%% Continue otherwise.
{error, _} ->
ok
end,
flush(Logger),
?MODULE:loop(LSocket, Transport, Logger, ConnsSup, MonitorRef).
flush(Logger) ->
receive Msg ->
ranch:log(warning,
"Ranch acceptor received unexpected message: ~p~n",
[Msg], Logger),
flush(Logger)
after 0 ->
ok
end.

View File

@ -0,0 +1,103 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_acceptors_sup).
-behaviour(supervisor).
-export([start_link/3]).
-export([init/1]).
-spec start_link(ranch:ref(), module(), module())
-> {ok, pid()}.
start_link(Ref, Transport, Logger) ->
supervisor:start_link(?MODULE, [Ref, Transport, Logger]).
-spec init([term()]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([Ref, Transport, Logger]) ->
TransOpts = ranch_server:get_transport_options(Ref),
NumAcceptors = maps:get(num_acceptors, TransOpts, 10),
NumListenSockets = maps:get(num_listen_sockets, TransOpts, 1),
LSockets = case get(lsockets) of
undefined ->
LSockets1 = start_listen_sockets(Ref, NumListenSockets, Transport, TransOpts, Logger),
put(lsockets, LSockets1),
LSockets1;
LSockets1 ->
LSockets1
end,
Procs = [begin
LSocketId = (AcceptorId rem NumListenSockets) + 1,
{_, LSocket} = lists:keyfind(LSocketId, 1, LSockets),
#{
id => {acceptor, self(), AcceptorId},
start => {ranch_acceptor, start_link, [Ref, AcceptorId, LSocket, Transport, Logger]},
shutdown => brutal_kill
}
end || AcceptorId <- lists:seq(1, NumAcceptors)],
{ok, {#{intensity => 1 + ceil(math:log2(NumAcceptors))}, Procs}}.
-spec start_listen_sockets(any(), pos_integer(), module(), map(), module())
-> [{pos_integer(), inet:socket()}].
start_listen_sockets(Ref, NumListenSockets, Transport, TransOpts0, Logger) when NumListenSockets > 0 ->
BaseSocket = start_listen_socket(Ref, Transport, TransOpts0, Logger),
{ok, Addr} = Transport:sockname(BaseSocket),
ExtraSockets = case Addr of
{local, _} when NumListenSockets > 1 ->
listen_error(Ref, Transport, TransOpts0, reuseport_local, Logger);
{local, _} ->
[];
{_, Port} ->
SocketOpts = maps:get(socket_opts, TransOpts0, []),
SocketOpts1 = lists:keystore(port, 1, SocketOpts, {port, Port}),
TransOpts1 = TransOpts0#{socket_opts => SocketOpts1},
[{N, start_listen_socket(Ref, Transport, TransOpts1, Logger)}
|| N <- lists:seq(2, NumListenSockets)]
end,
ranch_server:set_addr(Ref, Addr),
[{1, BaseSocket}|ExtraSockets].
-spec start_listen_socket(any(), module(), map(), module()) -> inet:socket().
start_listen_socket(Ref, Transport, TransOpts, Logger) ->
case Transport:listen(TransOpts) of
{ok, Socket} ->
PostListenCb = maps:get(post_listen_callback, TransOpts, fun (_) -> ok end),
case PostListenCb(Socket) of
ok ->
Socket;
{error, Reason} ->
listen_error(Ref, Transport, TransOpts, Reason, Logger)
end;
{error, Reason} ->
listen_error(Ref, Transport, TransOpts, Reason, Logger)
end.
-spec listen_error(any(), module(), any(), atom(), module()) -> no_return().
listen_error(Ref, Transport, TransOpts0, Reason, Logger) ->
SocketOpts0 = maps:get(socket_opts, TransOpts0, []),
SocketOpts1 = [{cert, '...'}|proplists:delete(cert, SocketOpts0)],
SocketOpts2 = [{key, '...'}|proplists:delete(key, SocketOpts1)],
SocketOpts = [{cacerts, '...'}|proplists:delete(cacerts, SocketOpts2)],
TransOpts = TransOpts0#{socket_opts => SocketOpts},
ranch:log(error,
"Failed to start Ranch listener ~p in ~p:listen(~999999p) for reason ~p (~s)~n",
[Ref, Transport, TransOpts, Reason, format_error(Reason)], Logger),
exit({listen_error, Ref, Reason}).
format_error(no_cert) ->
"no certificate provided; see cert, certfile, sni_fun or sni_hosts options";
format_error(reuseport_local) ->
"num_listen_sockets must be set to 1 for local sockets";
format_error(Reason) ->
inet:format_error(Reason).

48
ranch/src/ranch_app.erl Normal file
View File

@ -0,0 +1,48 @@
%% Copyright (c) 2011-2021, 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.
-module(ranch_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
-export([profile_output/0]).
-spec start(application:start_type(), term()) -> {ok, pid()} | {error, term()}.
start(_, _) ->
_ = consider_profiling(),
ranch_server = ets:new(ranch_server, [
ordered_set, public, named_table]),
ranch_sup:start_link().
-spec stop(term()) -> ok.
stop(_) ->
ok.
-spec profile_output() -> ok.
profile_output() ->
eprof:stop_profiling(),
eprof:log("procs.profile"),
eprof:analyze(procs),
eprof:log("total.profile"),
eprof:analyze(total).
consider_profiling() ->
case application:get_env(profile) of
{ok, true} ->
{ok, _Pid} = eprof:start(),
eprof:start_profiling([self()]);
_ ->
not_profiling
end.

View File

@ -0,0 +1,508 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
%%
%% 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.
%% Make sure to never reload this module outside a release upgrade,
%% as calling l(ranch_conns_sup) twice will kill the process and all
%% the currently open connections.
-module(ranch_conns_sup).
%% API.
-export([start_link/6]).
-export([start_protocol/3]).
-export([active_connections/1]).
%% Supervisor internals.
-export([init/7]).
-export([system_continue/3]).
-export([system_terminate/4]).
-export([system_code_change/4]).
-type conn_type() :: worker | supervisor.
-type shutdown() :: brutal_kill | timeout().
-record(state, {
parent = undefined :: pid(),
ref :: ranch:ref(),
id :: pos_integer(),
conn_type :: conn_type(),
shutdown :: shutdown(),
transport = undefined :: module(),
protocol = undefined :: module(),
opts :: any(),
handshake_timeout :: timeout(),
max_conns = undefined :: ranch:max_conns(),
stats_counters_ref :: counters:counters_ref(),
alarms = #{} :: #{term() => {map(), undefined | reference()}},
logger = undefined :: module()
}).
%% API.
-spec start_link(ranch:ref(), pos_integer(), module(), any(), module(), module()) -> {ok, pid()}.
start_link(Ref, Id, Transport, TransOpts, Protocol, Logger) ->
proc_lib:start_link(?MODULE, init,
[self(), Ref, Id, Transport, TransOpts, Protocol, Logger]).
%% We can safely assume we are on the same node as the supervisor.
%%
%% We can also safely avoid having a monitor and a timeout here
%% because only three things can happen:
%% * The supervisor died; rest_for_one strategy killed all acceptors
%% so this very calling process is going to di--
%% * There's too many connections, the supervisor will resume the
%% acceptor only when we get below the limit again.
%% * The supervisor is overloaded, there's either too many acceptors
%% or the max_connections limit is too large. It's better if we
%% don't keep accepting connections because this leaves
%% more room for the situation to be resolved.
%%
%% We do not need the reply, we only need the ok from the supervisor
%% to continue. The supervisor sends its own pid when the acceptor can
%% continue.
-spec start_protocol(pid(), reference(), inet:socket()) -> ok.
start_protocol(SupPid, MonitorRef, Socket) ->
SupPid ! {?MODULE, start_protocol, self(), Socket},
receive
SupPid ->
ok;
{'DOWN', MonitorRef, process, SupPid, Reason} ->
error(Reason)
end.
%% We can't make the above assumptions here. This function might be
%% called from anywhere.
-spec active_connections(pid()) -> non_neg_integer().
active_connections(SupPid) ->
Tag = erlang:monitor(process, SupPid),
catch erlang:send(SupPid, {?MODULE, active_connections, self(), Tag},
[noconnect]),
receive
{Tag, Ret} ->
erlang:demonitor(Tag, [flush]),
Ret;
{'DOWN', Tag, _, _, noconnection} ->
exit({nodedown, node(SupPid)});
{'DOWN', Tag, _, _, Reason} ->
exit(Reason)
after 5000 ->
erlang:demonitor(Tag, [flush]),
exit(timeout)
end.
%% Supervisor internals.
-spec init(pid(), ranch:ref(), pos_integer(), module(), any(), module(), module()) -> no_return().
init(Parent, Ref, Id, Transport, TransOpts, Protocol, Logger) ->
process_flag(trap_exit, true),
ok = ranch_server:set_connections_sup(Ref, Id, self()),
MaxConns = ranch_server:get_max_connections(Ref),
Alarms = get_alarms(TransOpts),
ConnType = maps:get(connection_type, TransOpts, worker),
Shutdown = maps:get(shutdown, TransOpts, 5000),
HandshakeTimeout = maps:get(handshake_timeout, TransOpts, 5000),
ProtoOpts = ranch_server:get_protocol_options(Ref),
StatsCounters = ranch_server:get_stats_counters(Ref),
ok = proc_lib:init_ack(Parent, {ok, self()}),
loop(#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType,
shutdown=Shutdown, transport=Transport, protocol=Protocol,
opts=ProtoOpts, stats_counters_ref=StatsCounters,
handshake_timeout=HandshakeTimeout,
max_conns=MaxConns, alarms=Alarms,
logger=Logger}, 0, 0, []).
loop(State=#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType,
transport=Transport, protocol=Protocol, opts=Opts, stats_counters_ref=StatsCounters,
alarms=Alarms, max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
receive
{?MODULE, start_protocol, To, Socket} ->
try Protocol:start_link(Ref, Transport, Opts) of
{ok, Pid} ->
inc_accept(StatsCounters, Id, 1),
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
{ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
inc_accept(StatsCounters, Id, 1),
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
Ret ->
To ! self(),
ranch:log(error,
"Ranch listener ~p connection process start failure; "
"~p:start_link/3 returned: ~999999p~n",
[Ref, Protocol, Ret], Logger),
Transport:close(Socket),
loop(State, CurConns, NbChildren, Sleepers)
catch Class:Reason ->
To ! self(),
ranch:log(error,
"Ranch listener ~p connection process start failure; "
"~p:start_link/3 crashed with reason: ~p:~999999p~n",
[Ref, Protocol, Class, Reason], Logger),
Transport:close(Socket),
loop(State, CurConns, NbChildren, Sleepers)
end;
{?MODULE, active_connections, To, Tag} ->
To ! {Tag, CurConns},
loop(State, CurConns, NbChildren, Sleepers);
%% Remove a connection from the count of connections.
{remove_connection, Ref, Pid} ->
case put(Pid, removed) of
active when Sleepers =:= [] ->
loop(State, CurConns - 1, NbChildren, Sleepers);
active ->
[To|Sleepers2] = Sleepers,
To ! self(),
loop(State, CurConns - 1, NbChildren, Sleepers2);
removed ->
loop(State, CurConns, NbChildren, Sleepers);
undefined ->
_ = erase(Pid),
loop(State, CurConns, NbChildren, Sleepers)
end;
%% Upgrade the max number of connections allowed concurrently.
%% We resume all sleeping acceptors if this number increases.
{set_max_conns, MaxConns2} when MaxConns2 > MaxConns ->
_ = [To ! self() || To <- Sleepers],
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, []);
{set_max_conns, MaxConns2} ->
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, Sleepers);
%% Upgrade the transport options.
{set_transport_options, TransOpts} ->
set_transport_options(State, CurConns, NbChildren, Sleepers, TransOpts);
%% Upgrade the protocol options.
{set_protocol_options, Opts2} ->
loop(State#state{opts=Opts2},
CurConns, NbChildren, Sleepers);
{timeout, _, {activate_alarm, AlarmName}} when is_map_key(AlarmName, Alarms) ->
{AlarmOpts, _} = maps:get(AlarmName, Alarms),
NewAlarm = trigger_alarm(Ref, AlarmName, {AlarmOpts, undefined}, CurConns),
loop(State#state{alarms=Alarms#{AlarmName => NewAlarm}}, CurConns, NbChildren, Sleepers);
{timeout, _, {activate_alarm, _}} ->
loop(State, CurConns, NbChildren, Sleepers);
{'EXIT', Parent, Reason} ->
terminate(State, Reason, NbChildren);
{'EXIT', Pid, Reason} when Sleepers =:= [] ->
case erase(Pid) of
active ->
inc_terminate(StatsCounters, Id, 1),
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns - 1, NbChildren - 1, Sleepers);
removed ->
inc_terminate(StatsCounters, Id, 1),
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns, NbChildren - 1, Sleepers);
undefined ->
loop(State, CurConns, NbChildren, Sleepers)
end;
%% Resume a sleeping acceptor if needed.
{'EXIT', Pid, Reason} ->
case erase(Pid) of
active when CurConns > MaxConns ->
inc_terminate(StatsCounters, Id, 1),
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns - 1, NbChildren - 1, Sleepers);
active ->
inc_terminate(StatsCounters, Id, 1),
report_error(Logger, Ref, Protocol, Pid, Reason),
[To|Sleepers2] = Sleepers,
To ! self(),
loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
removed ->
inc_terminate(StatsCounters, Id, 1),
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns, NbChildren - 1, Sleepers);
undefined ->
loop(State, CurConns, NbChildren, Sleepers)
end;
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
{State, CurConns, NbChildren, Sleepers});
%% Calls from the supervisor module.
{'$gen_call', {To, Tag}, which_children} ->
Children = [{Protocol, Pid, ConnType, [Protocol]}
|| {Pid, Type} <- get(),
Type =:= active orelse Type =:= removed],
To ! {Tag, Children},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, count_children} ->
Counts = case ConnType of
worker -> [{supervisors, 0}, {workers, NbChildren}];
supervisor -> [{supervisors, NbChildren}, {workers, 0}]
end,
Counts2 = [{specs, 1}, {active, NbChildren}|Counts],
To ! {Tag, Counts2},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, _} ->
To ! {Tag, {error, ?MODULE}},
loop(State, CurConns, NbChildren, Sleepers);
Msg ->
ranch:log(error,
"Ranch listener ~p received unexpected message ~p~n",
[Ref, Msg], Logger),
loop(State, CurConns, NbChildren, Sleepers)
end.
handshake(State=#state{ref=Ref, transport=Transport, handshake_timeout=HandshakeTimeout,
max_conns=MaxConns, alarms=Alarms0}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
case Transport:controlling_process(Socket, ProtocolPid) of
ok ->
ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout},
put(SupPid, active),
CurConns2 = CurConns + 1,
Sleepers2 = if CurConns2 < MaxConns ->
To ! self(),
Sleepers;
true ->
[To|Sleepers]
end,
Alarms1 = trigger_alarms(Ref, Alarms0, CurConns2),
loop(State#state{alarms=Alarms1}, CurConns2, NbChildren + 1, Sleepers2);
{error, _} ->
Transport:close(Socket),
%% Only kill the supervised pid, because the connection's pid,
%% when different, is supposed to be sitting under it and linked.
exit(SupPid, kill),
To ! self(),
loop(State, CurConns, NbChildren, Sleepers)
end.
trigger_alarms(Ref, Alarms, CurConns) ->
maps:map(
fun
(AlarmName, Alarm) ->
trigger_alarm(Ref, AlarmName, Alarm, CurConns)
end,
Alarms
).
trigger_alarm(Ref, AlarmName, {Opts=#{treshold := Treshold, callback := Callback}, undefined}, CurConns) when CurConns >= Treshold ->
ActiveConns = [Pid || {Pid, active} <- get()],
case Callback of
{Mod, Fun} ->
spawn(Mod, Fun, [Ref, AlarmName, self(), ActiveConns]);
_ ->
Self = self(),
spawn(fun () -> Callback(Ref, AlarmName, Self, ActiveConns) end)
end,
{Opts, schedule_activate_alarm(AlarmName, Opts)};
trigger_alarm(_, _, Alarm, _) ->
Alarm.
schedule_activate_alarm(AlarmName, #{cooldown := Cooldown}) when Cooldown > 0 ->
erlang:start_timer(Cooldown, self(), {activate_alarm, AlarmName});
schedule_activate_alarm(_, _) ->
undefined.
get_alarms(#{alarms := Alarms}) when is_map(Alarms) ->
maps:fold(
fun
(Name, Opts = #{type := num_connections, cooldown := _}, Acc) ->
Acc#{Name => {Opts, undefined}};
(Name, Opts = #{type := num_connections}, Acc) ->
Acc#{Name => {Opts#{cooldown => 5000}, undefined}};
(_, _, Acc) -> Acc
end,
#{},
Alarms
);
get_alarms(_) ->
#{}.
set_transport_options(State=#state{max_conns=MaxConns0}, CurConns, NbChildren, Sleepers0, TransOpts) ->
MaxConns1 = maps:get(max_connections, TransOpts, 1024),
HandshakeTimeout = maps:get(handshake_timeout, TransOpts, 5000),
Shutdown = maps:get(shutdown, TransOpts, 5000),
Sleepers1 = case MaxConns1 > MaxConns0 of
true ->
_ = [To ! self() || To <- Sleepers0],
[];
false ->
Sleepers0
end,
State1=set_alarm_option(State, TransOpts, CurConns),
loop(State1#state{max_conns=MaxConns1, handshake_timeout=HandshakeTimeout, shutdown=Shutdown},
CurConns, NbChildren, Sleepers1).
set_alarm_option(State=#state{ref=Ref, alarms=OldAlarms}, TransOpts, CurConns) ->
NewAlarms0 = get_alarms(TransOpts),
NewAlarms1 = merge_alarms(OldAlarms, NewAlarms0),
NewAlarms2 = trigger_alarms(Ref, NewAlarms1, CurConns),
State#state{alarms=NewAlarms2}.
merge_alarms(Old, New) ->
OldList = lists:sort(maps:to_list(Old)),
NewList = lists:sort(maps:to_list(New)),
Merged = merge_alarms(OldList, NewList, []),
maps:from_list(Merged).
merge_alarms([], News, Acc) ->
News ++ Acc;
merge_alarms([{_, {_, undefined}}|Olds], [], Acc) ->
merge_alarms(Olds, [], Acc);
merge_alarms([{_, {_, Timer}}|Olds], [], Acc) ->
_ = cancel_alarm_reactivation_timer(Timer),
merge_alarms(Olds, [], Acc);
merge_alarms([{Name, {OldOpts, Timer}}|Olds], [{Name, {NewOpts, _}}|News], Acc) ->
merge_alarms(Olds, News, [{Name, {NewOpts, adapt_alarm_timer(Name, Timer, OldOpts, NewOpts)}}|Acc]);
merge_alarms([{OldName, {_, Timer}}|Olds], News=[{NewName, _}|_], Acc) when OldName < NewName ->
_ = cancel_alarm_reactivation_timer(Timer),
merge_alarms(Olds, News, Acc);
merge_alarms(Olds, [New|News], Acc) ->
merge_alarms(Olds, News, [New|Acc]).
%% Not in cooldown.
adapt_alarm_timer(_, undefined, _, _) ->
undefined;
%% Cooldown unchanged.
adapt_alarm_timer(_, Timer, #{cooldown := Cooldown}, #{cooldown := Cooldown}) ->
Timer;
%% Cooldown changed to no cooldown, cancel cooldown timer.
adapt_alarm_timer(_, Timer, _, #{cooldown := 0}) ->
_ = cancel_alarm_reactivation_timer(Timer),
undefined;
%% Cooldown changed, cancel current and start new timer taking the already elapsed time into account.
adapt_alarm_timer(Name, Timer, #{cooldown := OldCooldown}, #{cooldown := NewCooldown}) ->
OldTimeLeft = cancel_alarm_reactivation_timer(Timer),
case NewCooldown-OldCooldown+OldTimeLeft of
NewTimeLeft when NewTimeLeft>0 ->
erlang:start_timer(NewTimeLeft, self(), {activate_alarm, Name});
_ ->
undefined
end.
cancel_alarm_reactivation_timer(Timer) ->
case erlang:cancel_timer(Timer) of
%% Timer had already expired when we tried to cancel it, so we flush the
%% reactivation message it sent and return 0 as remaining time.
false ->
ok = receive {timeout, Timer, {activate_alarm, _}} -> ok after 0 -> ok end,
0;
%% Timer has not yet expired, we return the amount of time that was remaining.
TimeLeft ->
TimeLeft
end.
-spec terminate(#state{}, any(), non_neg_integer()) -> no_return().
terminate(#state{shutdown=brutal_kill, id=Id,
stats_counters_ref=StatsCounters}, Reason, NbChildren) ->
kill_children(get_keys(active)),
kill_children(get_keys(removed)),
inc_terminate(StatsCounters, Id, NbChildren),
exit(Reason);
%% Attempt to gracefully shutdown all children.
terminate(#state{shutdown=Shutdown, id=Id,
stats_counters_ref=StatsCounters}, Reason, NbChildren) ->
shutdown_children(get_keys(active)),
shutdown_children(get_keys(removed)),
_ = if
Shutdown =:= infinity ->
ok;
true ->
erlang:send_after(Shutdown, self(), kill)
end,
wait_children(NbChildren),
inc_terminate(StatsCounters, Id, NbChildren),
exit(Reason).
inc_accept(StatsCounters, Id, N) ->
%% Accepts are counted in the odd indexes.
counters:add(StatsCounters, 2*Id-1, N).
inc_terminate(StatsCounters, Id, N) ->
%% Terminates are counted in the even indexes.
counters:add(StatsCounters, 2*Id, N).
%% Kill all children and then exit. We unlink first to avoid
%% getting a message for each child getting killed.
kill_children(Pids) ->
_ = [begin
unlink(P),
exit(P, kill)
end || P <- Pids],
ok.
%% Monitor processes so we can know which ones have shutdown
%% before the timeout. Unlink so we avoid receiving an extra
%% message. Then send a shutdown exit signal.
shutdown_children(Pids) ->
_ = [begin
monitor(process, P),
unlink(P),
exit(P, shutdown)
end || P <- Pids],
ok.
wait_children(0) ->
ok;
wait_children(NbChildren) ->
receive
{'DOWN', _, process, Pid, _} ->
case erase(Pid) of
active -> wait_children(NbChildren - 1);
removed -> wait_children(NbChildren - 1);
_ -> wait_children(NbChildren)
end;
kill ->
Active = get_keys(active),
_ = [exit(P, kill) || P <- Active],
Removed = get_keys(removed),
_ = [exit(P, kill) || P <- Removed],
ok
end.
-spec system_continue(_, _, any()) -> no_return().
system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) ->
loop(State, CurConns, NbChildren, Sleepers).
-spec system_terminate(any(), _, _, _) -> no_return().
system_terminate(Reason, _, _, {State, _, NbChildren, _}) ->
terminate(State, Reason, NbChildren).
-spec system_code_change(any(), _, _, _) -> {ok, any()}.
system_code_change({#state{parent=Parent, ref=Ref, conn_type=ConnType,
shutdown=Shutdown, transport=Transport, protocol=Protocol,
opts=Opts, handshake_timeout=HandshakeTimeout,
max_conns=MaxConns, logger=Logger}, CurConns, NbChildren,
Sleepers}, _, {down, _}, _) ->
{ok, {{state, Parent, Ref, ConnType, Shutdown, Transport, Protocol,
Opts, HandshakeTimeout, MaxConns, Logger}, CurConns, NbChildren,
Sleepers}};
system_code_change({{state, Parent, Ref, ConnType, Shutdown, Transport, Protocol,
Opts, HandshakeTimeout, MaxConns, Logger}, CurConns, NbChildren,
Sleepers}, _, _, _) ->
Self = self(),
[Id] = [Id || {Id, Pid} <- ranch_server:get_connections_sups(Ref), Pid=:=Self],
StatsCounters = ranch_server:get_stats_counters(Ref),
{ok, {#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType, shutdown=Shutdown,
transport=Transport, protocol=Protocol, opts=Opts,
handshake_timeout=HandshakeTimeout, max_conns=MaxConns,
stats_counters_ref=StatsCounters,
logger=Logger}, CurConns, NbChildren, Sleepers}};
system_code_change(Misc, _, _, _) ->
{ok, Misc}.
%% We use ~999999p here instead of ~w because the latter doesn't
%% support printable strings.
report_error(_, _, _, _, normal) ->
ok;
report_error(_, _, _, _, shutdown) ->
ok;
report_error(_, _, _, _, {shutdown, _}) ->
ok;
report_error(Logger, Ref, Protocol, Pid, Reason) ->
ranch:log(error,
"Ranch listener ~p had connection process started with "
"~p:start_link/3 at ~p exit with reason: ~999999p~n",
[Ref, Protocol, Pid, Reason], Logger).

View File

@ -0,0 +1,42 @@
%% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_conns_sup_sup).
-behaviour(supervisor).
-export([start_link/4]).
-export([init/1]).
-spec start_link(ranch:ref(), module(), module(), module()) -> {ok, pid()}.
start_link(Ref, Transport, Protocol, Logger) ->
ok = ranch_server:cleanup_connections_sups(Ref),
supervisor:start_link(?MODULE, {
Ref, Transport, Protocol, Logger
}).
-spec init({ranch:ref(), module(), module(), module()})
-> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init({Ref, Transport, Protocol, Logger}) ->
TransOpts = ranch_server:get_transport_options(Ref),
NumAcceptors = maps:get(num_acceptors, TransOpts, 10),
NumConnsSups = maps:get(num_conns_sups, TransOpts, NumAcceptors),
StatsCounters = counters:new(2*NumConnsSups, []),
ok = ranch_server:set_stats_counters(Ref, StatsCounters),
ChildSpecs = [#{
id => {ranch_conns_sup, N},
start => {ranch_conns_sup, start_link, [Ref, N, Transport, TransOpts, Protocol, Logger]},
type => supervisor
} || N <- lists:seq(1, NumConnsSups)],
{ok, {#{intensity => 1 + ceil(math:log2(NumConnsSups))}, ChildSpecs}}.

115
ranch/src/ranch_crc32c.erl Normal file
View File

@ -0,0 +1,115 @@
%% Copyright (c) 2018-2021, 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.
-module(ranch_crc32c).
-export([crc32c/1]).
-export([crc32c/2]).
-define(CRC32C_TABLE, {
16#00000000, 16#F26B8303, 16#E13B70F7, 16#1350F3F4,
16#C79A971F, 16#35F1141C, 16#26A1E7E8, 16#D4CA64EB,
16#8AD958CF, 16#78B2DBCC, 16#6BE22838, 16#9989AB3B,
16#4D43CFD0, 16#BF284CD3, 16#AC78BF27, 16#5E133C24,
16#105EC76F, 16#E235446C, 16#F165B798, 16#030E349B,
16#D7C45070, 16#25AFD373, 16#36FF2087, 16#C494A384,
16#9A879FA0, 16#68EC1CA3, 16#7BBCEF57, 16#89D76C54,
16#5D1D08BF, 16#AF768BBC, 16#BC267848, 16#4E4DFB4B,
16#20BD8EDE, 16#D2D60DDD, 16#C186FE29, 16#33ED7D2A,
16#E72719C1, 16#154C9AC2, 16#061C6936, 16#F477EA35,
16#AA64D611, 16#580F5512, 16#4B5FA6E6, 16#B93425E5,
16#6DFE410E, 16#9F95C20D, 16#8CC531F9, 16#7EAEB2FA,
16#30E349B1, 16#C288CAB2, 16#D1D83946, 16#23B3BA45,
16#F779DEAE, 16#05125DAD, 16#1642AE59, 16#E4292D5A,
16#BA3A117E, 16#4851927D, 16#5B016189, 16#A96AE28A,
16#7DA08661, 16#8FCB0562, 16#9C9BF696, 16#6EF07595,
16#417B1DBC, 16#B3109EBF, 16#A0406D4B, 16#522BEE48,
16#86E18AA3, 16#748A09A0, 16#67DAFA54, 16#95B17957,
16#CBA24573, 16#39C9C670, 16#2A993584, 16#D8F2B687,
16#0C38D26C, 16#FE53516F, 16#ED03A29B, 16#1F682198,
16#5125DAD3, 16#A34E59D0, 16#B01EAA24, 16#42752927,
16#96BF4DCC, 16#64D4CECF, 16#77843D3B, 16#85EFBE38,
16#DBFC821C, 16#2997011F, 16#3AC7F2EB, 16#C8AC71E8,
16#1C661503, 16#EE0D9600, 16#FD5D65F4, 16#0F36E6F7,
16#61C69362, 16#93AD1061, 16#80FDE395, 16#72966096,
16#A65C047D, 16#5437877E, 16#4767748A, 16#B50CF789,
16#EB1FCBAD, 16#197448AE, 16#0A24BB5A, 16#F84F3859,
16#2C855CB2, 16#DEEEDFB1, 16#CDBE2C45, 16#3FD5AF46,
16#7198540D, 16#83F3D70E, 16#90A324FA, 16#62C8A7F9,
16#B602C312, 16#44694011, 16#5739B3E5, 16#A55230E6,
16#FB410CC2, 16#092A8FC1, 16#1A7A7C35, 16#E811FF36,
16#3CDB9BDD, 16#CEB018DE, 16#DDE0EB2A, 16#2F8B6829,
16#82F63B78, 16#709DB87B, 16#63CD4B8F, 16#91A6C88C,
16#456CAC67, 16#B7072F64, 16#A457DC90, 16#563C5F93,
16#082F63B7, 16#FA44E0B4, 16#E9141340, 16#1B7F9043,
16#CFB5F4A8, 16#3DDE77AB, 16#2E8E845F, 16#DCE5075C,
16#92A8FC17, 16#60C37F14, 16#73938CE0, 16#81F80FE3,
16#55326B08, 16#A759E80B, 16#B4091BFF, 16#466298FC,
16#1871A4D8, 16#EA1A27DB, 16#F94AD42F, 16#0B21572C,
16#DFEB33C7, 16#2D80B0C4, 16#3ED04330, 16#CCBBC033,
16#A24BB5A6, 16#502036A5, 16#4370C551, 16#B11B4652,
16#65D122B9, 16#97BAA1BA, 16#84EA524E, 16#7681D14D,
16#2892ED69, 16#DAF96E6A, 16#C9A99D9E, 16#3BC21E9D,
16#EF087A76, 16#1D63F975, 16#0E330A81, 16#FC588982,
16#B21572C9, 16#407EF1CA, 16#532E023E, 16#A145813D,
16#758FE5D6, 16#87E466D5, 16#94B49521, 16#66DF1622,
16#38CC2A06, 16#CAA7A905, 16#D9F75AF1, 16#2B9CD9F2,
16#FF56BD19, 16#0D3D3E1A, 16#1E6DCDEE, 16#EC064EED,
16#C38D26C4, 16#31E6A5C7, 16#22B65633, 16#D0DDD530,
16#0417B1DB, 16#F67C32D8, 16#E52CC12C, 16#1747422F,
16#49547E0B, 16#BB3FFD08, 16#A86F0EFC, 16#5A048DFF,
16#8ECEE914, 16#7CA56A17, 16#6FF599E3, 16#9D9E1AE0,
16#D3D3E1AB, 16#21B862A8, 16#32E8915C, 16#C083125F,
16#144976B4, 16#E622F5B7, 16#F5720643, 16#07198540,
16#590AB964, 16#AB613A67, 16#B831C993, 16#4A5A4A90,
16#9E902E7B, 16#6CFBAD78, 16#7FAB5E8C, 16#8DC0DD8F,
16#E330A81A, 16#115B2B19, 16#020BD8ED, 16#F0605BEE,
16#24AA3F05, 16#D6C1BC06, 16#C5914FF2, 16#37FACCF1,
16#69E9F0D5, 16#9B8273D6, 16#88D28022, 16#7AB90321,
16#AE7367CA, 16#5C18E4C9, 16#4F48173D, 16#BD23943E,
16#F36E6F75, 16#0105EC76, 16#12551F82, 16#E03E9C81,
16#34F4F86A, 16#C69F7B69, 16#D5CF889D, 16#27A40B9E,
16#79B737BA, 16#8BDCB4B9, 16#988C474D, 16#6AE7C44E,
16#BE2DA0A5, 16#4C4623A6, 16#5F16D052, 16#AD7D5351
}).
%% The interface mirrors erlang:crc32/1,2.
-spec crc32c(iodata()) -> non_neg_integer().
crc32c(Data) ->
do_crc32c(16#ffffffff, iolist_to_binary(Data)).
-spec crc32c(CRC, iodata()) -> CRC when CRC::non_neg_integer().
crc32c(OldCrc, Data) ->
do_crc32c(OldCrc bxor 16#ffffffff, iolist_to_binary(Data)).
do_crc32c(OldCrc, <<C, Rest/bits>>) ->
do_crc32c((OldCrc bsr 8) bxor element(1 + ((OldCrc bxor C) band 16#ff), ?CRC32C_TABLE),
Rest);
do_crc32c(OldCrc, <<>>) ->
OldCrc bxor 16#ffffffff.
-ifdef(TEST).
crc32c_test_() ->
Tests = [
%% Tests from RFC3720 B.4.
{<<0:32/unit:8>>, 16#8a9136aa},
{iolist_to_binary([16#ff || _ <- lists:seq(1, 32)]), 16#62a8ab43},
{iolist_to_binary([N || N <- lists:seq(0, 16#1f)]), 16#46dd794e},
{iolist_to_binary([N || N <- lists:seq(16#1f, 0, -1)]), 16#113fdb5c},
{<<16#01c00000:32, 0:32, 0:32, 0:32, 16#14000000:32, 16#00000400:32, 16#00000014:32,
16#00000018:32, 16#28000000:32, 0:32, 16#02000000:32, 0:32>>, 16#d9963a56}
],
[{iolist_to_binary(io_lib:format("16#~8.16.0b", [R])),
fun() -> R = crc32c(V) end} || {V, R} <- Tests].
-endif.

View File

@ -0,0 +1,36 @@
%% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_embedded_sup).
-behavior(supervisor).
-export([start_link/5]).
-export([init/1]).
-spec start_link(ranch:ref(), module(), any(), module(), any())
-> {ok, pid()}.
start_link(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
supervisor:start_link(?MODULE, {Ref, Transport, TransOpts, Protocol, ProtoOpts}).
-spec init({ranch:ref(), module(), any(), module(), any()})
-> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init({Ref, Transport, TransOpts, Protocol, ProtoOpts}) ->
Proxy = #{id => ranch_server_proxy,
start => {ranch_server_proxy, start_link, []},
shutdown => brutal_kill},
Listener = #{id => {ranch_listener_sup, Ref},
start => {ranch_listener_sup, start_link, [Ref, Transport, TransOpts, Protocol, ProtoOpts]},
type => supervisor},
{ok, {#{strategy => rest_for_one}, [Proxy, Listener]}}.

View File

@ -0,0 +1,48 @@
%% Copyright (c) 2011-2021, 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.
-module(ranch_listener_sup).
-behaviour(supervisor).
-export([start_link/5]).
-export([init/1]).
-spec start_link(ranch:ref(), module(), any(), module(), any())
-> {ok, pid()}.
start_link(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
MaxConns = maps:get(max_connections, TransOpts, 1024),
Logger = maps:get(logger, TransOpts, logger),
ranch_server:set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts,
[Ref, Transport, TransOpts, Protocol, ProtoOpts]),
supervisor:start_link(?MODULE, {
Ref, Transport, Protocol, Logger
}).
-spec init({ranch:ref(), module(), module(), module()})
-> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init({Ref, Transport, Protocol, Logger}) ->
ok = ranch_server:set_listener_sup(Ref, self()),
ChildSpecs = [
#{
id => ranch_conns_sup_sup,
start => {ranch_conns_sup_sup, start_link, [Ref, Transport, Protocol, Logger]},
type => supervisor
},
#{
id => ranch_acceptors_sup,
start => {ranch_acceptors_sup, start_link, [Ref, Transport, Logger]},
type => supervisor
}
],
{ok, {#{strategy => rest_for_one}, ChildSpecs}}.

View File

@ -0,0 +1,23 @@
%% Copyright (c) 2012-2021, 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.
-module(ranch_protocol).
%% Start a new connection process for the given socket.
-callback start_link(
Ref::ranch:ref(),
Transport::module(),
ProtocolOptions::any())
-> {ok, ConnectionPid::pid()}
| {ok, SupPid::pid(), ConnectionPid::pid()}.

File diff suppressed because it is too large Load Diff

279
ranch/src/ranch_server.erl Normal file
View File

@ -0,0 +1,279 @@
%% Copyright (c) 2012-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_server).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
-export([set_new_listener_opts/5]).
-export([cleanup_listener_opts/1]).
-export([cleanup_connections_sups/1]).
-export([set_connections_sup/3]).
-export([get_connections_sup/2]).
-export([get_connections_sups/1]).
-export([get_connections_sups/0]).
-export([set_listener_sup/2]).
-export([get_listener_sup/1]).
-export([get_listener_sups/0]).
-export([set_addr/2]).
-export([get_addr/1]).
-export([set_max_connections/2]).
-export([get_max_connections/1]).
-export([set_stats_counters/2]).
-export([get_stats_counters/1]).
-export([set_transport_options/2]).
-export([get_transport_options/1]).
-export([set_protocol_options/2]).
-export([get_protocol_options/1]).
-export([get_listener_start_args/1]).
-export([count_connections/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-define(TAB, ?MODULE).
-type monitors() :: [{{reference(), pid()}, any()}].
-record(state, {
monitors = [] :: monitors()
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any(), any(), [any()]) -> ok.
set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts, StartArgs) ->
gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}).
-spec cleanup_listener_opts(ranch:ref()) -> ok.
cleanup_listener_opts(Ref) ->
_ = ets:delete(?TAB, {addr, Ref}),
_ = ets:delete(?TAB, {max_conns, Ref}),
_ = ets:delete(?TAB, {trans_opts, Ref}),
_ = ets:delete(?TAB, {proto_opts, Ref}),
_ = ets:delete(?TAB, {listener_start_args, Ref}),
%% We also remove the pid of the connection supervisors.
%% Depending on the timing, they might already have been deleted
%% when we handled the monitor DOWN message. However, in some
%% cases when calling stop_listener followed by get_connections_sup,
%% we could end up with the pid still being returned, when we
%% expected a crash (because the listener was stopped).
%% Deleting it explicitly here removes any possible confusion.
_ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}),
_ = ets:delete(?TAB, {stats_counters, Ref}),
%% Ditto for the listener supervisor.
_ = ets:delete(?TAB, {listener_sup, Ref}),
ok.
-spec cleanup_connections_sups(ranch:ref()) -> ok.
cleanup_connections_sups(Ref) ->
_ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}),
_ = ets:delete(?TAB, {stats_counters, Ref}),
ok.
-spec set_connections_sup(ranch:ref(), non_neg_integer(), pid()) -> ok.
set_connections_sup(Ref, Id, Pid) ->
gen_server:call(?MODULE, {set_connections_sup, Ref, Id, Pid}).
-spec get_connections_sup(ranch:ref(), pos_integer()) -> pid().
get_connections_sup(Ref, Id) ->
ConnsSups = get_connections_sups(Ref),
NConnsSups = length(ConnsSups),
{_, Pid} = lists:keyfind((Id rem NConnsSups) + 1, 1, ConnsSups),
Pid.
-spec get_connections_sups(ranch:ref()) -> [{pos_integer(), pid()}].
get_connections_sups(Ref) ->
[{Id, Pid} ||
[Id, Pid] <- ets:match(?TAB, {{conns_sup, Ref, '$1'}, '$2'})].
-spec get_connections_sups() -> [{ranch:ref(), pos_integer(), pid()}].
get_connections_sups() ->
[{Ref, Id, Pid} ||
[Ref, Id, Pid] <- ets:match(?TAB, {{conns_sup, '$1', '$2'}, '$3'})].
-spec set_listener_sup(ranch:ref(), pid()) -> ok.
set_listener_sup(Ref, Pid) ->
gen_server:call(?MODULE, {set_listener_sup, Ref, Pid}).
-spec get_listener_sup(ranch:ref()) -> pid().
get_listener_sup(Ref) ->
ets:lookup_element(?TAB, {listener_sup, Ref}, 2).
-spec get_listener_sups() -> [{ranch:ref(), pid()}].
get_listener_sups() ->
[{Ref, Pid} || [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})].
-spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()} |
{local, binary()} | {undefined, undefined}) -> ok.
set_addr(Ref, Addr) ->
gen_server:call(?MODULE, {set_addr, Ref, Addr}).
-spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()} |
{local, binary()} | {undefined, undefined}.
get_addr(Ref) ->
ets:lookup_element(?TAB, {addr, Ref}, 2).
-spec set_max_connections(ranch:ref(), ranch:max_conns()) -> ok.
set_max_connections(Ref, MaxConnections) ->
gen_server:call(?MODULE, {set_max_conns, Ref, MaxConnections}).
-spec get_max_connections(ranch:ref()) -> ranch:max_conns().
get_max_connections(Ref) ->
ets:lookup_element(?TAB, {max_conns, Ref}, 2).
-spec set_stats_counters(ranch:ref(), counters:counters_ref()) -> ok.
set_stats_counters(Ref, Counters) ->
gen_server:call(?MODULE, {set_stats_counters, Ref, Counters}).
-spec get_stats_counters(ranch:ref()) -> counters:counters_ref().
get_stats_counters(Ref) ->
ets:lookup_element(?TAB, {stats_counters, Ref}, 2).
-spec set_transport_options(ranch:ref(), any()) -> ok.
set_transport_options(Ref, TransOpts) ->
gen_server:call(?MODULE, {set_trans_opts, Ref, TransOpts}).
-spec get_transport_options(ranch:ref()) -> any().
get_transport_options(Ref) ->
ets:lookup_element(?TAB, {trans_opts, Ref}, 2).
-spec set_protocol_options(ranch:ref(), any()) -> ok.
set_protocol_options(Ref, ProtoOpts) ->
gen_server:call(?MODULE, {set_proto_opts, Ref, ProtoOpts}).
-spec get_protocol_options(ranch:ref()) -> any().
get_protocol_options(Ref) ->
ets:lookup_element(?TAB, {proto_opts, Ref}, 2).
-spec get_listener_start_args(ranch:ref()) -> [any()].
get_listener_start_args(Ref) ->
ets:lookup_element(?TAB, {listener_start_args, Ref}, 2).
-spec count_connections(ranch:ref()) -> non_neg_integer().
count_connections(Ref) ->
lists:foldl(
fun ({_, ConnsSup}, Acc) ->
Acc+ranch_conns_sup:active_connections(ConnsSup)
end,
0,
get_connections_sups(Ref)).
%% gen_server.
-spec init([]) -> {ok, #state{}}.
init([]) ->
ConnMonitors = [{{erlang:monitor(process, Pid), Pid}, {conns_sup, Ref, Id}} ||
[Ref, Id, Pid] <- ets:match(?TAB, {{conns_sup, '$1', '$2'}, '$3'})],
ListenerMonitors = [{{erlang:monitor(process, Pid), Pid}, {listener_sup, Ref}} ||
[Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})],
{ok, #state{monitors=ConnMonitors++ListenerMonitors}}.
-spec handle_call(term(), {pid(), reference()}, #state{}) -> {reply, ok | ignore, #state{}}.
handle_call({set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}, _, State) ->
ets:insert_new(?TAB, {{max_conns, Ref}, MaxConns}),
ets:insert_new(?TAB, {{trans_opts, Ref}, TransOpts}),
ets:insert_new(?TAB, {{proto_opts, Ref}, ProtoOpts}),
ets:insert_new(?TAB, {{listener_start_args, Ref}, StartArgs}),
{reply, ok, State};
handle_call({set_connections_sup, Ref, Id, Pid}, _, State0) ->
State = set_monitored_process({conns_sup, Ref, Id}, Pid, State0),
{reply, ok, State};
handle_call({set_listener_sup, Ref, Pid}, _, State0) ->
State = set_monitored_process({listener_sup, Ref}, Pid, State0),
{reply, ok, State};
handle_call({set_addr, Ref, Addr}, _, State) ->
true = ets:insert(?TAB, {{addr, Ref}, Addr}),
{reply, ok, State};
handle_call({set_max_conns, Ref, MaxConns}, _, State) ->
ets:insert(?TAB, {{max_conns, Ref}, MaxConns}),
_ = [ConnsSup ! {set_max_conns, MaxConns} || {_, ConnsSup} <- get_connections_sups(Ref)],
{reply, ok, State};
handle_call({set_stats_counters, Ref, Counters}, _, State) ->
ets:insert(?TAB, {{stats_counters, Ref}, Counters}),
{reply, ok, State};
handle_call({set_trans_opts, Ref, Opts}, _, State) ->
ets:insert(?TAB, {{trans_opts, Ref}, Opts}),
{reply, ok, State};
handle_call({set_proto_opts, Ref, Opts}, _, State) ->
ets:insert(?TAB, {{proto_opts, Ref}, Opts}),
_ = [ConnsSup ! {set_protocol_options, Opts} || {_, ConnsSup} <- get_connections_sups(Ref)],
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ignore, State}.
-spec handle_cast(_, #state{}) -> {noreply, #state{}}.
handle_cast(_Request, State) ->
{noreply, State}.
-spec handle_info(term(), #state{}) -> {noreply, #state{}}.
handle_info({'DOWN', MonitorRef, process, Pid, Reason},
State=#state{monitors=Monitors}) ->
{_, TypeRef} = lists:keyfind({MonitorRef, Pid}, 1, Monitors),
ok = case {TypeRef, Reason} of
{{listener_sup, Ref}, normal} ->
cleanup_listener_opts(Ref);
{{listener_sup, Ref}, shutdown} ->
cleanup_listener_opts(Ref);
{{listener_sup, Ref}, {shutdown, _}} ->
cleanup_listener_opts(Ref);
_ ->
_ = ets:delete(?TAB, TypeRef),
ok
end,
Monitors2 = lists:keydelete({MonitorRef, Pid}, 1, Monitors),
{noreply, State#state{monitors=Monitors2}};
handle_info(_Info, State) ->
{noreply, State}.
-spec terminate(_, #state{}) -> ok.
terminate(_Reason, _State) ->
ok.
-spec code_change(term() | {down, term()}, #state{}, term()) -> {ok, term()}.
code_change({down, _}, State, _Extra) ->
true = ets:match_delete(?TAB, {{stats_counters, '_'}, '_'}),
{ok, State};
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Internal.
set_monitored_process(Key, Pid, State=#state{monitors=Monitors0}) ->
%% First we cleanup the monitor if a residual one exists.
%% This can happen during crashes when the restart is faster
%% than the cleanup.
Monitors = case lists:keytake(Key, 2, Monitors0) of
false ->
Monitors0;
{value, {{OldMonitorRef, _}, _}, Monitors1} ->
true = erlang:demonitor(OldMonitorRef, [flush]),
Monitors1
end,
%% Then we unconditionally insert in the ets table.
%% If residual data is there, it will be overwritten.
true = ets:insert(?TAB, {Key, Pid}),
%% Finally we start monitoring this new process.
MonitorRef = erlang:monitor(process, Pid),
State#state{monitors=[{{MonitorRef, Pid}, Key}|Monitors]}.

View File

@ -0,0 +1,67 @@
%% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_server_proxy).
-behavior(gen_server).
-export([start_link/0]).
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([code_change/3]).
-spec start_link() -> {ok, pid()} | {error, term()}.
start_link() ->
gen_server:start_link(?MODULE, [], []).
-spec init([]) -> {ok, pid()} | {stop, term()}.
init([]) ->
case wait_ranch_server(50) of
{ok, Monitor} ->
{ok, Monitor, hibernate};
{error, Reason} ->
{stop, Reason}
end.
-spec handle_call(_, _, reference()) -> {noreply, reference(), hibernate}.
handle_call(_, _, Monitor) ->
{noreply, Monitor, hibernate}.
-spec handle_cast(_, reference()) -> {noreply, reference(), hibernate}.
handle_cast(_, Monitor) ->
{noreply, Monitor, hibernate}.
-spec handle_info(term(), reference()) -> {noreply, reference(), hibernate} | {stop, term(), reference()}.
handle_info({'DOWN', Monitor, process, _, Reason}, Monitor) ->
{stop, Reason, Monitor};
handle_info(_, Monitor) ->
{noreply, Monitor, hibernate}.
-spec code_change(term() | {down, term()}, reference(), term()) -> {ok, reference()}.
code_change(_, Monitor, _) ->
{ok, Monitor}.
wait_ranch_server(N) ->
case whereis(ranch_server) of
undefined when N > 0 ->
receive after 100 -> ok end,
wait_ranch_server(N - 1);
undefined ->
{error, noproc};
Pid ->
Monitor = monitor(process, Pid),
{ok, Monitor}
end.

341
ranch/src/ranch_ssl.erl Normal file
View File

@ -0,0 +1,341 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
%%
%% 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.
-module(ranch_ssl).
-behaviour(ranch_transport).
-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([disallowed_listen_options/0]).
-export([accept/2]).
-export([handshake/2]).
-export([handshake/3]).
-export([handshake_continue/2]).
-export([handshake_continue/3]).
-export([handshake_cancel/1]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
-export([recv_proxy_header/2]).
-export([send/2]).
-export([sendfile/2]).
-export([sendfile/4]).
-export([sendfile/5]).
-export([setopts/2]).
-export([getopts/2]).
-export([getstat/1]).
-export([getstat/2]).
-export([controlling_process/2]).
-export([peername/1]).
-export([sockname/1]).
-export([shutdown/2]).
-export([close/1]).
-export([cleanup/1]).
-type ssl_opt() :: {alpn_preferred_protocols, [binary()]}
| {anti_replay, '10k' | '100k' | {integer(), integer(), integer()}}
| {beast_mitigation, one_n_minus_one | zero_n | disabled}
| {cacertfile, file:filename()}
| {cacerts, [public_key:der_encoded()]}
| {cert, public_key:der_encoded()}
| {certfile, file:filename()}
| {ciphers, ssl:ciphers()}
| {client_renegotiation, boolean()}
| {crl_cache, [any()]}
| {crl_check, boolean() | peer | best_effort}
| {depth, integer()}
| {dh, binary()}
| {dhfile, file:filename()}
| {eccs, [ssl:named_curve()]}
| {fail_if_no_peer_cert, boolean()}
| {handshake, hello | full}
| {hibernate_after, timeout()}
| {honor_cipher_order, boolean()}
| {honor_ecc_order, boolean()}
| {key, ssl:key()}
| {key_update_at, pos_integer()}
| {keyfile, file:filename()}
| {log_alert, boolean()}
| {log_level, logger:level()}
| {max_handshake_size, integer()}
| {middlebox_comp_mode, boolean()}
| {next_protocols_advertised, [binary()]}
| {padding_check, boolean()}
| {partial_chain, fun()}
| {password, string()}
| {protocol, tls | dtls}
| {psk_identity, string()}
| {reuse_session, fun()}
| {reuse_sessions, boolean()}
| {secure_renegotiate, boolean()}
| {session_tickets, disabled | stateful | stateless}
| {signature_algs, [{ssl:hash(), ssl:sign_algo()}]}
| {signature_algs_cert, [ssl:sign_scheme()]}
| {sni_fun, fun()}
| {sni_hosts, [{string(), ssl_opt()}]}
| {supported_groups, [ssl:group()]}
| {user_lookup_fun, {fun(), any()}}
| {verify, verify_none | verify_peer}
| {verify_fun, {fun(), any()}}
| {versions, [ssl:protocol_version()]}.
-export_type([ssl_opt/0]).
-type opt() :: ranch_tcp:opt() | ssl_opt().
-export_type([opt/0]).
-type opts() :: [opt()].
-export_type([opts/0]).
-spec name() -> ssl.
name() -> ssl.
-spec secure() -> boolean().
secure() ->
true.
-spec messages() -> {ssl, ssl_closed, ssl_error, ssl_passive}.
messages() -> {ssl, ssl_closed, ssl_error, ssl_passive}.
-spec listen(ranch:transport_opts(opts())) -> {ok, ssl:sslsocket()} | {error, atom()}.
listen(TransOpts) ->
ok = cleanup(TransOpts),
SocketOpts = maps:get(socket_opts, TransOpts, []),
case lists:keymember(cert, 1, SocketOpts)
orelse lists:keymember(certfile, 1, SocketOpts)
orelse lists:keymember(sni_fun, 1, SocketOpts)
orelse lists:keymember(sni_hosts, 1, SocketOpts)
orelse lists:keymember(user_lookup_fun, 1, SocketOpts) of
true ->
Logger = maps:get(logger, TransOpts, logger),
do_listen(SocketOpts, Logger);
false ->
{error, no_cert}
end.
do_listen(SocketOpts0, Logger) ->
SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024),
SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
SocketOpts = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
DisallowedOpts0 = disallowed_listen_options(),
DisallowedOpts = unsupported_tls_options(SocketOpts) ++ DisallowedOpts0,
%% We set the port to 0 because it is given in the Opts directly.
%% The port in the options takes precedence over the one in the
%% first argument.
ssl:listen(0, ranch:filter_options(SocketOpts, DisallowedOpts,
[binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)).
%% 'binary' and 'list' are disallowed but they are handled
%% specifically as they do not have 2-tuple equivalents.
-spec disallowed_listen_options() -> [atom()].
disallowed_listen_options() ->
[alpn_advertised_protocols, client_preferred_next_protocols,
fallback, server_name_indication, srp_identity
|ranch_tcp:disallowed_listen_options()].
unsupported_tls_options(SocketOpts) ->
unsupported_tls_version_options(lists:usort(get_tls_versions(SocketOpts))).
unsupported_tls_version_options([tlsv1|_]) ->
[];
unsupported_tls_version_options(['tlsv1.1'|_]) ->
[beast_mitigation, padding_check];
unsupported_tls_version_options(['tlsv1.2'|_]) ->
[beast_mitigation, padding_check];
unsupported_tls_version_options(['tlsv1.3'|_]) ->
[beast_mitigation, client_renegotiation, next_protocols_advertised,
padding_check, psk_identity, reuse_session, reuse_sessions,
secure_renegotiate, user_lookup_fun];
unsupported_tls_version_options(_) ->
[].
-spec accept(ssl:sslsocket(), timeout())
-> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
ssl:transport_accept(LSocket, Timeout).
-spec handshake(inet:socket() | ssl:sslsocket(), timeout())
-> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}.
handshake(CSocket, Timeout) ->
handshake(CSocket, [], Timeout).
-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout())
-> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}.
handshake(CSocket, Opts, Timeout) ->
case ssl:handshake(CSocket, Opts, Timeout) of
OK = {ok, _} ->
OK;
OK = {ok, _, _} ->
OK;
Error = {error, _} ->
Error
end.
-spec handshake_continue(ssl:sslsocket(), timeout())
-> {ok, ssl:sslsocket()} | {error, any()}.
handshake_continue(CSocket, Timeout) ->
handshake_continue(CSocket, [], Timeout).
-spec handshake_continue(ssl:sslsocket(), [ssl:tls_server_option()], timeout())
-> {ok, ssl:sslsocket()} | {error, any()}.
handshake_continue(CSocket, Opts, Timeout) ->
case ssl:handshake_continue(CSocket, Opts, Timeout) of
OK = {ok, _} ->
OK;
Error = {error, _} ->
Error
end.
-spec handshake_cancel(ssl:sslsocket()) -> ok.
handshake_cancel(CSocket) ->
ok = ssl:handshake_cancel(CSocket).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts) when is_integer(Port) ->
ssl:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}]).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any(), timeout())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
ssl:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}],
Timeout).
-spec recv(ssl:sslsocket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | atom()}.
recv(Socket, Length, Timeout) ->
ssl:recv(Socket, Length, Timeout).
-spec recv_proxy_header(ssl:sslsocket(), timeout())
-> {ok, ranch_proxy_header:proxy_info()}
| {error, closed | atom()}
| {error, protocol_error, atom()}.
recv_proxy_header(SSLSocket, Timeout) ->
%% There's currently no documented way to perform a TCP recv
%% on an sslsocket(), even before the TLS handshake. However
%% nothing prevents us from retrieving the TCP socket and using
%% it. Since it's an undocumented interface this may however
%% make forward-compatibility more difficult.
{sslsocket, {gen_tcp, TCPSocket, _, _}, _} = SSLSocket,
ranch_tcp:recv_proxy_header(TCPSocket, Timeout).
-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
ssl:send(Socket, Packet).
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
non_neg_integer(), non_neg_integer())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
%% Unlike with TCP, no syscall can be used here, so sending files
%% through SSL will be much slower in comparison. Note that unlike
%% file:sendfile/5 this function accepts either a file or a file name.
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes, Opts) ->
ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts).
%% @todo Probably filter Opts?
-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}.
setopts(Socket, Opts) ->
ssl:setopts(Socket, Opts).
-spec getopts(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}.
getopts(Socket, Opts) ->
ssl:getopts(Socket, Opts).
-spec getstat(ssl:sslsocket()) -> {ok, list()} | {error, atom()}.
getstat(Socket) ->
ssl:getstat(Socket).
-spec getstat(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}.
getstat(Socket, OptionNames) ->
ssl:getstat(Socket, OptionNames).
-spec controlling_process(ssl:sslsocket(), pid())
-> ok | {error, closed | not_owner | atom()}.
controlling_process(Socket, Pid) ->
ssl:controlling_process(Socket, Pid).
-spec peername(ssl:sslsocket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
peername(Socket) ->
ssl:peername(Socket).
-spec sockname(ssl:sslsocket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
sockname(Socket) ->
ssl:sockname(Socket).
-spec shutdown(ssl:sslsocket(), read | write | read_write)
-> ok | {error, atom()}.
shutdown(Socket, How) ->
ssl:shutdown(Socket, How).
-spec close(ssl:sslsocket()) -> ok.
close(Socket) ->
ssl:close(Socket).
-spec cleanup(ranch:transport_opts(opts())) -> ok.
cleanup(#{socket_opts:=SocketOpts}) ->
case lists:keyfind(ip, 1, lists:reverse(SocketOpts)) of
{ip, {local, SockFile}} ->
_ = file:delete(SockFile),
ok;
_ ->
ok
end;
cleanup(_) ->
ok.
get_tls_versions(SocketOpts) ->
%% Socket options need to be reversed for keyfind because later options
%% take precedence when contained multiple times, but keyfind will return
%% the earliest occurence.
case lists:keyfind(versions, 1, lists:reverse(SocketOpts)) of
{versions, Versions} ->
Versions;
false ->
get_tls_versions_env()
end.
get_tls_versions_env() ->
case application:get_env(ssl, protocol_version) of
{ok, Versions} ->
Versions;
undefined ->
get_tls_versions_app()
end.
get_tls_versions_app() ->
{supported, Versions} = lists:keyfind(supported, 1, ssl:versions()),
Versions.

39
ranch/src/ranch_sup.erl Normal file
View File

@ -0,0 +1,39 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
Intensity = case application:get_env(ranch_sup_intensity) of
{ok, Value1} -> Value1;
undefined -> 1
end,
Period = case application:get_env(ranch_sup_period) of
{ok, Value2} -> Value2;
undefined -> 5
end,
Procs = [
#{id => ranch_server, start => {ranch_server, start_link, []}}
],
{ok, {#{intensity => Intensity, period => Period}, Procs}}.

287
ranch/src/ranch_tcp.erl Normal file
View File

@ -0,0 +1,287 @@
%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_tcp).
-behaviour(ranch_transport).
-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([disallowed_listen_options/0]).
-export([accept/2]).
-export([handshake/2]).
-export([handshake/3]).
-export([handshake_continue/2]).
-export([handshake_continue/3]).
-export([handshake_cancel/1]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
-export([recv_proxy_header/2]).
-export([send/2]).
-export([sendfile/2]).
-export([sendfile/4]).
-export([sendfile/5]).
-export([setopts/2]).
-export([getopts/2]).
-export([getstat/1]).
-export([getstat/2]).
-export([controlling_process/2]).
-export([peername/1]).
-export([sockname/1]).
-export([shutdown/2]).
-export([close/1]).
-export([cleanup/1]).
-type opt() :: {backlog, non_neg_integer()}
| {buffer, non_neg_integer()}
| {delay_send, boolean()}
| {dontroute, boolean()}
| {exit_on_close, boolean()}
| {fd, non_neg_integer()}
| {high_msgq_watermark, non_neg_integer()}
| {high_watermark, non_neg_integer()}
| inet
| inet6
| {ip, inet:ip_address() | inet:local_address()}
| {ipv6_v6only, boolean()}
| {keepalive, boolean()}
| {linger, {boolean(), non_neg_integer()}}
| {low_msgq_watermark, non_neg_integer()}
| {low_watermark, non_neg_integer()}
| {nodelay, boolean()}
| {port, inet:port_number()}
| {priority, integer()}
| {raw, non_neg_integer(), non_neg_integer(), binary()}
| {recbuf, non_neg_integer()}
| {send_timeout, timeout()}
| {send_timeout_close, boolean()}
| {sndbuf, non_neg_integer()}
| {tos, integer()}.
-export_type([opt/0]).
-type opts() :: [opt()].
-export_type([opts/0]).
-spec name() -> tcp.
name() -> tcp.
-spec secure() -> boolean().
secure() ->
false.
-spec messages() -> {tcp, tcp_closed, tcp_error, tcp_passive}.
messages() -> {tcp, tcp_closed, tcp_error, tcp_passive}.
-spec listen(ranch:transport_opts(opts())) -> {ok, inet:socket()} | {error, atom()}.
listen(TransOpts) ->
ok = cleanup(TransOpts),
Logger = maps:get(logger, TransOpts, logger),
SocketOpts = maps:get(socket_opts, TransOpts, []),
%% We set the port to 0 because it is given in the Opts directly.
%% The port in the options takes precedence over the one in the
%% first argument.
gen_tcp:listen(0, prepare_socket_opts(SocketOpts, Logger)).
prepare_socket_opts([Backend = {inet_backend, _}|SocketOpts], Logger) ->
%% In OTP/23, the inet_backend option may be used to activate the
%% experimental socket backend for inet/gen_tcp. If present, it must
%% be the first option in the list.
[Backend|prepare_socket_opts(SocketOpts, Logger)];
prepare_socket_opts(SocketOpts0, Logger) ->
SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024),
SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
SocketOpts4 = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
ranch:filter_options(SocketOpts4, disallowed_listen_options(),
[binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger).
%% 'binary' and 'list' are disallowed but they are handled
%% specifically as they do not have 2-tuple equivalents.
-spec disallowed_listen_options() -> [atom()].
disallowed_listen_options() ->
[active, header, mode, packet, packet_size, line_delimiter, reuseaddr].
-spec accept(inet:socket(), timeout())
-> {ok, inet:socket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
gen_tcp:accept(LSocket, Timeout).
-spec handshake(inet:socket(), timeout()) -> {ok, inet:socket()}.
handshake(CSocket, Timeout) ->
handshake(CSocket, [], Timeout).
-spec handshake(inet:socket(), opts(), timeout()) -> {ok, inet:socket()}.
handshake(CSocket, _, _) ->
{ok, CSocket}.
-spec handshake_continue(inet:socket(), timeout()) -> no_return().
handshake_continue(CSocket, Timeout) ->
handshake_continue(CSocket, [], Timeout).
-spec handshake_continue(inet:socket(), opts(), timeout()) -> no_return().
handshake_continue(_, _, _) ->
error(not_supported).
-spec handshake_cancel(inet:socket()) -> no_return().
handshake_cancel(_) ->
error(not_supported).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts) when is_integer(Port) ->
gen_tcp:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}]).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any(), timeout())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
gen_tcp:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}],
Timeout).
-spec recv(inet:socket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | atom()}.
recv(Socket, Length, Timeout) ->
gen_tcp:recv(Socket, Length, Timeout).
-spec recv_proxy_header(inet:socket(), timeout())
-> {ok, ranch_proxy_header:proxy_info()}
| {error, closed | atom()}
| {error, protocol_error, atom()}.
recv_proxy_header(Socket, Timeout) ->
case recv(Socket, 0, Timeout) of
{ok, Data} ->
case ranch_proxy_header:parse(Data) of
{ok, ProxyInfo, <<>>} ->
{ok, ProxyInfo};
{ok, ProxyInfo, Rest} ->
case gen_tcp:unrecv(Socket, Rest) of
ok ->
{ok, ProxyInfo};
Error ->
Error
end;
{error, HumanReadable} ->
{error, protocol_error, HumanReadable}
end;
Error ->
Error
end.
-spec send(inet:socket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
gen_tcp:send(Socket, Packet).
-spec sendfile(inet:socket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer(), [{chunk_size, non_neg_integer()}])
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename, Offset, Bytes, Opts)
when is_list(Filename) orelse is_atom(Filename)
orelse is_binary(Filename) ->
case file:open(Filename, [read, raw, binary]) of
{ok, RawFile} ->
try sendfile(Socket, RawFile, Offset, Bytes, Opts) of
Result -> Result
after
ok = file:close(RawFile)
end;
{error, _} = Error ->
Error
end;
sendfile(Socket, RawFile, Offset, Bytes, Opts) ->
Opts2 = case Opts of
[] -> [{chunk_size, 16#1FFF}];
_ -> Opts
end,
try file:sendfile(RawFile, Socket, Offset, Bytes, Opts2) of
Result -> Result
catch
error:{badmatch, {error, enotconn}} ->
%% file:sendfile/5 might fail by throwing a
%% {badmatch, {error, enotconn}}. This is because its
%% implementation fails with a badmatch in
%% prim_file:sendfile/10 if the socket is not connected.
{error, closed}
end.
%% @todo Probably filter Opts?
-spec setopts(inet:socket(), list()) -> ok | {error, atom()}.
setopts(Socket, Opts) ->
inet:setopts(Socket, Opts).
-spec getopts(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}.
getopts(Socket, Opts) ->
inet:getopts(Socket, Opts).
-spec getstat(inet:socket()) -> {ok, list()} | {error, atom()}.
getstat(Socket) ->
inet:getstat(Socket).
-spec getstat(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}.
getstat(Socket, OptionNames) ->
inet:getstat(Socket, OptionNames).
-spec controlling_process(inet:socket(), pid())
-> ok | {error, closed | not_owner | atom()}.
controlling_process(Socket, Pid) ->
gen_tcp:controlling_process(Socket, Pid).
-spec peername(inet:socket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
peername(Socket) ->
inet:peername(Socket).
-spec sockname(inet:socket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
sockname(Socket) ->
inet:sockname(Socket).
-spec shutdown(inet:socket(), read | write | read_write)
-> ok | {error, atom()}.
shutdown(Socket, How) ->
gen_tcp:shutdown(Socket, How).
-spec close(inet:socket()) -> ok.
close(Socket) ->
gen_tcp:close(Socket).
-spec cleanup(ranch:transport_opts(opts())) -> ok.
cleanup(#{socket_opts:=SocketOpts}) ->
case lists:keyfind(ip, 1, lists:reverse(SocketOpts)) of
{ip, {local, SockFile}} ->
_ = file:delete(SockFile),
ok;
_ ->
ok
end;
cleanup(_) ->
ok.

View File

@ -0,0 +1,157 @@
%% Copyright (c) 2012-2021, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
%%
%% 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.
-module(ranch_transport).
-export([sendfile/6]).
-type socket() :: any().
-export_type([socket/0]).
-type opts() :: any().
-type stats() :: any().
-type sendfile_opts() :: [{chunk_size, non_neg_integer()}].
-export_type([sendfile_opts/0]).
-callback name() -> atom().
-callback secure() -> boolean().
-callback messages() -> {OK::atom(), Closed::atom(), Error::atom(), Passive::atom()}.
-callback listen(ranch:transport_opts(any())) -> {ok, socket()} | {error, atom()}.
-callback accept(socket(), timeout())
-> {ok, socket()} | {error, closed | timeout | atom()}.
-callback handshake(socket(), timeout()) -> {ok, socket()} | {ok, socket(), any()} | {error, any()}.
-callback handshake(socket(), opts(), timeout()) -> {ok, socket()} | {ok, socket(), any()} | {error, any()}.
-callback handshake_continue(socket(), timeout()) -> {ok, socket()} | {error, any()}.
-callback handshake_continue(socket(), opts(), timeout()) -> {ok, socket()} | {error, any()}.
-callback handshake_cancel(socket()) -> ok.
-callback connect(string(), inet:port_number(), opts())
-> {ok, socket()} | {error, atom()}.
-callback connect(string(), inet:port_number(), opts(), timeout())
-> {ok, socket()} | {error, atom()}.
-callback recv(socket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | timeout | atom()}.
-callback recv_proxy_header(socket(), timeout())
-> {ok, ranch_proxy_header:proxy_info()}
| {error, closed | atom()}
| {error, protocol_error, atom()}.
-callback send(socket(), iodata()) -> ok | {error, atom()}.
-callback sendfile(socket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
-callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}.
-callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
-callback setopts(socket(), opts()) -> ok | {error, atom()}.
-callback getopts(socket(), [atom()]) -> {ok, opts()} | {error, atom()}.
-callback getstat(socket()) -> {ok, stats()} | {error, atom()}.
-callback getstat(socket(), [atom()]) -> {ok, stats()} | {error, atom()}.
-callback controlling_process(socket(), pid())
-> ok | {error, closed | not_owner | atom()}.
-callback peername(socket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
-callback sockname(socket())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
-callback shutdown(socket(), read | write | read_write)
-> ok | {error, atom()}.
-callback close(socket()) -> ok.
-callback cleanup(ranch:transport_opts(any())) -> ok.
%% A fallback for transports that don't have a native sendfile implementation.
%% Note that the ordering of arguments is different from file:sendfile/5 and
%% that this function accepts either a raw file or a file name.
-spec sendfile(module(), socket(), file:name_all() | file:fd(),
non_neg_integer(), non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Transport, Socket, Filename, Offset, Bytes, Opts)
when is_list(Filename) orelse is_atom(Filename)
orelse is_binary(Filename) ->
ChunkSize = chunk_size(Opts),
case file:open(Filename, [read, raw, binary]) of
{ok, RawFile} ->
_ = case Offset of
0 ->
ok;
_ ->
{ok, _} = file:position(RawFile, {bof, Offset})
end,
try
sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize)
after
ok = file:close(RawFile)
end;
{error, _Reason} = Error ->
Error
end;
sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) ->
ChunkSize = chunk_size(Opts),
Initial2 = case file:position(RawFile, {cur, 0}) of
{ok, Offset} ->
Offset;
{ok, Initial} ->
{ok, _} = file:position(RawFile, {bof, Offset}),
Initial
end,
case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of
{ok, _Sent} = Result ->
{ok, _} = file:position(RawFile, {bof, Initial2}),
Result;
{error, _Reason} = Error ->
Error
end.
-spec chunk_size(sendfile_opts()) -> pos_integer().
chunk_size(Opts) ->
case lists:keyfind(chunk_size, 1, Opts) of
{chunk_size, ChunkSize}
when is_integer(ChunkSize) andalso ChunkSize > 0 ->
ChunkSize;
{chunk_size, 0} ->
16#1FFF;
false ->
16#1FFF
end.
-spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(),
non_neg_integer(), pos_integer())
-> {ok, non_neg_integer()} | {error, any()}.
sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize)
when Sent =/= 0 ->
%% All requested data has been read and sent, return number of bytes sent.
{ok, Sent};
sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) ->
ReadSize = read_size(Bytes, Sent, ChunkSize),
case file:read(RawFile, ReadSize) of
{ok, IoData} ->
case Transport:send(Socket, IoData) of
ok ->
Sent2 = iolist_size(IoData) + Sent,
sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2,
ChunkSize);
{error, _Reason} = Error ->
Error
end;
eof ->
{ok, Sent};
{error, _Reason} = Error ->
Error
end.
-spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
non_neg_integer().
read_size(0, _Sent, ChunkSize) ->
ChunkSize;
read_size(Bytes, Sent, ChunkSize) ->
min(Bytes - Sent, ChunkSize).

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
-module(active_echo_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(Ref),
loop(Socket, Transport).
loop(Socket, Transport) ->
{OK, Closed, Error, _Passive} = Transport:messages(),
Transport:setopts(Socket, [{active, once}]),
receive
{OK, Socket, Data} ->
Transport:send(Socket, Data),
loop(Socket, Transport);
{Closed, Socket} ->
ok;
{Error, Socket, _} ->
ok = Transport:close(Socket)
end.

View File

@ -0,0 +1,30 @@
-module(batch_echo_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, [{batch_size, N}]) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, N]),
{ok, Pid}.
init(Ref, Transport, N) ->
{ok, Socket} = ranch:handshake(Ref),
Transport:setopts(Socket, [{active, N}]),
loop(Socket, Transport, N, <<>>).
loop(Socket, Transport, N, Acc) ->
{OK, Closed, Error, Passive} = Transport:messages(),
receive
{OK, Socket, Data} ->
Transport:send(Socket, <<"OK">>),
loop(Socket, Transport, N, <<Acc/binary, Data/binary>>);
{Passive, Socket} ->
Transport:send(Socket, Acc),
Transport:setopts(Socket, [{active, N}]),
loop(Socket, Transport, N, <<>>);
{Closed, Socket} ->
ok;
{Error, Socket, _} ->
ok = Transport:close(Socket)
end.

View File

@ -0,0 +1,16 @@
-module(check_tcp_options).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, _, [{pid, TestPid}|TcpOptions]) ->
Pid = spawn_link(?MODULE, init, [Ref, TestPid, TcpOptions]),
{ok, Pid}.
init(Ref, Pid, TcpOptions) ->
{ok, Socket} = ranch:handshake(Ref),
{ok, RealTcpOptions} = inet:getopts(Socket, [Key || {Key, _} <- TcpOptions]),
true = TcpOptions =:= RealTcpOptions,
Pid ! checked,
receive after 2500 -> ok end.

1
ranch/test/cover.spec Normal file
View File

@ -0,0 +1 @@
{incl_app, ranch, details}.

View File

@ -0,0 +1,7 @@
-module(crash_protocol).
-export([start_link/4]).
-spec start_link(_, _, _, _) -> no_return().
start_link(_, _, _, _) ->
exit(crash).

View File

@ -0,0 +1,22 @@
-module(echo_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, _Opts = []) ->
{ok, Socket} = ranch:handshake(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.

View File

@ -0,0 +1,28 @@
-module(embedded_sup).
-behaviour(supervisor).
-export([init/1]).
-export([start_link/0]).
-export([stop/1]).
-export([start_listener/6]).
-export([stop_listener/2]).
start_link() ->
supervisor:start_link(?MODULE, []).
stop(SupPid) ->
erlang:exit(SupPid, normal).
init([]) ->
{ok, {{one_for_one, 10, 10}, []}}.
start_listener(SupPid, Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
supervisor:start_child(
SupPid,
ranch:child_spec(Ref, Transport, TransOpts, Protocol, ProtoOpts)
).
stop_listener(SupPid, Ref) ->
ok = supervisor:terminate_child(SupPid, {ranch_embedded_sup, Ref}),
ok = supervisor:delete_child(SupPid, {ranch_embedded_sup, Ref}),
ranch_server:cleanup_listener_opts(Ref).

View File

@ -0,0 +1,32 @@
-module(handshake_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, Opts) ->
SniHost = case ranch:handshake(Ref) of
%% Due to a bug in ssl (https://bugs.erlang.org/browse/ERL-951,
%% fixed in OTP 22.0.3) the value for sni may be {sni, Hostname}
%% instead of Hostname.
{continue, #{sni := {sni, Hostname}}} ->
Hostname;
{continue, #{sni := Hostname}} ->
Hostname
end,
SniHostOpts = maps:get(SniHost, Opts),
{ok, Socket} = ranch:handshake_continue(Ref, SniHostOpts),
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.

View File

@ -0,0 +1,16 @@
-module(notify_and_wait_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/4]).
start_link(_, _, Opts = #{pid := TestPid}) ->
Msg = maps:get(msg, Opts, connected),
TerminateMsg = maps:get(terminate_msg, Opts, stop),
Timeout = maps:get(timeout, Opts, infinity),
Pid = spawn_link(?MODULE, init, [Msg, TestPid, TerminateMsg, Timeout]),
{ok, Pid}.
init(Msg, Pid, TerminateMsg, Timeout) ->
Pid ! {self(), Msg},
receive TerminateMsg -> ok after Timeout -> ok end.

View File

@ -0,0 +1,232 @@
%% Copyright (c) 2018-2021, 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.
-module(proxy_header_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-import(ct_helper, [doc/1]).
-import(ct_helper, [name/0]).
%% ct.
all() ->
ct_helper:all(?MODULE).
%% Tests.
recv_v1_proxy_header_tcp(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 1,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_tcp(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v1_proxy_header_tcp_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 1,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_tcp(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
recv_v2_proxy_header_tcp(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 2,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_tcp(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v2_proxy_header_tcp_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 2,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_tcp(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
recv_v2_local_header_tcp(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 2,
command => local
},
do_proxy_header_tcp(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v2_local_header_tcp_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 2,
command => local
},
do_proxy_header_tcp(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
do_proxy_header_tcp(Name, ProxyInfo, Data1, Data2) ->
{ok, _} = ranch:start_listener(Name,
ranch_tcp, #{},
proxy_protocol, []),
Port = ranch:get_port(Name),
{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
ok = gen_tcp:send(Socket, [ranch_proxy_header:header(ProxyInfo), Data1]),
receive
{proxy_protocol, ProxyInfo} ->
ok
after 2000 ->
error(timeout)
end,
ok = gen_tcp:send(Socket, Data2),
Len1 = byte_size(Data1),
Len2 = byte_size(Data2),
{ok, <<Data1:Len1/binary, Data2/binary>>} = gen_tcp:recv(Socket, Len1 + Len2, 1000),
ok = ranch:stop_listener(Name),
ok.
recv_v1_proxy_header_ssl(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 1,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_ssl(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v1_proxy_header_ssl_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 1,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_ssl(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
recv_v2_proxy_header_ssl(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 2,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_ssl(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v2_proxy_header_ssl_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 2,
command => proxy,
transport_family => ipv4,
transport_protocol => stream,
src_address => {127, 0, 0, 1},
src_port => 444,
dest_address => {192, 168, 0, 1},
dest_port => 443
},
do_proxy_header_ssl(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
recv_v2_local_header_ssl(_) ->
doc("Confirm we can read the proxy header at the start of the connection."),
Name = name(),
ProxyInfo = #{
version => 2,
command => local
},
do_proxy_header_ssl(Name, ProxyInfo, <<>>, <<"TCP Ranch is working!">>).
recv_v2_local_header_ssl_extra_data(_) ->
doc("Confirm we can read the proxy header at the start of the connection "
"and that the extra data in the first packet can be read afterwards."),
Name = name(),
ProxyInfo = #{
version => 2,
command => local
},
do_proxy_header_ssl(Name, ProxyInfo, <<"HELLO">>, <<"TCP Ranch is working!">>).
do_proxy_header_ssl(Name, ProxyInfo, Data1, Data2) ->
Opts = ct_helper:get_certs_from_ets(),
{ok, _} = ranch:start_listener(Name,
ranch_ssl, Opts,
proxy_protocol, []),
Port = ranch:get_port(Name),
{ok, Socket0} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
ok = gen_tcp:send(Socket0, [ranch_proxy_header:header(ProxyInfo)]),
{ok, Socket} = ssl:connect(Socket0, [], 1000),
ok = ssl:send(Socket, Data1),
receive
{proxy_protocol, ProxyInfo} ->
ok
after 2000 ->
error(timeout)
end,
ok = ssl:send(Socket, Data2),
Len1 = byte_size(Data1),
Len2 = byte_size(Data2),
{ok, <<Data1:Len1/binary, Data2/binary>>} = ssl:recv(Socket, Len1 + Len2, 1000),
ok = ranch:stop_listener(Name),
ok.

View File

@ -0,0 +1,28 @@
-module(proxy_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
{ok, Pid}.
init(Ref, Transport, _Opts = []) ->
{ok, ProxyInfo} = ranch:recv_proxy_header(Ref, 1000),
{ok, Socket} = ranch:handshake(Ref),
Pid = case Transport of
ranch_tcp -> ct_helper:get_remote_pid_tcp(Socket);
ranch_ssl -> ct_helper:get_remote_pid_tls(Socket)
end,
Pid ! {?MODULE, ProxyInfo},
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.

View File

@ -0,0 +1,83 @@
%% Copyright (c) 2020-2021, 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.
-module(ranch_concuerror).
-compile(export_all).
-compile(nowarn_export_all).
-concuerror_options([
{after_timeout, 5000},
{treat_as_normal, [
killed, %% Acceptors are killed on shutdown.
shutdown %% This is a normal exit reason in OTP.
]}
]).
%% Convenience functions.
do_start() ->
{ok, SupPid} = ranch_app:start(normal, []),
SupPid.
-spec do_stop(pid()) -> no_return().
do_stop(SupPid) ->
exit(SupPid, shutdown),
%% We make sure that SupPid terminated before the test ends,
%% because otherwise the shutdown will not be ordered and
%% can produce error exit reasons.
receive after infinity -> ok end.
%% Tests.
-spec start_stop() -> no_return().
start_stop() ->
%% Start a listener then stop it.
SupPid = do_start(),
{ok, _} = ranch:start_listener(?FUNCTION_NAME,
ranch_erlang_transport, #{
num_acceptors => 1
},
echo_protocol, []),
ok = ranch:stop_listener(?FUNCTION_NAME),
do_stop(SupPid).
%% @todo This takes a huge amount of time.
%start_stop_twice() ->
% %% Start a listener then stop it. Then start and stop it again.
% SupPid = do_start(),
% {ok, _} = ranch:start_listener(?FUNCTION_NAME,
% ranch_erlang_transport, #{
% num_acceptors => 1
% },
% echo_protocol, []),
% ok = ranch:stop_listener(?FUNCTION_NAME),
% {ok, _} = ranch:start_listener(?FUNCTION_NAME,
% ranch_erlang_transport, #{
% num_acceptors => 1
% },
% echo_protocol, []),
% ok = ranch:stop_listener(?FUNCTION_NAME),
% do_stop(SupPid).
-spec info() -> no_return().
info() ->
%% Ensure we can call ranch:info/1 after starting a listener.
SupPid = do_start(),
{ok, _} = ranch:start_listener(?FUNCTION_NAME,
ranch_erlang_transport, #{
num_acceptors => 1
},
echo_protocol, []),
#{} = ranch:info(?FUNCTION_NAME),
do_stop(SupPid).

View File

@ -0,0 +1,43 @@
%% Copyright (c) 2015-2021, 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.
-module(ranch_ct_hook).
-export([init/2]).
init(_, _) ->
%% Allow a more relaxed restart intensity because
%% some tests will cause quick restarts of several
%% ranch_sup children.
application:set_env(ranch, ranch_sup_intensity, 10),
application:set_env(ranch, ranch_sup_period, 1),
ok = application:load(ssl),
case {os:type(), application:get_key(ssl, vsn)} of
%% Internal active,N is broken on Windows since
%% OTP 21.2/ssl 9.1.
%% @todo Put an upper limit on the version when
%% this is fixed in a future OTP version.
{_, {ok, "9.0"++_}} ->
ok;
{{win32, nt}, {ok, "9."++_}} ->
application:set_env(ssl, internal_active_n, 1);
{{win32, nt}, {ok, "10."++_}} ->
application:set_env(ssl, internal_active_n, 1);
_ ->
ok
end,
ct_helper:start([ranch]),
ct_helper:make_certs_in_ets(),
error_logger:add_report_handler(ct_helper_error_h),
{ok, undefined}.

View File

@ -0,0 +1,174 @@
%% Copyright (c) 2020-2021, 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.
-module(ranch_erlang_transport).
-behaviour(ranch_transport).
-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([accept/2]).
-export([handshake/2]).
-export([handshake/3]).
-export([handshake_continue/2]).
-export([handshake_continue/3]).
-export([handshake_cancel/1]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
-export([recv_proxy_header/2]).
-export([send/2]).
-export([sendfile/2]).
-export([sendfile/4]).
-export([sendfile/5]).
-export([setopts/2]).
-export([getopts/2]).
-export([getstat/1]).
-export([getstat/2]).
-export([controlling_process/2]).
-export([peername/1]).
-export([sockname/1]).
-export([shutdown/2]).
-export([close/1]).
-export([cleanup/1]).
-type opts() :: [].
-export_type([opts/0]).
-spec name() -> erlang.
name() -> erlang.
-spec secure() -> boolean().
secure() ->
false.
-spec messages() -> {erlang, erlang_closed, erlang_error, erlang_passive}.
messages() -> {erlang, erlang_closed, erlang_error, erlang_passive}.
-spec listen(ranch:transport_opts(opts())) -> {ok, reference()}.
listen(_TransOpts) ->
{ok, make_ref()}.
-spec accept(reference(), timeout()) -> no_return(). % {ok, reference()}.
accept(_LSocket, _Timeout) ->
receive after infinity -> {ok, make_ref()} end.
-spec handshake(reference(), timeout()) -> {ok, reference()}.
handshake(CSocket, Timeout) ->
handshake(CSocket, [], Timeout).
-spec handshake(reference(), opts(), timeout()) -> {ok, reference()}.
handshake(CSocket, _, _) ->
{ok, CSocket}.
-spec handshake_continue(reference(), timeout()) -> no_return().
handshake_continue(CSocket, Timeout) ->
handshake_continue(CSocket, [], Timeout).
-spec handshake_continue(reference(), opts(), timeout()) -> no_return().
handshake_continue(_, _, _) ->
error(not_supported).
-spec handshake_cancel(reference()) -> no_return().
handshake_cancel(_) ->
error(not_supported).
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
-> {ok, reference()}.
connect(_Host, Port, _Opts) when is_integer(Port) ->
{ok, make_ref()}.
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any(), timeout())
-> {ok, reference()}.
connect(_Host, Port, _Opts, _Timeout) when is_integer(Port) ->
{ok, make_ref()}.
-spec recv(reference(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | atom()}.
recv(_Socket, _Length, _Timeout) ->
{ok, <<>>}.
-spec recv_proxy_header(reference(), timeout()) -> no_return().
recv_proxy_header(_Socket, _Timeout) ->
error(not_supported).
-spec send(reference(), iodata()) -> ok | {error, atom()}.
send(_Socket, _Packet) ->
ok.
-spec sendfile(reference(), file:name_all() | file:fd())
-> no_return().
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
-spec sendfile(reference(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer())
-> no_return().
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
-spec sendfile(reference(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer(), [{chunk_size, non_neg_integer()}])
-> no_return().
sendfile(_Socket, Filename, _Offset, _Bytes, _Opts)
when is_list(Filename) orelse is_atom(Filename)
orelse is_binary(Filename) ->
error(not_supported).
-spec setopts(reference(), list()) -> ok.
setopts(_Socket, _Opts) ->
ok.
-spec getopts(reference(), [atom()]) -> {ok, list()} | {error, atom()}.
getopts(_Socket, _Opts) ->
{ok, []}.
-spec getstat(reference()) -> {ok, list()} | {error, atom()}.
getstat(_Socket) ->
{ok, []}.
-spec getstat(reference(), [atom()]) -> {ok, list()} | {error, atom()}.
getstat(_Socket, _OptionNames) ->
{ok, []}.
-spec controlling_process(reference(), pid())
-> ok | {error, closed | not_owner | atom()}.
controlling_process(_Socket, _Pid) ->
ok.
-spec peername(reference())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
peername(_Socket) ->
{ok, {{127, 0, 0, 1}, 12701}}.
-spec sockname(reference())
-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
sockname(_Socket) ->
{ok, {{127, 0, 0, 1}, 12710}}.
-spec shutdown(reference(), read | write | read_write)
-> ok | {error, atom()}.
shutdown(_Socket, _How) ->
ok.
-spec close(reference()) -> ok.
close(_Socket) ->
ok.
-spec cleanup(ranch:transport_opts(opts())) -> ok.
cleanup(_) ->
ok.

View File

@ -0,0 +1,21 @@
-module(remove_conn_and_wait_protocol).
-behaviour(ranch_protocol).
-export([start_link/3]).
-export([init/3]).
start_link(Ref, _, [{remove, MaybeRemove, Timeout}]) ->
Pid = spawn_link(?MODULE, init, [Ref, MaybeRemove, Timeout]),
{ok, Pid}.
init(Ref, MaybeRemove, Timeout) ->
{ok, _} = ranch:handshake(Ref),
_ = case MaybeRemove of
true ->
ranch:remove_connection(Ref);
false ->
ok;
N ->
[ranch:remove_connection(Ref) || _ <- lists:seq(1, N)]
end,
receive after Timeout -> ok end.

View File

@ -0,0 +1,342 @@
%% Copyright (c) 2013, James Fish <james@fishcakez.com>
%% Copyright (c) 2015-2021, 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.
-module(sendfile_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
all() ->
[{group, tcp}, {group, tcp_socket}, {group, ssl}].
suite() ->
[{timetrap, {seconds, 60}}].
groups() ->
Tests = [
filename,
rawfile,
rawfile_bytes_large,
rawfile_bytes_zero,
rawfile_chunk_size_large,
rawfile_offset_large,
rawfile_range_large,
rawfile_range_medium,
rawfile_range_small
],
[
{tcp, [parallel], Tests},
{tcp_socket, [parallel], Tests},
{ssl, [parallel], Tests ++ [ssl_chunk_size]}
].
init_per_suite(Config) ->
Filename = filename:join(config(priv_dir, Config), "sendfile"),
Binary = crypto:strong_rand_bytes(20 * 1024 * 1024),
ok = file:write_file(Filename, Binary),
[{filename, Filename} | Config].
end_per_suite(Config) ->
Filename = config(filename, Config),
ok = file:delete(Filename),
ok.
init_per_group(ssl, Config) ->
SslOpts = ct_helper:get_certs_from_ets(),
[{transport, ranch_ssl}, {transport_opts, SslOpts} | Config];
init_per_group(tcp, Config) ->
[{transport, ranch_tcp}, {transport_opts, []} | Config];
init_per_group(tcp_socket, Config) ->
%% The socket backend for inet/gen_tcp was introduced as an experimental
%% feature in OTP/23.0, and bugs https://bugs.erlang.org/browse/ERL-1284,
%% 1287 and 1293 were solved in OTP/23.1. socket:use_registry/1 first
%% appears in this release.
%% Due to https://bugs.erlang.org/browse/ERL-1401, the socket backend
%% is not working on Windows.
case
os:type() =/= {win32, nt} andalso
code:ensure_loaded(socket) =:= {module, socket} andalso
erlang:function_exported(socket, use_registry, 1)
of
true ->
[{transport, ranch_tcp}, {transport_opts, [{inet_backend, socket}]} | Config];
false ->
{skip, "No socket backend support"}
end.
end_per_group(_, _) ->
ok.
filename(Config) ->
doc("Use sendfile with a filename."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
Ref = recv(Transport, Server, Size),
{ok, Size} = Transport:sendfile(Client, Filename),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile(Config) ->
doc("Use sendfile with a file descriptor (raw file)."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Ref = recv(Transport, Server, Size),
{ok, Size} = Transport:sendfile(Client, RawFile, 0, Size),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, 0} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_bytes_large(Config) ->
doc("Use sendfile with a file descriptor. Try to send a size larger than file size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Ref = recv(Transport, Server, Size),
%% Only send Size not Size * 2
{ok, Size} = Transport:sendfile(Client, RawFile, 0, Size * 2),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, 0} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_bytes_zero(Config) ->
doc("Use sendfile with a file descriptor. Ensure using a size of 0 sends the whole file."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Ref = recv(Transport, Server, Size),
{ok, Size} = Transport:sendfile(Client, RawFile, 0, 0),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, 0} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_chunk_size_large(Config) ->
doc("Use sendfile with a file descriptor. Try to use a chunk size larger than file size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Ref = recv(Transport, Server, Size),
{ok, Size} = Transport:sendfile(Client, RawFile, 0, Size, [{chunk_size, Size * 2}]),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, 0} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_offset_large(Config) ->
doc("Use sendfile with a file descriptor. Ensure using an offset larger than file size sends nothing."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
{ok, 0} = Transport:sendfile(Client, RawFile, Size, 1),
{error, timeout} = Transport:recv(Server, 1, 100),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_range_large(Config) ->
doc("Use sendfile with a file descriptor. "
"Set an offset and try to send a size larger than remaining file size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Initial = 499,
{ok, _} = file:position(RawFile, {bof, Initial}),
Offset = 75,
Bytes = Size * 2,
Sent = Size - Offset,
Ref = recv(Transport, Server, Sent),
{ok, Sent} = Transport:sendfile(Client, RawFile, Offset, Bytes),
Binary2 = binary:part(Binary, Offset, Sent),
{ok, Binary2} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, Initial} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_range_medium(Config) ->
doc("Use sendfile with a file descriptor. "
"Set an offset and try to send a size lower than remaining file size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Initial = 50,
{ok, _} = file:position(RawFile, {bof, Initial}),
Offset = 50,
Bytes = Size - Offset - 50,
Ref = recv(Transport, Server, Bytes),
{ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes),
Binary2 = binary:part(Binary, Offset, Bytes),
{ok, Binary2} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, Initial} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
rawfile_range_small(Config) ->
doc("Use sendfile with a file descriptor. "
"Set an offset and try to send a size lower than remaining file size, "
"which is in turn lower than the chunk size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Initial = 3,
{ok, _} = file:position(RawFile, {bof, Initial}),
Offset = 7,
Bytes = 19,
Ref = recv(Transport, Server, Bytes),
{ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes, [{chunk_size, 16#FFFF}]),
Binary2 = binary:part(Binary, Offset, Bytes),
{ok, Binary2} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
{ok, Initial} = file:position(RawFile, {cur, 0}),
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server).
ssl_chunk_size(Config) ->
doc("Use sendfile with SSL. Ensure the sendfile fallback respects the chunk size."),
Transport = config(transport, Config),
Filename = config(filename, Config),
{ok, Binary} = file:read_file(Filename),
Size = byte_size(Binary),
Self = self(),
ChunkSize = 8 * 1024,
Fun = fun() ->
receive go -> ok after 5000 -> error(timeout) end,
{ok, {Server, Client}} = sockets(Config),
{ok, RawFile} = file:open(Filename, [read, raw, binary]),
Ref = recv(Transport, Server, Size),
{ok, Size} = Transport:sendfile(Client, RawFile, 0, Size, [{chunk_size, ChunkSize}]),
{ok, Binary} = result(Ref),
{error, timeout} = Transport:recv(Server, 1, 100),
Self ! done,
ok = file:close(RawFile),
ok = Transport:close(Client),
ok = Transport:close(Server)
end,
Pid = spawn_link(Fun),
1 = erlang:trace(Pid, true, [call]),
_ = erlang:trace_pattern({Transport, send, 2}, true, [global]),
Pid ! go,
receive done -> ok after 30000 -> error(timeout) end,
Sizes = lists:duplicate(Size div ChunkSize, ChunkSize) ++
[Size rem ChunkSize || (Size rem ChunkSize) =/= 0],
ok = recv_send_trace(Sizes, Pid),
_ = erlang:trace(all, false, [all]),
ok = clean_traces().
%% Internal.
sockets(Config) ->
Transport = config(transport, Config),
TransportOpts = config(transport_opts, Config),
{ok, LSocket} = Transport:listen(#{socket_opts => TransportOpts}),
{ok, {_, Port}} = Transport:sockname(LSocket),
Self = self(),
Fun = fun() ->
{ok, Client} = Transport:connect("localhost", Port, TransportOpts),
ok = Transport:controlling_process(Client, Self),
Self ! {ok, Client}
end,
_ = spawn_link(Fun),
{ok, Server} = Transport:accept(LSocket, 5000),
{ok, _} = Transport:handshake(Server, [], 5000),
receive
{ok, Client} ->
ok = Transport:close(LSocket),
{ok, {Server, Client}}
after 1000 ->
{error, timeout}
end.
recv(Transport, Server, Size) ->
Self = self(),
Ref = make_ref(),
spawn_link(fun() -> Self ! {Ref, Transport:recv(Server, Size, 20000)} end),
Ref.
result(Ref) ->
receive
{Ref, Result} ->
Result
after
30000 ->
{error, result_timedout}
end.
recv_send_trace([], _Pid) ->
ok;
recv_send_trace([Size | Rest], Pid) ->
receive
{trace, Pid, call, {_, _, [_, Chunk]}} when byte_size(Chunk) == Size ->
recv_send_trace(Rest, Pid);
{trace, Pid, call, {_, _, [_, Chunk]}} ->
{error, {invalid_chunk, Size, byte_size(Chunk)}}
after 1000 ->
{error, timeout}
end.
clean_traces() ->
receive
{trace, _, _, _} ->
clean_traces();
{trace, _, _, _, _} ->
clean_traces()
after 0 ->
ok
end.

Some files were not shown because too many files have changed in this diff Show More