Import ranch.
Ranch is the listener used by cowboy to handle HTTP connections. URL: https://github.com/ninenines/ranchmain
parent
9fdf706bcb
commit
4aa09346d7
|
@ -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.
|
|
@ -0,0 +1,7 @@
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all:
|
||||||
|
${MAKE} -C src
|
||||||
|
|
||||||
|
clean:
|
||||||
|
${MAKE} -C src clean
|
|
@ -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]
|
|
@ -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]
|
|
@ -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.
|
||||||
|
----
|
|
@ -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.
|
|
@ -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.
|
|
@ -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]
|
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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`.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)]
|
||||||
|
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)]
|
|
@ -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)]
|
|
@ -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, []}
|
||||||
|
]}.
|
|
@ -0,0 +1,4 @@
|
||||||
|
PROJECT = tcp_echo
|
||||||
|
DEPS = ranch
|
||||||
|
dep_ranch_commit = master
|
||||||
|
include ../../erlang.mk
|
|
@ -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.
|
|
@ -0,0 +1,2 @@
|
||||||
|
{release, {tcp_echo_example, "1"}, [tcp_echo, sasl]}.
|
||||||
|
{extended_start_script, true}.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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}, []}}.
|
|
@ -0,0 +1,4 @@
|
||||||
|
PROJECT = tcp_reverse
|
||||||
|
DEPS = ranch
|
||||||
|
dep_ranch_commit = master
|
||||||
|
include ../../erlang.mk
|
|
@ -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.
|
|
@ -0,0 +1,2 @@
|
||||||
|
{release, {tcp_reverse_example, "1"}, [tcp_reverse, sasl]}.
|
||||||
|
{extended_start_script, true}.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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}, []}}.
|
|
@ -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
|
||||||
|
|
|
@ -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, []}}
|
||||||
|
]}]
|
||||||
|
}.
|
|
@ -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).
|
|
@ -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.
|
|
@ -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).
|
|
@ -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.
|
|
@ -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).
|
|
@ -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}}.
|
|
@ -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.
|
|
@ -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]}}.
|
|
@ -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}}.
|
|
@ -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
|
@ -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]}.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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}}.
|
|
@ -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.
|
|
@ -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
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
||||||
|
{incl_app, ranch, details}.
|
|
@ -0,0 +1,7 @@
|
||||||
|
-module(crash_protocol).
|
||||||
|
|
||||||
|
-export([start_link/4]).
|
||||||
|
|
||||||
|
-spec start_link(_, _, _, _) -> no_return().
|
||||||
|
start_link(_, _, _, _) ->
|
||||||
|
exit(crash).
|
|
@ -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.
|
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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).
|
|
@ -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}.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
Loading…
Reference in New Issue