pax_global_header00006660000000000000000000000064147536337750014536gustar00rootroot0000000000000052 comment=9c8520ab8e9c6f3890ac3251d04fbe0b9514940f ranch-2.2.0/000077500000000000000000000000001475363377500126325ustar00rootroot00000000000000ranch-2.2.0/.github/000077500000000000000000000000001475363377500141725ustar00rootroot00000000000000ranch-2.2.0/.github/workflows/000077500000000000000000000000001475363377500162275ustar00rootroot00000000000000ranch-2.2.0/.github/workflows/ci.yaml000066400000000000000000000016251475363377500175120ustar00rootroot00000000000000## Use workflows from ninenines/ci.erlang.mk to test Ranch. name: Check Ranch on: push: branches: - master pull_request: schedule: ## Every Monday at 4am. - cron: 0 4 * * 1 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: cleanup-master: name: Cleanup master build runs-on: ubuntu-latest permissions: actions: write steps: - name: Cleanup master build if necessary if: ${{ github.event_name == 'schedule' }} run: | gh cache delete Linux-X64-Erlang-master -R $REPO || true gh cache delete macOS-ARM64-Erlang-master -R $REPO || true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} check: name: Ranch needs: cleanup-master uses: ninenines/ci.erlang.mk/.github/workflows/ci.yaml@master ranch-2.2.0/.gitignore000066400000000000000000000003001475363377500146130ustar00rootroot00000000000000.erlang.mk .ranch.plt *.d deps doc/guide.pdf doc/html doc/man3 doc/man7 ebin/test ebin/ranch.appup ebin/*.beam examples/*/ebin examples/*/*.bak examples/*/*.d examples/*/_rel logs test/*.beam ranch-2.2.0/LICENSE000066400000000000000000000013651475363377500136440ustar00rootroot00000000000000Copyright (c) 2011-2025, Loïc Hoguin 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. ranch-2.2.0/Makefile000066400000000000000000000062471475363377500143030ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = ranch PROJECT_DESCRIPTION = Socket acceptor pool for TCP protocols. PROJECT_VERSION = 2.2.0 PROJECT_REGISTERED = ranch_server # Options. CT_OPTS += -pa test -ct_hooks ranch_ct_hook [] # -boot start_sasl PLT_APPS = crypto public_key tools # common_test ct_helper stampede # Dependencies. LOCAL_DEPS = ssl DOC_DEPS = asciideck TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper stampede dep_ct_helper = git https://github.com/ninenines/ct_helper master dep_stampede = git https://github.com/juhlig/stampede 0.6.0 # Concuerror tests. # CONCUERROR_OPTS = -v 7 -k CONCUERROR_TESTS = ranch_concuerror:start_stop ranch_concuerror:info # CI configuration. dep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master DEP_EARLY_PLUGINS = ci.erlang.mk AUTO_CI_OTP ?= OTP-LATEST-24+ AUTO_CI_WINDOWS ?= OTP-LATEST-24+ # Hex configuration. define HEX_TARBALL_EXTRA_METADATA #{ licenses => [<<"ISC">>], links => #{ <<"User guide">> => <<"https://ninenines.eu/docs/en/ranch/2.2/guide/">>, <<"Function reference">> => <<"https://ninenines.eu/docs/en/ranch/2.2/manual/">>, <<"GitHub">> => <<"https://github.com/ninenines/ranch">>, <<"Sponsor">> => <<"https://github.com/sponsors/essen">> } } endef # Standard targets. include erlang.mk # Don't run the havoc test suite by default. ifndef FULL CT_SUITES := $(filter-out stampede,$(CT_SUITES)) endif # Compile options. ERLC_OPTS += +warn_missing_spec +warn_untyped_record TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}' # Dialyze the tests. #DIALYZER_OPTS += --src -r test # Use erl_make_certs from the tested release during CI # and ensure that ct_helper is always recompiled. # # Note that erl_make_certs was removed from OTP-20.1. For now # we are fine using the most recent version from OTP-20. ci-setup:: $(DEPS_DIR)/ct_helper $(gen_verbose) cp ~/.kerl/builds/$(CI_OTP_RELEASE)/otp_src_git/lib/ssl/test/erl_make_certs.erl deps/ct_helper/src/ || true $(gen_verbose) $(MAKE) -C $(DEPS_DIR)/ct_helper clean app # Prepare for the release. prepare_tag: $(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA)) $(verbose) echo $(verbose) echo -n "Most recent tag: " $(verbose) git tag --sort=creatordate | tail -n1 $(verbose) git verify-tag `git tag --sort=creatordate | tail -n1` $(verbose) echo -n "MAKEFILE: " $(verbose) grep -m1 PROJECT_VERSION Makefile $(verbose) echo -n "APP: " $(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/ //g' $(verbose) echo -n "APPUP: " $(verbose) grep -m1 {\" src/$(PROJECT).appup $(verbose) echo -n "GUIDE: " $(verbose) grep -h dep_$(PROJECT)_commit doc/src/guide/*.asciidoc || true $(verbose) echo ; echo $(verbose) echo -n "LICENSE: " ; head -n1 LICENSE $(verbose) echo $(verbose) echo "Dependencies:" $(verbose) grep ^DEPS Makefile || echo "DEPS =" $(verbose) grep ^dep_ Makefile || true $(verbose) echo $(verbose) echo "Links in the README:" $(verbose) grep http.*:// README.asciidoc $(verbose) echo $(verbose) echo "Titles in most recent CHANGELOG:" $(verbose) for f in `ls -rv doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \ echo $$f:; \ grep == $$f; \ done ranch-2.2.0/README.asciidoc000066400000000000000000000025701475363377500152730ustar00rootroot00000000000000= 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.2/guide[User guide] * https://ninenines.eu/docs/en/ranch/2.2/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://discord.gg/x25nNq2fFE[Discord server] * https://github.com/ninenines/ranch/issues[Issues tracker] * https://ninenines.eu/services[Commercial Support] ranch-2.2.0/doc/000077500000000000000000000000001475363377500133775ustar00rootroot00000000000000ranch-2.2.0/doc/src/000077500000000000000000000000001475363377500141665ustar00rootroot00000000000000ranch-2.2.0/doc/src/guide/000077500000000000000000000000001475363377500152635ustar00rootroot00000000000000ranch-2.2.0/doc/src/guide/book.asciidoc000066400000000000000000000020261475363377500177150ustar00rootroot00000000000000// 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.1.asciidoc[Migrating from Ranch 2.1 to 2.2] 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] ranch-2.2.0/doc/src/guide/connection_draining.asciidoc000066400000000000000000000047461475363377500230100ustar00rootroot00000000000000== 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. ---- ranch-2.2.0/doc/src/guide/embedded.asciidoc000066400000000000000000000034301475363377500205140ustar00rootroot00000000000000== 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. ranch-2.2.0/doc/src/guide/internals.asciidoc000066400000000000000000000105421475363377500207640ustar00rootroot00000000000000== 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. ranch-2.2.0/doc/src/guide/introduction.asciidoc000066400000000000000000000013461475363377500215100ustar00rootroot00000000000000== 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 24+. 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] ranch-2.2.0/doc/src/guide/listeners.asciidoc000066400000000000000000000367121475363377500210040ustar00rootroot00000000000000== 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 threshold. 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 threshold 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, threshold => 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). ranch-2.2.0/doc/src/guide/migrating_from_1.5.asciidoc000066400000000000000000000053211475363377500223530ustar00rootroot00000000000000[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. ranch-2.2.0/doc/src/guide/migrating_from_1.6.asciidoc000066400000000000000000000027071475363377500223610ustar00rootroot00000000000000[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. ranch-2.2.0/doc/src/guide/migrating_from_1.7.asciidoc000066400000000000000000000141271475363377500223610ustar00rootroot00000000000000[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. ranch-2.2.0/doc/src/guide/migrating_from_1.x.asciidoc000066400000000000000000000050271475363377500224610ustar00rootroot00000000000000[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 ranch-2.2.0/doc/src/guide/migrating_from_2.0.asciidoc000066400000000000000000000045451475363377500223560ustar00rootroot00000000000000[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 threshold 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. ranch-2.2.0/doc/src/guide/migrating_from_2.1.asciidoc000066400000000000000000000030361475363377500223510ustar00rootroot00000000000000[appendix] == Migrating from Ranch 2.1 to Ranch 2.2 Ranch 2.2 is a maintenance release containing a small number of fixes and improvements. Ranch 2.2 is tested with Erlang/OTP 24.0 onward, although it should be compatible with Erlang/OTP 22.0 and above. === Features added * `DTLS` is now supported. * The `certs_keys` option from `ssl` is now accepted and documented. * The `UNIQUE_ID` PROXY protocol header extension is now supported. * The `ranch_transport` behavior has a new callback `format_error/1` which allows finer grained formatting of Ranch's error messages. The callback is currently optional and will become required in Ranch 3.0. === Changed behaviors * Ranch will now obfuscate certificates, keys and passwords in the error message produced on listen error. * The exception reason when a connection process has failed its handshake has been changed to `{shutdown, {Reason, PeerInfo}}` where `PeerInfo` contains the peer name when available. * The `{packet, raw}` socket option is no longer set explicitly, as this is already the default value. === Bugs fixed * `ranch:stop_listener/1` will now return an error instead of throwing an exception when the listener does not exist. * Fix `ranch:recv_proxy_header/2` for the upcoming Erlang/OTP 28. * Ensure that a user crash while stopping a listener does not prevent a subsequent attempt to stop it. * Alarm option `threshold` was mispelled in Ranch 2.1. This has been corrected. The wrong spelling `treshold` is still accepted and will be removed in Ranch 3.0. ranch-2.2.0/doc/src/guide/parsers.asciidoc000066400000000000000000000055461475363377500204540ustar00rootroot00000000000000== 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. ranch-2.2.0/doc/src/guide/protocols.asciidoc000066400000000000000000000071171475363377500210150ustar00rootroot00000000000000== 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`. ranch-2.2.0/doc/src/guide/ssl_auth.asciidoc000066400000000000000000000102201475363377500206000ustar00rootroot00000000000000== 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. ranch-2.2.0/doc/src/guide/transports.asciidoc000066400000000000000000000152061475363377500212060ustar00rootroot00000000000000== 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. It is highly recommended for a custom transport handler to implement the optional `format_error/1` callback, in order to provide a human-readable diagnostic string. Implementing this callback will become mandatory in Ranch 3.0. ranch-2.2.0/doc/src/manual/000077500000000000000000000000001475363377500154435ustar00rootroot00000000000000ranch-2.2.0/doc/src/manual/ranch.asciidoc000066400000000000000000000155201475363377500202410ustar00rootroot00000000000000= 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, threshold := 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 threshold. + 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`. threshold::: Threshold 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.1*: The `post_listen_callback` transport option was added. * *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)] ranch-2.2.0/doc/src/manual/ranch.child_spec.asciidoc000066400000000000000000000053751475363377500223440ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_addr.asciidoc000066400000000000000000000022511475363377500220060ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_max_connections.asciidoc000066400000000000000000000016521475363377500242670ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_port.asciidoc000066400000000000000000000015341475363377500220630ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_protocol_options.asciidoc000066400000000000000000000013311475363377500245060ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_status.asciidoc000066400000000000000000000016141475363377500224210ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.get_transport_options.asciidoc000066400000000000000000000013671475363377500247120ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.handshake.asciidoc000066400000000000000000000042421475363377500221650ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.handshake_cancel.asciidoc000066400000000000000000000017731475363377500235000ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.handshake_continue.asciidoc000066400000000000000000000024521475363377500240720ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.info.asciidoc000066400000000000000000000033461475363377500211760ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.procs.asciidoc000066400000000000000000000013721475363377500213660ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.recv_proxy_header.asciidoc000066400000000000000000000031311475363377500237430ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.remove_connection.asciidoc000066400000000000000000000016771475363377500237640ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.resume_listener.asciidoc000066400000000000000000000023141475363377500234420ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.set_max_connections.asciidoc000066400000000000000000000023611475363377500243010ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.set_protocol_options.asciidoc000066400000000000000000000025721475363377500245320ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.set_transport_options.asciidoc000066400000000000000000000046221475363377500247230ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.start_listener.asciidoc000066400000000000000000000062401475363377500233010ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.stop_listener.asciidoc000066400000000000000000000025421475363377500231320ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.suspend_listener.asciidoc000066400000000000000000000027271475363377500236330ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch.wait_for_connections.asciidoc000066400000000000000000000030521475363377500244510ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch_app.asciidoc000066400000000000000000000026351475363377500211040ustar00rootroot00000000000000= 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) ranch-2.2.0/doc/src/manual/ranch_protocol.asciidoc000066400000000000000000000030331475363377500221560ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch_proxy_header.asciidoc000066400000000000000000000100621475363377500230060ustar00rootroot00000000000000= 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. unique_id => binary(), %% Opaque byte sequence of up to 128 bytes. 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. unique_id:: An opaque byte sequence of up to 128 bytes generated by the upstream proxy that uniquely identifies the connection. 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 * *2.2*: The `unique_id` TLV was added. * *1.7*: Module introduced. == See also link:man:ranch(7)[ranch(7)] ranch-2.2.0/doc/src/manual/ranch_proxy_header.header.asciidoc000066400000000000000000000025321475363377500242400ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch_proxy_header.parse.asciidoc000066400000000000000000000020441475363377500241200ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc000066400000000000000000000020051475363377500266570ustar00rootroot00000000000000= 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)] ranch-2.2.0/doc/src/manual/ranch_ssl.asciidoc000066400000000000000000000204271475363377500211240ustar00rootroot00000000000000= 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()} | {certs_keys, [#{cert => public_key:der_encoded(), key => ssl:key(), certfile => file:filename(), keyfile => file:filename(), key_pem_password => iodata() | fun(() -> iodata())}]} | {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. certs_keys:: A list of a certificate (or possible a certificate and its chain) and the associated key of the certificate, that may be used to authenticate the client or the server. 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) ranch-2.2.0/doc/src/manual/ranch_tcp.asciidoc000066400000000000000000000103321475363377500211030ustar00rootroot00000000000000= 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) ranch-2.2.0/doc/src/manual/ranch_transport.asciidoc000066400000000000000000000200351475363377500223520ustar00rootroot00000000000000= 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. === format_error [source,erlang] ---- format_error(Reason :: term()) -> ReasonString :: string() ---- Format a listen error into a human-readable diagnostic string. This callback is optional, but implementing it is highly recommended. It will become mandatory in Ranch 3.0. === 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.2*: The callback `format_error/1` was added. * *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)] ranch-2.2.0/doc/src/manual/ranch_transport.sendfile.asciidoc000066400000000000000000000031361475363377500241450ustar00rootroot00000000000000= 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)] ranch-2.2.0/ebin/000077500000000000000000000000001475363377500135475ustar00rootroot00000000000000ranch-2.2.0/ebin/ranch.app000066400000000000000000000010441475363377500153430ustar00rootroot00000000000000{application, 'ranch', [ {description, "Socket acceptor pool for TCP protocols."}, {vsn, "2.2.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]}, {optional_applications, []}, {mod, {ranch_app, []}}, {env, []} ]}.ranch-2.2.0/erlang.mk000066400000000000000000007602201475363377500144420ustar00rootroot00000000000000# Copyright (c) 2013-2016, Loïc Hoguin # # 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. .PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) export ERLANG_MK_FILENAME ERLANG_MK_VERSION = d3485e7 ERLANG_MK_WITHOUT = # Make 3.81 and 3.82 are deprecated. ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.81) $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) endif ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.82) $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) endif # Core configuration. PROJECT ?= $(notdir $(CURDIR)) PROJECT := $(strip $(PROJECT)) PROJECT_VERSION ?= rolling PROJECT_MOD ?= $(PROJECT)_app PROJECT_ENV ?= [] # Verbosity. V ?= 0 verbose_0 = @ verbose_2 = set -x; verbose = $(verbose_$(V)) ifeq ($(V),3) SHELL := $(SHELL) -x endif gen_verbose_0 = @echo " GEN " $@; gen_verbose_2 = set -x; gen_verbose = $(gen_verbose_$(V)) gen_verbose_esc_0 = @echo " GEN " $$@; gen_verbose_esc_2 = set -x; gen_verbose_esc = $(gen_verbose_esc_$(V)) # Temporary files directory. ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk export ERLANG_MK_TMP # "erl" command. ERL = erl +A1 -noinput -boot no_dot_erlang # Platform detection. ifeq ($(PLATFORM),) UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) PLATFORM = linux else ifeq ($(UNAME_S),Darwin) PLATFORM = darwin else ifeq ($(UNAME_S),SunOS) PLATFORM = solaris else ifeq ($(UNAME_S),GNU) PLATFORM = gnu else ifeq ($(UNAME_S),FreeBSD) PLATFORM = freebsd else ifeq ($(UNAME_S),NetBSD) PLATFORM = netbsd else ifeq ($(UNAME_S),OpenBSD) PLATFORM = openbsd else ifeq ($(UNAME_S),DragonFly) PLATFORM = dragonfly else ifeq ($(shell uname -o),Msys) PLATFORM = msys2 else $(error Unable to detect platform. Please open a ticket with the output of uname -a.) endif export PLATFORM endif # Core targets. all:: deps app rel # Noop to avoid a Make warning when there's nothing to do. rel:: $(verbose) : relup:: deps app check:: tests clean:: clean-crashdump clean-crashdump: ifneq ($(wildcard erl_crash.dump),) $(gen_verbose) rm -f erl_crash.dump endif distclean:: clean distclean-tmp $(ERLANG_MK_TMP): $(verbose) mkdir -p $(ERLANG_MK_TMP) distclean-tmp: $(gen_verbose) rm -rf $(ERLANG_MK_TMP) help:: $(verbose) printf "%s\n" \ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ "Copyright (c) 2013-2016 Loïc Hoguin " \ "" \ "Usage: [V=1] $(MAKE) [target]..." \ "" \ "Core targets:" \ " all Run deps, app and rel targets in that order" \ " app Compile the project" \ " deps Fetch dependencies (if needed) and compile them" \ " fetch-deps Fetch dependencies recursively (if needed) without compiling them" \ " list-deps List dependencies recursively on stdout" \ " search q=... Search for a package in the built-in index" \ " rel Build a release for this project, if applicable" \ " docs Build the documentation for this project" \ " install-docs Install the man pages for this project" \ " check Compile and run all tests and analysis for this project" \ " tests Run the tests for this project" \ " clean Delete temporary and output files from most targets" \ " distclean Delete all temporary and output files" \ " help Display this help and exit" \ " erlang-mk Update erlang.mk to the latest version" # Core functions. empty := space := $(empty) $(empty) tab := $(empty) $(empty) comma := , define newline endef define comma_list $(subst $(space),$(comma),$(strip $(1))) endef define escape_dquotes $(subst ",\",$1) endef # Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy. define erlang $(ERL) $2 -pz $(ERLANG_MK_TMP)/rebar3/_build/prod/lib/*/ebin/ -eval "$(subst $(newline),,$(call escape_dquotes,$1))" -- erlang.mk endef ifeq ($(PLATFORM),msys2) core_native_path = $(shell cygpath -m $1) else core_native_path = $1 endif core_http_get = curl -Lf$(if $(filter-out 0,$(V)),,s)o $(call core_native_path,$1) $2 core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) # We skip files that contain spaces because they end up causing issues. # Files that begin with a dot are already ignored by the wildcard function. core_find = $(foreach f,$(wildcard $(1:%/=%)/*),$(if $(wildcard $f/.),$(call core_find,$f,$2),$(if $(filter $(subst *,%,$2),$f),$(if $(wildcard $f),$f)))) core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1))))))))))))))))))))))))))) core_ls = $(filter-out $(1),$(shell echo $(1))) # @todo Use a solution that does not require using perl. core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2) define core_render printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $(1))))))\n' > $(2) endef # Automated update. ERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk ERLANG_MK_COMMIT ?= ERLANG_MK_BUILD_CONFIG ?= build.config ERLANG_MK_BUILD_DIR ?= .erlang.mk.build erlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT) erlang-mk: ifdef ERLANG_MK_COMMIT $(verbose) git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR) $(verbose) cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT) else $(verbose) git clone --depth 1 $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR) endif $(verbose) if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi $(gen_verbose) $(MAKE) --no-print-directory -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))' UPGRADE=1 $(verbose) cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk $(verbose) rm -rf $(ERLANG_MK_BUILD_DIR) $(verbose) rm -rf $(ERLANG_MK_TMP) # The erlang.mk package index is bundled in the default erlang.mk build. # Search for the string "copyright" to skip to the rest of the code. # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-kerl KERL_INSTALL_DIR ?= $(HOME)/erlang ifeq ($(strip $(KERL)),) KERL := $(ERLANG_MK_TMP)/kerl/kerl endif KERL_DIR = $(ERLANG_MK_TMP)/kerl export KERL KERL_GIT ?= https://github.com/kerl/kerl KERL_COMMIT ?= master KERL_MAKEFLAGS ?= OTP_GIT ?= https://github.com/erlang/otp define kerl_otp_target $(KERL_INSTALL_DIR)/$(1): $(KERL) $(verbose) if [ ! -d $$@ ]; then \ MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $(1) $(1); \ $(KERL) install $(1) $(KERL_INSTALL_DIR)/$(1); \ fi endef $(KERL): $(KERL_DIR) $(KERL_DIR): | $(ERLANG_MK_TMP) $(gen_verbose) git clone --depth 1 $(KERL_GIT) $(ERLANG_MK_TMP)/kerl $(verbose) cd $(ERLANG_MK_TMP)/kerl && git checkout $(KERL_COMMIT) $(verbose) chmod +x $(KERL) distclean:: distclean-kerl distclean-kerl: $(gen_verbose) rm -rf $(KERL_DIR) # Allow users to select which version of Erlang/OTP to use for a project. ifneq ($(strip $(LATEST_ERLANG_OTP)),) # In some environments it is necessary to filter out master. ERLANG_OTP := $(notdir $(lastword $(sort\ $(filter-out $(KERL_INSTALL_DIR)/master $(KERL_INSTALL_DIR)/OTP_R%,\ $(filter-out %-rc1 %-rc2 %-rc3,$(wildcard $(KERL_INSTALL_DIR)/*[^-native])))))) endif ERLANG_OTP ?= # Use kerl to enforce a specific Erlang/OTP version for a project. ifneq ($(strip $(ERLANG_OTP)),) export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_OTP)/bin:$(PATH) SHELL := env PATH=$(PATH) $(SHELL) $(eval $(call kerl_otp_target,$(ERLANG_OTP))) # Build Erlang/OTP only if it doesn't already exist. ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_OTP))$(BUILD_ERLANG_OTP),) $(info Building Erlang/OTP $(ERLANG_OTP)... Please wait...) $(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_OTP) ERLANG_OTP=$(ERLANG_OTP) BUILD_ERLANG_OTP=1 >&2) endif endif PACKAGES += aberth pkg_aberth_name = aberth pkg_aberth_description = Generic BERT-RPC server in Erlang pkg_aberth_homepage = https://github.com/a13x/aberth pkg_aberth_fetch = git pkg_aberth_repo = https://github.com/a13x/aberth pkg_aberth_commit = master PACKAGES += active pkg_active_name = active pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running pkg_active_homepage = https://github.com/proger/active pkg_active_fetch = git pkg_active_repo = https://github.com/proger/active pkg_active_commit = master PACKAGES += aleppo pkg_aleppo_name = aleppo pkg_aleppo_description = Alternative Erlang Pre-Processor pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo pkg_aleppo_fetch = git pkg_aleppo_repo = https://github.com/ErlyORM/aleppo pkg_aleppo_commit = master PACKAGES += alog pkg_alog_name = alog pkg_alog_description = Simply the best logging framework for Erlang pkg_alog_homepage = https://github.com/siberian-fast-food/alogger pkg_alog_fetch = git pkg_alog_repo = https://github.com/siberian-fast-food/alogger pkg_alog_commit = master PACKAGES += annotations pkg_annotations_name = annotations pkg_annotations_description = Simple code instrumentation utilities pkg_annotations_homepage = https://github.com/hyperthunk/annotations pkg_annotations_fetch = git pkg_annotations_repo = https://github.com/hyperthunk/annotations pkg_annotations_commit = master PACKAGES += apns pkg_apns_name = apns pkg_apns_description = Apple Push Notification Server for Erlang pkg_apns_homepage = http://inaka.github.com/apns4erl pkg_apns_fetch = git pkg_apns_repo = https://github.com/inaka/apns4erl pkg_apns_commit = master PACKAGES += asciideck pkg_asciideck_name = asciideck pkg_asciideck_description = Asciidoc for Erlang. pkg_asciideck_homepage = https://ninenines.eu pkg_asciideck_fetch = git pkg_asciideck_repo = https://github.com/ninenines/asciideck pkg_asciideck_commit = master PACKAGES += backoff pkg_backoff_name = backoff pkg_backoff_description = Simple exponential backoffs in Erlang pkg_backoff_homepage = https://github.com/ferd/backoff pkg_backoff_fetch = git pkg_backoff_repo = https://github.com/ferd/backoff pkg_backoff_commit = master PACKAGES += barrel_tcp pkg_barrel_tcp_name = barrel_tcp pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang. pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp pkg_barrel_tcp_fetch = git pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp pkg_barrel_tcp_commit = master PACKAGES += basho_bench pkg_basho_bench_name = basho_bench pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for. pkg_basho_bench_homepage = https://github.com/basho/basho_bench pkg_basho_bench_fetch = git pkg_basho_bench_repo = https://github.com/basho/basho_bench pkg_basho_bench_commit = master PACKAGES += bcrypt pkg_bcrypt_name = bcrypt pkg_bcrypt_description = Bcrypt Erlang / C library pkg_bcrypt_homepage = https://github.com/erlangpack/bcrypt pkg_bcrypt_fetch = git pkg_bcrypt_repo = https://github.com/erlangpack/bcrypt.git pkg_bcrypt_commit = master PACKAGES += beam pkg_beam_name = beam pkg_beam_description = BEAM emulator written in Erlang pkg_beam_homepage = https://github.com/tonyrog/beam pkg_beam_fetch = git pkg_beam_repo = https://github.com/tonyrog/beam pkg_beam_commit = master PACKAGES += bear pkg_bear_name = bear pkg_bear_description = a set of statistics functions for erlang pkg_bear_homepage = https://github.com/boundary/bear pkg_bear_fetch = git pkg_bear_repo = https://github.com/boundary/bear pkg_bear_commit = master PACKAGES += bertconf pkg_bertconf_name = bertconf pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded pkg_bertconf_homepage = https://github.com/ferd/bertconf pkg_bertconf_fetch = git pkg_bertconf_repo = https://github.com/ferd/bertconf pkg_bertconf_commit = master PACKAGES += bifrost pkg_bifrost_name = bifrost pkg_bifrost_description = Erlang FTP Server Framework pkg_bifrost_homepage = https://github.com/thorstadt/bifrost pkg_bifrost_fetch = git pkg_bifrost_repo = https://github.com/thorstadt/bifrost pkg_bifrost_commit = master PACKAGES += binpp pkg_binpp_name = binpp pkg_binpp_description = Erlang Binary Pretty Printer pkg_binpp_homepage = https://github.com/jtendo/binpp pkg_binpp_fetch = git pkg_binpp_repo = https://github.com/jtendo/binpp pkg_binpp_commit = master PACKAGES += bisect pkg_bisect_name = bisect pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang pkg_bisect_homepage = https://github.com/knutin/bisect pkg_bisect_fetch = git pkg_bisect_repo = https://github.com/knutin/bisect pkg_bisect_commit = master PACKAGES += bitcask pkg_bitcask_name = bitcask pkg_bitcask_description = because you need another a key/value storage engine pkg_bitcask_homepage = https://github.com/basho/bitcask pkg_bitcask_fetch = git pkg_bitcask_repo = https://github.com/basho/bitcask pkg_bitcask_commit = develop PACKAGES += bootstrap pkg_bootstrap_name = bootstrap pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application. pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap pkg_bootstrap_fetch = git pkg_bootstrap_repo = https://github.com/schlagert/bootstrap pkg_bootstrap_commit = master PACKAGES += boss pkg_boss_name = boss pkg_boss_description = Erlang web MVC, now featuring Comet pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss pkg_boss_fetch = git pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss pkg_boss_commit = master PACKAGES += boss_db pkg_boss_db_name = boss_db pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db pkg_boss_db_fetch = git pkg_boss_db_repo = https://github.com/ErlyORM/boss_db pkg_boss_db_commit = master PACKAGES += brod pkg_brod_name = brod pkg_brod_description = Kafka client in Erlang pkg_brod_homepage = https://github.com/klarna/brod pkg_brod_fetch = git pkg_brod_repo = https://github.com/klarna/brod.git pkg_brod_commit = master PACKAGES += bson pkg_bson_name = bson pkg_bson_description = BSON documents in Erlang, see bsonspec.org pkg_bson_homepage = https://github.com/comtihon/bson-erlang pkg_bson_fetch = git pkg_bson_repo = https://github.com/comtihon/bson-erlang pkg_bson_commit = master PACKAGES += bullet pkg_bullet_name = bullet pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy. pkg_bullet_homepage = http://ninenines.eu pkg_bullet_fetch = git pkg_bullet_repo = https://github.com/ninenines/bullet pkg_bullet_commit = master PACKAGES += cache pkg_cache_name = cache pkg_cache_description = Erlang in-memory cache pkg_cache_homepage = https://github.com/fogfish/cache pkg_cache_fetch = git pkg_cache_repo = https://github.com/fogfish/cache pkg_cache_commit = master PACKAGES += cake pkg_cake_name = cake pkg_cake_description = Really simple terminal colorization pkg_cake_homepage = https://github.com/darach/cake-erl pkg_cake_fetch = git pkg_cake_repo = https://github.com/darach/cake-erl pkg_cake_commit = master PACKAGES += cberl pkg_cberl_name = cberl pkg_cberl_description = NIF based Erlang bindings for Couchbase pkg_cberl_homepage = https://github.com/chitika/cberl pkg_cberl_fetch = git pkg_cberl_repo = https://github.com/chitika/cberl pkg_cberl_commit = master PACKAGES += cecho pkg_cecho_name = cecho pkg_cecho_description = An ncurses library for Erlang pkg_cecho_homepage = https://github.com/mazenharake/cecho pkg_cecho_fetch = git pkg_cecho_repo = https://github.com/mazenharake/cecho pkg_cecho_commit = master PACKAGES += cferl pkg_cferl_name = cferl pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client pkg_cferl_homepage = https://github.com/ddossot/cferl pkg_cferl_fetch = git pkg_cferl_repo = https://github.com/ddossot/cferl pkg_cferl_commit = master PACKAGES += chaos_monkey pkg_chaos_monkey_name = chaos_monkey pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes. pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey pkg_chaos_monkey_fetch = git pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey pkg_chaos_monkey_commit = master PACKAGES += check_node pkg_check_node_name = check_node pkg_check_node_description = Nagios Scripts for monitoring Riak pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios pkg_check_node_fetch = git pkg_check_node_repo = https://github.com/basho-labs/riak_nagios pkg_check_node_commit = master PACKAGES += chronos pkg_chronos_name = chronos pkg_chronos_description = Timer module for Erlang that makes it easy to abstract time out of the tests. pkg_chronos_homepage = https://github.com/lehoff/chronos pkg_chronos_fetch = git pkg_chronos_repo = https://github.com/lehoff/chronos pkg_chronos_commit = master PACKAGES += chumak pkg_chumak_name = chumak pkg_chumak_description = Pure Erlang implementation of ZeroMQ Message Transport Protocol. pkg_chumak_homepage = http://choven.ca pkg_chumak_fetch = git pkg_chumak_repo = https://github.com/chovencorp/chumak pkg_chumak_commit = master PACKAGES += cl pkg_cl_name = cl pkg_cl_description = OpenCL binding for Erlang pkg_cl_homepage = https://github.com/tonyrog/cl pkg_cl_fetch = git pkg_cl_repo = https://github.com/tonyrog/cl pkg_cl_commit = master PACKAGES += clique pkg_clique_name = clique pkg_clique_description = CLI Framework for Erlang pkg_clique_homepage = https://github.com/basho/clique pkg_clique_fetch = git pkg_clique_repo = https://github.com/basho/clique pkg_clique_commit = develop PACKAGES += cloudi_core pkg_cloudi_core_name = cloudi_core pkg_cloudi_core_description = CloudI internal service runtime pkg_cloudi_core_homepage = http://cloudi.org/ pkg_cloudi_core_fetch = git pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core pkg_cloudi_core_commit = master PACKAGES += cloudi_service_api_requests pkg_cloudi_service_api_requests_name = cloudi_service_api_requests pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support) pkg_cloudi_service_api_requests_homepage = http://cloudi.org/ pkg_cloudi_service_api_requests_fetch = git pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests pkg_cloudi_service_api_requests_commit = master PACKAGES += cloudi_service_db_mysql pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql pkg_cloudi_service_db_mysql_description = MySQL CloudI Service pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/ pkg_cloudi_service_db_mysql_fetch = git pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql pkg_cloudi_service_db_mysql_commit = master PACKAGES += cloudi_service_db_pgsql pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/ pkg_cloudi_service_db_pgsql_fetch = git pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql pkg_cloudi_service_db_pgsql_commit = master PACKAGES += cloudi_service_filesystem pkg_cloudi_service_filesystem_name = cloudi_service_filesystem pkg_cloudi_service_filesystem_description = Filesystem CloudI Service pkg_cloudi_service_filesystem_homepage = http://cloudi.org/ pkg_cloudi_service_filesystem_fetch = git pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem pkg_cloudi_service_filesystem_commit = master PACKAGES += cloudi_service_http_client pkg_cloudi_service_http_client_name = cloudi_service_http_client pkg_cloudi_service_http_client_description = HTTP client CloudI Service pkg_cloudi_service_http_client_homepage = http://cloudi.org/ pkg_cloudi_service_http_client_fetch = git pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client pkg_cloudi_service_http_client_commit = master PACKAGES += cloudi_service_http_cowboy pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/ pkg_cloudi_service_http_cowboy_fetch = git pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy pkg_cloudi_service_http_cowboy_commit = master PACKAGES += cloudi_service_http_elli pkg_cloudi_service_http_elli_name = cloudi_service_http_elli pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service pkg_cloudi_service_http_elli_homepage = http://cloudi.org/ pkg_cloudi_service_http_elli_fetch = git pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli pkg_cloudi_service_http_elli_commit = master PACKAGES += cloudi_service_map_reduce pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/ pkg_cloudi_service_map_reduce_fetch = git pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce pkg_cloudi_service_map_reduce_commit = master PACKAGES += cloudi_service_oauth1 pkg_cloudi_service_oauth1_name = cloudi_service_oauth1 pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service pkg_cloudi_service_oauth1_homepage = http://cloudi.org/ pkg_cloudi_service_oauth1_fetch = git pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1 pkg_cloudi_service_oauth1_commit = master PACKAGES += cloudi_service_queue pkg_cloudi_service_queue_name = cloudi_service_queue pkg_cloudi_service_queue_description = Persistent Queue Service pkg_cloudi_service_queue_homepage = http://cloudi.org/ pkg_cloudi_service_queue_fetch = git pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue pkg_cloudi_service_queue_commit = master PACKAGES += cloudi_service_quorum pkg_cloudi_service_quorum_name = cloudi_service_quorum pkg_cloudi_service_quorum_description = CloudI Quorum Service pkg_cloudi_service_quorum_homepage = http://cloudi.org/ pkg_cloudi_service_quorum_fetch = git pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum pkg_cloudi_service_quorum_commit = master PACKAGES += cloudi_service_router pkg_cloudi_service_router_name = cloudi_service_router pkg_cloudi_service_router_description = CloudI Router Service pkg_cloudi_service_router_homepage = http://cloudi.org/ pkg_cloudi_service_router_fetch = git pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router pkg_cloudi_service_router_commit = master PACKAGES += cloudi_service_tcp pkg_cloudi_service_tcp_name = cloudi_service_tcp pkg_cloudi_service_tcp_description = TCP CloudI Service pkg_cloudi_service_tcp_homepage = http://cloudi.org/ pkg_cloudi_service_tcp_fetch = git pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp pkg_cloudi_service_tcp_commit = master PACKAGES += cloudi_service_udp pkg_cloudi_service_udp_name = cloudi_service_udp pkg_cloudi_service_udp_description = UDP CloudI Service pkg_cloudi_service_udp_homepage = http://cloudi.org/ pkg_cloudi_service_udp_fetch = git pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp pkg_cloudi_service_udp_commit = master PACKAGES += cloudi_service_validate pkg_cloudi_service_validate_name = cloudi_service_validate pkg_cloudi_service_validate_description = CloudI Validate Service pkg_cloudi_service_validate_homepage = http://cloudi.org/ pkg_cloudi_service_validate_fetch = git pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate pkg_cloudi_service_validate_commit = master PACKAGES += cloudi_service_zeromq pkg_cloudi_service_zeromq_name = cloudi_service_zeromq pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service pkg_cloudi_service_zeromq_homepage = http://cloudi.org/ pkg_cloudi_service_zeromq_fetch = git pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq pkg_cloudi_service_zeromq_commit = master PACKAGES += cluster_info pkg_cluster_info_name = cluster_info pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app pkg_cluster_info_homepage = https://github.com/basho/cluster_info pkg_cluster_info_fetch = git pkg_cluster_info_repo = https://github.com/basho/cluster_info pkg_cluster_info_commit = master PACKAGES += color pkg_color_name = color pkg_color_description = ANSI colors for your Erlang pkg_color_homepage = https://github.com/julianduque/erlang-color pkg_color_fetch = git pkg_color_repo = https://github.com/julianduque/erlang-color pkg_color_commit = master PACKAGES += confetti pkg_confetti_name = confetti pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids pkg_confetti_homepage = https://github.com/jtendo/confetti pkg_confetti_fetch = git pkg_confetti_repo = https://github.com/jtendo/confetti pkg_confetti_commit = master PACKAGES += couchbeam pkg_couchbeam_name = couchbeam pkg_couchbeam_description = Apache CouchDB client in Erlang pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam pkg_couchbeam_fetch = git pkg_couchbeam_repo = https://github.com/benoitc/couchbeam pkg_couchbeam_commit = master PACKAGES += covertool pkg_covertool_name = covertool pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports pkg_covertool_homepage = https://github.com/idubrov/covertool pkg_covertool_fetch = git pkg_covertool_repo = https://github.com/idubrov/covertool pkg_covertool_commit = master PACKAGES += cowboy pkg_cowboy_name = cowboy pkg_cowboy_description = Small, fast and modular HTTP server. pkg_cowboy_homepage = http://ninenines.eu pkg_cowboy_fetch = git pkg_cowboy_repo = https://github.com/ninenines/cowboy pkg_cowboy_commit = 1.0.4 PACKAGES += cowdb pkg_cowdb_name = cowdb pkg_cowdb_description = Pure Key/Value database library for Erlang Applications pkg_cowdb_homepage = https://github.com/refuge/cowdb pkg_cowdb_fetch = git pkg_cowdb_repo = https://github.com/refuge/cowdb pkg_cowdb_commit = master PACKAGES += cowlib pkg_cowlib_name = cowlib pkg_cowlib_description = Support library for manipulating Web protocols. pkg_cowlib_homepage = http://ninenines.eu pkg_cowlib_fetch = git pkg_cowlib_repo = https://github.com/ninenines/cowlib pkg_cowlib_commit = 1.0.2 PACKAGES += cpg pkg_cpg_name = cpg pkg_cpg_description = CloudI Process Groups pkg_cpg_homepage = https://github.com/okeuday/cpg pkg_cpg_fetch = git pkg_cpg_repo = https://github.com/okeuday/cpg pkg_cpg_commit = master PACKAGES += cqerl pkg_cqerl_name = cqerl pkg_cqerl_description = Native Erlang CQL client for Cassandra pkg_cqerl_homepage = https://matehat.github.io/cqerl/ pkg_cqerl_fetch = git pkg_cqerl_repo = https://github.com/matehat/cqerl pkg_cqerl_commit = master PACKAGES += cr pkg_cr_name = cr pkg_cr_description = Chain Replication pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm pkg_cr_fetch = git pkg_cr_repo = https://github.com/spawnproc/cr pkg_cr_commit = master PACKAGES += cuttlefish pkg_cuttlefish_name = cuttlefish pkg_cuttlefish_description = cuttlefish configuration abstraction pkg_cuttlefish_homepage = https://github.com/Kyorai/cuttlefish pkg_cuttlefish_fetch = git pkg_cuttlefish_repo = https://github.com/Kyorai/cuttlefish pkg_cuttlefish_commit = main PACKAGES += damocles pkg_damocles_name = damocles pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box. pkg_damocles_homepage = https://github.com/lostcolony/damocles pkg_damocles_fetch = git pkg_damocles_repo = https://github.com/lostcolony/damocles pkg_damocles_commit = master PACKAGES += debbie pkg_debbie_name = debbie pkg_debbie_description = .DEB Built In Erlang pkg_debbie_homepage = https://github.com/crownedgrouse/debbie pkg_debbie_fetch = git pkg_debbie_repo = https://github.com/crownedgrouse/debbie pkg_debbie_commit = master PACKAGES += decimal pkg_decimal_name = decimal pkg_decimal_description = An Erlang decimal arithmetic library pkg_decimal_homepage = https://github.com/egobrain/decimal pkg_decimal_fetch = git pkg_decimal_repo = https://github.com/egobrain/decimal pkg_decimal_commit = master PACKAGES += detergent pkg_detergent_name = detergent pkg_detergent_description = An emulsifying Erlang SOAP library pkg_detergent_homepage = https://github.com/devinus/detergent pkg_detergent_fetch = git pkg_detergent_repo = https://github.com/devinus/detergent pkg_detergent_commit = master PACKAGES += dh_date pkg_dh_date_name = dh_date pkg_dh_date_description = Date formatting / parsing library for erlang pkg_dh_date_homepage = https://github.com/daleharvey/dh_date pkg_dh_date_fetch = git pkg_dh_date_repo = https://github.com/daleharvey/dh_date pkg_dh_date_commit = master PACKAGES += dirbusterl pkg_dirbusterl_name = dirbusterl pkg_dirbusterl_description = DirBuster successor in Erlang pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl pkg_dirbusterl_fetch = git pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl pkg_dirbusterl_commit = master PACKAGES += dispcount pkg_dispcount_name = dispcount pkg_dispcount_description = Erlang task dispatcher based on ETS counters. pkg_dispcount_homepage = https://github.com/ferd/dispcount pkg_dispcount_fetch = git pkg_dispcount_repo = https://github.com/ferd/dispcount pkg_dispcount_commit = master PACKAGES += dlhttpc pkg_dlhttpc_name = dlhttpc pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc pkg_dlhttpc_fetch = git pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc pkg_dlhttpc_commit = master PACKAGES += dns pkg_dns_name = dns pkg_dns_description = Erlang DNS library pkg_dns_homepage = https://github.com/aetrion/dns_erlang pkg_dns_fetch = git pkg_dns_repo = https://github.com/aetrion/dns_erlang pkg_dns_commit = main PACKAGES += dynamic_compile pkg_dynamic_compile_name = dynamic_compile pkg_dynamic_compile_description = compile and load erlang modules from string input pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile pkg_dynamic_compile_fetch = git pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile pkg_dynamic_compile_commit = master PACKAGES += e2 pkg_e2_name = e2 pkg_e2_description = Library to simply writing correct OTP applications. pkg_e2_homepage = http://e2project.org pkg_e2_fetch = git pkg_e2_repo = https://github.com/gar1t/e2 pkg_e2_commit = master PACKAGES += eamf pkg_eamf_name = eamf pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf pkg_eamf_fetch = git pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf pkg_eamf_commit = master PACKAGES += eavro pkg_eavro_name = eavro pkg_eavro_description = Apache Avro encoder/decoder pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro pkg_eavro_fetch = git pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro pkg_eavro_commit = master PACKAGES += ecapnp pkg_ecapnp_name = ecapnp pkg_ecapnp_description = Cap'n Proto library for Erlang pkg_ecapnp_homepage = https://github.com/kaos/ecapnp pkg_ecapnp_fetch = git pkg_ecapnp_repo = https://github.com/kaos/ecapnp pkg_ecapnp_commit = master PACKAGES += econfig pkg_econfig_name = econfig pkg_econfig_description = simple Erlang config handler using INI files pkg_econfig_homepage = https://github.com/benoitc/econfig pkg_econfig_fetch = git pkg_econfig_repo = https://github.com/benoitc/econfig pkg_econfig_commit = master PACKAGES += edate pkg_edate_name = edate pkg_edate_description = date manipulation library for erlang pkg_edate_homepage = https://github.com/dweldon/edate pkg_edate_fetch = git pkg_edate_repo = https://github.com/dweldon/edate pkg_edate_commit = master PACKAGES += edgar pkg_edgar_name = edgar pkg_edgar_description = Erlang Does GNU AR pkg_edgar_homepage = https://github.com/crownedgrouse/edgar pkg_edgar_fetch = git pkg_edgar_repo = https://github.com/crownedgrouse/edgar pkg_edgar_commit = master PACKAGES += edns pkg_edns_name = edns pkg_edns_description = Erlang/OTP DNS server pkg_edns_homepage = https://github.com/hcvst/erlang-dns pkg_edns_fetch = git pkg_edns_repo = https://github.com/hcvst/erlang-dns pkg_edns_commit = master PACKAGES += edown pkg_edown_name = edown pkg_edown_description = EDoc extension for generating Github-flavored Markdown pkg_edown_homepage = https://github.com/uwiger/edown pkg_edown_fetch = git pkg_edown_repo = https://github.com/uwiger/edown pkg_edown_commit = master PACKAGES += eep pkg_eep_name = eep pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy pkg_eep_homepage = https://github.com/virtan/eep pkg_eep_fetch = git pkg_eep_repo = https://github.com/virtan/eep pkg_eep_commit = master PACKAGES += eep_app pkg_eep_app_name = eep_app pkg_eep_app_description = Embedded Event Processing pkg_eep_app_homepage = https://github.com/darach/eep-erl pkg_eep_app_fetch = git pkg_eep_app_repo = https://github.com/darach/eep-erl pkg_eep_app_commit = master PACKAGES += efene pkg_efene_name = efene pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX pkg_efene_homepage = https://github.com/efene/efene pkg_efene_fetch = git pkg_efene_repo = https://github.com/efene/efene pkg_efene_commit = master PACKAGES += egeoip pkg_egeoip_name = egeoip pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database. pkg_egeoip_homepage = https://github.com/mochi/egeoip pkg_egeoip_fetch = git pkg_egeoip_repo = https://github.com/mochi/egeoip pkg_egeoip_commit = master PACKAGES += ehsa pkg_ehsa_name = ehsa pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules pkg_ehsa_homepage = https://github.com/a12n/ehsa pkg_ehsa_fetch = git pkg_ehsa_repo = https://github.com/a12n/ehsa pkg_ehsa_commit = master PACKAGES += ej pkg_ej_name = ej pkg_ej_description = Helper module for working with Erlang terms representing JSON pkg_ej_homepage = https://github.com/seth/ej pkg_ej_fetch = git pkg_ej_repo = https://github.com/seth/ej pkg_ej_commit = master PACKAGES += ejabberd pkg_ejabberd_name = ejabberd pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform pkg_ejabberd_homepage = https://github.com/processone/ejabberd pkg_ejabberd_fetch = git pkg_ejabberd_repo = https://github.com/processone/ejabberd pkg_ejabberd_commit = master PACKAGES += ejwt pkg_ejwt_name = ejwt pkg_ejwt_description = erlang library for JSON Web Token pkg_ejwt_homepage = https://github.com/artefactop/ejwt pkg_ejwt_fetch = git pkg_ejwt_repo = https://github.com/artefactop/ejwt pkg_ejwt_commit = master PACKAGES += ekaf pkg_ekaf_name = ekaf pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang. pkg_ekaf_homepage = https://github.com/helpshift/ekaf pkg_ekaf_fetch = git pkg_ekaf_repo = https://github.com/helpshift/ekaf pkg_ekaf_commit = master PACKAGES += elarm pkg_elarm_name = elarm pkg_elarm_description = Alarm Manager for Erlang. pkg_elarm_homepage = https://github.com/esl/elarm pkg_elarm_fetch = git pkg_elarm_repo = https://github.com/esl/elarm pkg_elarm_commit = master PACKAGES += eleveldb pkg_eleveldb_name = eleveldb pkg_eleveldb_description = Erlang LevelDB API pkg_eleveldb_homepage = https://github.com/basho/eleveldb pkg_eleveldb_fetch = git pkg_eleveldb_repo = https://github.com/basho/eleveldb pkg_eleveldb_commit = develop PACKAGES += elixir pkg_elixir_name = elixir pkg_elixir_description = Elixir is a dynamic, functional language designed for building scalable and maintainable applications pkg_elixir_homepage = https://elixir-lang.org/ pkg_elixir_fetch = git pkg_elixir_repo = https://github.com/elixir-lang/elixir pkg_elixir_commit = main PACKAGES += elli pkg_elli_name = elli pkg_elli_description = Simple, robust and performant Erlang web server pkg_elli_homepage = https://github.com/elli-lib/elli pkg_elli_fetch = git pkg_elli_repo = https://github.com/elli-lib/elli pkg_elli_commit = main PACKAGES += elvis pkg_elvis_name = elvis pkg_elvis_description = Erlang Style Reviewer pkg_elvis_homepage = https://github.com/inaka/elvis pkg_elvis_fetch = git pkg_elvis_repo = https://github.com/inaka/elvis pkg_elvis_commit = master PACKAGES += emagick pkg_emagick_name = emagick pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool. pkg_emagick_homepage = https://github.com/kivra/emagick pkg_emagick_fetch = git pkg_emagick_repo = https://github.com/kivra/emagick pkg_emagick_commit = master PACKAGES += enm pkg_enm_name = enm pkg_enm_description = Erlang driver for nanomsg pkg_enm_homepage = https://github.com/basho/enm pkg_enm_fetch = git pkg_enm_repo = https://github.com/basho/enm pkg_enm_commit = master PACKAGES += entop pkg_entop_name = entop pkg_entop_description = A top-like tool for monitoring an Erlang node pkg_entop_homepage = https://github.com/mazenharake/entop pkg_entop_fetch = git pkg_entop_repo = https://github.com/mazenharake/entop pkg_entop_commit = master PACKAGES += epcap pkg_epcap_name = epcap pkg_epcap_description = Erlang packet capture interface using pcap pkg_epcap_homepage = https://github.com/msantos/epcap pkg_epcap_fetch = git pkg_epcap_repo = https://github.com/msantos/epcap pkg_epcap_commit = master PACKAGES += eper pkg_eper_name = eper pkg_eper_description = Erlang performance and debugging tools. pkg_eper_homepage = https://github.com/massemanet/eper pkg_eper_fetch = git pkg_eper_repo = https://github.com/massemanet/eper pkg_eper_commit = master PACKAGES += epgsql pkg_epgsql_name = epgsql pkg_epgsql_description = Erlang PostgreSQL client library. pkg_epgsql_homepage = https://github.com/epgsql/epgsql pkg_epgsql_fetch = git pkg_epgsql_repo = https://github.com/epgsql/epgsql pkg_epgsql_commit = master PACKAGES += episcina pkg_episcina_name = episcina pkg_episcina_description = A simple non intrusive resource pool for connections pkg_episcina_homepage = https://github.com/erlware/episcina pkg_episcina_fetch = git pkg_episcina_repo = https://github.com/erlware/episcina pkg_episcina_commit = master PACKAGES += eplot pkg_eplot_name = eplot pkg_eplot_description = A plot engine written in erlang. pkg_eplot_homepage = https://github.com/psyeugenic/eplot pkg_eplot_fetch = git pkg_eplot_repo = https://github.com/psyeugenic/eplot pkg_eplot_commit = master PACKAGES += epocxy pkg_epocxy_name = epocxy pkg_epocxy_description = Erlang Patterns of Concurrency pkg_epocxy_homepage = https://github.com/duomark/epocxy pkg_epocxy_fetch = git pkg_epocxy_repo = https://github.com/duomark/epocxy pkg_epocxy_commit = master PACKAGES += epubnub pkg_epubnub_name = epubnub pkg_epubnub_description = Erlang PubNub API pkg_epubnub_homepage = https://github.com/tsloughter/epubnub pkg_epubnub_fetch = git pkg_epubnub_repo = https://github.com/tsloughter/epubnub pkg_epubnub_commit = master PACKAGES += eqm pkg_eqm_name = eqm pkg_eqm_description = Erlang pub sub with supply-demand channels pkg_eqm_homepage = https://github.com/loucash/eqm pkg_eqm_fetch = git pkg_eqm_repo = https://github.com/loucash/eqm pkg_eqm_commit = master PACKAGES += eredis pkg_eredis_name = eredis pkg_eredis_description = Erlang Redis client pkg_eredis_homepage = https://github.com/wooga/eredis pkg_eredis_fetch = git pkg_eredis_repo = https://github.com/wooga/eredis pkg_eredis_commit = master PACKAGES += erl_streams pkg_erl_streams_name = erl_streams pkg_erl_streams_description = Streams in Erlang pkg_erl_streams_homepage = https://github.com/epappas/erl_streams pkg_erl_streams_fetch = git pkg_erl_streams_repo = https://github.com/epappas/erl_streams pkg_erl_streams_commit = master PACKAGES += erlang_localtime pkg_erlang_localtime_name = erlang_localtime pkg_erlang_localtime_description = Erlang library for conversion from one local time to another pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime pkg_erlang_localtime_fetch = git pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime pkg_erlang_localtime_commit = master PACKAGES += erlang_smtp pkg_erlang_smtp_name = erlang_smtp pkg_erlang_smtp_description = Erlang SMTP and POP3 server code. pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp pkg_erlang_smtp_fetch = git pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp pkg_erlang_smtp_commit = master PACKAGES += erlang_term pkg_erlang_term_name = erlang_term pkg_erlang_term_description = Erlang Term Info pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term pkg_erlang_term_fetch = git pkg_erlang_term_repo = https://github.com/okeuday/erlang_term pkg_erlang_term_commit = master PACKAGES += erlastic_search pkg_erlastic_search_name = erlastic_search pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface. pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search pkg_erlastic_search_fetch = git pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search pkg_erlastic_search_commit = master PACKAGES += erlbrake pkg_erlbrake_name = erlbrake pkg_erlbrake_description = Erlang Airbrake notification client pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake pkg_erlbrake_fetch = git pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake pkg_erlbrake_commit = master PACKAGES += erlcloud pkg_erlcloud_name = erlcloud pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB) pkg_erlcloud_homepage = https://github.com/gleber/erlcloud pkg_erlcloud_fetch = git pkg_erlcloud_repo = https://github.com/gleber/erlcloud pkg_erlcloud_commit = master PACKAGES += erlcron pkg_erlcron_name = erlcron pkg_erlcron_description = Erlang cronish system pkg_erlcron_homepage = https://github.com/erlware/erlcron pkg_erlcron_fetch = git pkg_erlcron_repo = https://github.com/erlware/erlcron pkg_erlcron_commit = master PACKAGES += erldb pkg_erldb_name = erldb pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang pkg_erldb_homepage = http://erldb.org pkg_erldb_fetch = git pkg_erldb_repo = https://github.com/erldb/erldb pkg_erldb_commit = master PACKAGES += erldis pkg_erldis_name = erldis pkg_erldis_description = redis erlang client library pkg_erldis_homepage = https://github.com/cstar/erldis pkg_erldis_fetch = git pkg_erldis_repo = https://github.com/cstar/erldis pkg_erldis_commit = master PACKAGES += erldns pkg_erldns_name = erldns pkg_erldns_description = DNS server, in erlang. pkg_erldns_homepage = https://github.com/aetrion/erl-dns pkg_erldns_fetch = git pkg_erldns_repo = https://github.com/aetrion/erl-dns pkg_erldns_commit = main PACKAGES += erldocker pkg_erldocker_name = erldocker pkg_erldocker_description = Docker Remote API client for Erlang pkg_erldocker_homepage = https://github.com/proger/erldocker pkg_erldocker_fetch = git pkg_erldocker_repo = https://github.com/proger/erldocker pkg_erldocker_commit = master PACKAGES += erlfsmon pkg_erlfsmon_name = erlfsmon pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon pkg_erlfsmon_fetch = git pkg_erlfsmon_repo = https://github.com/proger/erlfsmon pkg_erlfsmon_commit = master PACKAGES += erlgit pkg_erlgit_name = erlgit pkg_erlgit_description = Erlang convenience wrapper around git executable pkg_erlgit_homepage = https://github.com/gleber/erlgit pkg_erlgit_fetch = git pkg_erlgit_repo = https://github.com/gleber/erlgit pkg_erlgit_commit = master PACKAGES += erlguten pkg_erlguten_name = erlguten pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang. pkg_erlguten_homepage = https://github.com/richcarl/erlguten pkg_erlguten_fetch = git pkg_erlguten_repo = https://github.com/richcarl/erlguten pkg_erlguten_commit = master PACKAGES += erlmc pkg_erlmc_name = erlmc pkg_erlmc_description = Erlang memcached binary protocol client pkg_erlmc_homepage = https://github.com/jkvor/erlmc pkg_erlmc_fetch = git pkg_erlmc_repo = https://github.com/jkvor/erlmc pkg_erlmc_commit = master PACKAGES += erlmongo pkg_erlmongo_name = erlmongo pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo pkg_erlmongo_fetch = git pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo pkg_erlmongo_commit = master PACKAGES += erlog pkg_erlog_name = erlog pkg_erlog_description = Prolog interpreter in and for Erlang pkg_erlog_homepage = https://github.com/rvirding/erlog pkg_erlog_fetch = git pkg_erlog_repo = https://github.com/rvirding/erlog pkg_erlog_commit = master PACKAGES += erlpass pkg_erlpass_name = erlpass pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever. pkg_erlpass_homepage = https://github.com/ferd/erlpass pkg_erlpass_fetch = git pkg_erlpass_repo = https://github.com/ferd/erlpass pkg_erlpass_commit = master PACKAGES += erlsh pkg_erlsh_name = erlsh pkg_erlsh_description = Erlang shell tools pkg_erlsh_homepage = https://github.com/proger/erlsh pkg_erlsh_fetch = git pkg_erlsh_repo = https://github.com/proger/erlsh pkg_erlsh_commit = master PACKAGES += erlsha2 pkg_erlsha2_name = erlsha2 pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs. pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2 pkg_erlsha2_fetch = git pkg_erlsha2_repo = https://github.com/vinoski/erlsha2 pkg_erlsha2_commit = master PACKAGES += erlsom pkg_erlsom_name = erlsom pkg_erlsom_description = XML parser for Erlang pkg_erlsom_homepage = https://github.com/willemdj/erlsom pkg_erlsom_fetch = git pkg_erlsom_repo = https://github.com/willemdj/erlsom pkg_erlsom_commit = master PACKAGES += erlubi pkg_erlubi_name = erlubi pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer) pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi pkg_erlubi_fetch = git pkg_erlubi_repo = https://github.com/krestenkrab/erlubi pkg_erlubi_commit = master PACKAGES += erlvolt pkg_erlvolt_name = erlvolt pkg_erlvolt_description = VoltDB Erlang Client Driver pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang pkg_erlvolt_fetch = git pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang pkg_erlvolt_commit = master PACKAGES += erlware_commons pkg_erlware_commons_name = erlware_commons pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components. pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons pkg_erlware_commons_fetch = git pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons pkg_erlware_commons_commit = master PACKAGES += erlydtl pkg_erlydtl_name = erlydtl pkg_erlydtl_description = Django Template Language for Erlang. pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl pkg_erlydtl_fetch = git pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl pkg_erlydtl_commit = master PACKAGES += errd pkg_errd_name = errd pkg_errd_description = Erlang RRDTool library pkg_errd_homepage = https://github.com/archaelus/errd pkg_errd_fetch = git pkg_errd_repo = https://github.com/archaelus/errd pkg_errd_commit = master PACKAGES += erserve pkg_erserve_name = erserve pkg_erserve_description = Erlang/Rserve communication interface pkg_erserve_homepage = https://github.com/del/erserve pkg_erserve_fetch = git pkg_erserve_repo = https://github.com/del/erserve pkg_erserve_commit = master PACKAGES += escalus pkg_escalus_name = escalus pkg_escalus_description = An XMPP client library in Erlang for conveniently testing XMPP servers pkg_escalus_homepage = https://github.com/esl/escalus pkg_escalus_fetch = git pkg_escalus_repo = https://github.com/esl/escalus pkg_escalus_commit = master PACKAGES += esh_mk pkg_esh_mk_name = esh_mk pkg_esh_mk_description = esh template engine plugin for erlang.mk pkg_esh_mk_homepage = https://github.com/crownedgrouse/esh.mk pkg_esh_mk_fetch = git pkg_esh_mk_repo = https://github.com/crownedgrouse/esh.mk.git pkg_esh_mk_commit = master PACKAGES += espec pkg_espec_name = espec pkg_espec_description = ESpec: Behaviour driven development framework for Erlang pkg_espec_homepage = https://github.com/lucaspiller/espec pkg_espec_fetch = git pkg_espec_repo = https://github.com/lucaspiller/espec pkg_espec_commit = master PACKAGES += estatsd pkg_estatsd_name = estatsd pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite pkg_estatsd_homepage = https://github.com/RJ/estatsd pkg_estatsd_fetch = git pkg_estatsd_repo = https://github.com/RJ/estatsd pkg_estatsd_commit = master PACKAGES += etap pkg_etap_name = etap pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output. pkg_etap_homepage = https://github.com/ngerakines/etap pkg_etap_fetch = git pkg_etap_repo = https://github.com/ngerakines/etap pkg_etap_commit = master PACKAGES += etest pkg_etest_name = etest pkg_etest_description = A lightweight, convention over configuration test framework for Erlang pkg_etest_homepage = https://github.com/wooga/etest pkg_etest_fetch = git pkg_etest_repo = https://github.com/wooga/etest pkg_etest_commit = master PACKAGES += etest_http pkg_etest_http_name = etest_http pkg_etest_http_description = etest Assertions around HTTP (client-side) pkg_etest_http_homepage = https://github.com/wooga/etest_http pkg_etest_http_fetch = git pkg_etest_http_repo = https://github.com/wooga/etest_http pkg_etest_http_commit = master PACKAGES += etoml pkg_etoml_name = etoml pkg_etoml_description = TOML language erlang parser pkg_etoml_homepage = https://github.com/kalta/etoml pkg_etoml_fetch = git pkg_etoml_repo = https://github.com/kalta/etoml pkg_etoml_commit = master PACKAGES += eunit pkg_eunit_name = eunit pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository. pkg_eunit_homepage = https://github.com/richcarl/eunit pkg_eunit_fetch = git pkg_eunit_repo = https://github.com/richcarl/eunit pkg_eunit_commit = master PACKAGES += eunit_formatters pkg_eunit_formatters_name = eunit_formatters pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better. pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters pkg_eunit_formatters_fetch = git pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters pkg_eunit_formatters_commit = master PACKAGES += euthanasia pkg_euthanasia_name = euthanasia pkg_euthanasia_description = Merciful killer for your Erlang processes pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia pkg_euthanasia_fetch = git pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia pkg_euthanasia_commit = master PACKAGES += evum pkg_evum_name = evum pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM pkg_evum_homepage = https://github.com/msantos/evum pkg_evum_fetch = git pkg_evum_repo = https://github.com/msantos/evum pkg_evum_commit = master PACKAGES += exec pkg_exec_name = erlexec pkg_exec_description = Execute and control OS processes from Erlang/OTP. pkg_exec_homepage = http://saleyn.github.com/erlexec pkg_exec_fetch = git pkg_exec_repo = https://github.com/saleyn/erlexec pkg_exec_commit = master PACKAGES += exml pkg_exml_name = exml pkg_exml_description = XML parsing library in Erlang pkg_exml_homepage = https://github.com/paulgray/exml pkg_exml_fetch = git pkg_exml_repo = https://github.com/paulgray/exml pkg_exml_commit = master PACKAGES += exometer pkg_exometer_name = exometer pkg_exometer_description = Basic measurement objects and probe behavior pkg_exometer_homepage = https://github.com/Feuerlabs/exometer pkg_exometer_fetch = git pkg_exometer_repo = https://github.com/Feuerlabs/exometer pkg_exometer_commit = master PACKAGES += exs1024 pkg_exs1024_name = exs1024 pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang. pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024 pkg_exs1024_fetch = git pkg_exs1024_repo = https://github.com/jj1bdx/exs1024 pkg_exs1024_commit = master PACKAGES += exsplus116 pkg_exsplus116_name = exsplus116 pkg_exsplus116_description = Xorshift116plus for Erlang pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116 pkg_exsplus116_fetch = git pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116 pkg_exsplus116_commit = master PACKAGES += ezmtp pkg_ezmtp_name = ezmtp pkg_ezmtp_description = ZMTP protocol in pure Erlang. pkg_ezmtp_homepage = https://github.com/a13x/ezmtp pkg_ezmtp_fetch = git pkg_ezmtp_repo = https://github.com/a13x/ezmtp pkg_ezmtp_commit = master PACKAGES += fast_disk_log pkg_fast_disk_log_name = fast_disk_log pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log pkg_fast_disk_log_fetch = git pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log pkg_fast_disk_log_commit = master PACKAGES += feeder pkg_feeder_name = feeder pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds. pkg_feeder_homepage = https://github.com/michaelnisi/feeder pkg_feeder_fetch = git pkg_feeder_repo = https://github.com/michaelnisi/feeder pkg_feeder_commit = master PACKAGES += find_crate pkg_find_crate_name = find_crate pkg_find_crate_description = Find Rust libs and exes in Erlang application priv directory pkg_find_crate_homepage = https://github.com/goertzenator/find_crate pkg_find_crate_fetch = git pkg_find_crate_repo = https://github.com/goertzenator/find_crate pkg_find_crate_commit = master PACKAGES += fix pkg_fix_name = fix pkg_fix_description = http://fixprotocol.org/ implementation. pkg_fix_homepage = https://github.com/maxlapshin/fix pkg_fix_fetch = git pkg_fix_repo = https://github.com/maxlapshin/fix pkg_fix_commit = master PACKAGES += flower pkg_flower_name = flower pkg_flower_description = FlowER - a Erlang OpenFlow development platform pkg_flower_homepage = https://github.com/travelping/flower pkg_flower_fetch = git pkg_flower_repo = https://github.com/travelping/flower pkg_flower_commit = master PACKAGES += fn pkg_fn_name = fn pkg_fn_description = Function utilities for Erlang pkg_fn_homepage = https://github.com/reiddraper/fn pkg_fn_fetch = git pkg_fn_repo = https://github.com/reiddraper/fn pkg_fn_commit = master PACKAGES += folsom pkg_folsom_name = folsom pkg_folsom_description = Expose Erlang Events and Metrics pkg_folsom_homepage = https://github.com/boundary/folsom pkg_folsom_fetch = git pkg_folsom_repo = https://github.com/boundary/folsom pkg_folsom_commit = master PACKAGES += folsom_cowboy pkg_folsom_cowboy_name = folsom_cowboy pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper. pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy pkg_folsom_cowboy_fetch = git pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy pkg_folsom_cowboy_commit = master PACKAGES += fs pkg_fs_name = fs pkg_fs_description = Erlang FileSystem Listener pkg_fs_homepage = https://github.com/synrc/fs pkg_fs_fetch = git pkg_fs_repo = https://github.com/synrc/fs pkg_fs_commit = master PACKAGES += fuse pkg_fuse_name = fuse pkg_fuse_description = A Circuit Breaker for Erlang pkg_fuse_homepage = https://github.com/jlouis/fuse pkg_fuse_fetch = git pkg_fuse_repo = https://github.com/jlouis/fuse pkg_fuse_commit = master PACKAGES += gcm pkg_gcm_name = gcm pkg_gcm_description = An Erlang application for Google Cloud Messaging pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang pkg_gcm_fetch = git pkg_gcm_repo = https://github.com/pdincau/gcm-erlang pkg_gcm_commit = master PACKAGES += gcprof pkg_gcprof_name = gcprof pkg_gcprof_description = Garbage Collection profiler for Erlang pkg_gcprof_homepage = https://github.com/knutin/gcprof pkg_gcprof_fetch = git pkg_gcprof_repo = https://github.com/knutin/gcprof pkg_gcprof_commit = master PACKAGES += geas pkg_geas_name = geas pkg_geas_description = Guess Erlang Application Scattering pkg_geas_homepage = https://github.com/crownedgrouse/geas pkg_geas_fetch = git pkg_geas_repo = https://github.com/crownedgrouse/geas pkg_geas_commit = master PACKAGES += geef pkg_geef_name = geef pkg_geef_description = Git NEEEEF (Erlang NIF) pkg_geef_homepage = https://github.com/carlosmn/geef pkg_geef_fetch = git pkg_geef_repo = https://github.com/carlosmn/geef pkg_geef_commit = master PACKAGES += gen_coap pkg_gen_coap_name = gen_coap pkg_gen_coap_description = Generic Erlang CoAP Client/Server pkg_gen_coap_homepage = https://github.com/gotthardp/gen_coap pkg_gen_coap_fetch = git pkg_gen_coap_repo = https://github.com/gotthardp/gen_coap pkg_gen_coap_commit = master PACKAGES += gen_cycle pkg_gen_cycle_name = gen_cycle pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle pkg_gen_cycle_fetch = git pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle pkg_gen_cycle_commit = develop PACKAGES += gen_icmp pkg_gen_icmp_name = gen_icmp pkg_gen_icmp_description = Erlang interface to ICMP sockets pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp pkg_gen_icmp_fetch = git pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp pkg_gen_icmp_commit = master PACKAGES += gen_leader pkg_gen_leader_name = gen_leader pkg_gen_leader_description = leader election behavior pkg_gen_leader_homepage = https://github.com/garret-smith/gen_leader_revival pkg_gen_leader_fetch = git pkg_gen_leader_repo = https://github.com/garret-smith/gen_leader_revival pkg_gen_leader_commit = master PACKAGES += gen_nb_server pkg_gen_nb_server_name = gen_nb_server pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server pkg_gen_nb_server_fetch = git pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server pkg_gen_nb_server_commit = master PACKAGES += gen_paxos pkg_gen_paxos_name = gen_paxos pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos pkg_gen_paxos_fetch = git pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos pkg_gen_paxos_commit = master PACKAGES += gen_rpc pkg_gen_rpc_name = gen_rpc pkg_gen_rpc_description = A scalable RPC library for Erlang-VM based languages pkg_gen_rpc_homepage = https://github.com/priestjim/gen_rpc.git pkg_gen_rpc_fetch = git pkg_gen_rpc_repo = https://github.com/priestjim/gen_rpc.git pkg_gen_rpc_commit = master PACKAGES += gen_smtp pkg_gen_smtp_name = gen_smtp pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp pkg_gen_smtp_fetch = git pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp pkg_gen_smtp_commit = master PACKAGES += gen_tracker pkg_gen_tracker_name = gen_tracker pkg_gen_tracker_description = supervisor with ets handling of children and their metadata pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker pkg_gen_tracker_fetch = git pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker pkg_gen_tracker_commit = master PACKAGES += gen_unix pkg_gen_unix_name = gen_unix pkg_gen_unix_description = Erlang Unix socket interface pkg_gen_unix_homepage = https://github.com/msantos/gen_unix pkg_gen_unix_fetch = git pkg_gen_unix_repo = https://github.com/msantos/gen_unix pkg_gen_unix_commit = master PACKAGES += geode pkg_geode_name = geode pkg_geode_description = geohash/proximity lookup in pure, uncut erlang. pkg_geode_homepage = https://github.com/bradfordw/geode pkg_geode_fetch = git pkg_geode_repo = https://github.com/bradfordw/geode pkg_geode_commit = master PACKAGES += getopt pkg_getopt_name = getopt pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax pkg_getopt_homepage = https://github.com/jcomellas/getopt pkg_getopt_fetch = git pkg_getopt_repo = https://github.com/jcomellas/getopt pkg_getopt_commit = master PACKAGES += gettext pkg_gettext_name = gettext pkg_gettext_description = Erlang internationalization library. pkg_gettext_homepage = https://github.com/etnt/gettext pkg_gettext_fetch = git pkg_gettext_repo = https://github.com/etnt/gettext pkg_gettext_commit = master PACKAGES += giallo pkg_giallo_name = giallo pkg_giallo_description = Small and flexible web framework on top of Cowboy pkg_giallo_homepage = https://github.com/kivra/giallo pkg_giallo_fetch = git pkg_giallo_repo = https://github.com/kivra/giallo pkg_giallo_commit = master PACKAGES += gin pkg_gin_name = gin pkg_gin_description = The guards and for Erlang parse_transform pkg_gin_homepage = https://github.com/mad-cocktail/gin pkg_gin_fetch = git pkg_gin_repo = https://github.com/mad-cocktail/gin pkg_gin_commit = master PACKAGES += gitty pkg_gitty_name = gitty pkg_gitty_description = Git access in erlang pkg_gitty_homepage = https://github.com/maxlapshin/gitty pkg_gitty_fetch = git pkg_gitty_repo = https://github.com/maxlapshin/gitty pkg_gitty_commit = master PACKAGES += gpb pkg_gpb_name = gpb pkg_gpb_description = A Google Protobuf implementation for Erlang pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb pkg_gpb_fetch = git pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb pkg_gpb_commit = master PACKAGES += gproc pkg_gproc_name = gproc pkg_gproc_description = Extended process registry for Erlang pkg_gproc_homepage = https://github.com/uwiger/gproc pkg_gproc_fetch = git pkg_gproc_repo = https://github.com/uwiger/gproc pkg_gproc_commit = master PACKAGES += grapherl pkg_grapherl_name = grapherl pkg_grapherl_description = Create graphs of Erlang systems and programs pkg_grapherl_homepage = https://github.com/eproxus/grapherl pkg_grapherl_fetch = git pkg_grapherl_repo = https://github.com/eproxus/grapherl pkg_grapherl_commit = master PACKAGES += grpc pkg_grpc_name = grpc pkg_grpc_description = gRPC server in Erlang pkg_grpc_homepage = https://github.com/Bluehouse-Technology/grpc pkg_grpc_fetch = git pkg_grpc_repo = https://github.com/Bluehouse-Technology/grpc pkg_grpc_commit = master PACKAGES += grpc_client pkg_grpc_client_name = grpc_client pkg_grpc_client_description = gRPC client in Erlang pkg_grpc_client_homepage = https://github.com/Bluehouse-Technology/grpc_client pkg_grpc_client_fetch = git pkg_grpc_client_repo = https://github.com/Bluehouse-Technology/grpc_client pkg_grpc_client_commit = master PACKAGES += gun pkg_gun_name = gun pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang. pkg_gun_homepage = http//ninenines.eu pkg_gun_fetch = git pkg_gun_repo = https://github.com/ninenines/gun pkg_gun_commit = master PACKAGES += hackney pkg_hackney_name = hackney pkg_hackney_description = simple HTTP client in Erlang pkg_hackney_homepage = https://github.com/benoitc/hackney pkg_hackney_fetch = git pkg_hackney_repo = https://github.com/benoitc/hackney pkg_hackney_commit = master PACKAGES += hamcrest pkg_hamcrest_name = hamcrest pkg_hamcrest_description = Erlang port of Hamcrest pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang pkg_hamcrest_fetch = git pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang pkg_hamcrest_commit = master PACKAGES += hottub pkg_hottub_name = hottub pkg_hottub_description = Permanent Erlang Worker Pool pkg_hottub_homepage = https://github.com/bfrog/hottub pkg_hottub_fetch = git pkg_hottub_repo = https://github.com/bfrog/hottub pkg_hottub_commit = master PACKAGES += hpack pkg_hpack_name = hpack pkg_hpack_description = HPACK Implementation for Erlang pkg_hpack_homepage = https://github.com/joedevivo/hpack pkg_hpack_fetch = git pkg_hpack_repo = https://github.com/joedevivo/hpack pkg_hpack_commit = master PACKAGES += hyper pkg_hyper_name = hyper pkg_hyper_description = Erlang implementation of HyperLogLog pkg_hyper_homepage = https://github.com/GameAnalytics/hyper pkg_hyper_fetch = git pkg_hyper_repo = https://github.com/GameAnalytics/hyper pkg_hyper_commit = master PACKAGES += i18n pkg_i18n_name = i18n pkg_i18n_description = International components for unicode from Erlang (unicode, date, string, number, format, locale, localization, transliteration, icu4e) pkg_i18n_homepage = https://github.com/erlang-unicode/i18n pkg_i18n_fetch = git pkg_i18n_repo = https://github.com/erlang-unicode/i18n pkg_i18n_commit = master PACKAGES += ibrowse pkg_ibrowse_name = ibrowse pkg_ibrowse_description = Erlang HTTP client pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse pkg_ibrowse_fetch = git pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse pkg_ibrowse_commit = master PACKAGES += idna pkg_idna_name = idna pkg_idna_description = Erlang IDNA lib pkg_idna_homepage = https://github.com/benoitc/erlang-idna pkg_idna_fetch = git pkg_idna_repo = https://github.com/benoitc/erlang-idna pkg_idna_commit = master PACKAGES += irc_lib pkg_irc_lib_name = irc_lib pkg_irc_lib_description = Erlang irc client library pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib pkg_irc_lib_fetch = git pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib pkg_irc_lib_commit = master PACKAGES += ircd pkg_ircd_name = ircd pkg_ircd_description = A pluggable IRC daemon application/library for Erlang. pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd pkg_ircd_fetch = git pkg_ircd_repo = https://github.com/tonyg/erlang-ircd pkg_ircd_commit = master PACKAGES += iris pkg_iris_name = iris pkg_iris_description = Iris Erlang binding pkg_iris_homepage = https://github.com/project-iris/iris-erl pkg_iris_fetch = git pkg_iris_repo = https://github.com/project-iris/iris-erl pkg_iris_commit = master PACKAGES += iso8601 pkg_iso8601_name = iso8601 pkg_iso8601_description = Erlang ISO 8601 date formatter/parser pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601 pkg_iso8601_fetch = git pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601 pkg_iso8601_commit = master PACKAGES += jamdb_sybase pkg_jamdb_sybase_name = jamdb_sybase pkg_jamdb_sybase_description = Erlang driver for SAP Sybase ASE pkg_jamdb_sybase_homepage = https://github.com/erlangbureau/jamdb_sybase pkg_jamdb_sybase_fetch = git pkg_jamdb_sybase_repo = https://github.com/erlangbureau/jamdb_sybase pkg_jamdb_sybase_commit = master PACKAGES += jesse pkg_jesse_name = jesse pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang. pkg_jesse_homepage = https://github.com/for-GET/jesse pkg_jesse_fetch = git pkg_jesse_repo = https://github.com/for-GET/jesse pkg_jesse_commit = master PACKAGES += jiffy pkg_jiffy_name = jiffy pkg_jiffy_description = JSON NIFs for Erlang. pkg_jiffy_homepage = https://github.com/davisp/jiffy pkg_jiffy_fetch = git pkg_jiffy_repo = https://github.com/davisp/jiffy pkg_jiffy_commit = master PACKAGES += jiffy_v pkg_jiffy_v_name = jiffy_v pkg_jiffy_v_description = JSON validation utility pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v pkg_jiffy_v_fetch = git pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v pkg_jiffy_v_commit = master PACKAGES += jobs pkg_jobs_name = jobs pkg_jobs_description = Job scheduler for load regulation pkg_jobs_homepage = https://github.com/uwiger/jobs pkg_jobs_fetch = git pkg_jobs_repo = https://github.com/uwiger/jobs pkg_jobs_commit = master PACKAGES += joxa pkg_joxa_name = joxa pkg_joxa_description = A Modern Lisp for the Erlang VM pkg_joxa_homepage = https://github.com/joxa/joxa pkg_joxa_fetch = git pkg_joxa_repo = https://github.com/joxa/joxa pkg_joxa_commit = master PACKAGES += json_rec pkg_json_rec_name = json_rec pkg_json_rec_description = JSON to erlang record pkg_json_rec_homepage = https://github.com/justinkirby/json_rec pkg_json_rec_fetch = git pkg_json_rec_repo = https://github.com/justinkirby/json_rec pkg_json_rec_commit = master PACKAGES += jsone pkg_jsone_name = jsone pkg_jsone_description = An Erlang library for encoding, decoding JSON data. pkg_jsone_homepage = https://github.com/sile/jsone.git pkg_jsone_fetch = git pkg_jsone_repo = https://github.com/sile/jsone.git pkg_jsone_commit = master PACKAGES += jsonpath pkg_jsonpath_name = jsonpath pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath pkg_jsonpath_fetch = git pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath pkg_jsonpath_commit = master PACKAGES += jsonx pkg_jsonx_name = jsonx pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C. pkg_jsonx_homepage = https://github.com/iskra/jsonx pkg_jsonx_fetch = git pkg_jsonx_repo = https://github.com/iskra/jsonx pkg_jsonx_commit = master PACKAGES += jsx pkg_jsx_name = jsx pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON. pkg_jsx_homepage = https://github.com/talentdeficit/jsx pkg_jsx_fetch = git pkg_jsx_repo = https://github.com/talentdeficit/jsx pkg_jsx_commit = main PACKAGES += kafka_protocol pkg_kafka_protocol_name = kafka_protocol pkg_kafka_protocol_description = Kafka protocol Erlang library pkg_kafka_protocol_homepage = https://github.com/kafka4beam/kafka_protocol pkg_kafka_protocol_fetch = git pkg_kafka_protocol_repo = https://github.com/kafka4beam/kafka_protocol pkg_kafka_protocol_commit = master PACKAGES += kai pkg_kai_name = kai pkg_kai_description = DHT storage by Takeshi Inoue pkg_kai_homepage = https://github.com/synrc/kai pkg_kai_fetch = git pkg_kai_repo = https://github.com/synrc/kai pkg_kai_commit = master PACKAGES += katja pkg_katja_name = katja pkg_katja_description = A simple Riemann client written in Erlang. pkg_katja_homepage = https://github.com/nifoc/katja pkg_katja_fetch = git pkg_katja_repo = https://github.com/nifoc/katja pkg_katja_commit = master PACKAGES += key2value pkg_key2value_name = key2value pkg_key2value_description = Erlang 2-way map pkg_key2value_homepage = https://github.com/okeuday/key2value pkg_key2value_fetch = git pkg_key2value_repo = https://github.com/okeuday/key2value pkg_key2value_commit = master PACKAGES += keys1value pkg_keys1value_name = keys1value pkg_keys1value_description = Erlang set associative map for key lists pkg_keys1value_homepage = https://github.com/okeuday/keys1value pkg_keys1value_fetch = git pkg_keys1value_repo = https://github.com/okeuday/keys1value pkg_keys1value_commit = master PACKAGES += kinetic pkg_kinetic_name = kinetic pkg_kinetic_description = Erlang Kinesis Client pkg_kinetic_homepage = https://github.com/AdRoll/kinetic pkg_kinetic_fetch = git pkg_kinetic_repo = https://github.com/AdRoll/kinetic pkg_kinetic_commit = main PACKAGES += kjell pkg_kjell_name = kjell pkg_kjell_description = Erlang Shell pkg_kjell_homepage = https://github.com/karlll/kjell pkg_kjell_fetch = git pkg_kjell_repo = https://github.com/karlll/kjell pkg_kjell_commit = master PACKAGES += kraken pkg_kraken_name = kraken pkg_kraken_description = Distributed Pubsub Server for Realtime Apps pkg_kraken_homepage = https://github.com/Asana/kraken pkg_kraken_fetch = git pkg_kraken_repo = https://github.com/Asana/kraken pkg_kraken_commit = master PACKAGES += kucumberl pkg_kucumberl_name = kucumberl pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber pkg_kucumberl_homepage = https://github.com/openshine/kucumberl pkg_kucumberl_fetch = git pkg_kucumberl_repo = https://github.com/openshine/kucumberl pkg_kucumberl_commit = master PACKAGES += kvc pkg_kvc_name = kvc pkg_kvc_description = KVC - Key Value Coding for Erlang data structures pkg_kvc_homepage = https://github.com/etrepum/kvc pkg_kvc_fetch = git pkg_kvc_repo = https://github.com/etrepum/kvc pkg_kvc_commit = master PACKAGES += kvlists pkg_kvlists_name = kvlists pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang pkg_kvlists_homepage = https://github.com/jcomellas/kvlists pkg_kvlists_fetch = git pkg_kvlists_repo = https://github.com/jcomellas/kvlists pkg_kvlists_commit = master PACKAGES += kvs pkg_kvs_name = kvs pkg_kvs_description = Container and Iterator pkg_kvs_homepage = https://github.com/synrc/kvs pkg_kvs_fetch = git pkg_kvs_repo = https://github.com/synrc/kvs pkg_kvs_commit = master PACKAGES += lager pkg_lager_name = lager pkg_lager_description = A logging framework for Erlang/OTP. pkg_lager_homepage = https://github.com/erlang-lager/lager pkg_lager_fetch = git pkg_lager_repo = https://github.com/erlang-lager/lager pkg_lager_commit = master PACKAGES += lager_syslog pkg_lager_syslog_name = lager_syslog pkg_lager_syslog_description = Syslog backend for lager pkg_lager_syslog_homepage = https://github.com/erlang-lager/lager_syslog pkg_lager_syslog_fetch = git pkg_lager_syslog_repo = https://github.com/erlang-lager/lager_syslog pkg_lager_syslog_commit = master PACKAGES += lasse pkg_lasse_name = lasse pkg_lasse_description = SSE handler for Cowboy pkg_lasse_homepage = https://github.com/inaka/lasse pkg_lasse_fetch = git pkg_lasse_repo = https://github.com/inaka/lasse pkg_lasse_commit = master PACKAGES += ldap pkg_ldap_name = ldap pkg_ldap_description = LDAP server written in Erlang pkg_ldap_homepage = https://github.com/spawnproc/ldap pkg_ldap_fetch = git pkg_ldap_repo = https://github.com/spawnproc/ldap pkg_ldap_commit = master PACKAGES += lfe pkg_lfe_name = lfe pkg_lfe_description = Lisp Flavoured Erlang (LFE) pkg_lfe_homepage = https://github.com/rvirding/lfe pkg_lfe_fetch = git pkg_lfe_repo = https://github.com/rvirding/lfe pkg_lfe_commit = master PACKAGES += live pkg_live_name = live pkg_live_description = Automated module and configuration reloader. pkg_live_homepage = http://ninenines.eu pkg_live_fetch = git pkg_live_repo = https://github.com/ninenines/live pkg_live_commit = master PACKAGES += locker pkg_locker_name = locker pkg_locker_description = Atomic distributed 'check and set' for short-lived keys pkg_locker_homepage = https://github.com/wooga/locker pkg_locker_fetch = git pkg_locker_repo = https://github.com/wooga/locker pkg_locker_commit = master PACKAGES += locks pkg_locks_name = locks pkg_locks_description = A scalable, deadlock-resolving resource locker pkg_locks_homepage = https://github.com/uwiger/locks pkg_locks_fetch = git pkg_locks_repo = https://github.com/uwiger/locks pkg_locks_commit = master PACKAGES += log4erl pkg_log4erl_name = log4erl pkg_log4erl_description = A logger for erlang in the spirit of Log4J. pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl pkg_log4erl_fetch = git pkg_log4erl_repo = https://github.com/ahmednawras/log4erl pkg_log4erl_commit = master PACKAGES += lol pkg_lol_name = lol pkg_lol_description = Lisp on erLang, and programming is fun again pkg_lol_homepage = https://github.com/b0oh/lol pkg_lol_fetch = git pkg_lol_repo = https://github.com/b0oh/lol pkg_lol_commit = master PACKAGES += lucid pkg_lucid_name = lucid pkg_lucid_description = HTTP/2 server written in Erlang pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid pkg_lucid_fetch = git pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid pkg_lucid_commit = master PACKAGES += luerl pkg_luerl_name = luerl pkg_luerl_description = Lua in Erlang pkg_luerl_homepage = https://github.com/rvirding/luerl pkg_luerl_fetch = git pkg_luerl_repo = https://github.com/rvirding/luerl pkg_luerl_commit = develop PACKAGES += lux pkg_lux_name = lux pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands pkg_lux_homepage = https://github.com/hawk/lux pkg_lux_fetch = git pkg_lux_repo = https://github.com/hawk/lux pkg_lux_commit = master PACKAGES += mad pkg_mad_name = mad pkg_mad_description = Small and Fast Rebar Replacement pkg_mad_homepage = https://github.com/synrc/mad pkg_mad_fetch = git pkg_mad_repo = https://github.com/synrc/mad pkg_mad_commit = master PACKAGES += marina pkg_marina_name = marina pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client pkg_marina_homepage = https://github.com/lpgauth/marina pkg_marina_fetch = git pkg_marina_repo = https://github.com/lpgauth/marina pkg_marina_commit = master PACKAGES += mavg pkg_mavg_name = mavg pkg_mavg_description = Erlang :: Exponential moving average library pkg_mavg_homepage = https://github.com/EchoTeam/mavg pkg_mavg_fetch = git pkg_mavg_repo = https://github.com/EchoTeam/mavg pkg_mavg_commit = master PACKAGES += meck pkg_meck_name = meck pkg_meck_description = A mocking library for Erlang pkg_meck_homepage = https://github.com/eproxus/meck pkg_meck_fetch = git pkg_meck_repo = https://github.com/eproxus/meck pkg_meck_commit = master PACKAGES += mekao pkg_mekao_name = mekao pkg_mekao_description = SQL constructor pkg_mekao_homepage = https://github.com/ddosia/mekao pkg_mekao_fetch = git pkg_mekao_repo = https://github.com/ddosia/mekao pkg_mekao_commit = master PACKAGES += merl pkg_merl_name = merl pkg_merl_description = Metaprogramming in Erlang pkg_merl_homepage = https://github.com/richcarl/merl pkg_merl_fetch = git pkg_merl_repo = https://github.com/richcarl/merl pkg_merl_commit = master PACKAGES += mimerl pkg_mimerl_name = mimerl pkg_mimerl_description = library to handle mimetypes pkg_mimerl_homepage = https://github.com/benoitc/mimerl pkg_mimerl_fetch = git pkg_mimerl_repo = https://github.com/benoitc/mimerl pkg_mimerl_commit = master PACKAGES += mimetypes pkg_mimetypes_name = mimetypes pkg_mimetypes_description = Erlang MIME types library pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes pkg_mimetypes_fetch = git pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes pkg_mimetypes_commit = master PACKAGES += mixer pkg_mixer_name = mixer pkg_mixer_description = Mix in functions from other modules pkg_mixer_homepage = https://github.com/chef/mixer pkg_mixer_fetch = git pkg_mixer_repo = https://github.com/chef/mixer pkg_mixer_commit = main PACKAGES += mochiweb pkg_mochiweb_name = mochiweb pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers. pkg_mochiweb_homepage = https://github.com/mochi/mochiweb pkg_mochiweb_fetch = git pkg_mochiweb_repo = https://github.com/mochi/mochiweb pkg_mochiweb_commit = main PACKAGES += mochiweb_xpath pkg_mochiweb_xpath_name = mochiweb_xpath pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath pkg_mochiweb_xpath_fetch = git pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath pkg_mochiweb_xpath_commit = master PACKAGES += mockgyver pkg_mockgyver_name = mockgyver pkg_mockgyver_description = A mocking library for Erlang pkg_mockgyver_homepage = https://github.com/klajo/mockgyver pkg_mockgyver_fetch = git pkg_mockgyver_repo = https://github.com/klajo/mockgyver pkg_mockgyver_commit = master PACKAGES += modlib pkg_modlib_name = modlib pkg_modlib_description = Web framework based on Erlang's inets httpd pkg_modlib_homepage = https://github.com/gar1t/modlib pkg_modlib_fetch = git pkg_modlib_repo = https://github.com/gar1t/modlib pkg_modlib_commit = master PACKAGES += mongodb pkg_mongodb_name = mongodb pkg_mongodb_description = MongoDB driver for Erlang pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang pkg_mongodb_fetch = git pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang pkg_mongodb_commit = master PACKAGES += mongooseim pkg_mongooseim_name = mongooseim pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform pkg_mongooseim_fetch = git pkg_mongooseim_repo = https://github.com/esl/MongooseIM pkg_mongooseim_commit = master PACKAGES += moyo pkg_moyo_name = moyo pkg_moyo_description = Erlang utility functions library pkg_moyo_homepage = https://github.com/dwango/moyo pkg_moyo_fetch = git pkg_moyo_repo = https://github.com/dwango/moyo pkg_moyo_commit = master PACKAGES += msgpack pkg_msgpack_name = msgpack pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang pkg_msgpack_fetch = git pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang pkg_msgpack_commit = master PACKAGES += mu2 pkg_mu2_name = mu2 pkg_mu2_description = Erlang mutation testing tool pkg_mu2_homepage = https://github.com/ramsay-t/mu2 pkg_mu2_fetch = git pkg_mu2_repo = https://github.com/ramsay-t/mu2 pkg_mu2_commit = master PACKAGES += mustache pkg_mustache_name = mustache pkg_mustache_description = Mustache template engine for Erlang. pkg_mustache_homepage = https://github.com/mojombo/mustache.erl pkg_mustache_fetch = git pkg_mustache_repo = https://github.com/mojombo/mustache.erl pkg_mustache_commit = master PACKAGES += myproto pkg_myproto_name = myproto pkg_myproto_description = MySQL Server Protocol in Erlang pkg_myproto_homepage = https://github.com/altenwald/myproto pkg_myproto_fetch = git pkg_myproto_repo = https://github.com/altenwald/myproto pkg_myproto_commit = master PACKAGES += mysql pkg_mysql_name = mysql pkg_mysql_description = MySQL client library for Erlang/OTP pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp pkg_mysql_fetch = git pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp pkg_mysql_commit = 1.7.0 PACKAGES += n2o pkg_n2o_name = n2o pkg_n2o_description = WebSocket Application Server pkg_n2o_homepage = https://github.com/5HT/n2o pkg_n2o_fetch = git pkg_n2o_repo = https://github.com/5HT/n2o pkg_n2o_commit = master PACKAGES += nat_upnp pkg_nat_upnp_name = nat_upnp pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp pkg_nat_upnp_fetch = git pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp pkg_nat_upnp_commit = master PACKAGES += neo4j pkg_neo4j_name = neo4j pkg_neo4j_description = Erlang client library for Neo4J. pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang pkg_neo4j_fetch = git pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang pkg_neo4j_commit = master PACKAGES += neotoma pkg_neotoma_name = neotoma pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars. pkg_neotoma_homepage = https://github.com/seancribbs/neotoma pkg_neotoma_fetch = git pkg_neotoma_repo = https://github.com/seancribbs/neotoma pkg_neotoma_commit = master PACKAGES += nifty pkg_nifty_name = nifty pkg_nifty_description = Erlang NIF wrapper generator pkg_nifty_homepage = https://github.com/parapluu/nifty pkg_nifty_fetch = git pkg_nifty_repo = https://github.com/parapluu/nifty pkg_nifty_commit = master PACKAGES += nitrogen_core pkg_nitrogen_core_name = nitrogen_core pkg_nitrogen_core_description = The core Nitrogen library. pkg_nitrogen_core_homepage = http://nitrogenproject.com/ pkg_nitrogen_core_fetch = git pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core pkg_nitrogen_core_commit = master PACKAGES += nkpacket pkg_nkpacket_name = nkpacket pkg_nkpacket_description = Generic Erlang transport layer pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket pkg_nkpacket_fetch = git pkg_nkpacket_repo = https://github.com/Nekso/nkpacket pkg_nkpacket_commit = master PACKAGES += nksip pkg_nksip_name = nksip pkg_nksip_description = Erlang SIP application server pkg_nksip_homepage = https://github.com/kalta/nksip pkg_nksip_fetch = git pkg_nksip_repo = https://github.com/kalta/nksip pkg_nksip_commit = master PACKAGES += nodefinder pkg_nodefinder_name = nodefinder pkg_nodefinder_description = automatic node discovery via UDP multicast pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder pkg_nodefinder_fetch = git pkg_nodefinder_repo = https://github.com/okeuday/nodefinder pkg_nodefinder_commit = master PACKAGES += nprocreg pkg_nprocreg_name = nprocreg pkg_nprocreg_description = Minimal Distributed Erlang Process Registry pkg_nprocreg_homepage = http://nitrogenproject.com/ pkg_nprocreg_fetch = git pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg pkg_nprocreg_commit = master PACKAGES += oauth pkg_oauth_name = oauth pkg_oauth_description = An Erlang OAuth 1.0 implementation pkg_oauth_homepage = https://github.com/tim/erlang-oauth pkg_oauth_fetch = git pkg_oauth_repo = https://github.com/tim/erlang-oauth pkg_oauth_commit = main PACKAGES += oauth2 pkg_oauth2_name = oauth2 pkg_oauth2_description = Erlang Oauth2 implementation pkg_oauth2_homepage = https://github.com/kivra/oauth2 pkg_oauth2_fetch = git pkg_oauth2_repo = https://github.com/kivra/oauth2 pkg_oauth2_commit = master PACKAGES += observer_cli pkg_observer_cli_name = observer_cli pkg_observer_cli_description = Visualize Erlang/Elixir Nodes On The Command Line pkg_observer_cli_homepage = http://zhongwencool.github.io/observer_cli pkg_observer_cli_fetch = git pkg_observer_cli_repo = https://github.com/zhongwencool/observer_cli pkg_observer_cli_commit = master PACKAGES += octopus pkg_octopus_name = octopus pkg_octopus_description = Small and flexible pool manager written in Erlang pkg_octopus_homepage = https://github.com/erlangbureau/octopus pkg_octopus_fetch = git pkg_octopus_repo = https://github.com/erlangbureau/octopus pkg_octopus_commit = master PACKAGES += openflow pkg_openflow_name = openflow pkg_openflow_description = An OpenFlow controller written in pure erlang pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow pkg_openflow_fetch = git pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow pkg_openflow_commit = master PACKAGES += openid pkg_openid_name = openid pkg_openid_description = Erlang OpenID pkg_openid_homepage = https://github.com/brendonh/erl_openid pkg_openid_fetch = git pkg_openid_repo = https://github.com/brendonh/erl_openid pkg_openid_commit = master PACKAGES += openpoker pkg_openpoker_name = openpoker pkg_openpoker_description = Genesis Texas hold'em Game Server pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker pkg_openpoker_fetch = git pkg_openpoker_repo = https://github.com/hpyhacking/openpoker pkg_openpoker_commit = master PACKAGES += otpbp pkg_otpbp_name = otpbp pkg_otpbp_description = Parse transformer for use new OTP functions in old Erlang/OTP releases (R15, R16, 17, 18, 19) pkg_otpbp_homepage = https://github.com/Ledest/otpbp pkg_otpbp_fetch = git pkg_otpbp_repo = https://github.com/Ledest/otpbp pkg_otpbp_commit = master PACKAGES += pal pkg_pal_name = pal pkg_pal_description = Pragmatic Authentication Library pkg_pal_homepage = https://github.com/manifest/pal pkg_pal_fetch = git pkg_pal_repo = https://github.com/manifest/pal pkg_pal_commit = master PACKAGES += parse_trans pkg_parse_trans_name = parse_trans pkg_parse_trans_description = Parse transform utilities for Erlang pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans pkg_parse_trans_fetch = git pkg_parse_trans_repo = https://github.com/uwiger/parse_trans pkg_parse_trans_commit = master PACKAGES += parsexml pkg_parsexml_name = parsexml pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml pkg_parsexml_fetch = git pkg_parsexml_repo = https://github.com/maxlapshin/parsexml pkg_parsexml_commit = master PACKAGES += partisan pkg_partisan_name = partisan pkg_partisan_description = High-performance, high-scalability distributed computing with Erlang and Elixir. pkg_partisan_homepage = http://partisan.cloud pkg_partisan_fetch = git pkg_partisan_repo = https://github.com/lasp-lang/partisan pkg_partisan_commit = master PACKAGES += pegjs pkg_pegjs_name = pegjs pkg_pegjs_description = An implementation of PEG.js grammar for Erlang. pkg_pegjs_homepage = https://github.com/dmitriid/pegjs pkg_pegjs_fetch = git pkg_pegjs_repo = https://github.com/dmitriid/pegjs pkg_pegjs_commit = master PACKAGES += percept2 pkg_percept2_name = percept2 pkg_percept2_description = Concurrent profiling tool for Erlang pkg_percept2_homepage = https://github.com/huiqing/percept2 pkg_percept2_fetch = git pkg_percept2_repo = https://github.com/huiqing/percept2 pkg_percept2_commit = master PACKAGES += pgo pkg_pgo_name = pgo pkg_pgo_description = Erlang Postgres client and connection pool pkg_pgo_homepage = https://github.com/erleans/pgo.git pkg_pgo_fetch = git pkg_pgo_repo = https://github.com/erleans/pgo.git pkg_pgo_commit = main PACKAGES += pgsql pkg_pgsql_name = pgsql pkg_pgsql_description = Erlang PostgreSQL driver pkg_pgsql_homepage = https://github.com/semiocast/pgsql pkg_pgsql_fetch = git pkg_pgsql_repo = https://github.com/semiocast/pgsql pkg_pgsql_commit = master PACKAGES += pkgx pkg_pkgx_name = pkgx pkg_pkgx_description = Build .deb packages from Erlang releases pkg_pkgx_homepage = https://github.com/arjan/pkgx pkg_pkgx_fetch = git pkg_pkgx_repo = https://github.com/arjan/pkgx pkg_pkgx_commit = master PACKAGES += pkt pkg_pkt_name = pkt pkg_pkt_description = Erlang network protocol library pkg_pkt_homepage = https://github.com/msantos/pkt pkg_pkt_fetch = git pkg_pkt_repo = https://github.com/msantos/pkt pkg_pkt_commit = master PACKAGES += plain_fsm pkg_plain_fsm_name = plain_fsm pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs. pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm pkg_plain_fsm_fetch = git pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm pkg_plain_fsm_commit = master PACKAGES += pmod_transform pkg_pmod_transform_name = pmod_transform pkg_pmod_transform_description = Parse transform for parameterized modules pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform pkg_pmod_transform_fetch = git pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform pkg_pmod_transform_commit = master PACKAGES += pobox pkg_pobox_name = pobox pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang pkg_pobox_homepage = https://github.com/ferd/pobox pkg_pobox_fetch = git pkg_pobox_repo = https://github.com/ferd/pobox pkg_pobox_commit = master PACKAGES += ponos pkg_ponos_name = ponos pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang pkg_ponos_homepage = https://github.com/klarna/ponos pkg_ponos_fetch = git pkg_ponos_repo = https://github.com/klarna/ponos pkg_ponos_commit = master PACKAGES += poolboy pkg_poolboy_name = poolboy pkg_poolboy_description = A hunky Erlang worker pool factory pkg_poolboy_homepage = https://github.com/devinus/poolboy pkg_poolboy_fetch = git pkg_poolboy_repo = https://github.com/devinus/poolboy pkg_poolboy_commit = master PACKAGES += pooler pkg_pooler_name = pooler pkg_pooler_description = An OTP Process Pool Application pkg_pooler_homepage = https://github.com/seth/pooler pkg_pooler_fetch = git pkg_pooler_repo = https://github.com/seth/pooler pkg_pooler_commit = master PACKAGES += pqueue pkg_pqueue_name = pqueue pkg_pqueue_description = Erlang Priority Queues pkg_pqueue_homepage = https://github.com/okeuday/pqueue pkg_pqueue_fetch = git pkg_pqueue_repo = https://github.com/okeuday/pqueue pkg_pqueue_commit = master PACKAGES += procket pkg_procket_name = procket pkg_procket_description = Erlang interface to low level socket operations pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket pkg_procket_fetch = git pkg_procket_repo = https://github.com/msantos/procket pkg_procket_commit = master PACKAGES += prometheus pkg_prometheus_name = prometheus pkg_prometheus_description = Prometheus.io client in Erlang pkg_prometheus_homepage = https://github.com/deadtrickster/prometheus.erl pkg_prometheus_fetch = git pkg_prometheus_repo = https://github.com/deadtrickster/prometheus.erl pkg_prometheus_commit = master PACKAGES += prop pkg_prop_name = prop pkg_prop_description = An Erlang code scaffolding and generator system. pkg_prop_homepage = https://github.com/nuex/prop pkg_prop_fetch = git pkg_prop_repo = https://github.com/nuex/prop pkg_prop_commit = master PACKAGES += proper pkg_proper_name = proper pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang. pkg_proper_homepage = http://proper.softlab.ntua.gr pkg_proper_fetch = git pkg_proper_repo = https://github.com/manopapad/proper pkg_proper_commit = master PACKAGES += props pkg_props_name = props pkg_props_description = Property structure library pkg_props_homepage = https://github.com/greyarea/props pkg_props_fetch = git pkg_props_repo = https://github.com/greyarea/props pkg_props_commit = master PACKAGES += protobuffs pkg_protobuffs_name = protobuffs pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs. pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs pkg_protobuffs_fetch = git pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs pkg_protobuffs_commit = master PACKAGES += psycho pkg_psycho_name = psycho pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware. pkg_psycho_homepage = https://github.com/gar1t/psycho pkg_psycho_fetch = git pkg_psycho_repo = https://github.com/gar1t/psycho pkg_psycho_commit = master PACKAGES += purity pkg_purity_name = purity pkg_purity_description = A side-effect analyzer for Erlang pkg_purity_homepage = https://github.com/mpitid/purity pkg_purity_fetch = git pkg_purity_repo = https://github.com/mpitid/purity pkg_purity_commit = master PACKAGES += qdate pkg_qdate_name = qdate pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang. pkg_qdate_homepage = https://github.com/choptastic/qdate pkg_qdate_fetch = git pkg_qdate_repo = https://github.com/choptastic/qdate pkg_qdate_commit = master PACKAGES += qrcode pkg_qrcode_name = qrcode pkg_qrcode_description = QR Code encoder in Erlang pkg_qrcode_homepage = https://github.com/komone/qrcode pkg_qrcode_fetch = git pkg_qrcode_repo = https://github.com/komone/qrcode pkg_qrcode_commit = master PACKAGES += quest pkg_quest_name = quest pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang. pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest pkg_quest_fetch = git pkg_quest_repo = https://github.com/eriksoe/ErlangQuest pkg_quest_commit = master PACKAGES += quickrand pkg_quickrand_name = quickrand pkg_quickrand_description = Quick Erlang Random Number Generation pkg_quickrand_homepage = https://github.com/okeuday/quickrand pkg_quickrand_fetch = git pkg_quickrand_repo = https://github.com/okeuday/quickrand pkg_quickrand_commit = master PACKAGES += rabbit_exchange_type_riak pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange pkg_rabbit_exchange_type_riak_fetch = git pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange pkg_rabbit_exchange_type_riak_commit = master PACKAGES += rack pkg_rack_name = rack pkg_rack_description = Rack handler for erlang pkg_rack_homepage = https://github.com/erlyvideo/rack pkg_rack_fetch = git pkg_rack_repo = https://github.com/erlyvideo/rack pkg_rack_commit = master PACKAGES += radierl pkg_radierl_name = radierl pkg_radierl_description = RADIUS protocol stack implemented in Erlang. pkg_radierl_homepage = https://github.com/vances/radierl pkg_radierl_fetch = git pkg_radierl_repo = https://github.com/vances/radierl pkg_radierl_commit = master PACKAGES += ranch pkg_ranch_name = ranch pkg_ranch_description = Socket acceptor pool for TCP protocols. pkg_ranch_homepage = http://ninenines.eu pkg_ranch_fetch = git pkg_ranch_repo = https://github.com/ninenines/ranch pkg_ranch_commit = 1.2.1 PACKAGES += rbeacon pkg_rbeacon_name = rbeacon pkg_rbeacon_description = LAN discovery and presence in Erlang. pkg_rbeacon_homepage = https://github.com/refuge/rbeacon pkg_rbeacon_fetch = git pkg_rbeacon_repo = https://github.com/refuge/rbeacon pkg_rbeacon_commit = master PACKAGES += re2 pkg_re2_name = re2 pkg_re2_description = Erlang NIF bindings for RE2 regex library pkg_re2_homepage = https://github.com/dukesoferl/re2 pkg_re2_fetch = git pkg_re2_repo = https://github.com/dukesoferl/re2 pkg_re2_commit = master PACKAGES += rebus pkg_rebus_name = rebus pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang. pkg_rebus_homepage = https://github.com/olle/rebus pkg_rebus_fetch = git pkg_rebus_repo = https://github.com/olle/rebus pkg_rebus_commit = master PACKAGES += rec2json pkg_rec2json_name = rec2json pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily. pkg_rec2json_homepage = https://github.com/lordnull/rec2json pkg_rec2json_fetch = git pkg_rec2json_repo = https://github.com/lordnull/rec2json pkg_rec2json_commit = master PACKAGES += recon pkg_recon_name = recon pkg_recon_description = Collection of functions and scripts to debug Erlang in production. pkg_recon_homepage = https://github.com/ferd/recon pkg_recon_fetch = git pkg_recon_repo = https://github.com/ferd/recon pkg_recon_commit = master PACKAGES += record_info pkg_record_info_name = record_info pkg_record_info_description = Convert between record and proplist pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info pkg_record_info_fetch = git pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info pkg_record_info_commit = master PACKAGES += redgrid pkg_redgrid_name = redgrid pkg_redgrid_description = automatic Erlang node discovery via redis pkg_redgrid_homepage = https://github.com/jkvor/redgrid pkg_redgrid_fetch = git pkg_redgrid_repo = https://github.com/jkvor/redgrid pkg_redgrid_commit = master PACKAGES += redo pkg_redo_name = redo pkg_redo_description = pipelined erlang redis client pkg_redo_homepage = https://github.com/jkvor/redo pkg_redo_fetch = git pkg_redo_repo = https://github.com/jkvor/redo pkg_redo_commit = master PACKAGES += reload_mk pkg_reload_mk_name = reload_mk pkg_reload_mk_description = Live reload plugin for erlang.mk. pkg_reload_mk_homepage = https://github.com/bullno1/reload.mk pkg_reload_mk_fetch = git pkg_reload_mk_repo = https://github.com/bullno1/reload.mk pkg_reload_mk_commit = master PACKAGES += reltool_util pkg_reltool_util_name = reltool_util pkg_reltool_util_description = Erlang reltool utility functionality application pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util pkg_reltool_util_fetch = git pkg_reltool_util_repo = https://github.com/okeuday/reltool_util pkg_reltool_util_commit = master PACKAGES += relx pkg_relx_name = relx pkg_relx_description = Sane, simple release creation for Erlang pkg_relx_homepage = https://github.com/erlware/relx pkg_relx_fetch = git pkg_relx_repo = https://github.com/erlware/relx pkg_relx_commit = main PACKAGES += resource_discovery pkg_resource_discovery_name = resource_discovery pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster. pkg_resource_discovery_homepage = http://erlware.org/ pkg_resource_discovery_fetch = git pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery pkg_resource_discovery_commit = master PACKAGES += restc pkg_restc_name = restc pkg_restc_description = Erlang Rest Client pkg_restc_homepage = https://github.com/kivra/restclient pkg_restc_fetch = git pkg_restc_repo = https://github.com/kivra/restclient pkg_restc_commit = master PACKAGES += rfc4627_jsonrpc pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation. pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627 pkg_rfc4627_jsonrpc_fetch = git pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627 pkg_rfc4627_jsonrpc_commit = master PACKAGES += riak_core pkg_riak_core_name = riak_core pkg_riak_core_description = Distributed systems infrastructure used by Riak. pkg_riak_core_homepage = https://github.com/basho/riak_core pkg_riak_core_fetch = git pkg_riak_core_repo = https://github.com/basho/riak_core pkg_riak_core_commit = develop PACKAGES += riak_dt pkg_riak_dt_name = riak_dt pkg_riak_dt_description = Convergent replicated datatypes in Erlang pkg_riak_dt_homepage = https://github.com/basho/riak_dt pkg_riak_dt_fetch = git pkg_riak_dt_repo = https://github.com/basho/riak_dt pkg_riak_dt_commit = master PACKAGES += riak_ensemble pkg_riak_ensemble_name = riak_ensemble pkg_riak_ensemble_description = Multi-Paxos framework in Erlang pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble pkg_riak_ensemble_fetch = git pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble pkg_riak_ensemble_commit = develop PACKAGES += riak_kv pkg_riak_kv_name = riak_kv pkg_riak_kv_description = Riak Key/Value Store pkg_riak_kv_homepage = https://github.com/basho/riak_kv pkg_riak_kv_fetch = git pkg_riak_kv_repo = https://github.com/basho/riak_kv pkg_riak_kv_commit = develop PACKAGES += riak_pipe pkg_riak_pipe_name = riak_pipe pkg_riak_pipe_description = Riak Pipelines pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe pkg_riak_pipe_fetch = git pkg_riak_pipe_repo = https://github.com/basho/riak_pipe pkg_riak_pipe_commit = develop PACKAGES += riak_sysmon pkg_riak_sysmon_name = riak_sysmon pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon pkg_riak_sysmon_fetch = git pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon pkg_riak_sysmon_commit = master PACKAGES += riakc pkg_riakc_name = riakc pkg_riakc_description = Erlang clients for Riak. pkg_riakc_homepage = https://github.com/basho/riak-erlang-client pkg_riakc_fetch = git pkg_riakc_repo = https://github.com/basho/riak-erlang-client pkg_riakc_commit = master PACKAGES += rlimit pkg_rlimit_name = rlimit pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent pkg_rlimit_homepage = https://github.com/jlouis/rlimit pkg_rlimit_fetch = git pkg_rlimit_repo = https://github.com/jlouis/rlimit pkg_rlimit_commit = master PACKAGES += rust_mk pkg_rust_mk_name = rust_mk pkg_rust_mk_description = Build Rust crates in an Erlang application pkg_rust_mk_homepage = https://github.com/goertzenator/rust.mk pkg_rust_mk_fetch = git pkg_rust_mk_repo = https://github.com/goertzenator/rust.mk pkg_rust_mk_commit = master PACKAGES += safetyvalve pkg_safetyvalve_name = safetyvalve pkg_safetyvalve_description = A safety valve for your erlang node pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve pkg_safetyvalve_fetch = git pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve pkg_safetyvalve_commit = master PACKAGES += seestar pkg_seestar_name = seestar pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol pkg_seestar_homepage = https://github.com/iamaleksey/seestar pkg_seestar_fetch = git pkg_seestar_repo = https://github.com/iamaleksey/seestar pkg_seestar_commit = master PACKAGES += setup pkg_setup_name = setup pkg_setup_description = Generic setup utility for Erlang-based systems pkg_setup_homepage = https://github.com/uwiger/setup pkg_setup_fetch = git pkg_setup_repo = https://github.com/uwiger/setup pkg_setup_commit = master PACKAGES += sext pkg_sext_name = sext pkg_sext_description = Sortable Erlang Term Serialization pkg_sext_homepage = https://github.com/uwiger/sext pkg_sext_fetch = git pkg_sext_repo = https://github.com/uwiger/sext pkg_sext_commit = master PACKAGES += sfmt pkg_sfmt_name = sfmt pkg_sfmt_description = SFMT pseudo random number generator for Erlang. pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang pkg_sfmt_fetch = git pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang pkg_sfmt_commit = master PACKAGES += sgte pkg_sgte_name = sgte pkg_sgte_description = A simple Erlang Template Engine pkg_sgte_homepage = https://github.com/filippo/sgte pkg_sgte_fetch = git pkg_sgte_repo = https://github.com/filippo/sgte pkg_sgte_commit = master PACKAGES += sheriff pkg_sheriff_name = sheriff pkg_sheriff_description = Parse transform for type based validation. pkg_sheriff_homepage = http://ninenines.eu pkg_sheriff_fetch = git pkg_sheriff_repo = https://github.com/extend/sheriff pkg_sheriff_commit = master PACKAGES += shotgun pkg_shotgun_name = shotgun pkg_shotgun_description = better than just a gun pkg_shotgun_homepage = https://github.com/inaka/shotgun pkg_shotgun_fetch = git pkg_shotgun_repo = https://github.com/inaka/shotgun pkg_shotgun_commit = master PACKAGES += sidejob pkg_sidejob_name = sidejob pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang pkg_sidejob_homepage = https://github.com/basho/sidejob pkg_sidejob_fetch = git pkg_sidejob_repo = https://github.com/basho/sidejob pkg_sidejob_commit = develop PACKAGES += sieve pkg_sieve_name = sieve pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang pkg_sieve_homepage = https://github.com/benoitc/sieve pkg_sieve_fetch = git pkg_sieve_repo = https://github.com/benoitc/sieve pkg_sieve_commit = master PACKAGES += simhash pkg_simhash_name = simhash pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data. pkg_simhash_homepage = https://github.com/ferd/simhash pkg_simhash_fetch = git pkg_simhash_repo = https://github.com/ferd/simhash pkg_simhash_commit = master PACKAGES += simple_bridge pkg_simple_bridge_name = simple_bridge pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers. pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge pkg_simple_bridge_fetch = git pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge pkg_simple_bridge_commit = master PACKAGES += simple_oauth2 pkg_simple_oauth2_name = simple_oauth2 pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured) pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2 pkg_simple_oauth2_fetch = git pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2 pkg_simple_oauth2_commit = master PACKAGES += skel pkg_skel_name = skel pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang pkg_skel_homepage = https://github.com/ParaPhrase/skel pkg_skel_fetch = git pkg_skel_repo = https://github.com/ParaPhrase/skel pkg_skel_commit = master PACKAGES += slack pkg_slack_name = slack pkg_slack_description = Minimal slack notification OTP library. pkg_slack_homepage = https://github.com/DonBranson/slack pkg_slack_fetch = git pkg_slack_repo = https://github.com/DonBranson/slack.git pkg_slack_commit = master PACKAGES += snappyer pkg_snappyer_name = snappyer pkg_snappyer_description = Snappy as nif for Erlang pkg_snappyer_homepage = https://github.com/zmstone/snappyer pkg_snappyer_fetch = git pkg_snappyer_repo = https://github.com/zmstone/snappyer.git pkg_snappyer_commit = master PACKAGES += social pkg_social_name = social pkg_social_description = Cowboy handler for social login via OAuth2 providers pkg_social_homepage = https://github.com/dvv/social pkg_social_fetch = git pkg_social_repo = https://github.com/dvv/social pkg_social_commit = master PACKAGES += sqerl pkg_sqerl_name = sqerl pkg_sqerl_description = An Erlang-flavoured SQL DSL pkg_sqerl_homepage = https://github.com/hairyhum/sqerl pkg_sqerl_fetch = git pkg_sqerl_repo = https://github.com/hairyhum/sqerl pkg_sqerl_commit = master PACKAGES += srly pkg_srly_name = srly pkg_srly_description = Native Erlang Unix serial interface pkg_srly_homepage = https://github.com/msantos/srly pkg_srly_fetch = git pkg_srly_repo = https://github.com/msantos/srly pkg_srly_commit = master PACKAGES += sshrpc pkg_sshrpc_name = sshrpc pkg_sshrpc_description = Erlang SSH RPC module (experimental) pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc pkg_sshrpc_fetch = git pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc pkg_sshrpc_commit = master PACKAGES += stable pkg_stable_name = stable pkg_stable_description = Library of assorted helpers for Cowboy web server. pkg_stable_homepage = https://github.com/dvv/stable pkg_stable_fetch = git pkg_stable_repo = https://github.com/dvv/stable pkg_stable_commit = master PACKAGES += statebox pkg_statebox_name = statebox pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak. pkg_statebox_homepage = https://github.com/mochi/statebox pkg_statebox_fetch = git pkg_statebox_repo = https://github.com/mochi/statebox pkg_statebox_commit = master PACKAGES += statman pkg_statman_name = statman pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM pkg_statman_homepage = https://github.com/knutin/statman pkg_statman_fetch = git pkg_statman_repo = https://github.com/knutin/statman pkg_statman_commit = master PACKAGES += statsderl pkg_statsderl_name = statsderl pkg_statsderl_description = StatsD client (erlang) pkg_statsderl_homepage = https://github.com/lpgauth/statsderl pkg_statsderl_fetch = git pkg_statsderl_repo = https://github.com/lpgauth/statsderl pkg_statsderl_commit = master PACKAGES += stdinout_pool pkg_stdinout_pool_name = stdinout_pool pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication. pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool pkg_stdinout_pool_fetch = git pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool pkg_stdinout_pool_commit = master PACKAGES += stockdb pkg_stockdb_name = stockdb pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb pkg_stockdb_fetch = git pkg_stockdb_repo = https://github.com/maxlapshin/stockdb pkg_stockdb_commit = master PACKAGES += subproc pkg_subproc_name = subproc pkg_subproc_description = unix subprocess manager with {active,once|false} modes pkg_subproc_homepage = http://dozzie.jarowit.net/trac/wiki/subproc pkg_subproc_fetch = git pkg_subproc_repo = https://github.com/dozzie/subproc pkg_subproc_commit = v0.1.0 PACKAGES += supervisor3 pkg_supervisor3_name = supervisor3 pkg_supervisor3_description = OTP supervisor with additional strategies pkg_supervisor3_homepage = https://github.com/klarna/supervisor3 pkg_supervisor3_fetch = git pkg_supervisor3_repo = https://github.com/klarna/supervisor3.git pkg_supervisor3_commit = master PACKAGES += swab pkg_swab_name = swab pkg_swab_description = General purpose buffer handling module pkg_swab_homepage = https://github.com/crownedgrouse/swab pkg_swab_fetch = git pkg_swab_repo = https://github.com/crownedgrouse/swab pkg_swab_commit = master PACKAGES += swarm pkg_swarm_name = swarm pkg_swarm_description = Fast and simple acceptor pool for Erlang pkg_swarm_homepage = https://github.com/jeremey/swarm pkg_swarm_fetch = git pkg_swarm_repo = https://github.com/jeremey/swarm pkg_swarm_commit = master PACKAGES += switchboard pkg_switchboard_name = switchboard pkg_switchboard_description = A framework for processing email using worker plugins. pkg_switchboard_homepage = https://github.com/thusfresh/switchboard pkg_switchboard_fetch = git pkg_switchboard_repo = https://github.com/thusfresh/switchboard pkg_switchboard_commit = master PACKAGES += syn pkg_syn_name = syn pkg_syn_description = A global Process Registry and Process Group manager for Erlang. pkg_syn_homepage = https://github.com/ostinelli/syn pkg_syn_fetch = git pkg_syn_repo = https://github.com/ostinelli/syn pkg_syn_commit = master PACKAGES += sync pkg_sync_name = sync pkg_sync_description = On-the-fly recompiling and reloading in Erlang. pkg_sync_homepage = https://github.com/rustyio/sync pkg_sync_fetch = git pkg_sync_repo = https://github.com/rustyio/sync pkg_sync_commit = master PACKAGES += syntaxerl pkg_syntaxerl_name = syntaxerl pkg_syntaxerl_description = Syntax checker for Erlang pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl pkg_syntaxerl_fetch = git pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl pkg_syntaxerl_commit = master PACKAGES += syslog pkg_syslog_name = syslog pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3) pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog pkg_syslog_fetch = git pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog pkg_syslog_commit = master PACKAGES += taskforce pkg_taskforce_name = taskforce pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks. pkg_taskforce_homepage = https://github.com/g-andrade/taskforce pkg_taskforce_fetch = git pkg_taskforce_repo = https://github.com/g-andrade/taskforce pkg_taskforce_commit = master PACKAGES += tddreloader pkg_tddreloader_name = tddreloader pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader pkg_tddreloader_fetch = git pkg_tddreloader_repo = https://github.com/version2beta/tddreloader pkg_tddreloader_commit = master PACKAGES += tempo pkg_tempo_name = tempo pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang. pkg_tempo_homepage = https://github.com/selectel/tempo pkg_tempo_fetch = git pkg_tempo_repo = https://github.com/selectel/tempo pkg_tempo_commit = master PACKAGES += tinymq pkg_tinymq_name = tinymq pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq pkg_tinymq_fetch = git pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq pkg_tinymq_commit = master PACKAGES += tinymt pkg_tinymt_name = tinymt pkg_tinymt_description = TinyMT pseudo random number generator for Erlang. pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang pkg_tinymt_fetch = git pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang pkg_tinymt_commit = master PACKAGES += tirerl pkg_tirerl_name = tirerl pkg_tirerl_description = Erlang interface to Elastic Search pkg_tirerl_homepage = https://github.com/inaka/tirerl pkg_tirerl_fetch = git pkg_tirerl_repo = https://github.com/inaka/tirerl pkg_tirerl_commit = master PACKAGES += toml pkg_toml_name = toml pkg_toml_description = TOML (0.4.0) config parser pkg_toml_homepage = http://dozzie.jarowit.net/trac/wiki/TOML pkg_toml_fetch = git pkg_toml_repo = https://github.com/dozzie/toml pkg_toml_commit = v0.2.0 PACKAGES += traffic_tools pkg_traffic_tools_name = traffic_tools pkg_traffic_tools_description = Simple traffic limiting library pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools pkg_traffic_tools_fetch = git pkg_traffic_tools_repo = https://github.com/systra/traffic_tools pkg_traffic_tools_commit = master PACKAGES += trails pkg_trails_name = trails pkg_trails_description = A couple of improvements over Cowboy Routes pkg_trails_homepage = http://inaka.github.io/cowboy-trails/ pkg_trails_fetch = git pkg_trails_repo = https://github.com/inaka/cowboy-trails pkg_trails_commit = master PACKAGES += trane pkg_trane_name = trane pkg_trane_description = SAX style broken HTML parser in Erlang pkg_trane_homepage = https://github.com/massemanet/trane pkg_trane_fetch = git pkg_trane_repo = https://github.com/massemanet/trane pkg_trane_commit = master PACKAGES += trie pkg_trie_name = trie pkg_trie_description = Erlang Trie Implementation pkg_trie_homepage = https://github.com/okeuday/trie pkg_trie_fetch = git pkg_trie_repo = https://github.com/okeuday/trie pkg_trie_commit = master PACKAGES += triq pkg_triq_name = triq pkg_triq_description = Trifork QuickCheck pkg_triq_homepage = https://triq.gitlab.io pkg_triq_fetch = git pkg_triq_repo = https://gitlab.com/triq/triq.git pkg_triq_commit = master PACKAGES += tunctl pkg_tunctl_name = tunctl pkg_tunctl_description = Erlang TUN/TAP interface pkg_tunctl_homepage = https://github.com/msantos/tunctl pkg_tunctl_fetch = git pkg_tunctl_repo = https://github.com/msantos/tunctl pkg_tunctl_commit = master PACKAGES += unicorn pkg_unicorn_name = unicorn pkg_unicorn_description = Generic configuration server pkg_unicorn_homepage = https://github.com/shizzard/unicorn pkg_unicorn_fetch = git pkg_unicorn_repo = https://github.com/shizzard/unicorn pkg_unicorn_commit = master PACKAGES += unsplit pkg_unsplit_name = unsplit pkg_unsplit_description = Resolves conflicts in Mnesia after network splits pkg_unsplit_homepage = https://github.com/uwiger/unsplit pkg_unsplit_fetch = git pkg_unsplit_repo = https://github.com/uwiger/unsplit pkg_unsplit_commit = master PACKAGES += uuid pkg_uuid_name = uuid pkg_uuid_description = Erlang UUID Implementation pkg_uuid_homepage = https://github.com/okeuday/uuid pkg_uuid_fetch = git pkg_uuid_repo = https://github.com/okeuday/uuid pkg_uuid_commit = master PACKAGES += ux pkg_ux_name = ux pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation) pkg_ux_homepage = https://github.com/erlang-unicode/ux pkg_ux_fetch = git pkg_ux_repo = https://github.com/erlang-unicode/ux pkg_ux_commit = master PACKAGES += verx pkg_verx_name = verx pkg_verx_description = Erlang implementation of the libvirtd remote protocol pkg_verx_homepage = https://github.com/msantos/verx pkg_verx_fetch = git pkg_verx_repo = https://github.com/msantos/verx pkg_verx_commit = master PACKAGES += vmq_bridge pkg_vmq_bridge_name = vmq_bridge pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker pkg_vmq_bridge_homepage = https://verne.mq/ pkg_vmq_bridge_fetch = git pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge pkg_vmq_bridge_commit = master PACKAGES += vmstats pkg_vmstats_name = vmstats pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs. pkg_vmstats_homepage = https://github.com/ferd/vmstats pkg_vmstats_fetch = git pkg_vmstats_repo = https://github.com/ferd/vmstats pkg_vmstats_commit = master PACKAGES += walrus pkg_walrus_name = walrus pkg_walrus_description = Walrus - Mustache-like Templating pkg_walrus_homepage = https://github.com/devinus/walrus pkg_walrus_fetch = git pkg_walrus_repo = https://github.com/devinus/walrus pkg_walrus_commit = master PACKAGES += webmachine pkg_webmachine_name = webmachine pkg_webmachine_description = A REST-based system for building web applications. pkg_webmachine_homepage = https://github.com/basho/webmachine pkg_webmachine_fetch = git pkg_webmachine_repo = https://github.com/basho/webmachine pkg_webmachine_commit = master PACKAGES += websocket_client pkg_websocket_client_name = websocket_client pkg_websocket_client_description = Erlang websocket client (ws and wss supported) pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client pkg_websocket_client_fetch = git pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client pkg_websocket_client_commit = master PACKAGES += worker_pool pkg_worker_pool_name = worker_pool pkg_worker_pool_description = a simple erlang worker pool pkg_worker_pool_homepage = https://github.com/inaka/worker_pool pkg_worker_pool_fetch = git pkg_worker_pool_repo = https://github.com/inaka/worker_pool pkg_worker_pool_commit = main PACKAGES += wrangler pkg_wrangler_name = wrangler pkg_wrangler_description = Import of the Wrangler svn repository. pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html pkg_wrangler_fetch = git pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler pkg_wrangler_commit = master PACKAGES += wsock pkg_wsock_name = wsock pkg_wsock_description = Erlang library to build WebSocket clients and servers pkg_wsock_homepage = https://github.com/madtrick/wsock pkg_wsock_fetch = git pkg_wsock_repo = https://github.com/madtrick/wsock pkg_wsock_commit = master PACKAGES += xhttpc pkg_xhttpc_name = xhttpc pkg_xhttpc_description = Extensible HTTP Client for Erlang pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc pkg_xhttpc_fetch = git pkg_xhttpc_repo = https://github.com/seriyps/xhttpc pkg_xhttpc_commit = master PACKAGES += xref_runner pkg_xref_runner_name = xref_runner pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref) pkg_xref_runner_homepage = https://github.com/inaka/xref_runner pkg_xref_runner_fetch = git pkg_xref_runner_repo = https://github.com/inaka/xref_runner pkg_xref_runner_commit = master PACKAGES += yamerl pkg_yamerl_name = yamerl pkg_yamerl_description = YAML 1.2 parser in pure Erlang pkg_yamerl_homepage = https://github.com/yakaz/yamerl pkg_yamerl_fetch = git pkg_yamerl_repo = https://github.com/yakaz/yamerl pkg_yamerl_commit = master PACKAGES += yamler pkg_yamler_name = yamler pkg_yamler_description = libyaml-based yaml loader for Erlang pkg_yamler_homepage = https://github.com/goertzenator/yamler pkg_yamler_fetch = git pkg_yamler_repo = https://github.com/goertzenator/yamler pkg_yamler_commit = master PACKAGES += yaws pkg_yaws_name = yaws pkg_yaws_description = Yaws webserver pkg_yaws_homepage = http://yaws.hyber.org pkg_yaws_fetch = git pkg_yaws_repo = https://github.com/klacke/yaws pkg_yaws_commit = master PACKAGES += zippers pkg_zippers_name = zippers pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers pkg_zippers_homepage = https://github.com/ferd/zippers pkg_zippers_fetch = git pkg_zippers_repo = https://github.com/ferd/zippers pkg_zippers_commit = master PACKAGES += zlists pkg_zlists_name = zlists pkg_zlists_description = Erlang lazy lists library. pkg_zlists_homepage = https://github.com/vjache/erlang-zlists pkg_zlists_fetch = git pkg_zlists_repo = https://github.com/vjache/erlang-zlists pkg_zlists_commit = master PACKAGES += zucchini pkg_zucchini_name = zucchini pkg_zucchini_description = An Erlang INI parser pkg_zucchini_homepage = https://github.com/devinus/zucchini pkg_zucchini_fetch = git pkg_zucchini_repo = https://github.com/devinus/zucchini pkg_zucchini_commit = master # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: search define pkg_print $(verbose) printf "%s\n" \ $(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \ "App name: $(pkg_$(1)_name)" \ "Description: $(pkg_$(1)_description)" \ "Home page: $(pkg_$(1)_homepage)" \ "Fetch with: $(pkg_$(1)_fetch)" \ "Repository: $(pkg_$(1)_repo)" \ "Commit: $(pkg_$(1)_commit)" \ "" endef search: ifdef q $(foreach p,$(PACKAGES), \ $(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \ $(call pkg_print,$(p)))) else $(foreach p,$(PACKAGES),$(call pkg_print,$(p))) endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-deps clean-tmp-deps.log # Configuration. ifdef OTP_DEPS $(warning The variable OTP_DEPS is deprecated in favor of LOCAL_DEPS.) endif IGNORE_DEPS ?= export IGNORE_DEPS APPS_DIR ?= $(CURDIR)/apps export APPS_DIR DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR # When testing Erlang.mk and updating these, make sure # to delete test/test_rebar_git before running tests again. REBAR3_GIT ?= https://github.com/erlang/rebar3 REBAR3_COMMIT ?= bde4b54248d16280b2c70a244aca3bb7566e2033 # 3.23.0 CACHE_DEPS ?= 0 CACHE_DIR ?= $(if $(XDG_CACHE_HOME),$(XDG_CACHE_HOME),$(HOME)/.cache)/erlang.mk export CACHE_DIR # External "early" plugins (see core/plugins.mk for regular plugins). # They both use the core_dep_plugin macro. define core_dep_plugin ifeq ($(2),$(PROJECT)) -include $$(patsubst $(PROJECT)/%,%,$(1)) else -include $(DEPS_DIR)/$(1) $(DEPS_DIR)/$(1): $(DEPS_DIR)/$(2) ; endif endef DEP_EARLY_PLUGINS ?= $(foreach p,$(DEP_EARLY_PLUGINS),\ $(eval $(if $(findstring /,$p),\ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ $(call core_dep_plugin,$p/early-plugins.mk,$p)))) # Query functions. query_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$(1))) _qfm_dep = $(if $(dep_fetch_$(1)),$(1),$(if $(IS_DEP),legacy,fail)) _qfm_pkg = $(if $(pkg_$(1)_fetch),$(pkg_$(1)_fetch),fail) query_name = $(if $(dep_$(1)),$(1),$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$(1))) query_repo = $(call _qr,$(1),$(call query_fetch_method,$(1))) _qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$(1)),$(call dep_repo,$(1))) query_repo_default = $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo)) query_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$(1))) query_repo_git-subfolder = $(call query_repo_git,$(1)) query_repo_git-submodule = - query_repo_hg = $(call query_repo_default,$(1)) query_repo_svn = $(call query_repo_default,$(1)) query_repo_cp = $(call query_repo_default,$(1)) query_repo_ln = $(call query_repo_default,$(1)) query_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$(1)) query_repo_fail = - query_repo_legacy = - query_version = $(call _qv,$(1),$(call query_fetch_method,$(1))) _qv = $(if $(query_version_$(2)),$(call query_version_$(2),$(1)),$(call dep_commit,$(1))) query_version_default = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit))) query_version_git = $(call query_version_default,$(1)) query_version_git-subfolder = $(call query_version_git,$(1)) query_version_git-submodule = - query_version_hg = $(call query_version_default,$(1)) query_version_svn = - query_version_cp = - query_version_ln = - query_version_hex = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_commit))) query_version_fail = - query_version_legacy = - query_extra = $(call _qe,$(1),$(call query_fetch_method,$(1))) _qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$(1)),-) query_extra_git = - query_extra_git-subfolder = $(if $(dep_$(1)),subfolder=$(word 4,$(dep_$(1))),-) query_extra_git-submodule = - query_extra_hg = - query_extra_svn = - query_extra_cp = - query_extra_ln = - query_extra_hex = $(if $(dep_$(1)),package-name=$(word 3,$(dep_$(1))),-) query_extra_fail = - query_extra_legacy = - query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$(1))) # Deprecated legacy query functions. dep_fetch = $(call query_fetch_method,$(1)) dep_name = $(call query_name,$(1)) dep_repo = $(call query_repo_git,$(1)) dep_commit = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(if $(filter hex,$(word 1,$(dep_$(1)))),$(word 2,$(dep_$(1))),$(word 3,$(dep_$(1)))),$(pkg_$(1)_commit))) LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$(a)),$(APPS_DIR)/$(a))) ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call dep_name,$(dep)))) # When we are calling an app directly we don't want to include it here # otherwise it'll be treated both as an apps and a top-level project. ALL_APPS_DIRS = $(if $(wildcard $(APPS_DIR)/),$(filter-out $(APPS_DIR),$(shell find $(APPS_DIR) -maxdepth 1 -type d))) ifdef ROOT_DIR ifndef IS_APP ALL_APPS_DIRS := $(filter-out $(APPS_DIR)/$(notdir $(CURDIR)),$(ALL_APPS_DIRS)) endif endif ifeq ($(filter $(APPS_DIR) $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) ifeq ($(ERL_LIBS),) ERL_LIBS = $(APPS_DIR):$(DEPS_DIR) else ERL_LIBS := $(ERL_LIBS):$(APPS_DIR):$(DEPS_DIR) endif endif export ERL_LIBS export NO_AUTOPATCH # Verbosity. dep_verbose_0 = @echo " DEP $1 ($(call dep_commit,$1))"; dep_verbose_2 = set -x; dep_verbose = $(dep_verbose_$(V)) # Optimization: don't recompile deps unless truly necessary. ifndef IS_DEP ifneq ($(MAKELEVEL),0) $(shell rm -f ebin/dep_built) endif endif # Core targets. ALL_APPS_DIRS_TO_BUILD = $(if $(LOCAL_DEPS_DIRS)$(IS_APP),$(LOCAL_DEPS_DIRS),$(ALL_APPS_DIRS)) apps:: $(ALL_APPS_DIRS) clean-tmp-deps.log | $(ERLANG_MK_TMP) # Create ebin directory for all apps to make sure Erlang recognizes them # as proper OTP applications when using -include_lib. This is a temporary # fix, a proper fix would be to compile apps/* in the right order. ifndef IS_APP ifneq ($(ALL_APPS_DIRS),) $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ mkdir -p $$dep/ebin; \ done endif endif # At the toplevel: if LOCAL_DEPS is defined with at least one local app, only # compile that list of apps. Otherwise, compile everything. # Within an app: compile all LOCAL_DEPS that are (uncompiled) local apps. ifneq ($(ALL_APPS_DIRS_TO_BUILD),) $(verbose) set -e; for dep in $(ALL_APPS_DIRS_TO_BUILD); do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/apps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/apps.log; \ $(MAKE) -C $$dep $(if $(IS_TEST),test-build-app) IS_APP=1; \ fi \ done endif clean-tmp-deps.log: ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_TMP)/apps.log $(ERLANG_MK_TMP)/deps.log endif # Erlang.mk does not rebuild dependencies after they were compiled # once. If a developer is working on the top-level project and some # dependencies at the same time, he may want to change this behavior. # There are two solutions: # 1. Set `FULL=1` so that all dependencies are visited and # recursively recompiled if necessary. # 2. Set `FORCE_REBUILD=` to the specific list of dependencies that # should be recompiled (instead of the whole set). FORCE_REBUILD ?= ifeq ($(origin FULL),undefined) ifneq ($(strip $(force_rebuild_dep)$(FORCE_REBUILD)),) define force_rebuild_dep echo "$(FORCE_REBUILD)" | grep -qw "$$(basename "$1")" endef endif endif ifneq ($(SKIP_DEPS),) deps:: else deps:: $(ALL_DEPS_DIRS) apps clean-tmp-deps.log | $(ERLANG_MK_TMP) ifneq ($(ALL_DEPS_DIRS),) $(verbose) set -e; for dep in $(ALL_DEPS_DIRS); do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \ if [ -z "$(strip $(FULL))" ] $(if $(force_rebuild_dep),&& ! ($(call force_rebuild_dep,$$dep)),) && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \ :; \ elif [ "$$dep" = "$(DEPS_DIR)/hut" -a "$(HUT_PATCH)" ]; then \ $(MAKE) -C $$dep app IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \ elif [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \ else \ echo "Error: No Makefile to build dependency $$dep." >&2; \ exit 2; \ fi \ fi \ done endif endif # Deps related targets. # @todo rename GNUmakefile and makefile into Makefile first, if they exist # While Makefile file could be GNUmakefile or makefile, # in practice only Makefile is needed so far. define dep_autopatch if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \ rm -rf $(DEPS_DIR)/$1/ebin/; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ $(call dep_autopatch_erlang_mk,$(1)); \ elif [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \ $(call dep_autopatch2,$1); \ elif [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \ $(call dep_autopatch2,$(1)); \ elif [ 0 != `grep -ci "^[^#].*rebar" $(DEPS_DIR)/$(1)/Makefile` ]; then \ $(call dep_autopatch2,$(1)); \ elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;`" ]; then \ $(call dep_autopatch2,$(1)); \ fi \ else \ if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \ $(call dep_autopatch_noop,$(1)); \ else \ $(call dep_autopatch2,$(1)); \ fi \ fi endef define dep_autopatch2 ! test -f $(DEPS_DIR)/$1/ebin/$1.app || \ mv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \ rm -f $(DEPS_DIR)/$1/ebin/$1.app; \ if [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \ $(call erlang,$(call dep_autopatch_appsrc_script.erl,$(1))); \ fi; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ if [ -f $(DEPS_DIR)/$(1)/rebar -o -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \ $(call dep_autopatch_fetch_rebar); \ $(call dep_autopatch_rebar,$(1)); \ else \ $(call dep_autopatch_gen,$(1)); \ fi endef define dep_autopatch_noop printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile endef # Replace "include erlang.mk" with a line that will load the parent Erlang.mk # if given. Do it for all 3 possible Makefile file names. ifeq ($(NO_AUTOPATCH_ERLANG_MK),) define dep_autopatch_erlang_mk for f in Makefile makefile GNUmakefile; do \ if [ -f $(DEPS_DIR)/$1/$$f ]; then \ sed -i.bak s/'include *erlang.mk'/'include $$(if $$(ERLANG_MK_FILENAME),$$(ERLANG_MK_FILENAME),erlang.mk)'/ $(DEPS_DIR)/$1/$$f; \ fi \ done endef else define dep_autopatch_erlang_mk : endef endif define dep_autopatch_gen printf "%s\n" \ "ERLC_OPTS = +debug_info" \ "include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile endef # We use flock/lockf when available to avoid concurrency issues. define dep_autopatch_fetch_rebar if command -v flock >/dev/null; then \ flock $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \ elif command -v lockf >/dev/null; then \ lockf $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \ else \ $(call dep_autopatch_fetch_rebar2); \ fi endef define dep_autopatch_fetch_rebar2 if [ ! -d $(ERLANG_MK_TMP)/rebar3 ]; then \ git clone -q -n -- $(REBAR3_GIT) $(ERLANG_MK_TMP)/rebar3; \ cd $(ERLANG_MK_TMP)/rebar3; \ git checkout -q $(REBAR3_COMMIT); \ ./bootstrap; \ cd -; \ fi endef define dep_autopatch_rebar if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \ fi; \ $(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \ rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app endef define dep_autopatch_rebar.erl application:load(rebar), application:set_env(rebar, log_level, debug), {module, rebar3} = c:l(rebar3), Conf1 = case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)") of {ok, Conf0} -> Conf0; _ -> [] end, {Conf, OsEnv} = fun() -> case filelib:is_file("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)") of false -> {Conf1, []}; true -> Bindings0 = erl_eval:new_bindings(), Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0), Bindings = erl_eval:add_binding('SCRIPT', "$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings1), Before = os:getenv(), {ok, Conf2} = file:script("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings), {Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)} end end(), Write = fun (Text) -> file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append]) end, Escape = fun (Text) -> re:replace(Text, "\\\\$$", "\$$$$", [global, {return, list}]) end, Write("IGNORE_DEPS += edown eper eunit_formatters meck node_package " "rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"), Write("C_SRC_DIR = /path/do/not/exist\n"), Write("C_SRC_TYPE = rebar\n"), Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"), Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]), ToList = fun (V) when is_atom(V) -> atom_to_list(V); (V) when is_list(V) -> "'\\"" ++ V ++ "\\"'" end, fun() -> Write("ERLC_OPTS = +debug_info\n"), case lists:keyfind(erl_opts, 1, Conf) of false -> ok; {_, ErlOpts} -> lists:foreach(fun ({d, D}) -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); ({d, DKey, DVal}) -> Write("ERLC_OPTS += -D" ++ ToList(DKey) ++ "=" ++ ToList(DVal) ++ "\n"); ({i, I}) -> Write(["ERLC_OPTS += -I ", I, "\n"]); ({platform_define, Regex, D}) -> case rebar_utils:is_arch(Regex) of true -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); false -> ok end; ({parse_transform, PT}) -> Write("ERLC_OPTS += +'{parse_transform, " ++ ToList(PT) ++ "}'\n"); (_) -> ok end, ErlOpts) end, Write("\n") end(), GetHexVsn2 = fun(N, NP) -> case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of {ok, Lock} -> io:format("~p~n", [Lock]), LockPkgs = case lists:keyfind("1.2.0", 1, Lock) of {_, LP} -> LP; _ -> case lists:keyfind("1.1.0", 1, Lock) of {_, LP} -> LP; _ -> false end end, if is_list(LockPkgs) -> io:format("~p~n", [LockPkgs]), case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of {_, {pkg, _, Vsn}, _} -> io:format("~p~n", [Vsn]), {N, {hex, NP, binary_to_list(Vsn)}}; _ -> false end; true -> false end; _ -> false end end, GetHexVsn3Common = fun(N, NP, S0) -> case GetHexVsn2(N, NP) of false -> S2 = case S0 of " " ++ S1 -> S1; _ -> S0 end, S = case length([ok || $$. <- S2]) of 0 -> S2 ++ ".0.0"; 1 -> S2 ++ ".0"; _ -> S2 end, {N, {hex, NP, S}}; NameSource -> NameSource end end, GetHexVsn3 = fun (N, NP, "~>" ++ S0) -> GetHexVsn3Common(N, NP, S0); (N, NP, ">=" ++ S0) -> GetHexVsn3Common(N, NP, S0); (N, NP, S) -> {N, {hex, NP, S}} end, fun() -> File = case lists:keyfind(deps, 1, Conf) of false -> []; {_, Deps} -> [begin case case Dep of N when is_atom(N) -> GetHexVsn2(N, N); {N, S} when is_atom(N), is_list(S) -> GetHexVsn3(N, N, S); {N, {pkg, NP}} when is_atom(N) -> GetHexVsn2(N, NP); {N, S, {pkg, NP}} -> GetHexVsn3(N, NP, S); {N, S} when is_tuple(S) -> {N, S}; {N, _, S} -> {N, S}; {N, _, S, _} -> {N, S}; _ -> false end of false -> ok; {Name, Source} -> {Method, Repo, Commit} = case Source of {hex, NPV, V} -> {hex, V, NPV}; {git, R} -> {git, R, master}; {M, R, {branch, C}} -> {M, R, C}; {M, R, {ref, C}} -> {M, R, C}; {M, R, {tag, C}} -> {M, R, C}; {M, R, C} -> {M, R, C} end, Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit])) end end || Dep <- Deps] end end(), fun() -> case lists:keyfind(erl_first_files, 1, Conf) of false -> ok; {_, Files0} -> Files = [begin hd(filelib:wildcard("$(call core_native_path,$(DEPS_DIR)/$1/src/)**/" ++ filename:rootname(F) ++ ".*rl")) end || "src/" ++ F <- Files0], Names = [[" ", case lists:reverse(F) of "lre." ++ Elif -> lists:reverse(Elif); "lrx." ++ Elif -> lists:reverse(Elif); "lry." ++ Elif -> lists:reverse(Elif); Elif -> lists:reverse(Elif) end] || "$(call core_native_path,$(DEPS_DIR)/$1/src/)" ++ F <- Files], Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names])) end end(), Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"), Write("\npreprocess::\n"), Write("\npre-deps::\n"), Write("\npre-app::\n"), PatchHook = fun(Cmd) -> Cmd2 = re:replace(Cmd, "^([g]?make)(.*)( -C.*)", "\\\\1\\\\3\\\\2", [{return, list}]), case Cmd2 of "make -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); "gmake -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); "make " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); "gmake " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); _ -> Escape(Cmd) end end, fun() -> case lists:keyfind(pre_hooks, 1, Conf) of false -> ok; {_, Hooks} -> [case H of {'get-deps', Cmd} -> Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n"); {compile, Cmd} -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {{pc, compile}, Cmd} -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {Regex, compile, Cmd} -> case rebar_utils:is_arch(Regex) of true -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); false -> ok end; _ -> ok end || H <- Hooks] end end(), ShellToMk = fun(V0) -> V1 = re:replace(V0, "[$$][(]", "$$\(shell ", [global]), V = re:replace(V1, "([$$])(?![(])(\\\\w*)", "\\\\1(\\\\2)", [global]), re:replace(V, "-Werror\\\\b", "", [{return, list}, global]) end, PortSpecs = fun() -> case lists:keyfind(port_specs, 1, Conf) of false -> case filelib:is_dir("$(call core_native_path,$(DEPS_DIR)/$1/c_src)") of false -> []; true -> [{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"), proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}] end; {_, Specs} -> lists:flatten([case S of {Output, Input} -> {ShellToMk(Output), Input, []}; {Regex, Output, Input} -> case rebar_utils:is_arch(Regex) of true -> {ShellToMk(Output), Input, []}; false -> [] end; {Regex, Output, Input, [{env, Env}]} -> case rebar_utils:is_arch(Regex) of true -> {ShellToMk(Output), Input, Env}; false -> [] end end || S <- Specs]) end end(), PortSpecWrite = fun (Text) -> file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/c_src/Makefile.erlang.mk)", Text, [append]) end, case PortSpecs of [] -> ok; _ -> Write("\npre-app::\n\t@$$\(MAKE) --no-print-directory -f c_src/Makefile.erlang.mk\n"), PortSpecWrite(io_lib:format("ERL_CFLAGS ?= -finline-functions -Wall -fPIC -I \\"~s/erts-~s/include\\" -I \\"~s\\"\n", [code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])), PortSpecWrite(io_lib:format("ERL_LDFLAGS ?= -L \\"~s\\" -lei\n", [code:lib_dir(erl_interface, lib)])), [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv], FilterEnv = fun(Env) -> lists:flatten([case E of {_, _} -> E; {Regex, K, V} -> case rebar_utils:is_arch(Regex) of true -> {K, V}; false -> [] end end || E <- Env]) end, MergeEnv = fun(Env) -> lists:foldl(fun ({K, V}, Acc) -> case lists:keyfind(K, 1, Acc) of false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc]; {_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc] end end, [], Env) end, PortEnv = case lists:keyfind(port_env, 1, Conf) of false -> []; {_, PortEnv0} -> FilterEnv(PortEnv0) end, PortSpec = fun ({Output, Input0, Env}) -> filelib:ensure_dir("$(call core_native_path,$(DEPS_DIR)/$1/)" ++ Output), Input = [[" ", I] || I <- Input0], PortSpecWrite([ [["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))], case $(PLATFORM) of darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress"; _ -> "" end, "\n\nall:: ", Output, "\n\t@:\n\n", "%.o: %.c\n\t$$\(CC) -c -o $$\@ $$\< $$\(CFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.C\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cc\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cpp\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", [[Output, ": ", K, " += ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))], Output, ": $$\(foreach ext,.c .C .cc .cpp,", "$$\(patsubst %$$\(ext),%.o,$$\(filter %$$\(ext),$$\(wildcard", Input, "))))\n", "\t$$\(CC) -o $$\@ $$\? $$\(LDFLAGS) $$\(ERL_LDFLAGS) $$\(DRV_LDFLAGS) $$\(LDLIBS) $$\(EXE_LDFLAGS)", case {filename:extension(Output), $(PLATFORM)} of {[], _} -> "\n"; {".so", darwin} -> " -shared\n"; {".dylib", darwin} -> " -shared\n"; {_, darwin} -> "\n"; _ -> " -shared\n" end]) end, [PortSpec(S) || S <- PortSpecs] end, fun() -> case lists:keyfind(plugins, 1, Conf) of false -> ok; {_, Plugins0} -> Plugins = [P || P <- Plugins0, is_tuple(P)], case lists:keyfind('lfe-compile', 1, Plugins) of false -> ok; _ -> Write("\nBUILD_DEPS = lfe lfe.mk\ndep_lfe.mk = git https://github.com/ninenines/lfe.mk master\nDEP_PLUGINS = lfe.mk\n") end end end(), Write("\ninclude $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"), RunPlugin = fun(Plugin, Step) -> case erlang:function_exported(Plugin, Step, 2) of false -> ok; true -> c:cd("$(call core_native_path,$(DEPS_DIR)/$1/)"), Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(), dict:store(base_dir, "", dict:new())}, undefined), io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret]) end end, fun() -> case lists:keyfind(plugins, 1, Conf) of false -> ok; {_, Plugins0} -> Plugins = [P || P <- Plugins0, is_atom(P)], [begin case lists:keyfind(deps, 1, Conf) of false -> ok; {_, Deps} -> case lists:keyfind(P, 1, Deps) of false -> ok; _ -> Path = "$(call core_native_path,$(DEPS_DIR)/)" ++ atom_to_list(P), io:format("~s", [os:cmd("$(MAKE) -C $(call core_native_path,$(DEPS_DIR)/$1) " ++ Path)]), io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]), code:add_patha(Path ++ "/ebin") end end end || P <- Plugins], [case code:load_file(P) of {module, P} -> ok; _ -> case lists:keyfind(plugin_dir, 1, Conf) of false -> ok; {_, PluginsDir} -> ErlFile = "$(call core_native_path,$(DEPS_DIR)/$1/)" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl", {ok, P, Bin} = compile:file(ErlFile, [binary]), {module, P} = code:load_binary(P, ErlFile, Bin) end end || P <- Plugins], [RunPlugin(P, preprocess) || P <- Plugins], [RunPlugin(P, pre_compile) || P <- Plugins], [RunPlugin(P, compile) || P <- Plugins] end end(), halt() endef define dep_autopatch_appsrc_script.erl AppSrc = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", AppSrcScript = AppSrc ++ ".script", Conf1 = case file:consult(AppSrc) of {ok, Conf0} -> Conf0; {error, enoent} -> [] end, Bindings0 = erl_eval:new_bindings(), Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0), Bindings = erl_eval:add_binding('SCRIPT', AppSrcScript, Bindings1), Conf = case file:script(AppSrcScript, Bindings) of {ok, [C]} -> C; {ok, C} -> C end, ok = file:write_file(AppSrc, io_lib:format("~p.~n", [Conf])), halt() endef define dep_autopatch_appsrc.erl AppSrcOut = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)"; true -> AppSrcOut end, case filelib:is_regular(AppSrcIn) of false -> ok; true -> {ok, [{application, $(1), L0}]} = file:consult(AppSrcIn), L1 = lists:keystore(modules, 1, L0, {modules, []}), L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, lists:droplast(os:cmd("git -C $(DEPS_DIR)/$1 describe --dirty --tags --always"))}); {_, {cmd, _}} -> lists:keyreplace(vsn, 1, L1, {vsn, "cmd"}); _ -> L1 end, L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end, ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])), case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end end, halt() endef ifeq ($(CACHE_DEPS),1) define dep_cache_fetch_git mkdir -p $(CACHE_DIR)/git; \ if test -d "$(join $(CACHE_DIR)/git/,$(call dep_name,$1))"; then \ cd $(join $(CACHE_DIR)/git/,$(call dep_name,$1)); \ if ! git checkout -q $(call dep_commit,$1); then \ git remote set-url origin $(call dep_repo,$1) && \ git pull --all && \ git cat-file -e $(call dep_commit,$1) 2>/dev/null; \ fi; \ else \ git clone -q -n -- $(call dep_repo,$1) $(join $(CACHE_DIR)/git/,$(call dep_name,$1)); \ fi; \ git clone -q --branch $(call dep_commit,$1) --single-branch -- $(join $(CACHE_DIR)/git/,$(call dep_name,$1)) $2 endef define dep_fetch_git $(call dep_cache_fetch_git,$1,$(DEPS_DIR)/$(call dep_name,$1)); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ $(call dep_cache_fetch_git,$1,$(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)); \ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$1)) \ $(DEPS_DIR)/$(call dep_name,$1); endef else define dep_fetch_git git clone -q -n -- $(call dep_repo,$1) $(DEPS_DIR)/$(call dep_name,$1); \ cd $(DEPS_DIR)/$(call dep_name,$1) && git checkout -q $(call dep_commit,$1); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ git clone -q -n -- $(call dep_repo,$1) \ $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1); \ cd $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1) \ && git checkout -q $(call dep_commit,$1); \ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$1)) \ $(DEPS_DIR)/$(call dep_name,$1); endef endif define dep_fetch_git-submodule git submodule update --init -- $(DEPS_DIR)/$1; endef define dep_fetch_hg hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \ cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1)); endef define dep_fetch_svn svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); endef define dep_fetch_cp cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); endef define dep_fetch_ln ln -s $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); endef ifeq ($(CACHE_DEPS),1) # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(CACHE_DIR)/hex $(DEPS_DIR)/$1; \ $(eval hex_tar_name=$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar) \ $(if $(wildcard $(CACHE_DIR)/hex/$(hex_tar_name)),,$(call core_http_get,$(CACHE_DIR)/hex/$(hex_tar_name),\ https://repo.hex.pm/tarballs/$(hex_tar_name);)) \ tar -xOf $(CACHE_DIR)/hex/$(hex_tar_name) contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef else # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \ $(call core_http_get,$(ERLANG_MK_TMP)/hex/$1.tar,\ https://repo.hex.pm/tarballs/$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar); \ tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef endif define dep_fetch_fail echo "Error: Unknown or invalid dependency: $(1)." >&2; \ exit 78; endef # Kept for compatibility purposes with older Erlang.mk configuration. define dep_fetch_legacy $(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \ git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \ cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master); endef define dep_target $(DEPS_DIR)/$(call dep_name,$1): | $(ERLANG_MK_TMP) $(eval DEP_NAME := $(call dep_name,$1)) $(eval DEP_STR := $(if $(filter $1,$(DEP_NAME)),$1,"$1 ($(DEP_NAME))")) $(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \ echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)." >&2; \ exit 17; \ fi $(verbose) mkdir -p $(DEPS_DIR) $(dep_verbose) $(call dep_fetch_$(strip $(call dep_fetch,$(1))),$(1)) $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ] \ && [ ! -f $(DEPS_DIR)/$(1)/configure ]; then \ echo " AUTO " $(DEP_STR); \ cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \ fi - $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \ echo " CONF " $(DEP_STR); \ cd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \ fi ifeq ($(filter $(1),$(NO_AUTOPATCH)),) $(verbose) $$(MAKE) --no-print-directory autopatch-$(DEP_NAME) endif .PHONY: autopatch-$(call dep_name,$1) autopatch-$(call dep_name,$1):: $(verbose) if [ "$1" = "elixir" -a "$(ELIXIR_PATCH)" ]; then \ ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/; \ else \ $$(call dep_autopatch,$(call dep_name,$1)) \ fi endef $(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep)))) ifndef IS_APP clean:: clean-apps clean-apps: $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ $(MAKE) -C $$dep clean IS_APP=1; \ done distclean:: distclean-apps distclean-apps: $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ $(MAKE) -C $$dep distclean IS_APP=1; \ done endif ifndef SKIP_DEPS distclean:: distclean-deps distclean-deps: $(gen_verbose) rm -rf $(DEPS_DIR) endif ifeq ($(CACHE_DEPS),1) cacheclean:: cacheclean-git cacheclean-hex cacheclean-git: $(gen_verbose) rm -rf $(CACHE_DIR)/git cacheclean-hex: $(gen_verbose) rm -rf $(CACHE_DIR)/hex endif # Forward-declare variables used in core/deps-tools.mk. This is required # in case plugins use them. ERLANG_MK_RECURSIVE_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-deps-list.log ERLANG_MK_RECURSIVE_DOC_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-doc-deps-list.log ERLANG_MK_RECURSIVE_REL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-rel-deps-list.log ERLANG_MK_RECURSIVE_TEST_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-test-deps-list.log ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-shell-deps-list.log ERLANG_MK_QUERY_DEPS_FILE = $(ERLANG_MK_TMP)/query-deps.log ERLANG_MK_QUERY_DOC_DEPS_FILE = $(ERLANG_MK_TMP)/query-doc-deps.log ERLANG_MK_QUERY_REL_DEPS_FILE = $(ERLANG_MK_TMP)/query-rel-deps.log ERLANG_MK_QUERY_TEST_DEPS_FILE = $(ERLANG_MK_TMP)/query-test-deps.log ERLANG_MK_QUERY_SHELL_DEPS_FILE = $(ERLANG_MK_TMP)/query-shell-deps.log # Copyright (c) 2024, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: beam-cache-restore-app beam-cache-restore-test clean-beam-cache distclean-beam-cache BEAM_CACHE_DIR ?= $(ERLANG_MK_TMP)/beam-cache PROJECT_BEAM_CACHE_DIR = $(BEAM_CACHE_DIR)/$(PROJECT) clean:: clean-beam-cache clean-beam-cache: $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR) distclean:: distclean-beam-cache $(PROJECT_BEAM_CACHE_DIR): $(verbose) mkdir -p $(PROJECT_BEAM_CACHE_DIR) distclean-beam-cache: $(gen_verbose) rm -rf $(BEAM_CACHE_DIR) beam-cache-restore-app: | $(PROJECT_BEAM_CACHE_DIR) $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-test ifneq ($(wildcard ebin/),) $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-test endif ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-app),) $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-app ebin/ else $(verbose) $(MAKE) --no-print-directory clean-app endif beam-cache-restore-test: | $(PROJECT_BEAM_CACHE_DIR) $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-app ifneq ($(wildcard ebin/),) $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-app endif ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-test),) $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-test ebin/ else $(verbose) $(MAKE) --no-print-directory clean-app endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-app # Configuration. ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \ +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec COMPILE_FIRST ?= COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) ERLC_EXCLUDE ?= ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE))) ERLC_ASN1_OPTS ?= ERLC_MIB_OPTS ?= COMPILE_MIB_FIRST ?= COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST))) # Verbosity. app_verbose_0 = @echo " APP " $(PROJECT); app_verbose_2 = set -x; app_verbose = $(app_verbose_$(V)) appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose_2 = set -x; appsrc_verbose = $(appsrc_verbose_$(V)) makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d; makedep_verbose_2 = set -x; makedep_verbose = $(makedep_verbose_$(V)) erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\ $(filter %.erl %.core,$(?F))); erlc_verbose_2 = set -x; erlc_verbose = $(erlc_verbose_$(V)) xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); xyrl_verbose_2 = set -x; xyrl_verbose = $(xyrl_verbose_$(V)) asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F)); asn1_verbose_2 = set -x; asn1_verbose = $(asn1_verbose_$(V)) mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F)); mib_verbose_2 = set -x; mib_verbose = $(mib_verbose_$(V)) ifneq ($(wildcard src/),) # Targets. app:: $(if $(wildcard ebin/test),beam-cache-restore-app) deps $(verbose) $(MAKE) --no-print-directory $(PROJECT).d $(verbose) $(MAKE) --no-print-directory app-build ifeq ($(wildcard src/$(PROJECT_MOD).erl),) define app_file {application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), {id$(comma)$(space)"$(1)"}$(comma)) {modules, [$(call comma_list,$(2))]}, {registered, []}, {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]}, {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef else define app_file {application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), {id$(comma)$(space)"$(1)"}$(comma)) {modules, [$(call comma_list,$(2))]}, {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]}, {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]}, {mod, {$(PROJECT_MOD), []}}, {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef endif app-build: ebin/$(PROJECT).app $(verbose) : # Source files. ALL_SRC_FILES := $(sort $(call core_find,src/,*)) ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES)) CORE_FILES := $(filter %.core,$(ALL_SRC_FILES)) # ASN.1 files. ifneq ($(wildcard asn1/),) ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1)) ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) define compile_asn1 $(verbose) mkdir -p include/ $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $(1) $(verbose) mv asn1/*.erl src/ -$(verbose) mv asn1/*.hrl include/ $(verbose) mv asn1/*.asn1db include/ endef $(PROJECT).d:: $(ASN1_FILES) $(if $(strip $?),$(call compile_asn1,$?)) endif # SNMP MIB files. ifneq ($(wildcard mibs/),) MIB_FILES = $(sort $(call core_find,mibs/,*.mib)) $(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES) $(verbose) mkdir -p include/ priv/mibs/ $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $? $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?))) endif # Leex and Yecc files. XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES)) XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES)))) ERL_FILES += $(XRL_ERL_FILES) YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES)) YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES)))) ERL_FILES += $(YRL_ERL_FILES) $(PROJECT).d:: $(XRL_FILES) $(YRL_FILES) $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?) # Erlang and Core Erlang files. define makedep.erl E = ets:new(makedep, [bag]), G = digraph:new([acyclic]), ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")), DepsDir = "$(call core_native_path,$(DEPS_DIR))", AppsDir = "$(call core_native_path,$(APPS_DIR))", DepsDirsSrc = "$(if $(wildcard $(DEPS_DIR)/*/src), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/src)))", DepsDirsInc = "$(if $(wildcard $(DEPS_DIR)/*/include), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/include)))", AppsDirsSrc = "$(if $(wildcard $(APPS_DIR)/*/src), $(call core_native_path,$(wildcard $(APPS_DIR)/*/src)))", AppsDirsInc = "$(if $(wildcard $(APPS_DIR)/*/include), $(call core_native_path,$(wildcard $(APPS_DIR)/*/include)))", DepsDirs = lists:usort(string:tokens(DepsDirsSrc++DepsDirsInc, " ")), AppsDirs = lists:usort(string:tokens(AppsDirsSrc++AppsDirsInc, " ")), Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles], Add = fun (Mod, Dep) -> case lists:keyfind(Dep, 1, Modules) of false -> ok; {_, DepFile} -> {_, ModFile} = lists:keyfind(Mod, 1, Modules), ets:insert(E, {ModFile, DepFile}), digraph:add_vertex(G, Mod), digraph:add_vertex(G, Dep), digraph:add_edge(G, Mod, Dep) end end, AddHd = fun (F, Mod, DepFile) -> case file:open(DepFile, [read]) of {error, enoent} -> ok; {ok, Fd} -> {_, ModFile} = lists:keyfind(Mod, 1, Modules), case ets:match(E, {ModFile, DepFile}) of [] -> ets:insert(E, {ModFile, DepFile}), F(F, Fd, Mod,0); _ -> ok end end end, SearchHrl = fun F(_Hrl, []) -> {error,enoent}; F(Hrl, [Dir|Dirs]) -> HrlF = filename:join([Dir,Hrl]), case filelib:is_file(HrlF) of true -> {ok, HrlF}; false -> F(Hrl,Dirs) end end, Attr = fun (_F, Mod, behavior, Dep) -> Add(Mod, Dep); (_F, Mod, behaviour, Dep) -> Add(Mod, Dep); (_F, Mod, compile, {parse_transform, Dep}) -> Add(Mod, Dep); (_F, Mod, compile, Opts) when is_list(Opts) -> case proplists:get_value(parse_transform, Opts) of undefined -> ok; Dep -> Add(Mod, Dep) end; (F, Mod, include, Hrl) -> case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl); {error, _} -> false end; (F, Mod, include_lib, Hrl) -> case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl); {error, _} -> false end; (F, Mod, import, {Imp, _}) -> IsFile = case lists:keyfind(Imp, 1, Modules) of false -> false; {_, FilePath} -> filelib:is_file(FilePath) end, case IsFile of false -> ok; true -> Add(Mod, Imp) end; (_, _, _, _) -> ok end, MakeDepend = fun (F, Fd, Mod, StartLocation) -> case io:parse_erl_form(Fd, undefined, StartLocation) of {ok, AbsData, EndLocation} -> case AbsData of {attribute, _, Key, Value} -> Attr(F, Mod, Key, Value), F(F, Fd, Mod, EndLocation); _ -> F(F, Fd, Mod, EndLocation) end; {eof, _ } -> file:close(Fd); {error, ErrorDescription } -> file:close(Fd); {error, ErrorInfo, ErrorLocation} -> F(F, Fd, Mod, ErrorLocation) end, ok end, [begin Mod = list_to_atom(filename:basename(F, ".erl")), case file:open(F, [read]) of {ok, Fd} -> MakeDepend(MakeDepend, Fd, Mod,0); {error, enoent} -> ok end end || F <- ErlFiles], Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))), CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)], TargetPath = fun(Target) -> case lists:keyfind(Target, 1, Modules) of false -> ""; {_, DepFile} -> DirSubname = tl(string:tokens(filename:dirname(DepFile), "/")), string:join(DirSubname ++ [atom_to_list(Target)], "/") end end, Output0 = [ "# Generated by Erlang.mk. Edit at your own risk!\n\n", [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend], "\nCOMPILE_FIRST +=", [[" ", TargetPath(CF)] || CF <- CompileFirst], "\n" ], Output = case "é" of [233] -> unicode:characters_to_binary(Output0); _ -> Output0 end, ok = file:write_file("$(1)", Output), halt() endef ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),) $(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST) $(makedep_verbose) $(call erlang,$(call makedep.erl,$@)) endif ifeq ($(IS_APP)$(IS_DEP),) ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0) # Rebuild everything when the Makefile changes. $(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \ touch -c $(PROJECT).d; \ fi $(verbose) touch $@ $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change endif endif $(PROJECT).d:: $(verbose) : include $(wildcard $(PROJECT).d) ebin/$(PROJECT).app:: ebin/ ebin/: $(verbose) mkdir -p ebin/ define compile_erl $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \ -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1)) endef define validate_app_file case file:consult("ebin/$(PROJECT).app") of {ok, _} -> halt(); _ -> halt(1) end endef ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?)) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE))) # Older git versions do not have the --first-parent flag. Do without in that case. $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null \ || git describe --dirty --abbrev=7 --tags --always 2>/dev/null || true)) $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES))))))) ifeq ($(wildcard src/$(PROJECT).app.src),) $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \ > ebin/$(PROJECT).app $(verbose) if ! $(call erlang,$(call validate_app_file)); then \ echo "The .app file produced is invalid. Please verify the value of PROJECT_ENV." >&2; \ exit 1; \ fi else $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \ echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk documentation for instructions." >&2; \ exit 1; \ fi $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \ | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \ > ebin/$(PROJECT).app endif ifneq ($(wildcard src/$(PROJECT).appup),) $(verbose) cp src/$(PROJECT).appup ebin/ endif clean:: clean-app clean-app: $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \ $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \ $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \ $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \ $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) endif # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: docs-deps # Configuration. ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS)) # Targets. $(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) doc-deps: else doc-deps: $(ALL_DOC_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rel-deps # Configuration. ALL_REL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(REL_DEPS)) # Targets. $(foreach dep,$(REL_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) rel-deps: else rel-deps: $(ALL_REL_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: test-deps test-dir test-build clean-test-dir # Configuration. TEST_DIR ?= $(CURDIR)/test ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard TEST_ERLC_OPTS += -DTEST=1 # Targets. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) test-deps: else test-deps: $(ALL_TEST_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do \ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \ :; \ else \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \ fi \ done endif ifneq ($(wildcard $(TEST_DIR)),) test-dir: $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build @: test_erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\ $(filter %.erl %.core,$(notdir $(FILES_TO_COMPILE)))); test_erlc_verbose_2 = set -x; test_erlc_verbose = $(test_erlc_verbose_$(V)) define compile_test_erl $(test_erlc_verbose) erlc -v $(TEST_ERLC_OPTS) -o $(TEST_DIR) \ -pa ebin/ -I include/ $(1) endef ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl) $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST) # When we have to recompile files in src/ the .d file always gets rebuilt. # Therefore we want to ignore it when rebuilding test files. $(eval FILES_TO_COMPILE := $(if $(filter $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)),$?),$(filter $(ERL_TEST_FILES),$^),$(filter $(ERL_TEST_FILES),$?))) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@) endif test-build:: IS_TEST=1 test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) test-build:: $(if $(wildcard src),$(if $(wildcard ebin/test),,beam-cache-restore-test)) $(if $(IS_APP),,deps test-deps) # We already compiled everything when IS_APP=1. ifndef IS_APP ifneq ($(wildcard src),) $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(gen_verbose) touch ebin/test endif ifneq ($(wildcard $(TEST_DIR)),) $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" endif endif # Roughly the same as test-build, but when IS_APP=1. # We only care about compiling the current application. ifdef IS_APP test-build-app:: ERLC_OPTS=$(TEST_ERLC_OPTS) test-build-app:: deps test-deps ifneq ($(wildcard src),) $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(gen_verbose) touch ebin/test endif ifneq ($(wildcard $(TEST_DIR)),) $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" endif endif clean:: clean-test-dir clean-test-dir: ifneq ($(wildcard $(TEST_DIR)/*.beam),) $(gen_verbose) rm -f $(TEST_DIR)/*.beam $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rebar.config # We strip out -Werror because we don't want to fail due to # warnings when used as a dependency. compat_prepare_erlc_opts = $(shell echo "$1" | sed 's/, */,/g') define compat_convert_erlc_opts $(if $(filter-out -Werror,$1),\ $(if $(findstring +,$1),\ $(shell echo $1 | cut -b 2-))) endef define compat_erlc_opts_to_list [$(call comma_list,$(foreach o,$(call compat_prepare_erlc_opts,$1),$(call compat_convert_erlc_opts,$o)))] endef define compat_rebar_config {deps, [ $(call comma_list,$(foreach d,$(DEPS),\ $(if $(filter hex,$(call dep_fetch,$d)),\ {$(call dep_name,$d)$(comma)"$(call dep_repo,$d)"},\ {$(call dep_name,$d)$(comma)".*"$(comma){git,"$(call dep_repo,$d)"$(comma)"$(call dep_commit,$d)"}}))) ]}. {erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}. endef rebar.config: $(gen_verbose) $(call core_render,compat_rebar_config,rebar.config) # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck) .PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual # Core targets. docs:: asciidoc distclean:: distclean-asciidoc-guide distclean-asciidoc-manual # Plugin-specific targets. asciidoc: asciidoc-guide asciidoc-manual # User guide. ifeq ($(wildcard doc/src/guide/book.asciidoc),) asciidoc-guide: else asciidoc-guide: distclean-asciidoc-guide doc-deps a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/ distclean-asciidoc-guide: $(gen_verbose) rm -rf doc/html/ doc/guide.pdf endif # Man pages. ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc) ifeq ($(ASCIIDOC_MANUAL_FILES),) asciidoc-manual: else # Configuration. MAN_INSTALL_PATH ?= /usr/local/share/man MAN_SECTIONS ?= 3 7 MAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\U&\E/') MAN_VERSION ?= $(PROJECT_VERSION) # Plugin-specific targets. define asciidoc2man.erl try [begin io:format(" ADOC ~s~n", [F]), ok = asciideck:to_manpage(asciideck:parse_file(F), #{ compress => gzip, outdir => filename:dirname(F), extra2 => "$(MAN_PROJECT) $(MAN_VERSION)", extra3 => "$(MAN_PROJECT) Function Reference" }) end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]], halt(0) catch C:E$(if $V,:S) -> io:format("Exception: ~p:~p~n$(if $V,Stacktrace: ~p~n)", [C, E$(if $V,$(comma) S)]), halt(1) end. endef asciidoc-manual:: doc-deps asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES) $(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?)) $(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;) install-docs:: install-asciidoc install-asciidoc: asciidoc-manual $(foreach s,$(MAN_SECTIONS),\ mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \ install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;) distclean-asciidoc-manual: $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS)) endif endif # Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates # Core targets. help:: $(verbose) printf "%s\n" "" \ "Bootstrap targets:" \ " bootstrap Generate a skeleton of an OTP application" \ " bootstrap-lib Generate a skeleton of an OTP library" \ " bootstrap-rel Generate the files needed to build a release" \ " new-app in=NAME Create a new local OTP application NAME" \ " new-lib in=NAME Create a new local OTP library NAME" \ " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ " list-templates List available templates" # Bootstrap templates. define bs_appsrc {application, $p, [ {description, ""}, {vsn, "0.1.0"}, {id, "git"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib ]}, {mod, {$p_app, []}}, {env, []} ]}. endef define bs_appsrc_lib {application, $p, [ {description, ""}, {vsn, "0.1.0"}, {id, "git"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib ]} ]}. endef # To prevent autocompletion issues with ZSH, we add "include erlang.mk" # separately during the actual bootstrap. define bs_Makefile PROJECT = $p PROJECT_DESCRIPTION = New project PROJECT_VERSION = 0.1.0 $(if $(SP), # Whitespace to be used when creating files from templates. SP = $(SP) ) endef define bs_apps_Makefile PROJECT = $p PROJECT_DESCRIPTION = New project PROJECT_VERSION = 0.1.0 $(if $(SP), # Whitespace to be used when creating files from templates. SP = $(SP) ) # Make sure we know where the applications are located. ROOT_DIR ?= $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app) APPS_DIR ?= .. DEPS_DIR ?= $(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app) include $$(ROOT_DIR)/erlang.mk endef define bs_app -module($p_app). -behaviour(application). -export([start/2]). -export([stop/1]). start(_Type, _Args) -> $p_sup:start_link(). stop(_State) -> ok. endef define bs_relx_config {release, {$p_release, "1"}, [$p, sasl, runtime_tools]}. {dev_mode, false}. {include_erts, true}. {extended_start_script, true}. {sys_config, "config/sys.config"}. {vm_args, "config/vm.args"}. endef define bs_sys_config [ ]. endef define bs_vm_args -name $p@127.0.0.1 -setcookie $p -heart endef # Normal templates. define tpl_supervisor -module($(n)). -behaviour(supervisor). -export([start_link/0]). -export([init/1]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> Procs = [], {ok, {{one_for_one, 1, 5}, Procs}}. endef define tpl_gen_server -module($(n)). -behaviour(gen_server). %% API. -export([start_link/0]). %% gen_server. -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link(?MODULE, [], []). %% gen_server. init([]) -> {ok, #state{}}. handle_call(_Request, _From, State) -> {reply, ignored, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. endef define tpl_module -module($(n)). -export([]). endef define tpl_cowboy_http -module($(n)). -behaviour(cowboy_http_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). -record(state, { }). init(_, Req, _Opts) -> {ok, Req, #state{}}. handle(Req, State=#state{}) -> {ok, Req2} = cowboy_req:reply(200, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. endef define tpl_gen_fsm -module($(n)). -behaviour(gen_fsm). %% API. -export([start_link/0]). %% gen_fsm. -export([init/1]). -export([state_name/2]). -export([handle_event/3]). -export([state_name/3]). -export([handle_sync_event/4]). -export([handle_info/3]). -export([terminate/3]). -export([code_change/4]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_fsm:start_link(?MODULE, [], []). %% gen_fsm. init([]) -> {ok, state_name, #state{}}. state_name(_Event, StateData) -> {next_state, state_name, StateData}. handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. state_name(_Event, _From, StateData) -> {reply, ignored, state_name, StateData}. handle_sync_event(_Event, _From, StateName, StateData) -> {reply, ignored, StateName, StateData}. handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. terminate(_Reason, _StateName, _StateData) -> ok. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. endef define tpl_gen_statem -module($(n)). -behaviour(gen_statem). %% API. -export([start_link/0]). %% gen_statem. -export([callback_mode/0]). -export([init/1]). -export([state_name/3]). -export([handle_event/4]). -export([terminate/3]). -export([code_change/4]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_statem:start_link(?MODULE, [], []). %% gen_statem. callback_mode() -> state_functions. init([]) -> {ok, state_name, #state{}}. state_name(_EventType, _EventData, StateData) -> {next_state, state_name, StateData}. handle_event(_EventType, _EventData, StateName, StateData) -> {next_state, StateName, StateData}. terminate(_Reason, _StateName, _StateData) -> ok. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. endef define tpl_cowboy_loop -module($(n)). -behaviour(cowboy_loop_handler). -export([init/3]). -export([info/3]). -export([terminate/3]). -record(state, { }). init(_, Req, _Opts) -> {loop, Req, #state{}, 5000, hibernate}. info(_Info, Req, State) -> {loop, Req, State, hibernate}. terminate(_Reason, _Req, _State) -> ok. endef define tpl_cowboy_rest -module($(n)). -export([init/3]). -export([content_types_provided/2]). -export([get_html/2]). init(_, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. get_html(Req, State) -> {<<"This is REST!">>, Req, State}. endef define tpl_cowboy_ws -module($(n)). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). -record(state, { }). init(_, _, _) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_, Req, _Opts) -> Req2 = cowboy_req:compact(Req), {ok, Req2, #state{}}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}; websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. endef define tpl_ranch_protocol -module($(n)). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). -type opts() :: []. -export_type([opts/0]). -record(state, { socket :: inet:socket(), transport :: module() }). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. -spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. init(Ref, Socket, Transport, _Opts) -> ok = ranch:accept_ack(Ref), loop(#state{socket=Socket, transport=Transport}). loop(State) -> loop(State). endef # Plugin-specific targets. ifndef WS ifdef SP WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a)) else WS = $(tab) endif endif bootstrap: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(PROJECT)_sup) $(verbose) $(call core_render,bs_Makefile,Makefile) $(verbose) echo "include erlang.mk" >> Makefile $(verbose) mkdir src/ ifdef LEGACY $(verbose) $(call core_render,bs_appsrc,src/$(PROJECT).app.src) endif $(verbose) $(call core_render,bs_app,src/$(PROJECT)_app.erl) $(verbose) $(call core_render,tpl_supervisor,src/$(PROJECT)_sup.erl) bootstrap-lib: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) $(call core_render,bs_Makefile,Makefile) $(verbose) echo "include erlang.mk" >> Makefile $(verbose) mkdir src/ ifdef LEGACY $(verbose) $(call core_render,bs_appsrc_lib,src/$(PROJECT).app.src) endif bootstrap-rel: ifneq ($(wildcard relx.config),) $(error Error: relx.config already exists) endif ifneq ($(wildcard config/),) $(error Error: config/ directory already exists) endif $(eval p := $(PROJECT)) $(verbose) $(call core_render,bs_relx_config,relx.config) $(verbose) mkdir config/ $(verbose) $(call core_render,bs_sys_config,config/sys.config) $(verbose) $(call core_render,bs_vm_args,config/vm.args) $(verbose) awk '/^include erlang.mk/ && !ins {print "REL_DEPS += relx";ins=1};{print}' Makefile > Makefile.bak $(verbose) mv Makefile.bak Makefile new-app: ifndef in $(error Usage: $(MAKE) new-app in=APP) endif ifneq ($(wildcard $(APPS_DIR)/$in),) $(error Error: Application $in already exists) endif $(eval p := $(in)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(in)_sup) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY $(verbose) $(call core_render,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src) endif $(verbose) $(call core_render,bs_app,$(APPS_DIR)/$p/src/$p_app.erl) $(verbose) $(call core_render,tpl_supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) new-lib: ifndef in $(error Usage: $(MAKE) new-lib in=APP) endif ifneq ($(wildcard $(APPS_DIR)/$in),) $(error Error: Application $in already exists) endif $(eval p := $(in)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY $(verbose) $(call core_render,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src) endif new: ifeq ($(wildcard src/)$(in),) $(error Error: src/ directory does not exist) endif ifndef t $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) endif ifndef n $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) endif ifdef in $(verbose) $(call core_render,tpl_$(t),$(APPS_DIR)/$(in)/src/$(n).erl) else $(verbose) $(call core_render,tpl_$(t),src/$(n).erl) endif list-templates: $(verbose) @echo Available templates: $(verbose) printf " %s\n" $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) # Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-c_src distclean-c_src-env # Configuration. C_SRC_DIR ?= $(CURDIR)/c_src C_SRC_ENV ?= $(C_SRC_DIR)/env.mk C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT) C_SRC_TYPE ?= shared # System type and C compiler/flags. ifeq ($(PLATFORM),msys2) C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= .exe C_SRC_OUTPUT_SHARED_EXTENSION ?= .dll C_SRC_OUTPUT_STATIC_EXTENSION ?= .lib else C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= C_SRC_OUTPUT_SHARED_EXTENSION ?= .so C_SRC_OUTPUT_STATIC_EXTENSION ?= .a endif ifeq ($(C_SRC_TYPE),shared) C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION) else ifeq ($(C_SRC_TYPE),static) C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_STATIC_EXTENSION) else C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION) endif RANLIB ?= ranlib ARFLAGS ?= cr ifeq ($(PLATFORM),msys2) # We hardcode the compiler used on MSYS2. The default CC=cc does # not produce working code. The "gcc" MSYS2 package also doesn't. CC = /mingw64/bin/gcc export CC CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall else ifeq ($(PLATFORM),darwin) CC ?= cc CFLAGS ?= -O3 -std=c99 -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -Wall LDFLAGS ?= -flat_namespace -undefined suppress else ifeq ($(PLATFORM),freebsd) CC ?= cc CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall else ifeq ($(PLATFORM),linux) CC ?= gcc CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall endif ifneq ($(PLATFORM),msys2) CFLAGS += -fPIC CXXFLAGS += -fPIC endif ifeq ($(C_SRC_TYPE),static) CFLAGS += -DSTATIC_ERLANG_NIF=1 CXXFLAGS += -DSTATIC_ERLANG_NIF=1 endif CFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" LDLIBS += -L"$(ERL_INTERFACE_LIB_DIR)" -lei # Verbosity. c_verbose_0 = @echo " C " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F)); c_verbose = $(c_verbose_$(V)) cpp_verbose_0 = @echo " CPP " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F)); cpp_verbose = $(cpp_verbose_$(V)) link_verbose_0 = @echo " LD " $(@F); link_verbose = $(link_verbose_$(V)) ar_verbose_0 = @echo " AR " $(@F); ar_verbose = $(ar_verbose_$(V)) ranlib_verbose_0 = @echo " RANLIB" $(@F); ranlib_verbose = $(ranlib_verbose_$(V)) # Targets. ifeq ($(wildcard $(C_SRC_DIR)),) else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),) app:: app-c_src test-build:: app-c_src app-c_src: $(MAKE) -C $(C_SRC_DIR) clean:: $(MAKE) -C $(C_SRC_DIR) clean else ifeq ($(SOURCES),) SOURCES := $(sort $(foreach pat,*.c *.C *.cc *.cpp,$(call core_find,$(C_SRC_DIR)/,$(pat)))) endif OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c app:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) ifneq ($(C_SRC_TYPE),static) $(C_SRC_OUTPUT_FILE): $(OBJECTS) $(verbose) mkdir -p $(dir $@) $(link_verbose) $(CC) $(OBJECTS) \ $(LDFLAGS) $(if $(filter $(C_SRC_TYPE),shared),-shared) $(LDLIBS) \ -o $(C_SRC_OUTPUT_FILE) else $(C_SRC_OUTPUT_FILE): $(OBJECTS) $(verbose) mkdir -p $(dir $@) $(ar_verbose) $(AR) $(ARFLAGS) $(C_SRC_OUTPUT_FILE) $(OBJECTS) $(ranlib_verbose) $(RANLIB) $(C_SRC_OUTPUT_FILE) endif $(OBJECTS): $(MAKEFILE_LIST) $(C_SRC_ENV) %.o: %.c $(COMPILE_C) $(OUTPUT_OPTION) $< %.o: %.cc $(COMPILE_CPP) $(OUTPUT_OPTION) $< %.o: %.C $(COMPILE_CPP) $(OUTPUT_OPTION) $< %.o: %.cpp $(COMPILE_CPP) $(OUTPUT_OPTION) $< clean:: clean-c_src clean-c_src: $(gen_verbose) rm -f $(C_SRC_OUTPUT_FILE) $(OBJECTS) endif ifneq ($(wildcard $(C_SRC_DIR)),) ERL_ERTS_DIR = $(shell $(ERL) -eval 'io:format("~s~n", [code:lib_dir(erts)]), halt().') $(C_SRC_ENV): $(verbose) $(ERL) -eval "file:write_file(\"$(call core_native_path,$(C_SRC_ENV))\", \ io_lib:format( \ \"# Generated by Erlang.mk. Edit at your own risk!~n~n\" \ \"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \ \"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \ \"ERL_INTERFACE_LIB_DIR ?= ~s~n\" \ \"ERTS_DIR ?= $(ERL_ERTS_DIR)~n\", \ [code:root_dir(), erlang:system_info(version), \ code:lib_dir(erl_interface, include), \ code:lib_dir(erl_interface, lib)])), \ halt()." distclean:: distclean-c_src-env distclean-c_src-env: $(gen_verbose) rm -f $(C_SRC_ENV) -include $(C_SRC_ENV) ifneq ($(ERL_ERTS_DIR),$(ERTS_DIR)) $(shell rm -f $(C_SRC_ENV)) endif endif # Templates. define bs_c_nif #include "erl_nif.h" static int loads = 0; static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { /* Initialize private data. */ *priv_data = NULL; loads++; return 0; } static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) { /* Convert the private data to the new version. */ *priv_data = *old_priv_data; loads++; return 0; } static void unload(ErlNifEnv* env, void* priv_data) { if (loads == 1) { /* Destroy the private data. */ } loads--; } static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { if (enif_is_atom(env, argv[0])) { return enif_make_tuple2(env, enif_make_atom(env, "hello"), argv[0]); } return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, "badarg")); } static ErlNifFunc nif_funcs[] = { {"hello", 1, hello} }; ERL_NIF_INIT($n, nif_funcs, load, NULL, upgrade, unload) endef define bs_erl_nif -module($n). -export([hello/1]). -on_load(on_load/0). on_load() -> PrivDir = case code:priv_dir(?MODULE) of {error, _} -> AppPath = filename:dirname(filename:dirname(code:which(?MODULE))), filename:join(AppPath, "priv"); Path -> Path end, erlang:load_nif(filename:join(PrivDir, atom_to_list(?MODULE)), 0). hello(_) -> erlang:nif_error({not_loaded, ?MODULE}). endef new-nif: ifneq ($(wildcard $(C_SRC_DIR)/$n.c),) $(error Error: $(C_SRC_DIR)/$n.c already exists) endif ifneq ($(wildcard src/$n.erl),) $(error Error: src/$n.erl already exists) endif ifndef n $(error Usage: $(MAKE) new-nif n=NAME [in=APP]) endif ifdef in $(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new-nif n=$n in= else $(verbose) mkdir -p $(C_SRC_DIR) src/ $(verbose) $(call core_render,bs_c_nif,$(C_SRC_DIR)/$n.c) $(verbose) $(call core_render,bs_erl_nif,src/$n.erl) endif # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: ci ci-prepare ci-setup CI_OTP ?= ifeq ($(strip $(CI_OTP)),) ci:: else ci:: $(addprefix ci-,$(CI_OTP)) ci-prepare: $(addprefix ci-prepare-,$(CI_OTP)) ci-setup:: $(verbose) : ci-extra:: $(verbose) : ci_verbose_0 = @echo " CI " $(1); ci_verbose = $(ci_verbose_$(V)) define ci_target ci-prepare-$1: $(KERL_INSTALL_DIR)/$2 $(verbose) : ci-$1: ci-prepare-$1 $(verbose) $(MAKE) --no-print-directory clean $(ci_verbose) \ PATH="$(KERL_INSTALL_DIR)/$2/bin:$(PATH)" \ CI_OTP_RELEASE="$1" \ CT_OPTS="-label $1" \ CI_VM="$3" \ $(MAKE) ci-setup tests $(verbose) $(MAKE) --no-print-directory ci-extra endef $(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp),$(otp),otp))) $(foreach otp,$(filter-out $(ERLANG_OTP),$(CI_OTP)),$(eval $(call kerl_otp_target,$(otp)))) help:: $(verbose) printf "%s\n" "" \ "Continuous Integration targets:" \ " ci Run '$(MAKE) tests' on all configured Erlang versions." \ "" \ "The CI_OTP variable must be defined with the Erlang versions" \ "that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3" endif # Copyright (c) 2020, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifdef CONCUERROR_TESTS .PHONY: concuerror distclean-concuerror # Configuration CONCUERROR_LOGS_DIR ?= $(CURDIR)/logs CONCUERROR_OPTS ?= # Core targets. check:: concuerror ifndef KEEP_LOGS distclean:: distclean-concuerror endif # Plugin-specific targets. $(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP) $(verbose) git clone https://github.com/parapluu/Concuerror $(ERLANG_MK_TMP)/Concuerror $(verbose) $(MAKE) -C $(ERLANG_MK_TMP)/Concuerror $(CONCUERROR_LOGS_DIR): $(verbose) mkdir -p $(CONCUERROR_LOGS_DIR) define concuerror_html_report Concuerror HTML report

Concuerror HTML report

Generated on $(concuerror_date)

    $(foreach t,$(concuerror_targets),
  • $(t)
  • )
endef concuerror: $(addprefix concuerror-,$(subst :,-,$(CONCUERROR_TESTS))) $(eval concuerror_date := $(shell date)) $(eval concuerror_targets := $^) $(verbose) $(call core_render,concuerror_html_report,$(CONCUERROR_LOGS_DIR)/concuerror.html) define concuerror_target .PHONY: concuerror-$1-$2 concuerror-$1-$2: test-build | $(ERLANG_MK_TMP)/Concuerror/bin/concuerror $(CONCUERROR_LOGS_DIR) $(ERLANG_MK_TMP)/Concuerror/bin/concuerror \ --pa $(CURDIR)/ebin --pa $(TEST_DIR) \ -o $(CONCUERROR_LOGS_DIR)/concuerror-$1-$2.txt \ $$(CONCUERROR_OPTS) -m $1 -t $2 endef $(foreach test,$(CONCUERROR_TESTS),$(eval $(call concuerror_target,$(firstword $(subst :, ,$(test))),$(lastword $(subst :, ,$(test)))))) distclean-concuerror: $(gen_verbose) rm -rf $(CONCUERROR_LOGS_DIR) endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: ct apps-ct distclean-ct # Configuration. CT_OPTS ?= ifneq ($(wildcard $(TEST_DIR)),) ifndef CT_SUITES CT_SUITES := $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl)))) endif endif CT_SUITES ?= CT_LOGS_DIR ?= $(CURDIR)/logs # Core targets. tests:: ct ifndef KEEP_LOGS distclean:: distclean-ct endif help:: $(verbose) printf "%s\n" "" \ "Common_test targets:" \ " ct Run all the common_test suites for this project" \ "" \ "All your common_test suites have their associated targets." \ "A suite named http_SUITE can be ran using the ct-http target." # Plugin-specific targets. CT_RUN = ct_run \ -no_auto_compile \ -noinput \ -pa $(CURDIR)/ebin $(TEST_DIR) \ -dir $(TEST_DIR) \ -logdir $(CT_LOGS_DIR) ifeq ($(CT_SUITES),) ct: $(if $(IS_APP)$(ROOT_DIR),,apps-ct) else # We do not run tests if we are in an apps/* with no test directory. ifneq ($(IS_APP)$(wildcard $(TEST_DIR)),1) ct: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-ct) $(verbose) mkdir -p $(CT_LOGS_DIR) $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) endif endif ifneq ($(ALL_APPS_DIRS),) define ct_app_target apps-ct-$1: test-build $$(MAKE) -C $1 ct IS_APP=1 endef $(foreach app,$(ALL_APPS_DIRS),$(eval $(call ct_app_target,$(app)))) apps-ct: $(addprefix apps-ct-,$(ALL_APPS_DIRS)) endif ifdef t ifeq (,$(findstring :,$t)) CT_EXTRA = -group $t else t_words = $(subst :, ,$t) CT_EXTRA = -group $(firstword $(t_words)) -case $(lastword $(t_words)) endif else ifdef c CT_EXTRA = -case $c else CT_EXTRA = endif endif define ct_suite_target ct-$1: test-build $$(verbose) mkdir -p $$(CT_LOGS_DIR) $$(gen_verbose_esc) $$(CT_RUN) -sname ct_$$(PROJECT) -suite $$(addsuffix _SUITE,$1) $$(CT_EXTRA) $$(CT_OPTS) endef $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) distclean-ct: $(gen_verbose) rm -rf $(CT_LOGS_DIR) # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: plt distclean-plt dialyze # Configuration. DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt export DIALYZER_PLT PLT_APPS ?= DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS) DIALYZER_OPTS ?= -Werror_handling -Wunmatched_returns # -Wunderspecs DIALYZER_PLT_OPTS ?= # Core targets. check:: dialyze distclean:: distclean-plt help:: $(verbose) printf "%s\n" "" \ "Dialyzer targets:" \ " plt Build a PLT file for this project" \ " dialyze Analyze the project using Dialyzer" # Plugin-specific targets. define filter_opts.erl Opts = init:get_plain_arguments(), {Filtered, _} = lists:foldl(fun (O, {Os, true}) -> {[O|Os], false}; (O = "-D", {Os, _}) -> {[O|Os], true}; (O = [\\$$-, \\$$D, _ | _], {Os, _}) -> {[O|Os], false}; (O = "-I", {Os, _}) -> {[O|Os], true}; (O = [\\$$-, \\$$I, _ | _], {Os, _}) -> {[O|Os], false}; (O = "-pa", {Os, _}) -> {[O|Os], true}; (_, Acc) -> Acc end, {[], false}, Opts), io:format("~s~n", [string:join(lists:reverse(Filtered), " ")]), halt(). endef # DIALYZER_PLT is a variable understood directly by Dialyzer. # # We append the path to erts at the end of the PLT. This works # because the PLT file is in the external term format and the # function binary_to_term/1 ignores any trailing data. $(DIALYZER_PLT): deps app $(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \ while read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log)) $(verbose) dialyzer --build_plt $(DIALYZER_PLT_OPTS) --apps \ erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG) || test $$? -eq 2 $(verbose) $(ERL) -eval 'io:format("~n~s~n", [code:lib_dir(erts)]), halt().' >> $@ plt: $(DIALYZER_PLT) distclean-plt: $(gen_verbose) rm -f $(DIALYZER_PLT) ifneq ($(wildcard $(DIALYZER_PLT)),) dialyze: $(if $(filter --src,$(DIALYZER_DIRS)),,deps app) $(verbose) if ! tail -n1 $(DIALYZER_PLT) | \ grep -q "^`$(ERL) -eval 'io:format("~s", [code:lib_dir(erts)]), halt().'`$$"; then \ rm $(DIALYZER_PLT); \ $(MAKE) plt; \ fi else dialyze: $(DIALYZER_PLT) endif $(verbose) dialyzer `$(ERL) \ -eval "$(subst $(newline),,$(call escape_dquotes,$(call filter_opts.erl)))" \ -extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS) $(if $(wildcard ebin/),-pa ebin/) # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-edoc edoc # Configuration. EDOC_OPTS ?= EDOC_SRC_DIRS ?= EDOC_OUTPUT ?= doc define edoc.erl SrcPaths = lists:foldl(fun(P, Acc) -> filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}") ++ lists:filter(fun(D) -> filelib:is_dir(D) end, filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}/**")) ++ Acc end, [], [$(call comma_list,$(patsubst %,'%',$(call core_native_path,$(EDOC_SRC_DIRS))))]), DefaultOpts = [{dir, "$(EDOC_OUTPUT)"}, {source_path, SrcPaths}, {subpackages, false}], edoc:application($(1), ".", [$(2)] ++ DefaultOpts), halt(0). endef # Core targets. ifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),) docs:: edoc endif distclean:: distclean-edoc # Plugin-specific targets. edoc: distclean-edoc doc-deps $(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS))) distclean-edoc: $(gen_verbose) rm -f $(EDOC_OUTPUT)/*.css $(EDOC_OUTPUT)/*.html $(EDOC_OUTPUT)/*.png $(EDOC_OUTPUT)/edoc-info # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # Configuration. DTL_FULL_PATH ?= DTL_PATH ?= templates/ DTL_PREFIX ?= DTL_SUFFIX ?= _dtl DTL_OPTS ?= # Verbosity. dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose = $(dtl_verbose_$(V)) # Core targets. DTL_PATH := $(abspath $(DTL_PATH)) DTL_FILES := $(sort $(call core_find,$(DTL_PATH),*.dtl)) ifneq ($(DTL_FILES),) DTL_NAMES = $(addprefix $(DTL_PREFIX),$(addsuffix $(DTL_SUFFIX),$(DTL_FILES:$(DTL_PATH)/%.dtl=%))) DTL_MODULES = $(if $(DTL_FULL_PATH),$(subst /,_,$(DTL_NAMES)),$(notdir $(DTL_NAMES))) BEAM_FILES += $(addsuffix .beam,$(addprefix ebin/,$(DTL_MODULES))) ifneq ($(words $(DTL_FILES)),0) # Rebuild templates when the Makefile changes. $(ERLANG_MK_TMP)/last-makefile-change-erlydtl: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(DTL_FILES); \ fi $(verbose) touch $@ ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change-erlydtl endif define erlydtl_compile.erl [begin Module0 = case "$(strip $(DTL_FULL_PATH))" of "" -> filename:basename(F, ".dtl"); _ -> "$(call core_native_path,$(DTL_PATH))/" ++ F2 = filename:rootname(F, ".dtl"), re:replace(F2, "/", "_", [{return, list}, global]) end, Module = list_to_atom("$(DTL_PREFIX)" ++ string:to_lower(Module0) ++ "$(DTL_SUFFIX)"), case erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, "ebin/"}, return_errors]) of ok -> ok; {ok, _} -> ok end end || F <- string:tokens("$(1)", " ")], halt(). endef ebin/$(PROJECT).app:: $(DTL_FILES) | ebin/ $(if $(strip $?),\ $(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$(call core_native_path,$?)),\ -pa ebin/)) endif # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2014, Dave Cottlehuber # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-escript escript escript-zip # Configuration. ESCRIPT_NAME ?= $(PROJECT) ESCRIPT_FILE ?= $(ESCRIPT_NAME) ESCRIPT_SHEBANG ?= /usr/bin/env escript ESCRIPT_COMMENT ?= This is an -*- erlang -*- file ESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME) ESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null) ESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip # Core targets. distclean:: distclean-escript help:: $(verbose) printf "%s\n" "" \ "Escript targets:" \ " escript Build an executable escript archive" \ # Plugin-specific targets. escript-zip:: FULL=1 escript-zip:: deps app $(verbose) mkdir -p $(dir $(abspath $(ESCRIPT_ZIP_FILE))) $(verbose) rm -f $(abspath $(ESCRIPT_ZIP_FILE)) $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(PROJECT)/ebin/* ifneq ($(DEPS),) $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) \ $(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \ $(addsuffix /ebin,$(shell cat $(ERLANG_MK_TMP)/deps.log))))) endif escript:: escript-zip $(gen_verbose) printf "%s\n" \ "#!$(ESCRIPT_SHEBANG)" \ "%% $(ESCRIPT_COMMENT)" \ "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE) $(verbose) cat $(abspath $(ESCRIPT_ZIP_FILE)) >> $(ESCRIPT_FILE) $(verbose) chmod +x $(ESCRIPT_FILE) distclean-escript: $(gen_verbose) rm -f $(ESCRIPT_FILE) $(abspath $(ESCRIPT_ZIP_FILE)) # Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, Enrique Fernandez # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: eunit apps-eunit # Configuration EUNIT_OPTS ?= EUNIT_ERL_OPTS ?= EUNIT_TEST_SPEC ?= $1 # Core targets. tests:: eunit help:: $(verbose) printf "%s\n" "" \ "EUnit targets:" \ " eunit Run all the EUnit tests for this project" # Plugin-specific targets. define eunit.erl $(call cover.erl) CoverSetup(), case eunit:test($(call EUNIT_TEST_SPEC,$1), [$(EUNIT_OPTS)]) of ok -> ok; error -> halt(2) end, CoverExport("$(call core_native_path,$(COVER_DATA_DIR))/eunit.coverdata"), halt() endef EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(CURDIR)/ebin ifdef t ifeq (,$(findstring :,$(t))) eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS)) else eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS)) endif else EUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES))) EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl))) EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \ $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)') eunit: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-eunit) cover-data-dir ifneq ($(wildcard src/ $(TEST_DIR)),) $(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS)) endif ifneq ($(ALL_APPS_DIRS),) apps-eunit: test-build $(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \ [ $$? -ne 0 ] && eunit_retcode=1 ; done ; \ exit $$eunit_retcode endif endif # Copyright (c) 2020, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. HEX_CORE_GIT ?= https://github.com/hexpm/hex_core HEX_CORE_COMMIT ?= v0.7.0 PACKAGES += hex_core pkg_hex_core_name = hex_core pkg_hex_core_description = Reference implementation of Hex specifications pkg_hex_core_homepage = $(HEX_CORE_GIT) pkg_hex_core_fetch = git pkg_hex_core_repo = $(HEX_CORE_GIT) pkg_hex_core_commit = $(HEX_CORE_COMMIT) # We automatically depend on hex_core when the project isn't already. $(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\ $(eval $(call dep_target,hex_core))) hex-core: $(DEPS_DIR)/hex_core $(verbose) if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \ $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \ touch $(DEPS_DIR)/hex_core/ebin/dep_built; \ fi # @todo This must also apply to fetching. HEX_CONFIG ?= define hex_config.erl begin Config0 = hex_core:default_config(), Config0$(HEX_CONFIG) end endef define hex_user_create.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), case hex_api_user:create(Config, <<"$(strip $1)">>, <<"$(strip $2)">>, <<"$(strip $3)">>) of {ok, {201, _, #{<<"email">> := Email, <<"url">> := URL, <<"username">> := Username}}} -> io:format("User ~s (~s) created at ~s~n" "Please check your inbox for a confirmation email.~n" "You must confirm before you are allowed to publish packages.~n", [Username, Email, URL]), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(80) end endef # The $(info ) call inserts a new line after the password prompt. hex-user-create: hex-core $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p "Email: " email; echo $$email))) $(gen_verbose) $(call erlang,$(call hex_user_create.erl,$(HEX_USERNAME),$(HEX_PASSWORD),$(HEX_EMAIL))) define hex_key_add.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => iolist_to_binary([<<"Basic ">>, base64:encode(<<"$(strip $1):$(strip $2)">>)])}, Permissions = [ case string:split(P, <<":">>) of [D] -> #{domain => D}; [D, R] -> #{domain => D, resource => R} end || P <- string:split(<<"$(strip $4)">>, <<",">>, all)], case hex_api_key:add(ConfigF, <<"$(strip $3)">>, Permissions) of {ok, {201, _, #{<<"secret">> := Secret}}} -> io:format("Key ~s created for user ~s~nSecret: ~s~n" "Please store the secret in a secure location, such as a password store.~n" "The secret will be requested for most Hex-related operations.~n", [<<"$(strip $3)">>, <<"$(strip $1)">>, Secret]), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(81) end endef hex-key-add: hex-core $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\ $(if $(name),$(name),$(shell hostname)-erlang-mk),\ $(if $(perm),$(perm),api))) HEX_TARBALL_EXTRA_METADATA ?= # @todo Check that we can += files HEX_TARBALL_FILES ?= \ $(wildcard early-plugins.mk) \ $(wildcard ebin/$(PROJECT).app) \ $(wildcard ebin/$(PROJECT).appup) \ $(wildcard $(notdir $(ERLANG_MK_FILENAME))) \ $(sort $(call core_find,include/,*.hrl)) \ $(wildcard LICENSE*) \ $(wildcard Makefile) \ $(wildcard plugins.mk) \ $(sort $(call core_find,priv/,*)) \ $(wildcard README*) \ $(wildcard rebar.config) \ $(sort $(call core_find,src/,*)) HEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar # @todo Need to check for rebar.config and/or the absence of DEPS to know # whether a project will work with Rebar. # # @todo contributors licenses links in HEX_TARBALL_EXTRA_METADATA # In order to build the requirements metadata we look into DEPS. # We do not require that the project use Hex dependencies, however # Hex.pm does require that the package name and version numbers # correspond to a real Hex package. define hex_tarball_create.erl Files0 = [$(call comma_list,$(patsubst %,"%",$(HEX_TARBALL_FILES)))], Requirements0 = #{ $(foreach d,$(DEPS), <<"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))">> => #{ <<"app">> => <<"$d">>, <<"optional">> => false, <<"requirement">> => <<"$(call query_version,$d)">> },) $(if $(DEPS),dummy => dummy) }, Requirements = maps:remove(dummy, Requirements0), Metadata0 = #{ app => <<"$(strip $(PROJECT))">>, build_tools => [<<"make">>, <<"rebar3">>], description => <<"$(strip $(PROJECT_DESCRIPTION))">>, files => [unicode:characters_to_binary(F) || F <- Files0], name => <<"$(strip $(PROJECT))">>, requirements => Requirements, version => <<"$(strip $(PROJECT_VERSION))">> }, Metadata = Metadata0$(HEX_TARBALL_EXTRA_METADATA), Files = [case file:read_file(F) of {ok, Bin} -> {F, Bin}; {error, Reason} -> io:format("Error trying to open file ~0p: ~0p~n", [F, Reason]), halt(82) end || F <- Files0], case hex_tarball:create(Metadata, Files) of {ok, #{tarball := Tarball}} -> ok = file:write_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))", Tarball), halt(0); {error, Reason} -> io:format("Error ~0p~n", [Reason]), halt(83) end endef hex_tar_verbose_0 = @echo " TAR $(notdir $(ERLANG_MK_TMP))/$(@F)"; hex_tar_verbose_2 = set -x; hex_tar_verbose = $(hex_tar_verbose_$(V)) $(HEX_TARBALL_OUTPUT_FILE): hex-core app $(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl)) hex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE) define hex_release_publish_summary.erl {ok, Tarball} = erl_tar:open("$(strip $(HEX_TARBALL_OUTPUT_FILE))", [read]), ok = erl_tar:extract(Tarball, [{cwd, "$(ERLANG_MK_TMP)"}, {files, ["metadata.config"]}]), {ok, Metadata} = file:consult("$(ERLANG_MK_TMP)/metadata.config"), #{ <<"name">> := Name, <<"version">> := Version, <<"files">> := Files, <<"requirements">> := Deps } = maps:from_list(Metadata), io:format("Publishing ~s ~s~n Dependencies:~n", [Name, Version]), case Deps of [] -> io:format(" (none)~n"); _ -> [begin #{<<"app">> := DA, <<"requirement">> := DR} = maps:from_list(D), io:format(" ~s ~s~n", [DA, DR]) end || {_, D} <- Deps] end, io:format(" Included files:~n"), [io:format(" ~s~n", [F]) || F <- Files], io:format("You may also review the contents of the tarball file.~n" "Please enter your secret key to proceed.~n"), halt(0) endef define hex_release_publish.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, {ok, Tarball} = file:read_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))"), case hex_api_release:publish(ConfigF, Tarball, [{replace, $2}]) of {ok, {200, _, #{}}} -> io:format("Release replaced~n"), halt(0); {ok, {201, _, #{}}} -> io:format("Release published~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(84) end endef hex-release-tarball: hex-core $(HEX_TARBALL_OUTPUT_FILE) $(verbose) $(call erlang,$(call hex_release_publish_summary.erl)) hex-release-publish: hex-core hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false)) hex-release-replace: hex-core hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true)) define hex_release_delete.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api_release:delete(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $(PROJECT_VERSION))">>) of {ok, {204, _, _}} -> io:format("Release $(strip $(PROJECT_VERSION)) deleted~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(85) end endef hex-release-delete: hex-core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET))) define hex_release_retire.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, Params = #{<<"reason">> => <<"$(strip $3)">>, <<"message">> => <<"$(strip $4)">>}, case hex_api_release:retire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>, Params) of {ok, {204, _, _}} -> io:format("Release $(strip $2) has been retired~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(86) end endef hex-release-retire: hex-core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\ $(if $(HEX_REASON),$(HEX_REASON),invalid),\ $(HEX_MESSAGE))) define hex_release_unretire.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api_release:unretire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>) of {ok, {204, _, _}} -> io:format("Release $(strip $2) is not retired anymore~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(87) end endef hex-release-unretire: hex-core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) HEX_DOCS_DOC_DIR ?= doc/ HEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*)) HEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz $(HEX_DOCS_TARBALL_OUTPUT_FILE): hex-core app docs $(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \ $(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%) hex-docs-tarball-create: $(HEX_DOCS_TARBALL_OUTPUT_FILE) define hex_docs_publish.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, {ok, Tarball} = file:read_file("$(strip $(HEX_DOCS_TARBALL_OUTPUT_FILE))"), case hex_api:post(ConfigF, ["packages", "$(strip $(PROJECT))", "releases", "$(strip $(PROJECT_VERSION))", "docs"], {"application/octet-stream", Tarball}) of {ok, {Status, _, _}} when Status >= 200, Status < 300 -> io:format("Docs published~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(88) end endef hex-docs-publish: hex-core hex-docs-tarball-create $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET))) define hex_docs_delete.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api:delete(ConfigF, ["packages", "$(strip $(PROJECT))", "releases", "$(strip $2)", "docs"]) of {ok, {Status, _, _}} when Status >= 200, Status < 300 -> io:format("Docs removed~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(89) end endef hex-docs-delete: hex-core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter proper,$(DEPS) $(TEST_DEPS)),proper) .PHONY: proper # Targets. tests:: proper define proper_check.erl $(call cover.erl) code:add_pathsa([ "$(call core_native_path,$(CURDIR)/ebin)", "$(call core_native_path,$(DEPS_DIR)/*/ebin)", "$(call core_native_path,$(TEST_DIR))"]), Module = fun(M) -> [true] =:= lists:usort([ case atom_to_list(F) of "prop_" ++ _ -> io:format("Testing ~p:~p/0~n", [M, F]), proper:quickcheck(M:F(), nocolors); _ -> true end || {F, 0} <- M:module_info(exports)]) end, try begin CoverSetup(), Res = case $(1) of all -> [true] =:= lists:usort([Module(M) || M <- [$(call comma_list,$(3))]]); module -> Module($(2)); function -> proper:quickcheck($(2), nocolors) end, CoverExport("$(COVER_DATA_DIR)/proper.coverdata"), Res end of true -> halt(0); _ -> halt(1) catch error:undef$(if $V,:Stacktrace) -> io:format("Undefined property or module?~n$(if $V,~p~n)", [$(if $V,Stacktrace)]), halt(0) end. endef ifdef t ifeq (,$(findstring :,$(t))) proper: test-build cover-data-dir $(verbose) $(call erlang,$(call proper_check.erl,module,$(t))) else proper: test-build cover-data-dir $(verbose) echo Testing $(t)/0 $(verbose) $(call erlang,$(call proper_check.erl,function,$(t)())) endif else proper: test-build cover-data-dir $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam)))))) $(gen_verbose) $(call erlang,$(call proper_check.erl,all,undefined,$(MODULES))) endif endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # Verbosity. proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F)); proto_verbose = $(proto_verbose_$(V)) # Core targets. ifneq ($(wildcard src/),) ifneq ($(filter gpb protobuffs,$(BUILD_DEPS) $(DEPS)),) PROTO_FILES := $(filter %.proto,$(ALL_SRC_FILES)) ERL_FILES += $(addprefix src/,$(patsubst %.proto,%_pb.erl,$(notdir $(PROTO_FILES)))) ifeq ($(PROTO_FILES),) $(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(verbose) : else # Rebuild proto files when the Makefile changes. # We exclude $(PROJECT).d to avoid a circular dependency. $(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(PROTO_FILES); \ fi $(verbose) touch $@ $(PROJECT).d:: $(ERLANG_MK_TMP)/last-makefile-change-protobuffs endif ifeq ($(filter gpb,$(BUILD_DEPS) $(DEPS)),) define compile_proto.erl [begin protobuffs_compile:generate_source(F, [ {output_include_dir, "./include"}, {output_src_dir, "./src"}]) end || F <- string:tokens("$1", " ")], halt(). endef else define compile_proto.erl [begin gpb_compile:file(F, [ $(foreach i,$(sort $(dir $(PROTO_FILES))),{i$(comma) "$i"}$(comma)) {include_as_lib, true}, {module_name_suffix, "_pb"}, {o_hrl, "./include"}, {o_erl, "./src"}, {use_packages, true} ]) end || F <- string:tokens("$1", " ")], halt(). endef endif ifneq ($(PROTO_FILES),) $(PROJECT).d:: $(PROTO_FILES) $(verbose) mkdir -p ebin/ include/ $(if $(strip $?),$(proto_verbose) $(call erlang,$(call compile_proto.erl,$?))) endif endif endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter relx,$(BUILD_DEPS) $(DEPS) $(REL_DEPS)),relx) .PHONY: relx-rel relx-relup distclean-relx-rel run # Configuration. RELX_CONFIG ?= $(CURDIR)/relx.config RELX_CONFIG_SCRIPT ?= $(CURDIR)/relx.config.script RELX_OUTPUT_DIR ?= _rel RELX_REL_EXT ?= RELX_TAR ?= 1 ifdef SFX RELX_TAR = 1 endif # Core targets. ifeq ($(IS_DEP),) ifneq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),) rel:: relx-rel relup:: relx-relup endif endif distclean:: distclean-relx-rel # Plugin-specific targets. define relx_get_config.erl (fun() -> Config0 = case file:consult("$(call core_native_path,$(RELX_CONFIG))") of {ok, Terms} -> Terms; {error, _} -> [] end, case filelib:is_file("$(call core_native_path,$(RELX_CONFIG_SCRIPT))") of true -> Bindings = erl_eval:add_binding('CONFIG', Config0, erl_eval:new_bindings()), {ok, Config1} = file:script("$(call core_native_path,$(RELX_CONFIG_SCRIPT))", Bindings), Config1; false -> Config0 end end)() endef define relx_release.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_release(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef define relx_tar.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_tar(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef define relx_relup.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_relup(Name, Vsn, undefined, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef relx-rel: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(verbose) $(MAKE) relx-post-rel ifeq ($(RELX_TAR),1) $(call erlang,$(call relx_tar.erl),-pa ebin/) endif relx-relup: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(MAKE) relx-post-rel $(call erlang,$(call relx_relup.erl),-pa ebin/) ifeq ($(RELX_TAR),1) $(call erlang,$(call relx_tar.erl),-pa ebin/) endif distclean-relx-rel: $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) # Default hooks. relx-post-rel:: $(verbose) : # Run target. ifeq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),) run:: else define get_relx_release.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, Extended = case lists:keyfind(extended_start_script, 1, Config) of {_, true} -> "1"; _ -> "" end, io:format("~s ~s ~s", [Name, Vsn, Extended]), halt(0). endef RELX_REL := $(shell $(call erlang,$(get_relx_release.erl))) RELX_REL_NAME := $(word 1,$(RELX_REL)) RELX_REL_VSN := $(word 2,$(RELX_REL)) RELX_REL_CMD := $(if $(word 3,$(RELX_REL)),console) ifeq ($(PLATFORM),msys2) RELX_REL_EXT := .cmd endif run:: all $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) $(RELX_REL_CMD) ifdef RELOAD rel:: $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) ping $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) \ eval "io:format(\"~p~n\", [c:lm()])." endif help:: $(verbose) printf "%s\n" "" \ "Relx targets:" \ " run Compile the project, build the release and run it" endif endif # Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, M Robert Martin # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: shell # Configuration. SHELL_ERL ?= erl SHELL_PATHS ?= $(CURDIR)/ebin $(TEST_DIR) SHELL_OPTS ?= ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) # Core targets help:: $(verbose) printf "%s\n" "" \ "Shell targets:" \ " shell Run an erlang shell with SHELL_OPTS or reasonable default" # Plugin-specific targets. $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) build-shell-deps: else build-shell-deps: $(ALL_SHELL_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do \ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \ :; \ else \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \ fi \ done endif shell:: build-shell-deps $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS) # Copyright 2017, Stanislaw Klekot # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-sphinx sphinx # Configuration. SPHINX_BUILD ?= sphinx-build SPHINX_SOURCE ?= doc SPHINX_CONFDIR ?= SPHINX_FORMATS ?= html SPHINX_DOCTREES ?= $(ERLANG_MK_TMP)/sphinx.doctrees SPHINX_OPTS ?= #sphinx_html_opts = #sphinx_html_output = html #sphinx_man_opts = #sphinx_man_output = man #sphinx_latex_opts = #sphinx_latex_output = latex # Helpers. sphinx_build_0 = @echo " SPHINX" $1; $(SPHINX_BUILD) -N -q sphinx_build_1 = $(SPHINX_BUILD) -N sphinx_build_2 = set -x; $(SPHINX_BUILD) sphinx_build = $(sphinx_build_$(V)) define sphinx.build $(call sphinx_build,$1) -b $1 -d $(SPHINX_DOCTREES) $(if $(SPHINX_CONFDIR),-c $(SPHINX_CONFDIR)) $(SPHINX_OPTS) $(sphinx_$1_opts) -- $(SPHINX_SOURCE) $(call sphinx.output,$1) endef define sphinx.output $(if $(sphinx_$1_output),$(sphinx_$1_output),$1) endef # Targets. ifneq ($(wildcard $(if $(SPHINX_CONFDIR),$(SPHINX_CONFDIR),$(SPHINX_SOURCE))/conf.py),) docs:: sphinx distclean:: distclean-sphinx endif help:: $(verbose) printf "%s\n" "" \ "Sphinx targets:" \ " sphinx Generate Sphinx documentation." \ "" \ "ReST sources and 'conf.py' file are expected in directory pointed by" \ "SPHINX_SOURCE ('doc' by default). SPHINX_FORMATS lists formats to build (only" \ "'html' format is generated by default); target directory can be specified by" \ 'setting sphinx_$${format}_output, for example: sphinx_html_output = output/html' \ "Additional Sphinx options can be set in SPHINX_OPTS." # Plugin-specific targets. sphinx: $(foreach F,$(SPHINX_FORMATS),$(call sphinx.build,$F)) distclean-sphinx: $(gen_verbose) rm -rf $(filter-out $(SPHINX_SOURCE),$(foreach F,$(SPHINX_FORMATS),$(call sphinx.output,$F))) # Copyright (c) 2017, Jean-Sébastien Pédron # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: show-ERL_LIBS show-ERLC_OPTS show-TEST_ERLC_OPTS show-ERL_LIBS: @echo $(ERL_LIBS) show-ERLC_OPTS: @$(foreach opt,$(ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) show-TEST_ERLC_OPTS: @$(foreach opt,$(TEST_ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq) .PHONY: triq # Targets. tests:: triq define triq_check.erl $(call cover.erl) code:add_pathsa([ "$(call core_native_path,$(CURDIR)/ebin)", "$(call core_native_path,$(DEPS_DIR)/*/ebin)", "$(call core_native_path,$(TEST_DIR))"]), try begin CoverSetup(), Res = case $(1) of all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]); module -> triq:check($(2)); function -> triq:check($(2)) end, CoverExport("$(COVER_DATA_DIR)/triq.coverdata"), Res end of true -> halt(0); _ -> halt(1) catch error:undef$(if $V,:Stacktrace) -> io:format("Undefined property or module?~n$(if $V,~p~n)", [$(if $V,Stacktrace)]), halt(0) end. endef ifdef t ifeq (,$(findstring :,$(t))) triq: test-build cover-data-dir $(verbose) $(call erlang,$(call triq_check.erl,module,$(t))) else triq: test-build cover-data-dir $(verbose) echo Testing $(t)/0 $(verbose) $(call erlang,$(call triq_check.erl,function,$(t)())) endif else triq: test-build cover-data-dir $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam)))))) $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES))) endif endif # Copyright (c) 2022, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: xref # Configuration. # We do not use locals_not_used or deprecated_function_calls # because the compiler will error out by default in those # cases with Erlang.mk. Deprecated functions may make sense # in some cases but few libraries define them. We do not # use exports_not_used by default because it hinders more # than it helps library projects such as Cowboy. Finally, # undefined_functions provides little that undefined_function_calls # doesn't already provide, so it's not enabled by default. XREF_CHECKS ?= [undefined_function_calls] # Instead of predefined checks a query can be evaluated # using the Xref DSL. The $q variable is used in that case. # The scope is a list of keywords that correspond to # application directories, being essentially an easy way # to configure which applications to analyze. With: # # - app: . # - apps: $(ALL_APPS_DIRS) # - deps: $(ALL_DEPS_DIRS) # - otp: Built-in Erlang/OTP applications. # # The default is conservative (app) and will not be # appropriate for all types of queries (for example # application_call requires adding all applications # that might be called or they will not be found). XREF_SCOPE ?= app # apps deps otp # If the above is not enough, additional application # directories can be configured. XREF_EXTRA_APP_DIRS ?= # As well as additional non-application directories. XREF_EXTRA_DIRS ?= # Erlang.mk supports -ignore_xref([...]) with forms # {M, F, A} | {F, A} | M, the latter ignoring whole # modules. Ignores can also be provided project-wide. XREF_IGNORE ?= [] # All callbacks may be ignored. Erlang.mk will ignore # them automatically for exports_not_used (unless it # is explicitly disabled by the user). XREF_IGNORE_CALLBACKS ?= # Core targets. help:: $(verbose) printf '%s\n' '' \ 'Xref targets:' \ ' xref Analyze the project using Xref' \ ' xref q=QUERY Evaluate an Xref query' # Plugin-specific targets. define xref.erl {ok, Xref} = xref:start([]), Scope = [$(call comma_list,$(XREF_SCOPE))], AppDirs0 = [$(call comma_list,$(foreach d,$(XREF_EXTRA_APP_DIRS),"$d"))], AppDirs1 = case lists:member(otp, Scope) of false -> AppDirs0; true -> RootDir = code:root_dir(), AppDirs0 ++ [filename:dirname(P) || P <- code:get_path(), lists:prefix(RootDir, P)] end, AppDirs2 = case lists:member(deps, Scope) of false -> AppDirs1; true -> [$(call comma_list,$(foreach d,$(ALL_DEPS_DIRS),"$d"))] ++ AppDirs1 end, AppDirs3 = case lists:member(apps, Scope) of false -> AppDirs2; true -> [$(call comma_list,$(foreach d,$(ALL_APPS_DIRS),"$d"))] ++ AppDirs2 end, AppDirs = case lists:member(app, Scope) of false -> AppDirs3; true -> ["../$(notdir $(CURDIR))"|AppDirs3] end, [{ok, _} = xref:add_application(Xref, AppDir, [{builtins, true}]) || AppDir <- AppDirs], ExtraDirs = [$(call comma_list,$(foreach d,$(XREF_EXTRA_DIRS),"$d"))], [{ok, _} = xref:add_directory(Xref, ExtraDir, [{builtins, true}]) || ExtraDir <- ExtraDirs], ok = xref:set_library_path(Xref, code:get_path() -- (["ebin", "."] ++ AppDirs ++ ExtraDirs)), Checks = case {$1, is_list($2)} of {check, true} -> $2; {check, false} -> [$2]; {query, _} -> [$2] end, FinalRes = [begin IsInformational = case $1 of query -> true; check -> is_tuple(Check) andalso lists:member(element(1, Check), [call, use, module_call, module_use, application_call, application_use]) end, {ok, Res0} = case $1 of check -> xref:analyze(Xref, Check); query -> xref:q(Xref, Check) end, Res = case IsInformational of true -> Res0; false -> lists:filter(fun(R) -> {Mod, InMFA, MFA} = case R of {InMFA0 = {M, _, _}, MFA0} -> {M, InMFA0, MFA0}; {M, _, _} -> {M, R, R} end, Attrs = try Mod:module_info(attributes) catch error:undef -> [] end, InlineIgnores = lists:flatten([ [case V of M when is_atom(M) -> {M, '_', '_'}; {F, A} -> {Mod, F, A}; _ -> V end || V <- Values] || {ignore_xref, Values} <- Attrs]), BuiltinIgnores = [ {eunit_test, wrapper_test_exported_, 0} ], DoCallbackIgnores = case {Check, "$(strip $(XREF_IGNORE_CALLBACKS))"} of {exports_not_used, ""} -> true; {_, "0"} -> false; _ -> true end, CallbackIgnores = case DoCallbackIgnores of false -> []; true -> Behaviors = lists:flatten([ [BL || {behavior, BL} <- Attrs], [BL || {behaviour, BL} <- Attrs] ]), [{Mod, CF, CA} || B <- Behaviors, {CF, CA} <- B:behaviour_info(callbacks)] end, WideIgnores = if is_list($(XREF_IGNORE)) -> [if is_atom(I) -> {I, '_', '_'}; true -> I end || I <- $(XREF_IGNORE)]; true -> [$(XREF_IGNORE)] end, Ignores = InlineIgnores ++ BuiltinIgnores ++ CallbackIgnores ++ WideIgnores, not (lists:member(InMFA, Ignores) orelse lists:member(MFA, Ignores) orelse lists:member({Mod, '_', '_'}, Ignores)) end, Res0) end, case Res of [] -> ok; _ when IsInformational -> case Check of {call, {CM, CF, CA}} -> io:format("Functions that ~s:~s/~b calls:~n", [CM, CF, CA]); {use, {CM, CF, CA}} -> io:format("Function ~s:~s/~b is called by:~n", [CM, CF, CA]); {module_call, CMod} -> io:format("Modules that ~s calls:~n", [CMod]); {module_use, CMod} -> io:format("Module ~s is used by:~n", [CMod]); {application_call, CApp} -> io:format("Applications that ~s calls:~n", [CApp]); {application_use, CApp} -> io:format("Application ~s is used by:~n", [CApp]); _ when $1 =:= query -> io:format("Query ~s returned:~n", [Check]) end, [case R of {{InM, InF, InA}, {M, F, A}} -> io:format("- ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {M, F, A} -> io:format("- ~s:~s/~b~n", [M, F, A]); ModOrApp -> io:format("- ~s~n", [ModOrApp]) end || R <- Res], ok; _ -> [case {Check, R} of {undefined_function_calls, {{InM, InF, InA}, {M, F, A}}} -> io:format("Undefined function ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {undefined_functions, {M, F, A}} -> io:format("Undefined function ~s:~s/~b~n", [M, F, A]); {locals_not_used, {M, F, A}} -> io:format("Unused local function ~s:~s/~b~n", [M, F, A]); {exports_not_used, {M, F, A}} -> io:format("Unused exported function ~s:~s/~b~n", [M, F, A]); {deprecated_function_calls, {{InM, InF, InA}, {M, F, A}}} -> io:format("Deprecated function ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {deprecated_functions, {M, F, A}} -> io:format("Deprecated function ~s:~s/~b~n", [M, F, A]); _ -> io:format("~p: ~p~n", [Check, R]) end || R <- Res], error end end || Check <- Checks], stopped = xref:stop(Xref), case lists:usort(FinalRes) of [ok] -> halt(0); _ -> halt(1) end endef xref: deps app ifdef q $(verbose) $(call erlang,$(call xref.erl,query,"$q"),-pa ebin/) else $(verbose) $(call erlang,$(call xref.erl,check,$(XREF_CHECKS)),-pa ebin/) endif # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. COVER_REPORT_DIR ?= cover COVER_DATA_DIR ?= $(COVER_REPORT_DIR) ifdef COVER COVER_APPS ?= $(notdir $(ALL_APPS_DIRS)) COVER_DEPS ?= COVER_EXCLUDE_MODS ?= endif # Code coverage for Common Test. ifdef COVER ifdef CT_RUN ifneq ($(wildcard $(TEST_DIR)),) test-build:: $(TEST_DIR)/ct.cover.spec $(TEST_DIR)/ct.cover.spec: cover-data-dir $(gen_verbose) printf "%s\n" \ "{incl_app, '$(PROJECT)', details}." \ "{incl_dirs, '$(PROJECT)', [\"$(call core_native_path,$(CURDIR)/ebin)\" \ $(foreach a,$(COVER_APPS),$(comma) \"$(call core_native_path,$(APPS_DIR)/$a/ebin)\") \ $(foreach d,$(COVER_DEPS),$(comma) \"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\")]}." \ '{export,"$(call core_native_path,$(abspath $(COVER_DATA_DIR))/ct.coverdata)"}.' \ "{excl_mods, '$(PROJECT)', [$(call comma_list,$(COVER_EXCLUDE_MODS))]}." > $@ CT_RUN += -cover $(TEST_DIR)/ct.cover.spec endif endif endif # Code coverage for other tools. ifdef COVER define cover.erl CoverSetup = fun() -> Dirs = ["$(call core_native_path,$(CURDIR)/ebin)" $(foreach a,$(COVER_APPS),$(comma) "$(call core_native_path,$(APPS_DIR)/$a/ebin)") $(foreach d,$(COVER_DEPS),$(comma) "$(call core_native_path,$(DEPS_DIR)/$d/ebin)")], Excludes = [$(call comma_list,$(foreach e,$(COVER_EXCLUDE_MODS),"$e"))], [case file:list_dir(Dir) of {error, enotdir} -> false; {error, _} -> halt(2); {ok, Files} -> BeamFiles = [filename:join(Dir, File) || File <- Files, not lists:member(filename:basename(File, ".beam"), Excludes), filename:extension(File) =:= ".beam"], case cover:compile_beam(BeamFiles) of {error, _} -> halt(1); _ -> true end end || Dir <- Dirs] end, CoverExport = fun(Filename) -> cover:export(Filename) end, endef else define cover.erl CoverSetup = fun() -> ok end, CoverExport = fun(_) -> ok end, endef endif # Core targets ifdef COVER ifneq ($(COVER_REPORT_DIR),) tests:: $(verbose) $(MAKE) --no-print-directory cover-report endif cover-data-dir: | $(COVER_DATA_DIR) $(COVER_DATA_DIR): $(verbose) mkdir -p $(COVER_DATA_DIR) else cover-data-dir: endif clean:: coverdata-clean ifneq ($(COVER_REPORT_DIR),) distclean:: cover-report-clean endif help:: $(verbose) printf "%s\n" "" \ "Cover targets:" \ " cover-report Generate a HTML coverage report from previously collected" \ " cover data." \ " all.coverdata Merge all coverdata files into all.coverdata." \ "" \ "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \ "target tests additionally generates a HTML coverage report from the combined" \ "coverdata files from each of these testing tools. HTML reports can be disabled" \ "by setting COVER_REPORT_DIR to empty." # Plugin specific targets COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata)) .PHONY: coverdata-clean coverdata-clean: $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec # Merge all coverdata files into one. define cover_export.erl $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) cover:export("$(COVER_DATA_DIR)/$@"), halt(0). endef all.coverdata: $(COVERDATA) cover-data-dir $(gen_verbose) $(call erlang,$(cover_export.erl)) # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to # empty if you want the coverdata files but not the HTML report. ifneq ($(COVER_REPORT_DIR),) .PHONY: cover-report-clean cover-report cover-report-clean: $(gen_verbose) rm -rf $(COVER_REPORT_DIR) ifneq ($(COVER_REPORT_DIR),$(COVER_DATA_DIR)) $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR)) endif ifeq ($(COVERDATA),) cover-report: else # Modules which include eunit.hrl always contain one line without coverage # because eunit defines test/0 which is never called. We compensate for this. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \ grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \ | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq)) define cover_report.erl $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) Ms = cover:imported_modules(), [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M) ++ ".COVER.html", [html]) || M <- Ms], Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms], EunitHrlMods = [$(EUNIT_HRL_MODS)], Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report], TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]), TotalN = lists:sum([N || {_, {_, N}} <- Report1]), Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end, TotalPerc = Perc(TotalY, TotalN), {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]), io:format(F, "~n" "~n" "Coverage report~n" "~n", []), io:format(F, "

Coverage

~n

Total: ~p%

~n", [TotalPerc]), io:format(F, "~n", []), [io:format(F, "" "~n", [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1], How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))", Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")", io:format(F, "
ModuleCoverage
~p~p%
~n" "

Generated using ~s and erlang.mk on ~s.

~n" "", [How, Date]), halt(). endef cover-report: $(verbose) mkdir -p $(COVER_REPORT_DIR) $(gen_verbose) $(call erlang,$(cover_report.erl)) endif endif # ifneq ($(COVER_REPORT_DIR),) # Copyright (c) 2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: sfx ifdef RELX_REL ifdef SFX # Configuration. SFX_ARCHIVE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/$(RELX_REL_NAME)-$(RELX_REL_VSN).tar.gz SFX_OUTPUT_FILE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME).run # Core targets. rel:: sfx # Plugin-specific targets. define sfx_stub #!/bin/sh TMPDIR=`mktemp -d` ARCHIVE=`awk '/^__ARCHIVE_BELOW__$$/ {print NR + 1; exit 0;}' $$0` FILENAME=$$(basename $$0) REL=$${FILENAME%.*} tail -n+$$ARCHIVE $$0 | tar -xzf - -C $$TMPDIR $$TMPDIR/bin/$$REL console RET=$$? rm -rf $$TMPDIR exit $$RET __ARCHIVE_BELOW__ endef sfx: $(verbose) $(call core_render,sfx_stub,$(SFX_OUTPUT_FILE)) $(gen_verbose) cat $(SFX_ARCHIVE) >> $(SFX_OUTPUT_FILE) $(verbose) chmod +x $(SFX_OUTPUT_FILE) endif endif # Copyright (c) 2013-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # External plugins. DEP_PLUGINS ?= $(foreach p,$(DEP_PLUGINS),\ $(eval $(if $(findstring /,$p),\ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ $(call core_dep_plugin,$p/plugins.mk,$p)))) help:: help-plugins help-plugins:: $(verbose) : # Copyright (c) 2013-2015, Loïc Hoguin # Copyright (c) 2015-2016, Jean-Sébastien Pédron # This file is part of erlang.mk and subject to the terms of the ISC License. # Fetch dependencies recursively (without building them). .PHONY: fetch-deps fetch-doc-deps fetch-rel-deps fetch-test-deps \ fetch-shell-deps .PHONY: $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) fetch-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) fetch-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) fetch-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) fetch-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) fetch-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) ifneq ($(SKIP_DEPS),) $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(verbose) :> $@ else # By default, we fetch "normal" dependencies. They are also included no # matter the type of requested dependencies. # # $(ALL_DEPS_DIRS) includes $(BUILD_DEPS). $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_DOC_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_REL_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_TEST_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_SHELL_DEPS_DIRS) # Allow to use fetch-deps and $(DEP_TYPES) to fetch multiple types of # dependencies with a single target. ifneq ($(filter doc,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DOC_DEPS_DIRS) endif ifneq ($(filter rel,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_REL_DEPS_DIRS) endif ifneq ($(filter test,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_TEST_DEPS_DIRS) endif ifneq ($(filter shell,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_SHELL_DEPS_DIRS) endif ERLANG_MK_RECURSIVE_TMP_LIST := $(abspath $(ERLANG_MK_TMP)/recursive-tmp-deps-$(shell echo $$PPID).log) $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): | $(ERLANG_MK_TMP) ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST) endif $(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST) $(verbose) set -e; for dep in $^ ; do \ if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \ echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \ if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \ $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \ $(MAKE) -C $$dep fetch-deps \ IS_DEP=1 \ ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \ fi \ fi \ done ifeq ($(IS_APP)$(IS_DEP),) $(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | \ uniq > $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $(verbose) cmp -s $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ \ || mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST) endif endif # ifneq ($(SKIP_DEPS),) # List dependencies recursively. .PHONY: list-deps list-doc-deps list-rel-deps list-test-deps \ list-shell-deps list-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) list-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) list-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) list-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) list-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) list-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps: $(verbose) cat $^ # Query dependencies recursively. .PHONY: query-deps query-doc-deps query-rel-deps query-test-deps \ query-shell-deps QUERY ?= name fetch_method repo version define query_target $(1): $(2) clean-tmp-query.log ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(4) endif $(verbose) $(foreach dep,$(3),\ echo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $(4) ;) $(if $(filter-out query-deps,$(1)),,\ $(verbose) set -e; for dep in $(3) ; do \ if grep -qs ^$$$$dep$$$$ $(ERLANG_MK_TMP)/query.log; then \ :; \ else \ echo $$$$dep >> $(ERLANG_MK_TMP)/query.log; \ $(MAKE) -C $(DEPS_DIR)/$$$$dep $$@ QUERY="$(QUERY)" IS_DEP=1 || true; \ fi \ done) ifeq ($(IS_APP)$(IS_DEP),) $(verbose) touch $(4) $(verbose) cat $(4) endif endef clean-tmp-query.log: ifeq ($(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_TMP)/query.log endif $(eval $(call query_target,query-deps,$(ERLANG_MK_RECURSIVE_DEPS_LIST),$(BUILD_DEPS) $(DEPS),$(ERLANG_MK_QUERY_DEPS_FILE))) $(eval $(call query_target,query-doc-deps,$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST),$(DOC_DEPS),$(ERLANG_MK_QUERY_DOC_DEPS_FILE))) $(eval $(call query_target,query-rel-deps,$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST),$(REL_DEPS),$(ERLANG_MK_QUERY_REL_DEPS_FILE))) $(eval $(call query_target,query-test-deps,$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST),$(TEST_DEPS),$(ERLANG_MK_QUERY_TEST_DEPS_FILE))) $(eval $(call query_target,query-shell-deps,$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST),$(SHELL_DEPS),$(ERLANG_MK_QUERY_SHELL_DEPS_FILE))) ranch-2.2.0/examples/000077500000000000000000000000001475363377500144505ustar00rootroot00000000000000ranch-2.2.0/examples/tcp_echo/000077500000000000000000000000001475363377500162345ustar00rootroot00000000000000ranch-2.2.0/examples/tcp_echo/Makefile000066400000000000000000000001451475363377500176740ustar00rootroot00000000000000PROJECT = tcp_echo DEPS = ranch dep_ranch_commit = master REL_DEPS = relx include ../../erlang.mk ranch-2.2.0/examples/tcp_echo/README.md000066400000000000000000000010371475363377500175140ustar00rootroot00000000000000Ranch 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. ranch-2.2.0/examples/tcp_echo/relx.config000066400000000000000000000001251475363377500203730ustar00rootroot00000000000000{release, {tcp_echo_example, "1"}, [tcp_echo, sasl]}. {extended_start_script, true}. ranch-2.2.0/examples/tcp_echo/src/000077500000000000000000000000001475363377500170235ustar00rootroot00000000000000ranch-2.2.0/examples/tcp_echo/src/echo_protocol.erl000066400000000000000000000011021475363377500223600ustar00rootroot00000000000000%% 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. ranch-2.2.0/examples/tcp_echo/src/tcp_echo_app.erl000066400000000000000000000005531475363377500221560ustar00rootroot00000000000000%% 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. ranch-2.2.0/examples/tcp_echo/src/tcp_echo_sup.erl000066400000000000000000000005621475363377500222050ustar00rootroot00000000000000%% 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}, []}}. ranch-2.2.0/examples/tcp_reverse/000077500000000000000000000000001475363377500167715ustar00rootroot00000000000000ranch-2.2.0/examples/tcp_reverse/Makefile000066400000000000000000000001501475363377500204250ustar00rootroot00000000000000PROJECT = tcp_reverse DEPS = ranch dep_ranch_commit = master REL_DEPS = relx include ../../erlang.mk ranch-2.2.0/examples/tcp_reverse/README.md000066400000000000000000000014621475363377500202530ustar00rootroot00000000000000Ranch 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. ranch-2.2.0/examples/tcp_reverse/relx.config000066400000000000000000000001331475363377500211270ustar00rootroot00000000000000{release, {tcp_reverse_example, "1"}, [tcp_reverse, sasl]}. {extended_start_script, true}. ranch-2.2.0/examples/tcp_reverse/src/000077500000000000000000000000001475363377500175605ustar00rootroot00000000000000ranch-2.2.0/examples/tcp_reverse/src/reverse_protocol.erl000066400000000000000000000045671475363377500236740ustar00rootroot00000000000000%% 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), <> = B0, case <> of %% Take care of different possible line terminators. <<$\n, $\r, B2/binary>> -> %% CR/LF (Windows) <>; <<$\n, B2/binary>> -> %% LF (Linux, Mac OS X and later) <>; <<$\r, B2/binary>> -> %% CR (Mac Classic, ie prior to Mac OS X) <> end. ranch-2.2.0/examples/tcp_reverse/src/tcp_reverse_app.erl000066400000000000000000000005751475363377500234540ustar00rootroot00000000000000%% 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. ranch-2.2.0/examples/tcp_reverse/src/tcp_reverse_sup.erl000066400000000000000000000005701475363377500234760ustar00rootroot00000000000000%% 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}, []}}. ranch-2.2.0/src/000077500000000000000000000000001475363377500134215ustar00rootroot00000000000000ranch-2.2.0/src/ranch.appup000066400000000000000000000126071475363377500155710ustar00rootroot00000000000000%% 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.2.0", [{<<"2\\.1\\.[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\\.1\\.[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.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, []}} ]}] }. ranch-2.2.0/src/ranch.erl000066400000000000000000000515731475363377500152330ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% Copyright (c) Maria Scott %% %% 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]). %% Internal -export([compat_normalize_alarms_option/1]). -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, threshold := 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, _) -> Alarms1 = compat_normalize_alarms_option(Alarms), maps:fold( fun (_, Opts, true) -> validate_alarm(Opts); (_, _, false) -> false end, true, Alarms1); 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, threshold := Threshold, callback := Callback}) -> is_integer(Threshold) andalso Threshold >= 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) -> %% The stop procedure must be executed in a separate %% process to make sure that it won't be interrupted %% in the middle in case the calling process crashes. %% We use erpc:call locally so we don't have to %% implement a custom spawn/call mechanism. %% We need to provide an integer timeout to erpc:call, %% otherwise the function will be executed in the calling %% process. 5 minutes should be enough. erpc:call(node(), fun() -> stop_listener1(Ref) end, 300000). stop_listener1(Ref) -> TransportAndOpts = maybe_get_transport_and_opts(Ref), _ = supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}), ok = ranch_server:cleanup_listener_opts(Ref), Result = supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref}), ok = stop_listener2(TransportAndOpts), Result. stop_listener2({Transport, TransOpts}) -> Transport:cleanup(TransOpts), ok; stop_listener2(undefined) -> ok. maybe_get_transport_and_opts(Ref) -> try [_, Transport, _, _, _] = ranch_server:get_listener_start_args(Ref), TransOpts = get_transport_options(Ref), {Transport, TransOpts} catch error:badarg -> undefined 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} -> PeerInfo = get_peer_info(Transport, CSocket, undefined), Handshake = handshake_transport(Transport, handshake, CSocket, Opts, Timeout), handshake_result(Handshake, Ref, Transport, CSocket, PeerInfo, 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} -> PeerInfo = get_peer_info(Transport, CSocket, undefined), Handshake = handshake_transport(Transport, handshake_continue, CSocket, Opts, Timeout), handshake_result(Handshake, Ref, Transport, CSocket, PeerInfo, 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, PeerInfo0, Timeout) -> case Result of OK = {ok, _} -> OK; {ok, CSocket2, Info} -> self() ! {handshake_continue, Ref, Transport, CSocket2, Timeout}, {continue, Info}; {error, Reason} -> PeerInfo = get_peer_info(Transport, CSocket, PeerInfo0), ok = Transport:close(CSocket), exit({shutdown, {Reason, PeerInfo}}) end. -spec handshake_cancel(ref()) -> ok. handshake_cancel(Ref) -> receive {handshake_continue, Ref, Transport, CSocket, _} -> Transport:handshake_cancel(CSocket) end. get_peer_info(Transport, Socket, Default) -> case Transport:peername(Socket) of {ok, Peer} -> Peer; {error, _} -> Default 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). %% For backwards compatibility with the misspelled alarm %% setting `treshold`. %% See https://github.com/ninenines/ranch/issues/349 -spec compat_normalize_alarms_option(any()) -> any(). compat_normalize_alarms_option(Alarms = #{}) -> maps:map( fun (_, Alarm = #{threshold := _}) -> maps:remove(treshold, Alarm); (_, Alarm = #{treshold := Threshold}) -> maps:put(threshold, Threshold, maps:remove(treshold, Alarm)); (_, Alarm) -> Alarm end, Alarms ); compat_normalize_alarms_option(Alarms) -> Alarms. ranch-2.2.0/src/ranch_acceptor.erl000066400000000000000000000051341475363377500171030ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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. ranch-2.2.0/src/ranch_acceptors_sup.erl000066400000000000000000000121321475363377500201510ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% %% 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, []), SocketOpts = hide_socket_opts(SocketOpts0), TransOpts = TransOpts0#{socket_opts => SocketOpts}, ranch:log(error, "Failed to start Ranch listener ~p in ~p:listen(~0p) for reason ~p (~s)~n", [Ref, Transport, TransOpts, Reason, format_error(Transport, Reason)], Logger), exit({listen_error, Ref, Reason}). hide_socket_opts([]) -> []; hide_socket_opts([{cert, _}|SocketOpts]) -> [{cert, '...'}|hide_socket_opts(SocketOpts)]; hide_socket_opts([{key, _}|SocketOpts]) -> [{key, '...'}|hide_socket_opts(SocketOpts)]; hide_socket_opts([{cacerts, _}|SocketOpts]) -> [{cacerts, '...'}|hide_socket_opts(SocketOpts)]; hide_socket_opts([{password, _}|SocketOpts]) -> [{password, '...'}|hide_socket_opts(SocketOpts)]; hide_socket_opts([{certs_keys, _}|SocketOpts]) -> [{certs_keys, '...'}|hide_socket_opts(SocketOpts)]; hide_socket_opts([SocketOpt|SocketOpts]) -> [SocketOpt|hide_socket_opts(SocketOpts)]. %% Handling of no_cert really should be done in ranch_ssl. We leave it here for %% backwards compatibility with possibly existing custom transports without a %% format_error/1 implementation that may rely on this module handling it. %% TODO: Remove in Ranch 3.0 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(Transport, Reason) -> %% TODO: Required callback in Ranch 3.0 case erlang:function_exported(Transport, format_error, 1) of true -> Transport:format_error(Reason); false -> lists:flatten(io_lib:format("~0p", [Reason])) end. ranch-2.2.0/src/ranch_app.erl000066400000000000000000000027631475363377500160700ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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. ranch-2.2.0/src/ranch_conns_sup.erl000066400000000000000000000426431475363377500173200ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Maria Scott %% %% 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: ~0p~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:~0p~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=#{threshold := Threshold, callback := Callback}, undefined}, CurConns) when CurConns >= Threshold -> 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) -> Alarms1 = ranch:compat_normalize_alarms_option(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, #{}, Alarms1 ); 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(Misc, _, _, _) -> {ok, Misc}. %% We use ~0p 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: ~0p~n", [Ref, Protocol, Pid, Reason], Logger). ranch-2.2.0/src/ranch_conns_sup_sup.erl000066400000000000000000000035011475363377500201750ustar00rootroot00000000000000%% Copyright (c) Jan Uhlig %% %% 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}}. ranch-2.2.0/src/ranch_crc32c.erl000066400000000000000000000123651475363377500163660ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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, <>) -> 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. ranch-2.2.0/src/ranch_embedded_sup.erl000066400000000000000000000031421475363377500177200ustar00rootroot00000000000000%% Copyright (c) Jan Uhlig %% %% 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]}}. ranch-2.2.0/src/ranch_listener_sup.erl000066400000000000000000000035441475363377500200220ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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}}. ranch-2.2.0/src/ranch_protocol.erl000066400000000000000000000020121475363377500171340ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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()}. ranch-2.2.0/src/ranch_proxy_header.erl000066400000000000000000000773151475363377500200060ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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_proxy_header). -export([parse/1]). -export([header/1]). -export([header/2]). -export([to_connection_info/1]). -type 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. unique_id => binary(), %% Opaque byte sequence of up to 128 bytes. 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. }, netns => binary(), %% US-ASCII. %% Unknown TLVs can't be parsed so the raw data is given. raw_tlvs => [{0..255, binary()}] }. -export_type([proxy_info/0]). -type build_opts() :: #{ checksum => crc32c, padding => pos_integer() %% >= 3 }. %% Parsing. -spec parse(Data) -> {ok, proxy_info(), Data} | {error, atom()} when Data::binary(). parse(<<"\r\n\r\n\0\r\nQUIT\n", Rest/bits>>) -> parse_v2(Rest); parse(<<"PROXY ", Rest/bits>>) -> parse_v1(Rest); parse(_) -> {error, 'The PROXY protocol header signature was not recognized. (PP 2.1, PP 2.2)'}. -ifdef(TEST). parse_unrecognized_header_test() -> {error, _} = parse(<<"GET / HTTP/1.1\r\n">>), ok. -endif. %% Human-readable header format (Version 1). parse_v1(<<"TCP4 ", Rest/bits>>) -> parse_v1(Rest, ipv4); parse_v1(<<"TCP6 ", Rest/bits>>) -> parse_v1(Rest, ipv6); parse_v1(<<"UNKNOWN\r\n", Rest/bits>>) -> {ok, #{ version => 1, command => proxy, transport_family => undefined, transport_protocol => undefined }, Rest}; parse_v1(<<"UNKNOWN ", Rest0/bits>>) -> case binary:split(Rest0, <<"\r\n">>) of [_, Rest] -> {ok, #{ version => 1, command => proxy, transport_family => undefined, transport_protocol => undefined }, Rest}; [_] -> {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'} end; parse_v1(_) -> {error, 'The INET protocol and family string was not recognized. (PP 2.1)'}. parse_v1(Rest0, Family) -> try {ok, SrcAddr, Rest1} = parse_ip(Rest0, Family), {ok, DestAddr, Rest2} = parse_ip(Rest1, Family), {ok, SrcPort, Rest3} = parse_port(Rest2, $\s), {ok, DestPort, Rest4} = parse_port(Rest3, $\r), <<"\n", Rest/bits>> = Rest4, {ok, #{ version => 1, command => proxy, transport_family => Family, transport_protocol => stream, src_address => SrcAddr, src_port => SrcPort, dest_address => DestAddr, dest_port => DestPort }, Rest} catch throw:parse_ipv4_error -> {error, 'Failed to parse an IPv4 address in the PROXY protocol header line. (PP 2.1)'}; throw:parse_ipv6_error -> {error, 'Failed to parse an IPv6 address in the PROXY protocol header line. (PP 2.1)'}; throw:parse_port_error -> {error, 'Failed to parse a port number in the PROXY protocol header line. (PP 2.1)'}; _:_ -> {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'} end. parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(<>, ipv4) -> parse_ipv4(Addr, Rest); parse_ip(Data, ipv6) -> [Addr, Rest] = binary:split(Data, <<$\s>>), parse_ipv6(Addr, Rest). parse_ipv4(Addr0, Rest) -> case inet:parse_ipv4strict_address(binary_to_list(Addr0)) of {ok, Addr} -> {ok, Addr, Rest}; {error, einval} -> throw(parse_ipv4_error) end. parse_ipv6(Addr0, Rest) -> case inet:parse_ipv6strict_address(binary_to_list(Addr0)) of {ok, Addr} -> {ok, Addr, Rest}; {error, einval} -> throw(parse_ipv6_error) end. parse_port(<>, C) -> parse_port(Port, Rest); parse_port(<>, C) -> parse_port(Port, Rest); parse_port(<>, C) -> parse_port(Port, Rest); parse_port(<>, C) -> parse_port(Port, Rest); parse_port(<>, C) -> parse_port(Port, Rest); parse_port(Port0, Rest) -> try binary_to_integer(Port0) of Port when Port > 0, Port =< 65535 -> {ok, Port, Rest}; _ -> throw(parse_port_error) catch _:_ -> throw(parse_port_error) end. -ifdef(TEST). parse_v1_test() -> %% Examples taken from the PROXY protocol header specification. {ok, #{ version := 1, command := proxy, transport_family := ipv4, transport_protocol := stream, src_address := {255, 255, 255, 255}, src_port := 65535, dest_address := {255, 255, 255, 255}, dest_port := 65535 }, <<>>} = parse(<<"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n">>), {ok, #{ version := 1, command := proxy, transport_family := ipv6, transport_protocol := stream, src_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, src_port := 65535, dest_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, dest_port := 65535 }, <<>>} = parse(<<"PROXY TCP6 " "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff " "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>), {ok, #{ version := 1, command := proxy, transport_family := undefined, transport_protocol := undefined }, <<>>} = parse(<<"PROXY UNKNOWN\r\n">>), {ok, #{ version := 1, command := proxy, transport_family := undefined, transport_protocol := undefined }, <<>>} = parse(<<"PROXY UNKNOWN " "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff " "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>), {ok, #{ version := 1, command := proxy, transport_family := ipv4, transport_protocol := stream, src_address := {192, 168, 0, 1}, src_port := 56324, dest_address := {192, 168, 0, 11}, dest_port := 443 }, <<"GET / HTTP/1.1\r\nHost: 192.168.0.11\r\n\r\n">>} = parse(<< "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n" "GET / HTTP/1.1\r\n" "Host: 192.168.0.11\r\n" "\r\n">>), %% Test cases taken from tomciopp/proxy_protocol. {ok, #{ version := 1, command := proxy, transport_family := ipv4, transport_protocol := stream, src_address := {192, 168, 0, 1}, src_port := 56324, dest_address := {192, 168, 0, 11}, dest_port := 443 }, <<"GET / HTTP/1.1\r">>} = parse(<< "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>), {error, _} = parse(<<"PROXY TCP4 192.1638.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>), {error, _} = parse(<<"PROXY TCP4 192.168.0.1 192.168.0.11 1111111 443\r\nGET / HTTP/1.1\r">>), {ok, #{ version := 1, command := proxy, transport_family := ipv6, transport_protocol := stream, src_address := {8193, 3512, 0, 66, 0, 35374, 880, 29492}, src_port := 4124, dest_address := {8193, 3512, 0, 66, 0, 35374, 880, 29493}, dest_port := 443 }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY TCP6 " "2001:0db8:0000:0042:0000:8a2e:0370:7334 " "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>), {error, _} = parse(<<"PROXY TCP6 " "2001:0db8:0000:0042:0000:8a2e:0370:7334 " "2001:0db8:00;0:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>), {error, _} = parse(<<"PROXY TCP6 " "2001:0db8:0000:0042:0000:8a2e:0370:7334 " "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 foo\r\nGET / HTTP/1.1\r">>), {ok, #{ version := 1, command := proxy, transport_family := undefined, transport_protocol := undefined }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN 4124 443\r\nGET / HTTP/1.1\r">>), {ok, #{ version := 1, command := proxy, transport_family := undefined, transport_protocol := undefined }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN\r\nGET / HTTP/1.1\r">>), ok. -endif. %% Binary header format (version 2). %% LOCAL. parse_v2(<<2:4, 0:4, _:8, Len:16, Rest0/bits>>) -> case Rest0 of <<_:Len/binary, Rest/bits>> -> {ok, #{ version => 2, command => local }, Rest}; _ -> {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'} end; %% PROXY. parse_v2(<<2:4, 1:4, Family:4, Protocol:4, Len:16, Rest/bits>>) when Family =< 3, Protocol =< 2 -> case Rest of <> -> parse_v2(Rest, Len, parse_family(Family), parse_protocol(Protocol), <>); _ -> {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'} end; %% Errors. parse_v2(<>) when Version =/= 2 -> {error, 'Invalid version in the PROXY protocol binary header. (PP 2.2)'}; parse_v2(<<_:4, Command:4, _/bits>>) when Command > 1 -> {error, 'Invalid command in the PROXY protocol binary header. (PP 2.2)'}; parse_v2(<<_:8, Family:4, _/bits>>) when Family > 3 -> {error, 'Invalid address family in the PROXY protocol binary header. (PP 2.2)'}; parse_v2(<<_:12, Protocol:4, _/bits>>) when Protocol > 2 -> {error, 'Invalid transport protocol in the PROXY protocol binary header. (PP 2.2)'}. parse_family(0) -> undefined; parse_family(1) -> ipv4; parse_family(2) -> ipv6; parse_family(3) -> unix. parse_protocol(0) -> undefined; parse_protocol(1) -> stream; parse_protocol(2) -> dgram. parse_v2(Data, Len, Family, Protocol, _) when Family =:= undefined; Protocol =:= undefined -> <<_:Len/binary, Rest/bits>> = Data, {ok, #{ version => 2, command => proxy, %% In case only one value was undefined, we set both explicitly. %% It doesn't make sense to have only one known value. transport_family => undefined, transport_protocol => undefined }, Rest}; parse_v2(<< S1, S2, S3, S4, D1, D2, D3, D4, SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv4, Protocol, Header) when Len >= 12 -> parse_tlv(Rest, Len - 12, #{ version => 2, command => proxy, transport_family => Family, transport_protocol => Protocol, src_address => {S1, S2, S3, S4}, src_port => SrcPort, dest_address => {D1, D2, D3, D4}, dest_port => DestPort }, Header); parse_v2(<< S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16, D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16, SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv6, Protocol, Header) when Len >= 36 -> parse_tlv(Rest, Len - 36, #{ version => 2, command => proxy, transport_family => Family, transport_protocol => Protocol, src_address => {S1, S2, S3, S4, S5, S6, S7, S8}, src_port => SrcPort, dest_address => {D1, D2, D3, D4, D5, D6, D7, D8}, dest_port => DestPort }, Header); parse_v2(<>, Len, Family=unix, Protocol, Header) when Len >= 216 -> try [SrcAddr, _] = binary:split(SrcAddr0, <<0>>), true = byte_size(SrcAddr) > 0, [DestAddr, _] = binary:split(DestAddr0, <<0>>), true = byte_size(DestAddr) > 0, parse_tlv(Rest, Len - 216, #{ version => 2, command => proxy, transport_family => Family, transport_protocol => Protocol, src_address => SrcAddr, dest_address => DestAddr }, Header) catch _:_ -> {error, 'Invalid UNIX address in PROXY protocol binary header. (PP 2.2)'} end; parse_v2(_, _, _, _, _) -> {error, 'Invalid length in the PROXY protocol binary header. (PP 2.2)'}. -ifdef(TEST). parse_v2_test() -> %% Test cases taken from tomciopp/proxy_protocol. {ok, #{ 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 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 33, %% Version and command. 17, %% Family and protocol. 0, 12, %% Length. 127, 0, 0, 1, %% Source address. 192, 168, 0, 1, %% Destination address. 1, 188, %% Source port. 1, 187, %% Destination port. "GET / HTTP/1.1\r\n">>), {ok, #{ version := 2, command := proxy, transport_family := ipv4, transport_protocol := dgram, src_address := {127, 0, 0, 1}, src_port := 444, dest_address := {192, 168, 0, 1}, dest_port := 443 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 33, %% Version and command. 18, %% Family and protocol. 0, 12, %% Length. 127, 0, 0, 1, %% Source address. 192, 168, 0, 1, %% Destination address. 1, 188, %% Source port. 1, 187, %% Destination port. "GET / HTTP/1.1\r\n">>), {ok, #{ version := 2, command := proxy, transport_family := ipv6, transport_protocol := stream, src_address := {5532, 4240, 1, 0, 0, 0, 0, 0}, src_port := 444, dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0}, dest_port := 443 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 33, %% Version and command. 33, %% Family and protocol. 0, 36, %% Length. 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address. 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address. 1, 188, %% Source port. 1, 187, %% Destination port. "GET / HTTP/1.1\r\n">>), {ok, #{ version := 2, command := proxy, transport_family := ipv6, transport_protocol := dgram, src_address := {5532, 4240, 1, 0, 0, 0, 0, 0}, src_port := 444, dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0}, dest_port := 443 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 33, %% Version and command. 34, %% Family and protocol. 0, 36, %% Length. 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address. 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address. 1, 188, %% Source port. 1, 187, %% Destination port. "GET / HTTP/1.1\r\n">>), Path = <<"/var/pgsql_sock">>, Len = byte_size(Path), Padding = 8 * (108 - Len), {ok, #{ version := 2, command := proxy, transport_family := unix, transport_protocol := stream, src_address := Path, dest_address := Path }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 49, 0, 216, Path/binary, 0:Padding, Path/binary, 0:Padding, "GET / HTTP/1.1\r\n">>), {ok, #{ version := 2, command := proxy, transport_family := unix, transport_protocol := dgram, src_address := Path, dest_address := Path }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 50, 0, 216, Path/binary, 0:Padding, Path/binary, 0:Padding, "GET / HTTP/1.1\r\n">>), ok. parse_v2_regression_test() -> %% Real packet received from AWS. We confirm that the CRC32C %% check succeeds only (in other words that ok is returned). {ok, _, <<>>} = parse(<< 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 84, 172, 31, 7, 113, 172, 31, 10, 31, 200, 242, 0, 80, 3, 0, 4, 232, 214, 137, 45, 234, 0, 23, 1, 118, 112, 99, 101, 45, 48, 56, 100, 50, 98, 102, 49, 53, 102, 97, 99, 53, 48, 48, 49, 99, 57, 4, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>), ok. -endif. parse_tlv(Rest, 0, Info, _) -> {ok, Info, Rest}; %% PP2_TYPE_ALPN. parse_tlv(<<16#1, TLVLen:16, ALPN:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> parse_tlv(Rest, Len - TLVLen - 3, Info#{alpn => ALPN}, Header); %% PP2_TYPE_AUTHORITY. parse_tlv(<<16#2, TLVLen:16, Authority:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> parse_tlv(Rest, Len - TLVLen - 3, Info#{authority => Authority}, Header); %% PP2_TYPE_CRC32C. parse_tlv(<<16#3, TLVLen:16, CRC32C:32, Rest/bits>>, Len0, Info, Header) when TLVLen =:= 4 -> Len = Len0 - TLVLen - 3, BeforeLen = byte_size(Header) - Len - TLVLen, <> = Header, %% The initial CRC is ranch_crc32c:crc32c(<<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>). case ranch_crc32c:crc32c(2900412422, [Before, <<0:32>>, After]) of CRC32C -> parse_tlv(Rest, Len, Info, Header); _ -> {error, 'Failed CRC32C verification in PROXY protocol binary header. (PP 2.2)'} end; %% PP2_TYPE_NOOP. parse_tlv(<<16#4, TLVLen:16, _:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> parse_tlv(Rest, Len - TLVLen - 3, Info, Header); %% PP2_TYPE_UNIQUE_ID. parse_tlv(<<16#5, TLVLen:16, UniqueID:TLVLen/binary, Rest/bits>>, Len, Info, Header) when TLVLen =< 128 -> parse_tlv(Rest, Len - TLVLen - 3, Info#{unique_id => UniqueID}, Header); parse_tlv(<<16#5, _/bits>>, _, _, _) -> {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2, PP 2.2.5)'}; %% PP2_TYPE_SSL. parse_tlv(<<16#20, TLVLen:16, Client, Verify:32, Rest0/bits>>, Len, Info, Header) -> SubsLen = TLVLen - 5, case Rest0 of <> -> SSL0 = #{ client => parse_client(<>), verified => Verify =:= 0 }, case parse_ssl_tlv(Subs, SubsLen, SSL0) of {ok, SSL, <<>>} -> parse_tlv(Rest, Len - TLVLen - 3, Info#{ssl => SSL}, Header); Error={error, _} -> Error end; _ -> {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'} end; %% PP2_TYPE_NETNS. parse_tlv(<<16#30, TLVLen:16, NetNS:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> parse_tlv(Rest, Len - TLVLen - 3, Info#{netns => NetNS}, Header); %% Unknown TLV. parse_tlv(<>, Len, Info, Header) -> RawTLVs = maps:get(raw_tlvs, Info, []), parse_tlv(Rest, Len - TLVLen - 3, Info#{raw_tlvs => [{TLVType, TLVValue}|RawTLVs]}, Header); %% Invalid TLV length. parse_tlv(_, _, _, _) -> {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}. parse_client(<<_:5, ClientCertSess:1, ClientCertConn:1, ClientSSL:1>>) -> Client0 = case ClientCertSess of 0 -> []; 1 -> [cert_sess] end, Client1 = case ClientCertConn of 0 -> Client0; 1 -> [cert_conn|Client0] end, case ClientSSL of 0 -> Client1; 1 -> [ssl|Client1] end. parse_ssl_tlv(Rest, 0, Info) -> {ok, Info, Rest}; %% Valid TLVs. parse_ssl_tlv(<>, Len, Info) -> case ssl_subtype(TLVType) of undefined -> {error, 'Invalid TLV subtype for PP2_TYPE_SSL in PROXY protocol binary header. (PP 2.2)'}; Type -> parse_ssl_tlv(Rest, Len - TLVLen - 3, Info#{Type => TLVValue}) end; %% Invalid TLV length. parse_ssl_tlv(_, _, _) -> {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}. ssl_subtype(16#21) -> version; ssl_subtype(16#22) -> cn; ssl_subtype(16#23) -> cipher; ssl_subtype(16#24) -> sig_alg; ssl_subtype(16#25) -> key_alg; ssl_subtype(_) -> undefined. %% Building. -spec header(proxy_info()) -> iodata(). header(ProxyInfo) -> header(ProxyInfo, #{}). -spec header(proxy_info(), build_opts()) -> iodata(). header(#{version := 2, command := local}, _) -> <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 0:28>>; header(#{version := 2, command := proxy, transport_family := Family, transport_protocol := Protocol}, _) when Family =:= undefined; Protocol =:= undefined -> <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4, 0:24>>; header(ProxyInfo=#{version := 2, command := proxy, transport_family := Family, transport_protocol := Protocol}, Opts) -> Addresses = addresses(ProxyInfo), TLVs = tlvs(ProxyInfo, Opts), ExtraLen = case Opts of #{checksum := crc32c} -> 7; _ -> 0 end, Len = iolist_size(Addresses) + iolist_size(TLVs) + ExtraLen, Header = [ <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>, <<(family(Family)):4, (protocol(Protocol)):4>>, <>, Addresses, TLVs ], case Opts of #{checksum := crc32c} -> CRC32C = ranch_crc32c:crc32c([Header, <<16#3, 4:16, 0:32>>]), [Header, <<16#3, 4:16, CRC32C:32>>]; _ -> Header end; header(#{version := 1, command := proxy, transport_family := undefined, transport_protocol := undefined}, _) -> <<"PROXY UNKNOWN\r\n">>; header(#{version := 1, command := proxy, transport_family := Family0, transport_protocol := stream, src_address := SrcAddress, src_port := SrcPort, dest_address := DestAddress, dest_port := DestPort}, _) when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> [ <<"PROXY ">>, case Family0 of ipv4 when tuple_size(SrcAddress) =:= 4, tuple_size(DestAddress) =:= 4 -> [<<"TCP4 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)]; ipv6 when tuple_size(SrcAddress) =:= 8, tuple_size(DestAddress) =:= 8 -> [<<"TCP6 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)] end, $\s, integer_to_binary(SrcPort), $\s, integer_to_binary(DestPort), $\r, $\n ]. family(ipv4) -> 1; family(ipv6) -> 2; family(unix) -> 3. protocol(stream) -> 1; protocol(dgram) -> 2. addresses(#{transport_family := ipv4, src_address := {S1, S2, S3, S4}, src_port := SrcPort, dest_address := {D1, D2, D3, D4}, dest_port := DestPort}) when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> <>; addresses(#{transport_family := ipv6, src_address := {S1, S2, S3, S4, S5, S6, S7, S8}, src_port := SrcPort, dest_address := {D1, D2, D3, D4, D5, D6, D7, D8}, dest_port := DestPort}) when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> << S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16, D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16, SrcPort:16, DestPort:16 >>; addresses(#{transport_family := unix, src_address := SrcAddress, dest_address := DestAddress}) when byte_size(SrcAddress) =< 108, byte_size(DestAddress) =< 108 -> SrcPadding = 8 * (108 - byte_size(SrcAddress)), DestPadding = 8 * (108 - byte_size(DestAddress)), << SrcAddress/binary, 0:SrcPadding, DestAddress/binary, 0:DestPadding >>. tlvs(ProxyInfo, Opts) -> [ binary_tlv(ProxyInfo, alpn, 16#1), binary_tlv(ProxyInfo, authority, 16#2), binary_tlv(ProxyInfo, unique_id, 16#5), ssl_tlv(ProxyInfo), binary_tlv(ProxyInfo, netns, 16#30), raw_tlvs(ProxyInfo), noop_tlv(Opts) ]. binary_tlv(Info, Key, Type) -> case Info of #{Key := Bin} -> Len = byte_size(Bin), <>; _ -> <<>> end. noop_tlv(#{padding := Len0}) when Len0 >= 3 -> Len = Len0 - 3, <<16#4, Len:16, 0:Len/unit:8>>; noop_tlv(_) -> <<>>. ssl_tlv(#{ssl := Info=#{client := Client0, verified := Verify0}}) -> Client = client(Client0, 0), Verify = if Verify0 -> 0; not Verify0 -> 1 end, TLVs = [ binary_tlv(Info, version, 16#21), binary_tlv(Info, cn, 16#22), binary_tlv(Info, cipher, 16#23), binary_tlv(Info, sig_alg, 16#24), binary_tlv(Info, key_alg, 16#25) ], Len = iolist_size(TLVs) + 5, [<<16#20, Len:16, Client, Verify:32>>, TLVs]; ssl_tlv(_) -> <<>>. client([], Client) -> Client; client([ssl|Tail], Client) -> client(Tail, Client bor 16#1); client([cert_conn|Tail], Client) -> client(Tail, Client bor 16#2); client([cert_sess|Tail], Client) -> client(Tail, Client bor 16#4). raw_tlvs(Info) -> [begin Len = byte_size(Bin), <> end || {Type, Bin} <- maps:get(raw_tlvs, Info, [])]. -ifdef(TEST). v1_test() -> Test1 = #{ version => 1, command => proxy, transport_family => undefined, transport_protocol => undefined }, {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), Test2 = #{ version => 1, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), Test3 = #{ version => 1, command => proxy, transport_family => ipv6, transport_protocol => stream, src_address => {1, 2, 3, 4, 5, 6, 7, 8}, src_port => 1234, dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8}, dest_port => 23456 }, {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), ok. v2_test() -> Test0 = #{ version => 2, command => local }, {ok, Test0, <<>>} = parse(iolist_to_binary(header(Test0))), Test1 = #{ version => 2, command => proxy, transport_family => undefined, transport_protocol => undefined }, {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), Test2 = #{ version => 2, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), Test3 = #{ version => 2, command => proxy, transport_family => ipv6, transport_protocol => stream, src_address => {1, 2, 3, 4, 5, 6, 7, 8}, src_port => 1234, dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8}, dest_port => 23456 }, {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), Test4 = #{ version => 2, command => proxy, transport_family => unix, transport_protocol => dgram, src_address => <<"/run/source.sock">>, dest_address => <<"/run/destination.sock">> }, {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))), ok. v2_tlvs_test() -> Common = #{ version => 2, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, Test1 = Common#{alpn => <<"h2">>}, {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), Test2 = Common#{authority => <<"internal.example.org">>}, {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), Test3 = Common#{netns => <<"/var/run/netns/example">>}, {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), Test4 = Common#{ssl => #{ client => [ssl, cert_conn, cert_sess], verified => true, version => <<"TLSv1.3">>, cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>, sig_alg => <<"SHA256">>, key_alg => <<"RSA2048">>, cn => <<"example.com">> }}, {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))), %% Note that the raw_tlvs order is not relevant and therefore %% the parser does not reverse the list it builds. Test5In = Common#{raw_tlvs => RawTLVs=[ %% The only custom TLV I am aware of is defined at: %% https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol {16#ea, <<16#1, "instance-id">>}, %% This TLV is entirely fictional. {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>} ]}, Test5Out = Test5In#{raw_tlvs => lists:reverse(RawTLVs)}, {ok, Test5Out, <<>>} = parse(iolist_to_binary(header(Test5In))), Test6 = Common#{unique_id => rand:bytes(rand:uniform(128))}, {ok, Test6, <<>>} = parse(iolist_to_binary(header(Test6))), ok. v2_checksum_test() -> Test = #{ version => 2, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{checksum => crc32c}))), ok. v2_padding_test() -> Test = #{ version => 2, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))), ok. -endif. %% Helper to convert proxy_info() to ssl:connection_info(). %% %% Because there isn't a lot of fields common to both types %% this only ends up returning the keys protocol, selected_cipher_suite %% and sni_hostname *at most*. %% The type ssl:connection_info/0 is not exported. We just %% replicate the relevant info tuples here. -type ssl_connection_info() :: [ {sni_hostname, term()} | {selected_cipher_suite, ssl:erl_cipher_suite()} | {protocol, ssl:protocol_version()} ]. -spec to_connection_info(proxy_info()) -> ssl_connection_info(). to_connection_info(ProxyInfo=#{ssl := SSL}) -> ConnInfo0 = case ProxyInfo of #{authority := Authority} -> [{sni_hostname, Authority}]; _ -> [] end, ConnInfo = case SSL of #{cipher := Cipher} -> case ssl:str_to_suite(binary_to_list(Cipher)) of {error, {not_recognized, _}} -> ConnInfo0; CipherInfo -> [{selected_cipher_suite, CipherInfo}|ConnInfo0] end; _ -> ConnInfo0 end, %% https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html case SSL of #{version := <<"TLSv1.3">>} -> [{protocol, 'tlsv1.3'}|ConnInfo]; #{version := <<"TLSv1.2">>} -> [{protocol, 'tlsv1.2'}|ConnInfo]; #{version := <<"TLSv1.1">>} -> [{protocol, 'tlsv1.1'}|ConnInfo]; #{version := <<"TLSv1">>} -> [{protocol, tlsv1}|ConnInfo]; #{version := <<"SSLv3">>} -> [{protocol, sslv3}|ConnInfo]; #{version := <<"SSLv2">>} -> [{protocol, sslv2}|ConnInfo]; %% <<"unknown">>, unsupported or missing version. _ -> ConnInfo end; %% No SSL/TLS information available. to_connection_info(_) -> []. -ifdef(TEST). to_connection_info_test() -> Common = #{ version => 2, command => proxy, transport_family => ipv4, transport_protocol => stream, src_address => {127, 0, 0, 1}, src_port => 1234, dest_address => {10, 11, 12, 13}, dest_port => 23456 }, %% Version 1. [] = to_connection_info(#{ version => 1, command => proxy, transport_family => undefined, transport_protocol => undefined }), [] = to_connection_info(Common#{version => 1}), %% Version 2, no ssl data. [] = to_connection_info(#{ version => 2, command => local }), [] = to_connection_info(#{ version => 2, command => proxy, transport_family => undefined, transport_protocol => undefined }), [] = to_connection_info(Common), [] = to_connection_info(#{ version => 2, command => proxy, transport_family => unix, transport_protocol => dgram, src_address => <<"/run/source.sock">>, dest_address => <<"/run/destination.sock">> }), [] = to_connection_info(Common#{netns => <<"/var/run/netns/example">>}), [] = to_connection_info(Common#{raw_tlvs => [ {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>} ]}), %% Version 2, with ssl-related data. [] = to_connection_info(Common#{alpn => <<"h2">>}), %% The authority alone is not enough to deduce that this is SNI. [] = to_connection_info(Common#{authority => <<"internal.example.org">>}), [ {protocol, 'tlsv1.3'}, {selected_cipher_suite, #{ cipher := aes_128_gcm, key_exchange := ecdhe_rsa, mac := aead, prf := sha256 }} ] = to_connection_info(Common#{ssl => #{ client => [ssl, cert_conn, cert_sess], verified => true, version => <<"TLSv1.3">>, cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>, sig_alg => <<"SHA256">>, key_alg => <<"RSA2048">>, cn => <<"example.com">> }}), [ {protocol, 'tlsv1.3'}, {selected_cipher_suite, #{ cipher := aes_128_gcm, key_exchange := ecdhe_rsa, mac := aead, prf := sha256 }}, {sni_hostname, <<"internal.example.org">>} ] = to_connection_info(Common#{authority => <<"internal.example.org">>, ssl => #{ client => [ssl, cert_conn, cert_sess], verified => true, version => <<"TLSv1.3">>, cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>, sig_alg => <<"SHA256">>, key_alg => <<"RSA2048">>, cn => <<"example.com">> }}), ok. -endif. ranch-2.2.0/src/ranch_server.erl000066400000000000000000000243051475363377500166120ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% %% 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]}. ranch-2.2.0/src/ranch_server_proxy.erl000066400000000000000000000042131475363377500200470ustar00rootroot00000000000000%% Copyright (c) Jan Uhlig %% %% 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. ranch-2.2.0/src/ranch_ssl.erl000066400000000000000000000302571475363377500161100ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% Copyright (c) Maria Scott %% %% 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]). -export([format_error/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()} | {certs_keys, [#{cert => public_key:der_encoded(), key => ssl:key(), certfile => file:filename(), keyfile => file:filename(), key_pem_password => iodata() | fun(() -> iodata())}]} | {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) orelse lists:keymember(certs_keys, 1, SocketOpts) of true -> Logger = maps:get(logger, TransOpts, logger), do_listen(SocketOpts, Logger); false -> {error, no_cert} end. do_listen(SocketOpts0, Logger) -> SocketOpts = set_default_options(SocketOpts0), 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}, {reuseaddr, true}], Logger)). set_default_options(SocketOpts0) -> case proplists:get_value(protocol, SocketOpts0, tls) of tls -> 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), ranch:set_option_default(SocketOpts3, send_timeout_close, true); dtls -> SocketOpts0 end. %% '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}]). %% @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}], 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. TCPSocket = case element(2, SSLSocket) of %% Before OTP-28. {gen_tcp, TCPSocket0, _, _} -> TCPSocket0; %% OTP-28+. TCPSocket0 -> TCPSocket0 end, 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. -spec format_error({error, ssl:reason()} | ssl:reason()) -> string(). format_error(no_cert) -> "no certificate provided; see cert, certfile, sni_fun or sni_hosts options"; format_error(Reason) -> ssl:format_error(Reason). 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. ranch-2.2.0/src/ranch_sup.erl000066400000000000000000000027061475363377500161140ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% %% 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}}. ranch-2.2.0/src/ranch_tcp.erl000066400000000000000000000224021475363377500160660ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% %% 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]). -export([format_error/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}, {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}]). %% @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}], 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. -spec format_error(inet:posix() | system_limit) -> string(). format_error(Reason) -> inet:format_error(Reason). ranch-2.2.0/src/ranch_transport.erl000066400000000000000000000142641475363377500173430ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% %% 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. -callback format_error(term()) -> string(). %% TODO: Required callback in Ranch 3.0 -optional_callbacks([format_error/1]). %% 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). ranch-2.2.0/test/000077500000000000000000000000001475363377500136115ustar00rootroot00000000000000ranch-2.2.0/test/acceptor_SUITE.erl000066400000000000000000002236621475363377500171010ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Jan Uhlig %% Copyright (c) Maria Scott %% %% 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(acceptor_SUITE). -compile(export_all). -compile(nowarn_export_all). -dialyzer({nowarn_function, misc_wait_for_connections/1}). %% @todo Remove when specs in ssl are updated to accept local addresses. -dialyzer({nowarn_function, do_ssl_local_echo/0}). -import(ct_helper, [config/2]). -import(ct_helper, [doc/1]). -import(ct_helper, [name/0]). %% ct. all() -> [{group, tcp}, {group, tcp_socket}, {group, ssl}, {group, misc}, {group, supervisor}]. groups() -> [{tcp, [ tcp_active_echo, tcp_active_n_echo, tcp_echo, tcp_local_echo, tcp_graceful, tcp_inherit_options, tcp_max_connections, tcp_max_connections_and_beyond, tcp_max_connections_infinity, tcp_remove_connections, tcp_remove_connections_acceptor_wakeup, tcp_set_max_connections, tcp_set_max_connections_clean, tcp_getopts_capability, tcp_getstat_capability, tcp_upgrade, tcp_10_acceptors_10_listen_sockets, tcp_many_listen_sockets_no_reuseport, tcp_error_eaddrinuse, tcp_error_eacces ]}, {tcp_socket, [ tcp_active_echo, tcp_active_n_echo, tcp_echo, tcp_graceful, tcp_inherit_options, tcp_max_connections, tcp_max_connections_and_beyond, tcp_max_connections_infinity, tcp_remove_connections, tcp_remove_connections_acceptor_wakeup, tcp_set_max_connections, tcp_set_max_connections_clean, tcp_getopts_capability, tcp_getstat_capability, tcp_upgrade, %% @TODO: Enable when https://github.com/erlang/otp/issues/5122 %% is in an official release, probably 24.1. % tcp_10_acceptors_10_listen_sockets, % tcp_many_listen_sockets_no_reuseport, tcp_error_eaddrinuse %% @TODO: Not working in OTP/24.0 but fixed in current master. %% Enable when fixed in an official release, probably 24.1. % tcp_error_eacces ]}, {ssl, [ ssl_accept_error, ssl_active_echo, ssl_active_n_echo, ssl_echo, ssl_local_echo, ssl_dtls_echo, ssl_graceful, ssl_handshake, ssl_handshake_error, ssl_sni_echo, ssl_sni_fail, ssl_tls_psk, ssl_tls_psk_fail, ssl_upgrade_from_tcp, ssl_getopts_capability, ssl_getstat_capability, ssl_10_acceptors_10_listen_sockets, ssl_many_listen_sockets_no_reuseport, ssl_error_eaddrinuse, ssl_error_no_cert, ssl_error_eacces, ssl_unsupported_tlsv13_options ]}, {misc, [ misc_bad_transport, misc_bad_transport_options, misc_repeated_remove, misc_info, misc_info_embedded, misc_metrics, misc_opts_logger, misc_post_listen_callback, misc_post_listen_callback_error, misc_set_transport_options, misc_wait_for_connections, misc_multiple_ip_local_socket_opts, misc_connection_alarms, misc_stop_unknown_listener, misc_repeated_start_stop, misc_listen_error ]}, {supervisor, [ connection_type_supervisor, connection_type_supervisor_separate_from_connection, supervisor_10_acceptors_1_conns_sup, supervisor_9_acceptors_4_conns_sups, supervisor_10_acceptors_10_conns_sups, supervisor_1_acceptor_10_conns_sups, supervisor_changed_options_restart, supervisor_clean_child_restart, supervisor_clean_restart, supervisor_conns_alive, supervisor_embedded_ranch_server_crash, supervisor_protocol_start_link_crash, supervisor_server_recover_state, supervisor_unexpected_message ]}]. 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 -> [{socket_opts, [{inet_backend, socket}]}|Config]; false -> {skip, "No socket backend support"} end; init_per_group(_, Config) -> case code:ensure_loaded(socket) =:= {module, socket} andalso erlang:function_exported(socket, use_registry, 1) of true -> [{socket_opts, [{inet_backend, inet}]}|Config]; false -> [{socket_opts, []}|Config] end. end_per_group(_, _) -> ok. init_per_testcase(_, Config) -> Config. end_per_testcase(_, _) -> %% Stop all listeners that a test case may have left running. _ = [catch ranch:stop_listener(Name) || Name <- maps:keys(ranch:info())], ok. %% misc. misc_bad_transport(_) -> doc("Reject invalid transport modules."), {error, {bad_transport, invalid_transport}} = ranch:start_listener(misc_bad_transport, invalid_transport, #{}, echo_protocol, []), ok. misc_bad_transport_options(_) -> doc("Ignore invalid transport options."), {ok, _} = ranch:start_listener(misc_bad_transport_options, ranch_tcp, [binary, {packet, 4}, <<"garbage">>, raw, backlog], echo_protocol, []), ok. misc_info(_) -> doc("Information about listeners."), %% Open a listener with a few connections. {ok, Pid1} = ranch:start_listener({misc_info, tcp}, ranch_tcp, #{num_acceptors => 1}, remove_conn_and_wait_protocol, [{remove, true, 2500}]), Port1 = ranch:get_port({misc_info, tcp}), %% Open a few more listeners with different arguments. {ok, Pid2} = ranch:start_listener({misc_info, act}, ranch_tcp, #{num_acceptors => 2}, active_echo_protocol, {}), Port2 = ranch:get_port({misc_info, act}), ranch:set_max_connections({misc_info, act}, infinity), Opts = ct_helper:get_certs_from_ets(), {ok, Pid3} = ranch:start_listener({misc_info, ssl}, ranch_ssl, #{num_acceptors => 3, socket_opts => Opts}, echo_protocol, [{}]), Port3 = ranch:get_port({misc_info, ssl}), %% Open 5 connections, 3 removed from the count. {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), receive after 250 -> ok end, ranch:set_protocol_options({misc_info, tcp}, [{remove, false, 2500}]), receive after 250 -> ok end, {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), receive after 250 -> ok end, %% Confirm the info returned by Ranch is correct. #{ {misc_info, act} := #{ pid := Pid2, port := Port2, max_connections := infinity, %% Option was modified. active_connections := 0, all_connections := 0, transport := ranch_tcp, transport_options := #{num_acceptors := 2}, protocol := active_echo_protocol, protocol_options := {} }, {misc_info, ssl} := #{ pid := Pid3, port := Port3, max_connections := 1024, active_connections := 0, all_connections := 0, transport := ranch_ssl, transport_options := #{num_acceptors := 3, socket_opts := Opts}, protocol := echo_protocol, protocol_options := [{}] }, {misc_info, tcp} := #{ pid := Pid1, port := Port1, max_connections := 1024, active_connections := 2, all_connections := 5, transport := ranch_tcp, transport_options := #{num_acceptors := 1}, protocol := remove_conn_and_wait_protocol, protocol_options := [{remove, false, 2500}] %% Option was modified. } } = ranch:info(), %% Get acceptors. [_] = ranch:procs({misc_info, tcp}, acceptors), [_, _] = ranch:procs({misc_info, act}, acceptors), [_, _, _] = ranch:procs({misc_info, ssl}, acceptors), %% Get connections. [_, _, _, _, _] = ranch:procs({misc_info, tcp}, connections), [] = ranch:procs({misc_info, act}, connections), [] = ranch:procs({misc_info, ssl}, connections), ok. misc_info_embedded(_) -> doc("Information about listeners in embedded mode."), {ok, SupPid} = embedded_sup:start_link(), %% Open a listener with a few connections. {ok, EmbeddedSupPid1} = embedded_sup:start_listener(SupPid, {misc_info_embedded, tcp}, ranch_tcp, #{num_acceptors => 1}, remove_conn_and_wait_protocol, [{remove, true, 2500}]), {_, Pid1, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, tcp}}, 1, supervisor:which_children(EmbeddedSupPid1)), Port1 = ranch:get_port({misc_info_embedded, tcp}), %% Open a few more listeners with different arguments. {ok, EmbeddedSupPid2} = embedded_sup:start_listener(SupPid, {misc_info_embedded, act}, ranch_tcp, #{num_acceptors => 2}, active_echo_protocol, {}), {_, Pid2, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, act}}, 1, supervisor:which_children(EmbeddedSupPid2)), Port2 = ranch:get_port({misc_info_embedded, act}), ranch:set_max_connections({misc_info_embedded, act}, infinity), Opts = ct_helper:get_certs_from_ets(), {ok, EmbeddedSupPid3} = embedded_sup:start_listener(SupPid, {misc_info_embedded, ssl}, ranch_ssl, #{num_acceptors => 3, socket_opts => Opts}, echo_protocol, [{}]), {_, Pid3, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, ssl}}, 1, supervisor:which_children(EmbeddedSupPid3)), Port3 = ranch:get_port({misc_info_embedded, ssl}), %% Open 5 connections, 3 removed from the count. {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), receive after 250 -> ok end, ranch:set_protocol_options({misc_info_embedded, tcp}, [{remove, false, 2500}]), receive after 250 -> ok end, {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), {ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]), receive after 250 -> ok end, %% Confirm the info returned by Ranch is correct. #{ {misc_info_embedded, act} := #{ pid := Pid2, port := Port2, max_connections := infinity, %% Option was modified. active_connections := 0, all_connections := 0, transport := ranch_tcp, transport_options := #{num_acceptors := 2}, protocol := active_echo_protocol, protocol_options := {} }, {misc_info_embedded, ssl} := #{ pid := Pid3, port := Port3, max_connections := 1024, active_connections := 0, all_connections := 0, transport := ranch_ssl, transport_options := #{num_acceptors := 3, socket_opts := Opts}, protocol := echo_protocol, protocol_options := [{}] }, {misc_info_embedded, tcp} := #{ pid := Pid1, port := Port1, max_connections := 1024, active_connections := 2, all_connections := 5, transport := ranch_tcp, transport_options := #{num_acceptors := 1}, protocol := remove_conn_and_wait_protocol, protocol_options := [{remove, false, 2500}] %% Option was modified. } } = ranch:info(), %% Get acceptors. [_] = ranch:procs({misc_info_embedded, tcp}, acceptors), [_, _] = ranch:procs({misc_info_embedded, act}, acceptors), [_, _, _] = ranch:procs({misc_info_embedded, ssl}, acceptors), %% Get connections. [_, _, _, _, _] = ranch:procs({misc_info_embedded, tcp}, connections), [] = ranch:procs({misc_info_embedded, act}, connections), [] = ranch:procs({misc_info_embedded, ssl}, connections), %% Stop embedded tcp listener and ensure it is gone. ok = embedded_sup:stop_listener(SupPid, {misc_info_embedded, tcp}), timer:sleep(500), false = maps:is_key({misc_info_embedded, tcp}, ranch:info()), %% Stop embedded act listener and ensure it is gone. ok = embedded_sup:stop_listener(SupPid, {misc_info_embedded, act}), timer:sleep(500), false = maps:is_key({misc_info_embedded, act}, ranch:info()), %% Stop embedded ssl listener and ensure it is gone. ok = embedded_sup:stop_listener(SupPid, {misc_info_embedded, ssl}), timer:sleep(500), false = maps:is_key({misc_info_embedded, ssl}, ranch:info()), %% Stop embedded supervisor. embedded_sup:stop(SupPid), ok. misc_metrics(_) -> doc("Confirm accept/terminate metrics are correct."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), %% Start 10 connections. ok = connect_loop(Port, 10, 0), {10, ConnPids1} = receive_loop(connected, 400), #{metrics := Metrics1} = ranch:info(Name), {10, 0} = do_accumulate_metrics(Metrics1), %% Start 10 more connections. ok = connect_loop(Port, 10, 0), {10, ConnPids2} = receive_loop(connected, 400), #{metrics := Metrics2} = ranch:info(Name), {20, 0} = do_accumulate_metrics(Metrics2), %% Terminate 10 connections. ok = terminate_loop(stop, ConnPids2), timer:sleep(100), #{metrics := Metrics3} = ranch:info(Name), {20, 10} = do_accumulate_metrics(Metrics3), %% Terminate 10 more connections. ok = terminate_loop(stop, ConnPids1), timer:sleep(100), #{metrics := Metrics4} = ranch:info(Name), {20, 20} = do_accumulate_metrics(Metrics4), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. do_accumulate_metrics(Metrics) -> maps:fold( fun ({conns_sup, _, accept}, N, {Accepts, Terminates}) -> {Accepts+N, Terminates}; ({conns_sup, _, terminate}, N, {Accepts, Terminates}) -> {Accepts, Terminates+N} end, {0, 0}, Metrics ). misc_opts_logger(_) -> doc("Confirm that messages are sent via the configured logger module."), register(misc_opts_logger, self()), {ok, _} = ranch:start_listener(name(), ranch_tcp, #{logger => ?MODULE, socket_opts => [<<"garbage">>]}, echo_protocol, []), receive {warning, "Transport option " ++ _, [<<"garbage">>]} -> ok after 1000 -> error(timeout) end. warning(Format, Args) -> misc_opts_logger ! {warning, Format, Args}. misc_post_listen_callback(_) -> doc("Ensure that the post-listen callback works."), Name = name(), Self = self(), Ref = make_ref(), PostListenCb = fun (LSock) -> ok = ranch_tcp:setopts(LSock, [{send_timeout, 1000}]), Self ! {post_listen, Ref, LSock}, ok end, {ok, _} = ranch:start_listener(Name, ranch_tcp, #{post_listen_callback => PostListenCb, socket_opts => [{send_timeout, infinity}]}, echo_protocol, []), receive {post_listen, Ref, LSock} -> {ok, [{send_timeout, 1000}]} = ranch_tcp:getopts(LSock, [send_timeout]), ok after 1000 -> error(timeout) end, Port = ranch:get_port(Name), {ok, S} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(S, <<"Test">>), {ok, <<"Test">>} = gen_tcp:recv(S, 4, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(S, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. misc_post_listen_callback_error(_) -> doc("Ensure that starting a listener fails when the post-listen callback returns an error."), Name = name(), PostListenCb = fun (_) -> {error, test} end, {error, _} = ranch:start_listener(Name, ranch_tcp, #{post_listen_callback => PostListenCb}, echo_protocol, []), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. misc_repeated_remove(_) -> doc("Ensure repeated removal of connection does not crash the connection supervisor."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, remove_conn_and_wait_protocol, [{remove, 5, 0}]), Port = ranch:get_port(Name), ConnsSups = lists:sort(ranch_server:get_connections_sups(Name)), {ok, _} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), timer:sleep(1000), ConnsSups = lists:sort(ranch_server:get_connections_sups(Name)), true = lists:all(fun ({_, ConnsSup}) -> erlang:is_process_alive(ConnsSup) end, ConnsSups), ok = ranch:stop_listener(Name). misc_set_transport_options(_) -> doc(""), Name = name(), {ok, ListenerSupPid} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 10, handshake_timeout => 5000, shutdown => 1000, num_acceptors => 1, socket_opts => [{send_timeout, 5000}]}, echo_protocol, []), ok = ranch:set_transport_options(Name, #{max_connections => 20, handshake_timeout => 5001, num_acceptors => 2, shutdown => 1001, socket_opts => [{send_timeout, 5002}]}), ConnsSups = [ConnsSup || {_, ConnsSup} <- ranch_server:get_connections_sups(Name)], _ = [begin {State, _, _, _} = sys:get_state(ConnsSup), 20 = element(11, State), 5001 = element(10, State), 1001 = element(6, State) end || ConnsSup <- ConnsSups], ok = ranch:suspend_listener(Name), ok = ranch:resume_listener(Name), 2 = length(ranch:procs(Name, acceptors)), LSocket = do_get_listener_socket(ListenerSupPid), {ok, [{send_timeout, 5002}]} = ranch_tcp:getopts(LSocket, [send_timeout]), ok = ranch:stop_listener(Name). misc_wait_for_connections(_) -> doc("Ensure wait for connections works."), Name = name(), Self = self(), %% Ensure invalid arguments are rejected. {'EXIT', {badarg, _}} = begin catch ranch:wait_for_connections(Name, 'foo', 0) end, {'EXIT', {badarg, _}} = begin catch ranch:wait_for_connections(Name, '==', -1) end, {'EXIT', {badarg, _}} = begin catch ranch:wait_for_connections(Name, '==', 0, -1) end, {'EXIT', {badarg, _}} = begin catch ranch:wait_for_connections(Name, '<', 0) end, %% Create waiters for increasing number of connections. Pid1GT = do_create_waiter(Self, Name, '>', 0), Pid1GE = do_create_waiter(Self, Name, '>=', 1), Pid1EQ = do_create_waiter(Self, Name, '==', 1), Pid2GT = do_create_waiter(Self, Name, '>', 1), Pid2GE = do_create_waiter(Self, Name, '>=', 2), Pid2EQ = do_create_waiter(Self, Name, '==', 2), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{num_acceptors => 1}, echo_protocol, []), Port = ranch:get_port(Name), %% Create some connections, ensure that waiters respond. {ok, Sock1} = gen_tcp:connect("localhost", Port, []), ok = do_expect_waiter(Pid1GT), ok = do_expect_waiter(Pid1GE), ok = do_expect_waiter(Pid1EQ), ok = do_expect_waiter(undefined), {ok, Sock2} = gen_tcp:connect("localhost", Port, []), ok = do_expect_waiter(Pid2GT), ok = do_expect_waiter(Pid2GE), ok = do_expect_waiter(Pid2EQ), ok = do_expect_waiter(undefined), %% Create waiters for decreasing number of connections. Pid3LT = do_create_waiter(Self, Name, '<', 2), Pid3LE = do_create_waiter(Self, Name, '=<', 1), Pid3EQ = do_create_waiter(Self, Name, '==', 1), Pid4LT = do_create_waiter(Self, Name, '<', 1), Pid4LE = do_create_waiter(Self, Name, '=<', 0), Pid4EQ = do_create_waiter(Self, Name, '==', 0), %% Close connections, ensure that waiters respond. ok = gen_tcp:close(Sock1), ok = do_expect_waiter(Pid3LT), ok = do_expect_waiter(Pid3LE), ok = do_expect_waiter(Pid3EQ), ok = do_expect_waiter(undefined), ok = gen_tcp:close(Sock2), ok = do_expect_waiter(Pid4LT), ok = do_expect_waiter(Pid4LE), ok = do_expect_waiter(Pid4EQ), ok = do_expect_waiter(undefined), ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. do_create_waiter(ReplyTo, Ref, Op, NumConns) -> spawn(fun () -> ok = ranch:wait_for_connections(Ref, Op, NumConns, 100), ReplyTo ! {wait_connections, self()} end). do_expect_waiter(WaiterPid) -> receive {wait_connections, _} when WaiterPid=:=undefined -> error; {wait_connections, Pid} when Pid=:=WaiterPid -> ok after 1000 -> case WaiterPid of undefined -> ok; _ -> timeout end end. misc_multiple_ip_local_socket_opts(_) -> case do_os_supports_local_sockets() of true -> do_misc_multiple_ip_local_socket_opts(); false -> {skip, "No local socket support."} end. do_misc_multiple_ip_local_socket_opts() -> doc("Ensure that a listener uses the expected ip option if multiple are given."), Name = name(), SockFile1 = do_tempname(), SockFile2 = do_tempname(), Opts = [{ip, {local, SockFile1}}, {ip, {local, SockFile2}}], {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => Opts}, echo_protocol, []), {local, SockFile2} = ranch:get_addr(Name), %% Make sure the socket file from the ignored ip option %% has not been created. {error, enoent} = file:read_file_info(SockFile1), ok = ranch:stop_listener(Name), %% Make sure the socket file from the accepted ip option %% is removed. {error, enoent} = file:read_file_info(SockFile2), ok. misc_connection_alarms(_) -> doc("Ensure that connection alarms work."), Name = name(), Self = self(), TransOpts0 = #{num_conns_sups => 1}, AlarmCallback = fun (Ref, AlarmName, _, ActiveConns) -> Self ! {connection_alarm, {Ref, AlarmName, length(ActiveConns)}} end, Alarms0 = #{ test1 => Alarm1 = #{type => num_connections, threshold => 2, cooldown => 0, callback => AlarmCallback}, %% The test2 alarm uses the misspelled treshold key to test for backwards compatibility. %% @TODO: Change to use the proper spelling when treshold gets removed in Ranch 3.0. test2 => Alarm2 = #{type => num_connections, treshold => 3, cooldown => 0, callback => AlarmCallback} }, ConnectOpts = [binary, {active, false}, {packet, raw}], {ok, _} = ranch:start_listener(Name, ranch_tcp, TransOpts0#{alarms => Alarms0}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, ConnectOpts), {1, [Conn1]} = receive_loop(connected, 100), #{test1 := undefined, test2 := undefined} = do_recv_connection_alarms(Name, 100), {ok, _} = gen_tcp:connect("localhost", Port, ConnectOpts), {1, [Conn2]} = receive_loop(connected, 100), #{test1 := 2, test2 := undefined} = do_recv_connection_alarms(Name, 100), {ok, _} = gen_tcp:connect("localhost", Port, ConnectOpts), {1, [Conn3]} = receive_loop(connected, 100), #{test1 := 3, test2 := 3} = do_recv_connection_alarms(Name, 100), Alarms1 = #{ test1 => Alarm1#{cooldown => 100}, test2 => Alarm2#{cooldown => 100} }, ok = ranch:set_transport_options(Name, TransOpts0#{alarms => Alarms1}), ok = do_flush_connection_alarms(Name), #{test1 := 3, test2 := 3} = do_recv_connection_alarms(Name, 100), ok = do_flush_connection_alarms(Name), #{test1 := 3, test2 := 3} = do_recv_connection_alarms(Name, 100), Conn3 ! stop, timer:sleep(100), ok = do_flush_connection_alarms(Name), #{test1 := 2, test2 := undefined} = do_recv_connection_alarms(Name, 100), Conn2 ! stop, timer:sleep(100), ok = do_flush_connection_alarms(Name), #{test1 := undefined, test2 := undefined} = do_recv_connection_alarms(Name, 100), Conn1 ! stop, ok = ranch:stop_listener(Name), ok. do_recv_connection_alarms(Name, Timeout) -> do_recv_connection_alarms(Name, Timeout, #{test1 => undefined, test2 => undefined}). do_recv_connection_alarms(Name, Timeout, Acc) -> receive {connection_alarm, {Name, AlarmName, N}} -> do_recv_connection_alarms(Name, Timeout, Acc#{AlarmName => N}) after Timeout -> Acc end. do_flush_connection_alarms(Name) -> receive {connection_alarm, {Name, _, _}} -> do_flush_connection_alarms(Name) after 0 -> ok end. misc_stop_unknown_listener(_) -> doc("Ensure that stopping an unknown listener returns an error."), {error, not_found} = ranch:stop_listener(make_ref()), ok. misc_repeated_start_stop(_) -> doc("Ensure that repeated starts and stops of a listener works."), Name = name(), lists:foreach( fun(_) -> {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, echo_protocol, []), true = is_integer(ranch:get_port(Name)), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end end, lists:seq(1, 10) ), ok. misc_listen_error(_) -> doc("Ensure Transport:format_error/1 gets called on Transport:listen/1 error."), Name = name(), Self = self(), logger:add_primary_filter(Name, {fun(Event, _Args) -> Self ! {Name, Event}, ignore end, undefined}), {error, _} = ranch:start_listener(Name, ranch_listen_error_transport, #{}, echo_protocol, []), receive {Name, #{msg := {Format, Args}}} -> FormattedMsg = lists:flatten(io_lib:format(Format, Args)), NormalizedMsg = re:replace(FormattedMsg, "\\s", "", [global, {return, list}]), "FailedtostartRanchlistenermisc_listen_error" "inranch_listen_error_transport:listen(#{socket_opts=>[]})" "forreason{ranch_listen_error_transport,listen_error}" "(Therewasanerrorinranch_listen_error_transport:listen_error)" = NormalizedMsg after 1000 -> error(timeout) end, logger:remove_primary_filter(Name), ok. %% ssl. ssl_accept_error(_) -> doc("Acceptor must not crash if client disconnects in the middle of SSL handshake."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_ssl, #{num_acceptors => 1, socket_opts => Opts}, echo_protocol, []), Port = ranch:get_port(Name), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, AcceptorsSup, _, _} = lists:keyfind(ranch_acceptors_sup, 1, ListenerSupChildren), [{{acceptor, _, _}, AcceptorPid, _, _}] = supervisor:which_children(AcceptorsSup), true = is_process_alive(AcceptorPid), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:close(Socket), receive after 500 -> ok end, true = is_process_alive(AcceptorPid), ok = ranch:stop_listener(Name). ssl_10_acceptors_10_listen_sockets(_) -> case do_os_supports_reuseport() of true -> ok = do_ssl_10_acceptors_10_listen_sockets(); false -> {skip, "No SO_REUSEPORT support."} end. do_ssl_10_acceptors_10_listen_sockets() -> doc("Ensure that we can use 10 listen sockets across 10 acceptors with SSL."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, ListenerSupPid} = ranch:start_listener(Name, ranch_ssl, #{ num_acceptors => 10, num_listen_sockets => 10, socket_opts => [{raw, 1, 15, <<1:32/native>>}|Opts]}, echo_protocol, []), 10 = length(do_get_listener_sockets(ListenerSupPid)), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_many_listen_sockets_no_reuseport(_) -> case do_os_supports_reuseport() of true -> ok = do_ssl_many_listen_sockets_no_reuseport(); false -> {skip, "No SO_REUSEPORT support."} end. do_ssl_many_listen_sockets_no_reuseport() -> doc("Confirm that ranch:start_listener/5 fails when SO_REUSEPORT is not available with SSL."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {error, eaddrinuse} = ranch:start_listener(Name, ranch_ssl, #{ num_acceptors => 10, num_listen_sockets => 10, socket_opts => [{raw, 1, 15, <<0:32/native>>}|Opts]}, echo_protocol, []), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_active_echo(_) -> doc("Ensure that active mode works with SSL transport."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], active_echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_active_n_echo(_) -> case do_get_ssl_version() >= {9, 2, 0} of true -> do_ssl_active_n_echo(); false -> {skip, "No Active N support."} end. do_ssl_active_n_echo() -> doc("Ensure that active N mode works with SSL transport."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], batch_echo_protocol, [{batch_size, 3}]), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket, <<"One">>), {ok, <<"OK">>} = ssl:recv(Socket, 2, 1000), ok = ssl:send(Socket, <<"Two">>), {ok, <<"OK">>} = ssl:recv(Socket, 2, 1000), ok = ssl:send(Socket, <<"Three">>), {ok, <<"OK">>} = ssl:recv(Socket, 2, 1000), {ok, <<"OneTwoThree">>} = ssl:recv(Socket, 11, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_echo(_) -> doc("Ensure that passive mode works with SSL transport."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_dtls_echo(_) -> doc("Ensure that passive mode works with SSL transport."), Name = name(), %% We are using DTLS so the version should be 'dtlsv1.2'. %% But since we don't really need it we simply don't set 'versions'. Opts = ct_helper:get_certs_from_ets() -- [{versions, ['tlsv1.2']}], {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{protocol, dtls}, {verify, verify_none}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {protocol, dtls}, {verify, verify_none}]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_handshake(_) -> doc("Ensure that multiple steps handshake works with SSL transport."), Name = name(), {CaCert1, Cert1, Key1} = ct_helper:make_certs(), {CaCert2, Cert2, Key2} = ct_helper:make_certs(), Opts1 = [{cert, Cert1}, {key, Key1}, {cacerts, [CaCert1]}, {verify, verify_none}, {fail_if_no_peer_cert, false}], Opts2 = [{cert, Cert2}, {key, Key2}, {cacerts, [CaCert2]}, {verify, verify_none}, {fail_if_no_peer_cert, false}], DefaultOpts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, [{handshake, hello}|DefaultOpts], handshake_protocol, #{"ranch1" => Opts1, "ranch2" => Opts2}), Port = ranch:get_port(Name), {ok, Socket1} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}, {server_name_indication, "ranch1"}], 5000), {ok, Cert1} = ssl:peercert(Socket1), ok = ssl:send(Socket1, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket1, 21, 1000), {ok, Socket2} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}, {server_name_indication, "ranch2"}], 5000), {ok, Cert2} = ssl:peercert(Socket2), ok = ssl:send(Socket2, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket2, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket1, 0, 1000), {error, closed} = ssl:recv(Socket2, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_handshake_error(_) -> doc("Acceptor must not crash if client disconnects in the middle of SSL handshake."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, #{num_acceptors => 1, socket_opts => Opts}, echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), receive after 500 -> ok end, [ConnPid] = ranch:procs(Name, connections), ConnMon = monitor(process, ConnPid), ok = gen_tcp:send(Socket, <<"GARBAGE">>), ok = gen_tcp:close(Socket), receive {'DOWN', ConnMon, process, ConnPid, R} -> {shutdown, {_, _}} = R after 1000 -> error(timeout) end, receive after 500 -> ok end, ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_local_echo(_) -> case do_os_supports_local_sockets() of true -> do_ssl_local_echo(); false -> {skip, "No local socket support."} end. do_ssl_local_echo() -> doc("Ensure that listening on a local socket works with SSL transport."), SockFile = do_tempname(), try Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, #{socket_opts => [{ip, {local, SockFile}}|Opts] ++ [{verify, verify_none}]}, echo_protocol, []), undefined = ranch:get_port(Name), {ok, Socket} = ssl:connect({local, SockFile}, 0, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, %% Make sure the socket file is removed. {error, enoent} = file:read_file_info(SockFile), ok after file:delete(SockFile) end. ssl_sni_echo(_) -> doc("Ensure that SNI works with SSL transport."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, [{sni_hosts, [{"localhost", Opts ++ [{verify, verify_none}]}]}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_sni_fail(_) -> doc("Ensure that connection fails when host is not in SNI list."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, [{sni_hosts, [{"pouet", Opts}]}], echo_protocol, []), Port = ranch:get_port(Name), %% We stick to TLS 1.2 because there seems to be a bug in OTP-23.0rc2 %% that leads to a malformed_handshake_data error. {error, _} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_tls_psk(_) -> doc("Ensure that TLS-PSK works without certificate."), Name = name(), Ciphers = [#{cipher => aes_256_gcm, key_exchange => psk, mac => aead, prf => sha384}], LookupFun = {fun psk_lookup_helper/3, <<"shared_secret">>}, {ok, _} = ranch:start_listener(Name, ranch_ssl, [{ciphers, Ciphers}, {user_lookup_fun, LookupFun}, {versions, ['tlsv1.2']}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {ciphers, Ciphers}, {user_lookup_fun, LookupFun}, {verify, verify_none}, {versions, ['tlsv1.2']} ]), ok = ssl:send(Socket, <<"SSL Ranch is working!">>), {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_tls_psk_fail(_) -> doc("Ensure that TLS-PSK filed for different shared keys."), Name = name(), Ciphers = [#{cipher => aes_256_gcm, key_exchange => psk, mac => aead, prf => sha384}], ServerLookupFun = {fun psk_lookup_helper/3, <<"server_secret">>}, ClientLookupFun = {fun psk_lookup_helper/3, <<"client_secret">>}, {ok, _} = ranch:start_listener(Name, ranch_ssl, [{ciphers, Ciphers}, {user_lookup_fun, ServerLookupFun}, {versions, ['tlsv1.2']}], echo_protocol, []), Port = ranch:get_port(Name), {error, _} = ssl:connect("localhost", Port, [ binary, {active, false}, {ciphers, Ciphers}, {user_lookup_fun, ClientLookupFun}, {verify, verify_none}, {versions, ['tlsv1.2']} ]), ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. psk_lookup_helper(psk, _PskIdentity, UserState) -> {ok, UserState}. ssl_upgrade_from_tcp(_) -> doc("Ensure a TCP socket can be upgraded to SSL"), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, ssl_upgrade_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"ECHO Before upgrading to SSL">>), {ok, <<"Before upgrading to SSL">>} = gen_tcp:recv(Socket, 23, 1000), ok = gen_tcp:send(Socket, <<"UPGRADE">>), {ok, <<"READY">>} = gen_tcp:recv(Socket, 5, 1000), {ok, SslSocket} = ssl:connect(Socket, [{verify, verify_none}, {versions, ['tlsv1.2']}], 5000), ok = ssl:send(SslSocket, <<"ECHO After upgrading to SSL">>), {ok, <<"After upgrading to SSL">>} = ssl:recv(SslSocket, 22, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(SslSocket, 0, 1000), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_graceful(_) -> doc("Ensure suspending and resuming of listeners does not kill active connections."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], echo_protocol, []), Port = ranch:get_port(Name), %% Make sure connections with a fresh listener work. running = ranch:get_status(Name), {ok, Socket1} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket1, <<"SSL with fresh listener">>), {ok, <<"SSL with fresh listener">>} = ssl:recv(Socket1, 23, 1000), %% Suspend listener, make sure established connections keep running. ok = ranch:suspend_listener(Name), suspended = ranch:get_status(Name), ok = ssl:send(Socket1, <<"SSL with suspended listener">>), {ok, <<"SSL with suspended listener">>} = ssl:recv(Socket1, 27, 1000), %% Make sure new connections are refused on the suspended listener. {error, econnrefused} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), %% Make sure transport options can be changed when listener is suspended. ok = ranch:set_transport_options(Name, #{socket_opts => [{port, Port}|Opts] ++ [{verify, verify_none}]}), %% Resume listener, make sure connections can be established again. ok = ranch:resume_listener(Name), running = ranch:get_status(Name), {ok, Socket2} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok = ssl:send(Socket2, <<"SSL with resumed listener">>), {ok, <<"SSL with resumed listener">>} = ssl:recv(Socket2, 25, 1000), ok = ranch:stop_listener(Name), {error, closed} = ssl:recv(Socket1, 0, 1000), {error, closed} = ssl:recv(Socket2, 0, 1000), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_getopts_capability(_) -> doc("Ensure getopts/2 capability."), Name=name(), Opts=ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], transport_capabilities_protocol, []), Port=ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok=ssl:send(Socket, <<"getopts/2">>), {ok, <<"OK">>}=ssl:recv(Socket, 0, 1000), ok=ranch:stop_listener(Name), {error, closed}=ssl:recv(Socket, 0, 1000), {'EXIT', _}=begin catch ranch:get_port(Name) end, ok. ssl_getstat_capability(_) -> doc("Ensure getstat/1,2 capability."), Name=name(), Opts=ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts ++ [{verify, verify_none}], transport_capabilities_protocol, []), Port=ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {packet, raw}, {verify, verify_none}, {versions, ['tlsv1.2']}]), ok=ssl:send(Socket, <<"getstat/1">>), {ok, <<"OK">>}=ssl:recv(Socket, 0, 1000), ok=ssl:send(Socket, <<"getstat/2">>), {ok, <<"OK">>}=ssl:recv(Socket, 0, 1000), ok=ranch:stop_listener(Name), {error, closed}=ssl:recv(Socket, 0, 1000), {'EXIT', _}=begin catch ranch:get_port(Name) end, ok. ssl_error_eaddrinuse(_) -> doc("Ensure that failure due to an eaddrinuse returns a compact readable error."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts, active_echo_protocol, []), Port = ranch:get_port(Name), {error, eaddrinuse} = ranch:start_listener({Name, fails}, ranch_ssl, [{port, Port}|Opts], active_echo_protocol, []), ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. ssl_error_no_cert(_) -> doc("Ensure that failure due to missing certificate returns a compact readable error."), {error, no_cert} = ranch:start_listener(name(), ranch_ssl, #{}, active_echo_protocol, []), ok. ssl_error_eacces(_) -> case os:type() of {win32, nt} -> {skip, "No privileged ports."}; {unix, darwin} -> {skip, "No privileged ports."}; _ -> doc("Ensure that failure due to an eacces returns a compact readable error."), Name = name(), Opts = ct_helper:get_certs_from_ets(), {error, eacces} = ranch:start_listener(Name, ranch_ssl, [{port, 283}|Opts], active_echo_protocol, []), ok end. ssl_unsupported_tlsv13_options(_) -> {available, Versions} = lists:keyfind(available, 1, ssl:versions()), case {lists:member('tlsv1.3', Versions), do_get_ssl_version() >= {10, 0, 0}} of {true, true} -> do_ssl_unsupported_tlsv13_options(); {false, _} -> {skip, "No TLSv1.3 support."}; {_, false} -> {skip, "No TLSv1.3 option dependency checking."} end. do_ssl_unsupported_tlsv13_options() -> doc("Ensure that a listener can be started when TLSv1.3 is " "the only protocol and unsupported options are present."), CheckOpts = [ {beast_mitigation, one_n_minus_one}, {client_renegotiation, true}, {next_protocols_advertised, [<<"dummy">>]}, {padding_check, true}, {psk_identity, "dummy"}, {secure_renegotiate, true}, {reuse_session, fun (_, _, _, _) -> true end}, {reuse_sessions, true}, {user_lookup_fun, {fun (_, _, _) -> error end, <<"dummy">>}} ], Name = name(), Opts = ct_helper:get_certs_from_ets() ++ [{versions, ['tlsv1.3']}], ok = lists:foreach( fun (CheckOpt) -> Opts1 = Opts ++ [CheckOpt], case ssl:listen(0, Opts1) of {error, {options, dependency, _}} -> ok; %% Before OTP-26. {error, {options, incompatible, _}} -> ok %% OTP-26+. end, {ok, _} = ranch:start_listener(Name, ranch_ssl, #{socket_opts => Opts1}, echo_protocol, []), ok = ranch:stop_listener(Name) end, CheckOpts ), ok. %% tcp. tcp_10_acceptors_10_listen_sockets(Config) -> case do_os_supports_reuseport() of true -> ok = do_tcp_10_acceptors_10_listen_sockets(Config); false -> {skip, "No SO_REUSEPORT support."} end. do_tcp_10_acceptors_10_listen_sockets(Config) -> doc("Ensure that we can use 10 listen sockets across 10 acceptors with TCP."), Name = name(), SockOpts = config(socket_opts, Config), Self = self(), Tag = make_ref(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{ num_acceptors => 10, num_listen_sockets => 10, socket_opts => SockOpts ++ [{raw, 1, 15, <<1:32/native>>}], post_listen_callback => fun (LSocket) -> Self ! {Tag, LSocket}, ok end}, echo_protocol, []), LSockets = [receive {Tag, LSocket} -> LSocket after 1000 -> error(timeout) end || _ <- lists:seq(1, 10)], 10 = length(LSockets), 10 = length(lists:usort(LSockets)), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_many_listen_sockets_no_reuseport(Config) -> case do_os_supports_reuseport() of true -> ok = do_tcp_many_listen_sockets_no_reuseport(Config); false -> {skip, "No SO_REUSEPORT support."} end. do_tcp_many_listen_sockets_no_reuseport(Config) -> doc("Confirm that ranch:start_listener/5 fails when SO_REUSEPORT is not available with TCP."), Name = name(), SockOpts = config(socket_opts, Config), {error, eaddrinuse} = ranch:start_listener(Name, ranch_tcp, #{ num_acceptors => 10, num_listen_sockets => 10, socket_opts => SockOpts ++ [{raw, 1, 15, <<0:32/native>>}]}, echo_protocol, []), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_active_echo(Config) -> doc("Ensure that active mode works with TCP transport."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, active_echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_active_n_echo(Config) -> doc("Ensure that active N mode works with TCP transport."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, batch_echo_protocol, [{batch_size, 3}]), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"One">>), {ok, <<"OK">>} = gen_tcp:recv(Socket, 2, 1000), ok = gen_tcp:send(Socket, <<"Two">>), {ok, <<"OK">>} = gen_tcp:recv(Socket, 2, 1000), ok = gen_tcp:send(Socket, <<"Three">>), {ok, <<"OK">>} = gen_tcp:recv(Socket, 2, 1000), {ok, <<"OneTwoThree">>} = gen_tcp:recv(Socket, 11, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_echo(Config) -> doc("Ensure that passive mode works with TCP transport."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_local_echo(_) -> case do_os_supports_local_sockets() of true -> do_tcp_local_echo(); false -> {skip, "No local socket support."} end. do_tcp_local_echo() -> doc("Ensure that listening on a local socket works with TCP transport."), SockFile = do_tempname(), try Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => [{ip, {local, SockFile}}]}, echo_protocol, []), undefined = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect({local, SockFile}, 0, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, %% Make sure the socket file is removed. {error, enoent} = file:read_file_info(SockFile), ok after file:delete(SockFile) end. tcp_graceful(Config) -> doc("Ensure suspending and resuming of listeners does not kill active connections."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, echo_protocol, []), Port = ranch:get_port(Name), %% Make sure connections with a fresh listener work. running = ranch:get_status(Name), {ok, Socket1} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket1, <<"TCP with fresh listener">>), {ok, <<"TCP with fresh listener">>} = gen_tcp:recv(Socket1, 23, 1000), %% Suspend listener, make sure established connections keep running. ok = ranch:suspend_listener(Name), suspended = ranch:get_status(Name), ok = gen_tcp:send(Socket1, <<"TCP with suspended listener">>), {ok, <<"TCP with suspended listener">>} = gen_tcp:recv(Socket1, 27, 1000), %% Make sure new connections are refused on the suspended listener. {error, econnrefused} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), %% Make sure transport options can be changed when listener is suspended. ok = ranch:set_transport_options(Name, [{port, Port}]), %% Resume listener, make sure connections can be established again. ok = ranch:resume_listener(Name), running = ranch:get_status(Name), {ok, Socket2} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket2, <<"TCP with resumed listener">>), {ok, <<"TCP with resumed listener">>} = gen_tcp:recv(Socket2, 25, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket1, 0, 1000), {error, closed} = gen_tcp:recv(Socket2, 0, 1000), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_inherit_options(Config) -> doc("Ensure TCP options are inherited in the protocol."), Name = name(), Opts0 = config(socket_opts, Config), Opts1 = [{nodelay, false}, {send_timeout_close, false}], {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => Opts0 ++ Opts1}, check_tcp_options, [{pid, self()} | Opts1]), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, true}, {packet, raw}]), receive checked -> ok after 1000 -> error(timeout) end, ok = gen_tcp:close(Socket), ok = ranch:stop_listener(Name). tcp_max_connections(Config) -> doc("Ensure the max_connections option actually limits connections."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), ok = connect_loop(Port, 11, 150), 10 = ranch_server:count_connections(Name), {10, Pids1} = receive_loop(connected, 400), ok = terminate_loop(stop, Pids1), {1, Pids2} = receive_loop(connected, 1000), ok = terminate_loop(stop, Pids2), ok = ranch:stop_listener(Name). tcp_max_connections_and_beyond(Config) -> doc("Ensure the max_connections option works when connections are removed from the count."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts}, remove_conn_and_wait_protocol, [{remove, true, 2500}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 10, 0), receive after 250 -> ok end, 0 = ranch_server:count_connections(Name), 10 = length(do_conns_which_children(Name)), Counts = do_conns_count_children(Name), {_, 1} = lists:keyfind(specs, 1, Counts), {_, 0} = lists:keyfind(supervisors, 1, Counts), {_, 10} = lists:keyfind(active, 1, Counts), {_, 10} = lists:keyfind(workers, 1, Counts), ranch:set_protocol_options(Name, [{remove, false, 2500}]), receive after 250 -> ok end, ok = connect_loop(Port, 10, 0), receive after 250 -> ok end, 10 = ranch_server:count_connections(Name), 20 = length(do_conns_which_children(Name)), Counts2 = do_conns_count_children(Name), {_, 20} = lists:keyfind(active, 1, Counts2), {_, 20} = lists:keyfind(workers, 1, Counts2), ok = ranch:stop_listener(Name). tcp_max_connections_infinity(Config) -> doc("Set the max_connections option from 10 to infinity and back to 10."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), ok = connect_loop(Port, 20, 0), 10 = ranch_server:count_connections(Name), {10, Pids1} = receive_loop(connected, 1000), 10 = ranch_server:count_connections(Name), 10 = ranch:get_max_connections(Name), ranch:set_max_connections(Name, infinity), receive after 250 -> ok end, 20 = ranch_server:count_connections(Name), infinity = ranch:get_max_connections(Name), ranch:set_max_connections(Name, 10), 20 = ranch_server:count_connections(Name), {10, Pids2} = receive_loop(connected, 1000), ok = terminate_loop(stop, Pids1 ++ Pids2), ok = ranch:stop_listener(Name). tcp_remove_connections(Config) -> doc("Ensure that removed connections are only removed once."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, remove_conn_and_wait_protocol, [{remove, true, 0}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 10, 0), receive after 250 -> ok end, 0 = ranch_server:count_connections(Name), ok = ranch:stop_listener(Name). tcp_remove_connections_acceptor_wakeup(Config) -> doc("Ensure that removed connections wake up acceptors."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 1, num_acceptors => 1, socket_opts => SockOpts}, remove_conn_and_wait_protocol, [{remove, true, infinity}]), Port = ranch:get_port(Name), ConnectOptions = [binary, {active, false}], Localhost = "localhost", {ok, Socket1} = gen_tcp:connect(Localhost, Port, ConnectOptions), {ok, Socket2} = gen_tcp:connect(Localhost, Port, ConnectOptions), {ok, Socket3} = gen_tcp:connect(Localhost, Port, ConnectOptions), ok = gen_tcp:send(Socket3, <<"bye">>), true = maps:get(all_connections, ranch:info(Name)) >= 2, ok = gen_tcp:send(Socket1, <<"bye">>), ok = gen_tcp:send(Socket2, <<"bye">>), ok = ranch:stop_listener(Name). tcp_set_max_connections(Config) -> doc("Ensure that changing the max_connections option to a larger value allows for more connections."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), ok = connect_loop(Port, 20, 0), 10 = ranch_server:count_connections(Name), {10, Pids1} = receive_loop(connected, 1000), 10 = ranch:get_max_connections(Name), ranch:set_max_connections(Name, 20), {10, Pids2} = receive_loop(connected, 1000), 20 = ranch:get_max_connections(Name), ok = terminate_loop(stop, Pids1 ++ Pids2), ok = ranch:stop_listener(Name). tcp_set_max_connections_clean(Config) -> doc("Ensure that setting max_connections does not crash any process."), Name = name(), SockOpts = config(socket_opts, Config), {ok, ListSupPid} = ranch:start_listener(Name, ranch_tcp, #{max_connections => 4, socket_opts => SockOpts}, notify_and_wait_protocol, #{pid => self()}), Children = supervisor:which_children(ListSupPid), {_, AccSupPid, _, _} = lists:keyfind(ranch_acceptors_sup, 1, Children), 1 = erlang:trace(ListSupPid, true, [procs]), 1 = erlang:trace(AccSupPid, true, [procs]), Port = ranch:get_port(Name), N = 20, ok = connect_loop(Port, N*5, 0), %% Randomly set max_connections. [spawn(ranch, set_max_connections, [Name, Max]) || Max <- lists:flatten(lists:duplicate(N, [6, 4, 8, infinity]))], receive {trace, _, spawn, _, _} -> error(dirty_set_max_connections) after 2000 -> ok end, _ = erlang:trace(all, false, [all]), ok = clean_traces(), ok = ranch:stop_listener(Name). tcp_getopts_capability(Config) -> doc("Ensure getopts/2 capability."), Name=name(), SockOpts = config(socket_opts, Config), {ok, _}=ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, transport_capabilities_protocol, []), Port=ranch:get_port(Name), {ok, Socket}=gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok=gen_tcp:send(Socket, <<"getopts/2">>), {ok, <<"OK">>}=gen_tcp:recv(Socket, 0, 1000), ok=ranch:stop_listener(Name), {error, closed}=gen_tcp:recv(Socket, 0, 1000), {'EXIT', _}=begin catch ranch:get_port(Name) end, ok. tcp_getstat_capability(Config) -> doc("Ensure getstat/1,2 capability."), Name=name(), SockOpts = config(socket_opts, Config), {ok, _}=ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, transport_capabilities_protocol, []), Port=ranch:get_port(Name), {ok, Socket}=gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok=gen_tcp:send(Socket, <<"getstat/1">>), {ok, <<"OK">>}=gen_tcp:recv(Socket, 0, 1000), ok=gen_tcp:send(Socket, <<"getstat/2">>), {ok, <<"OK">>}=gen_tcp:recv(Socket, 0, 1000), ok=ranch:stop_listener(Name), {error, closed}=gen_tcp:recv(Socket, 0, 1000), {'EXIT', _}=begin catch ranch:get_port(Name) end, ok. tcp_upgrade(Config) -> doc("Ensure that protocol options can be updated."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), ok = connect_loop(Port, 1, 0), {1, Pids1} = receive_loop(connected, 1000), ranch:set_protocol_options(Name, #{msg => upgraded, pid => self()}), ok = connect_loop(Port, 1, 0), {1, Pids2} = receive_loop(upgraded, 1000), ok = terminate_loop(stop, Pids1 ++ Pids2), ok = ranch:stop_listener(Name). tcp_error_eaddrinuse(Config) -> doc("Ensure that failure due to an eaddrinuse returns a compact readable error."), Name = name(), SockOpts = config(socket_opts, Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, active_echo_protocol, []), Port = ranch:get_port(Name), {error, eaddrinuse} = ranch:start_listener({Name, fails}, ranch_tcp, [{port, Port}], active_echo_protocol, []), ok = ranch:stop_listener(Name), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. tcp_error_eacces(Config) -> case os:type() of {win32, nt} -> {skip, "No privileged ports."}; {unix, darwin} -> {skip, "No privileged ports."}; _ -> doc("Ensure that failure due to an eacces returns a compact readable error."), Name = name(), SockOpts = config(socket_opts, Config), {error, eacces} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts ++ [{port, 283}]}, active_echo_protocol, []), ok end. %% Supervisor tests connection_type_supervisor(_) -> doc("The supervisor connection type must be reflected in the specifications."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{connection_type => supervisor}, echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), [{echo_protocol, _, supervisor, [echo_protocol]}] = do_conns_which_children(Name), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. connection_type_supervisor_separate_from_connection(_) -> doc("The supervisor connection type allows separate supervised and connection processes."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{connection_type => supervisor}, supervisor_separate, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), [{supervisor_separate, _, supervisor, [supervisor_separate]}] = do_conns_which_children(Name), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. supervisor_10_acceptors_1_conns_sup(_) -> doc("Ensure that using 10 acceptors and 1 connection supervisor works."), ok = do_supervisor_n_acceptors_m_conns_sups(10, 1). supervisor_9_acceptors_4_conns_sups(_) -> doc("Ensure that using 9 acceptors and 4 connection supervisors works."), ok = do_supervisor_n_acceptors_m_conns_sups(9, 4). supervisor_10_acceptors_10_conns_sups(_) -> doc("Ensure that using 10 acceptors and 10 connection supervisors works."), ok = do_supervisor_n_acceptors_m_conns_sups(10, 10). supervisor_1_acceptor_10_conns_sups(_) -> doc("Ensure that using 1 acceptor and 10 connection supervisors works."), ok = do_supervisor_n_acceptors_m_conns_sups(1, 10). do_supervisor_n_acceptors_m_conns_sups(NumAcceptors, NumConnsSups) -> Name = name(), {ok, Pid} = ranch:start_listener(Name, ranch_tcp, #{num_conns_sups => NumConnsSups, num_acceptors => NumAcceptors}, notify_and_wait_protocol, #{pid => self()}), Port = ranch:get_port(Name), ConnsSups = [ConnsSup || {_, ConnsSup} <- ranch_server:get_connections_sups(Name)], NumConnsSups = length(ConnsSups), {ranch_acceptors_sup, AcceptorsSup, supervisor, _} = lists:keyfind(ranch_acceptors_sup, 1, supervisor:which_children(Pid)), AcceptorIds = [AcceptorId || {{acceptor, _, AcceptorId}, _, worker, _} <- supervisor:which_children(AcceptorsSup)], NumAcceptors = length(AcceptorIds), AcceptorConnsSups0 = [ranch_server:get_connections_sup(Name, AcceptorId) || AcceptorId <- AcceptorIds], AcceptorConnsSups1 = lists:usort(AcceptorConnsSups0), if NumAcceptors > NumConnsSups -> NumConnsSups = length(AcceptorConnsSups1), [] = ConnsSups -- AcceptorConnsSups1; NumAcceptors < NumConnsSups -> NumAcceptors = length(AcceptorConnsSups1), [] = AcceptorConnsSups1 -- ConnsSups; NumAcceptors =:= NumConnsSups -> NumConnsSups = length(AcceptorConnsSups1), NumAcceptors = length(AcceptorConnsSups1), [] = ConnsSups -- AcceptorConnsSups1, [] = AcceptorConnsSups1 -- ConnsSups end, ok = connect_loop(Port, 100, 0), {100, Pids} = receive_loop(connected, 1000), 100 = ranch_server:count_connections(Name), ok = terminate_loop(stop, Pids), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. supervisor_changed_options_restart(_) -> doc("Ensure that a listener is restarted with changed transport options."), Name = name(), %% Start a listener using send_timeout as option change marker. {ok, ListenerSupPid1} = ranch:start_listener(Name, ranch_tcp, [{send_timeout, 300000}], echo_protocol, []), %% Ensure send_timeout is really set to initial value. {ok, [{send_timeout, 300000}]} = inet:getopts(do_get_listener_socket(ListenerSupPid1), [send_timeout]), %% Change send_timeout option. ok = ranch:suspend_listener(Name), ok = ranch:set_transport_options(Name, [{send_timeout, 300001}]), ok = ranch:resume_listener(Name), %% Ensure send_timeout is really set to the changed value. {ok, [{send_timeout, 300001}]} = inet:getopts(do_get_listener_socket(ListenerSupPid1), [send_timeout]), %% Crash the listener_sup process, allow a short time for restart to succeed. %% We silence the expected log events coming from the relevant supervisors. ListenerChilds = [ChildPid || {_, ChildPid, _, _} <- supervisor:which_children(ListenerSupPid1)], FilterFun = fun (#{meta := #{pid := EventPid}}, _) -> case lists:member(EventPid, ListenerChilds) of true -> stop; false -> ignore end end, ok = logger:add_primary_filter(?MODULE, {FilterFun, undefined}), try exit(ListenerSupPid1, kill), timer:sleep(1000) after ok = logger:remove_primary_filter(?MODULE) end, %% Obtain pid of restarted listener_sup process. [ListenerSupPid2] = [Pid || {{ranch_listener_sup, Ref}, Pid, supervisor, _} <- supervisor:which_children(ranch_sup), Ref =:= Name], %% Ensure send_timeout is still set to the changed value. {ok, [{send_timeout, 300001}]} = inet:getopts(do_get_listener_socket(ListenerSupPid2), [send_timeout]), ok = ranch:stop_listener(Name), {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. supervisor_clean_child_restart(_) -> doc("Verify that only the relevant parts of the supervision tree restarted " "when the listening socket is closed."), Name = name(), %% Trace socket allocations. {module, ranch_tcp} = code:ensure_loaded(ranch_tcp), _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_tcp, listen, 1}, [{'_', [], [{return_trace}]}], [global]), {ok, Pid} = ranch:start_listener(Name, ranch_tcp, #{num_acceptors => 1}, echo_protocol, []), %% Trace supervisor spawns. 1 = erlang:trace(Pid, true, [procs, set_on_spawn]), ConnsSups = ranch_server:get_connections_sups(Name), %% Manually shut the listening socket down. LSocket = receive {trace, _, return_from, {ranch_tcp, listen, 1}, {ok, Socket}} -> Socket after 0 -> error(lsocket_unknown) end, ok = gen_tcp:close(LSocket), receive after 1000 -> ok end, %% Verify that supervisor and its first two children are alive. true = is_process_alive(Pid), true = lists:all(fun erlang:is_process_alive/1, [ConnsSup || {_, ConnsSup} <- ConnsSups]), %% Check that acceptors_sup is restarted properly. AccSupPid = receive {trace, Pid, spawn, Pid1, _} -> Pid1 end, receive {trace, AccSupPid, spawn, _, _} -> ok end, %% No more traces then. receive {trace, _, spawn, _, _} -> error(invalid_restart) after 1000 -> ok end, %% Verify that children still registered right. ConnsSups = ranch_server:get_connections_sups(Name), _ = erlang:trace_pattern({ranch_tcp, listen, 1}, false, []), _ = erlang:trace(all, false, [all]), ok = clean_traces(), ok = ranch:stop_listener(Name). supervisor_clean_restart(_) -> doc("Verify that killing ranch_conns_sup does not crash everything " "and that it restarts properly."), Name = name(), NumAcc = 4, {ok, Pid} = ranch:start_listener(Name, ranch_tcp, #{num_acceptors => NumAcc}, echo_protocol, []), %% Trace supervisor spawns. 1 = erlang:trace(Pid, true, [procs, set_on_spawn]), {_, ConnsSupSup0, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, supervisor:which_children(Pid)), exit(ConnsSupSup0, kill), receive after 1000 -> ok end, %% Verify that supervisor is alive true = is_process_alive(Pid), %% ...but children are dead. false = is_process_alive(ConnsSupSup0), %% Receive traces from newly started children ConnsSupSup = receive {trace, Pid, spawn, Pid2, _} -> Pid2 end, [receive {trace, ConnsSupSup, spawn, _Pid, _} -> ok end || _ <- lists:seq(1, NumAcc)], AccSupPid = receive {trace, Pid, spawn, Pid3, _} -> Pid3 end, %% ...and its acceptors. [receive {trace, AccSupPid, spawn, _Pid, _} -> ok end || _ <- lists:seq(1, NumAcc)], %% No more traces then. receive {trace, EPid, spawn, _, _} when EPid == Pid; EPid == AccSupPid -> error(invalid_restart) after 1000 -> ok end, %% Verify that new children registered themselves properly. _ = erlang:trace(all, false, [all]), ok = clean_traces(), ok = ranch:stop_listener(Name). supervisor_conns_alive(_) -> doc("Ensure that active connections stay open when the listening socket gets closed."), Name = name(), {module, ranch_tcp} = code:ensure_loaded(ranch_tcp), _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_tcp, listen, 1}, [{'_', [], [{return_trace}]}], [global]), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, remove_conn_and_wait_protocol, [{remove, false, 2500}]), %% Get the listener socket LSocket = receive {trace, _, return_from, {ranch_tcp, listen, 1}, {ok, S}} -> S after 500 -> error(lsocket_unknown) end, TcpPort = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", TcpPort, [binary, {active, true}, {packet, raw}]), receive after 500 -> ok end, %% Shut the socket down ok = gen_tcp:close(LSocket), %% Assert that client is still viable. receive {tcp_closed, _} -> error(closed) after 1500 -> ok end, ok = gen_tcp:send(Socket, <<"poke">>), receive {tcp_closed, _} -> ok end, _ = erlang:trace(all, false, [all]), ok = clean_traces(), ok = ranch:stop_listener(Name). supervisor_embedded_ranch_server_crash(_) -> doc("Ensure that restarting ranch_server also restarts embedded listeners."), Name = name(), {ok, SupPid} = embedded_sup:start_link(), {ok, EmbeddedSupPid} = embedded_sup:start_listener(SupPid, Name, ranch_tcp, #{}, echo_protocol, []), [{{ranch_listener_sup, Name}, ListenerPid, supervisor, _}, {ranch_server_proxy, ProxyPid, worker, _}] = supervisor:which_children(EmbeddedSupPid), ProxyMonitor = monitor(process, ProxyPid), ListenerMonitor = monitor(process, ListenerPid), ok = supervisor:terminate_child(ranch_sup, ranch_server), receive {'DOWN', ProxyMonitor, process, ProxyPid, shutdown} -> ok after 1000 -> exit(timeout) end, receive {'DOWN', ListenerMonitor, process, ListenerPid, shutdown} -> ok after 1000 -> exit(timeout) end, {ok, _} = supervisor:restart_child(ranch_sup, ranch_server), receive after 1000 -> ok end, [{{ranch_listener_sup, Name}, _, supervisor, _}, {ranch_server_proxy, _, worker, _}] = supervisor:which_children(EmbeddedSupPid), embedded_sup:stop_listener(SupPid, Name), embedded_sup:stop(SupPid), ok. supervisor_protocol_start_link_crash(_) -> doc("Ensure a protocol start crash does not kill all connections."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, crash_protocol, []), ConnsSups = ranch_server:get_connections_sups(Name), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, [binary, {active, true}, {packet, raw}]), receive after 500 -> ok end, ConnsSups = ranch_server:get_connections_sups(Name), ok = ranch:stop_listener(Name). supervisor_server_recover_state(_) -> doc("Ensure that when ranch_server crashes and restarts, it recovers " "its state and continues monitoring the same processes."), Name = name(), _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_server, init, 1}, [{'_', [], [{return_trace}]}], [global]), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, echo_protocol, []), ConnsSups = ranch_server:get_connections_sups(Name), ServerPid = erlang:whereis(ranch_server), {monitors, Monitors} = erlang:process_info(ServerPid, monitors), erlang:exit(ServerPid, kill), receive {trace, ServerPid2, return_from, {ranch_server, init, 1}, _Result} -> {monitors, Monitors2} = erlang:process_info(ServerPid2, monitors), %% Check that ranch_server is monitoring the same processes. true = (lists:usort(Monitors) == lists:usort(Monitors2)) after 1000 -> error(timeout) end, ConnsSups = ranch_server:get_connections_sups(Name), ok = ranch:stop_listener(Name), %% Check ranch_server has removed the ranch_conns_sup. [] = (catch ranch_server:get_connections_sups(Name)), _ = erlang:trace(all, false, [all]), ok = clean_traces(). supervisor_unexpected_message(_) -> doc("Ensure the connections supervisor stays alive when it receives " "an unexpected message."), Name = name(), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{}, echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), %% Send the unexpected message to all ranch_conns_sups. _ = [ConnSup ! hello || {_, ConnSup} <- ranch_server:get_connections_sups(Name)], %% Connection is still up. ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000), ok = ranch:stop_listener(Name), {error, closed} = gen_tcp:recv(Socket, 0, 1000), %% Make sure the listener stopped. {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. %% Utility functions. connect_loop(_, 0, _) -> ok; connect_loop(Port, N, Sleep) -> {ok, _} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), receive after Sleep -> ok end, connect_loop(Port, N - 1, Sleep). receive_loop(Message, Timeout) -> receive_loop(Message, Timeout, 0, []). receive_loop(Message, Timeout, N, Acc) -> receive {Pid, Message} -> receive_loop(Message, Timeout, N + 1, [Pid|Acc]) after Timeout -> {N, Acc} end. terminate_loop(_, []) -> ok; terminate_loop(Message, [Pid|Pids]) -> Pid ! Message, terminate_loop(Message, Pids). clean_traces() -> receive {trace, _, _, _} -> clean_traces(); {trace, _, _, _, _} -> clean_traces() after 0 -> ok end. do_get_listener_socket(ListenerSupPid) -> [LSocket] = do_get_listener_sockets(ListenerSupPid), LSocket. do_get_listener_sockets(ListenerSupPid) -> [AcceptorsSupPid] = [Pid || {ranch_acceptors_sup, Pid, supervisor, _} <- supervisor:which_children(ListenerSupPid)], {links, Links} = erlang:process_info(AcceptorsSupPid, links), [P || P <- Links, is_port(P)]. do_conns_which_children(Name) -> Conns = [supervisor:which_children(ConnsSup) || {_, ConnsSup} <- ranch_server:get_connections_sups(Name)], lists:flatten(Conns). do_conns_count_children(Name) -> lists:foldl( fun (Stats, undefined) -> Stats; (Stats, Acc) -> lists:zipwith( fun ({K, V1}, {K, V2}) -> {K, V1+V2} end, Acc, Stats ) end, undefined, [supervisor:count_children(ConnsSup) || {_, ConnsSup} <- ranch_server:get_connections_sups(Name)] ). do_os_supports_reuseport() -> case {os:type(), os:version()} of {{unix, linux}, {Major, _, _}} when Major > 3 -> true; {{unix, linux}, {3, Minor, _}} when Minor >= 9 -> true; _ -> false end. do_os_supports_local_sockets() -> case os:type() of {unix, _} -> true; _ -> false end. do_tempname() -> list_to_binary(lists:droplast(os:cmd("mktemp -u"))). do_get_ssl_version() -> {ok, Vsn} = application:get_key(ssl, vsn), Vsns0 = re:split(Vsn, "\\D+", [{return, list}]), Vsns1 = lists:map(fun list_to_integer/1, Vsns0), case Vsns1 of [] -> {0, 0, 0}; [Major] -> {Major, 0, 0}; [Major, Minor] -> {Major, Minor, 0}; [Major, Minor, Patch|_] -> {Major, Minor, Patch} end. ranch-2.2.0/test/active_echo_protocol.erl000066400000000000000000000011621475363377500205070ustar00rootroot00000000000000-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. ranch-2.2.0/test/batch_echo_protocol.erl000066400000000000000000000014541475363377500203210ustar00rootroot00000000000000-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, <>); {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. ranch-2.2.0/test/check_tcp_options.erl000066400000000000000000000007151475363377500200160ustar00rootroot00000000000000-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. ranch-2.2.0/test/cover.spec000066400000000000000000000000341475363377500156000ustar00rootroot00000000000000{incl_app, ranch, details}. ranch-2.2.0/test/crash_protocol.erl000066400000000000000000000002111475363377500173300ustar00rootroot00000000000000-module(crash_protocol). -export([start_link/4]). -spec start_link(_, _, _, _) -> no_return(). start_link(_, _, _, _) -> exit(crash). ranch-2.2.0/test/echo_protocol.erl000066400000000000000000000007601475363377500171570ustar00rootroot00000000000000-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. ranch-2.2.0/test/embedded_sup.erl000066400000000000000000000013031475363377500167320ustar00rootroot00000000000000-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). ranch-2.2.0/test/handshake_protocol.erl000066400000000000000000000015421475363377500201660ustar00rootroot00000000000000-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. ranch-2.2.0/test/notify_and_wait_protocol.erl000066400000000000000000000007571475363377500214250ustar00rootroot00000000000000-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. ranch-2.2.0/test/proxy_header_SUITE.erl000066400000000000000000000163021475363377500177610ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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, <>} = 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 ++ [{verify, verify_none}], 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, [{verify, verify_none}, {versions, ['tlsv1.2']}], 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, <>} = ssl:recv(Socket, Len1 + Len2, 1000), ok = ranch:stop_listener(Name), ok. ranch-2.2.0/test/proxy_protocol.erl000066400000000000000000000013211475363377500174140ustar00rootroot00000000000000-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. ranch-2.2.0/test/ranch_concuerror.erl000066400000000000000000000047561475363377500176650ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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). ranch-2.2.0/test/ranch_ct_hook.erl000066400000000000000000000032121475363377500171140ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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}. ranch-2.2.0/test/ranch_erlang_transport.erl000066400000000000000000000120771475363377500210630ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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. ranch-2.2.0/test/ranch_listen_error_transport.erl000066400000000000000000000144631475363377500223230ustar00rootroot00000000000000%% Copyright (c) Jan Uhlig %% %% 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_listen_error_transport). -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]). -export([format_error/1]). -type opt() :: ranch_tcp:opt(). -export_type([opt/0]). -type opts() :: ranch_tcp:opts(). -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) -> {error, {?MODULE, listen_error}}. -spec disallowed_listen_options() -> [atom()]. disallowed_listen_options() -> ranch_tcp:disallowed_listen_options(). -spec accept(inet:socket(), timeout()) -> {ok, inet:socket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> ranch_tcp:accept(LSocket, Timeout). -spec handshake(inet:socket(), timeout()) -> {ok, inet:socket()}. handshake(CSocket, Timeout) -> iranch_tcp:handshake(CSocket, Timeout). -spec handshake(inet:socket(), opts(), timeout()) -> {ok, inet:socket()}. handshake(CSocket, Opts, Timeout) -> ranch_tcp:handshake(CSocket, Opts, Timeout). -spec handshake_continue(inet:socket(), timeout()) -> no_return(). handshake_continue(CSocket, Timeout) -> ranch_tcp:handshake_continue(CSocket, Timeout). -spec handshake_continue(inet:socket(), opts(), timeout()) -> no_return(). handshake_continue(CSocket, Opts, Timeout) -> ranch_tcp:handshake_continue(CSocket, Opts, Timeout). -spec handshake_cancel(inet:socket()) -> no_return(). handshake_cancel(CSocket) -> ranch_tcp: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) -> ranch_tcp:connect(Host, Port, Opts). %% @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) -> ranch_tcp:connect(Host, Port, Opts, Timeout). -spec recv(inet:socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> ranch_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) -> ranch_tcp:recv_proxy_header(Socket, Timeout). -spec send(inet:socket(), iodata()) -> ok | {error, atom()}. send(Socket, Packet) -> ranch_tcp:send(Socket, Packet). -spec sendfile(inet:socket(), file:name_all() | file:fd()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename) -> ranch_tcp:sendfile(Socket, Filename). -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) -> ranch_tcp: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) -> ranch_tcp:sendfile(Socket, Filename, Offset, Bytes, Opts). %% @todo Probably filter Opts? -spec setopts(inet:socket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> ranch_tcp:setopts(Socket, Opts). -spec getopts(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}. getopts(Socket, Opts) -> ranch_tcp:getopts(Socket, Opts). -spec getstat(inet:socket()) -> {ok, list()} | {error, atom()}. getstat(Socket) -> ranch_tcp:getstat(Socket). -spec getstat(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}. getstat(Socket, OptionNames) -> ranch_tcp:getstat(Socket, OptionNames). -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> ranch_tcp:controlling_process(Socket, Pid). -spec peername(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}. peername(Socket) -> ranch_tcp:peername(Socket). -spec sockname(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}. sockname(Socket) -> ranch_tcp:sockname(Socket). -spec shutdown(inet:socket(), read | write | read_write) -> ok | {error, atom()}. shutdown(Socket, How) -> ranch_tcp:shutdown(Socket, How). -spec close(inet:socket()) -> ok. close(Socket) -> ranch_tcp:close(Socket). -spec cleanup(ranch:transport_opts(opts())) -> ok. cleanup(Opts) -> ranch_tcp:cleanup(Opts). -spec format_error(inet:posix() | system_limit) -> string(). format_error({?MODULE, Reason}) -> io_lib:format("There was an error in ~0p: ~0p", [?MODULE, Reason]); format_error(Reason) -> ranch_tcp:format_error(Reason). ranch-2.2.0/test/remove_conn_and_wait_protocol.erl000066400000000000000000000007711475363377500224230ustar00rootroot00000000000000-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. ranch-2.2.0/test/sendfile_SUITE.erl000066400000000000000000000270611475363377500170650ustar00rootroot00000000000000%% Copyright (c) James Fish %% Copyright (c) Loïc Hoguin %% %% 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. ranch-2.2.0/test/shutdown_SUITE.erl000066400000000000000000000135041475363377500171440ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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(shutdown_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. brutal_kill(_) -> doc("Shutdown Ranch listener with shutdown option set to brutal_kill."), Name = name(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_tcp, #{shutdown => brutal_kill}, echo_protocol, []), Port = ranch:get_port(Name), ok = do_connect_and_ping(Port), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, ListenerSupChildren), [Pid] = do_get_conn_pids(ConnsSupSup), true = is_process_alive(Pid), ok = ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. infinity(_) -> doc("Shutdown Ranch listener with shutdown option set to infinity."), Name = name(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_tcp, #{shutdown => infinity}, echo_protocol, []), Port = ranch:get_port(Name), ok = do_connect_and_ping(Port), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, ListenerSupChildren), [Pid] = do_get_conn_pids(ConnsSupSup), true = is_process_alive(Pid), ok = ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. infinity_trap_exit(_) -> doc("Shutdown Ranch listener with shutdown option set to infinity " "and protocol process trapping exits. The listener must not stop " "until the protocol process terminates."), Name = name(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_tcp, #{shutdown => infinity}, trap_exit_protocol, []), Port = ranch:get_port(Name), ok = do_connect_and_ping(Port), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, ListenerSupChildren), [Pid] = do_get_conn_pids(ConnsSupSup), true = is_process_alive(Pid), %% This call will block infinitely. SpawnPid = spawn(fun() -> ok = ranch:stop_listener(Name) end), receive after 100 -> ok end, %% The protocol traps exit signals, and ignore them, so it won't die. true = is_process_alive(Pid), %% The listener will stay up forever too. true = is_process_alive(ListenerSup), %% We can't connect, though. {error, _} = gen_tcp:connect("localhost", Port, []), %% Killing the process unblocks everything. exit(Pid, kill), receive after 100 -> ok end, false = is_process_alive(ListenerSup), false = is_process_alive(SpawnPid), ok. timeout(_) -> doc("Shutdown Ranch listener with shutdown option set to 500ms."), Name = name(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_tcp, #{shutdown => 500}, echo_protocol, []), Port = ranch:get_port(Name), ok = do_connect_and_ping(Port), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, ListenerSupChildren), [Pid] = do_get_conn_pids(ConnsSupSup), true = is_process_alive(Pid), ok = ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. timeout_trap_exit(_) -> doc("Shutdown Ranch listener with shutdown option set to 500ms " "and protocol process trapping exits. The listener will only stop " "after the 500ms timeout."), Name = name(), {ok, ListenerSup} = ranch:start_listener(Name, ranch_tcp, #{shutdown => 500}, trap_exit_protocol, []), Port = ranch:get_port(Name), ok = do_connect_and_ping(Port), ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSupSup, _, _} = lists:keyfind(ranch_conns_sup_sup, 1, ListenerSupChildren), [Pid] = do_get_conn_pids(ConnsSupSup), true = is_process_alive(Pid), %% This call will block for the duration of the shutdown. SpawnPid = spawn(fun() -> ok = ranch:stop_listener(Name) end), receive after 100 -> ok end, %% The protocol traps exit signals, and ignore them, so it won't die. true = is_process_alive(Pid), %% The listener will stay up for now too. true = is_process_alive(ListenerSup), %% We can't connect, though. {error, _} = gen_tcp:connect("localhost", Port, []), %% Wait for the timeout to finish and see that everything is killed. receive after 500 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), false = is_process_alive(SpawnPid), ok. do_get_conn_pids(ConnsSupSup) -> ConnsSups = [ConnsSup || {_, ConnsSup, _, _} <- supervisor:which_children(ConnsSupSup)], ConnChildren = lists:flatten( [supervisor:which_children(ConnsSup) || ConnsSup <- ConnsSups]), [ConnPid || {_, ConnPid, _, _} <- ConnChildren]. do_connect_and_ping(Port) -> {ok, Conn} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Conn, <<"PING">>), {ok, <<"PING">>} = gen_tcp:recv(Conn, 4, 1000), ok. ranch-2.2.0/test/ssl_upgrade_protocol.erl000066400000000000000000000014651475363377500205540ustar00rootroot00000000000000-module(ssl_upgrade_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, <<"UPGRADE">>} when Transport =:= ranch_tcp -> ok = Transport:send(Socket, <<"READY">>), Opts = ct_helper:get_certs_from_ets(), {ok, NewSocket} = ranch_ssl:handshake(Socket, [{fail_if_no_peer_cert, false}, {verify, verify_none}|Opts], 1000), loop(NewSocket, ranch_ssl); {ok, <<"ECHO ", More/binary>>} -> ok = Transport:send(Socket, More), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. ranch-2.2.0/test/stampede_SUITE.erl000066400000000000000000000143261475363377500170760ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% %% 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(stampede_SUITE). -compile(export_all). -compile(nowarn_export_all). -import(ct_helper, [doc/1]). -import(ct_helper, [name/0]). %% ct. all() -> [{group, tcp}, {group, tcp_socket}, {group, ssl}]. groups() -> [ { tcp, [], [ stampede_tcp, stampede_embedded ] }, { tcp_socket, [], [ stampede_tcp, stampede_embedded ] }, { ssl, [], [ stampede_ssl ] } ]. 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 -> [{socket_opts, [{inet_backend, socket}]}|Config]; false -> {skip, "No socket backend support"} end; init_per_group(_, Config) -> Config. end_per_group(_, _) -> ok. init_per_suite(Config) -> {ok, _} = application:ensure_all_started(ranch), ok = application:start(stampede), %% Enable logging of progress reports. %% They will only be available in the HTML reports by default. ok = logger:set_primary_config(level, none), ok = logger:set_module_level(?MODULE, info), ok = logger:set_application_level(stampede, error), Config. end_per_suite(_) -> ok = application:stop(stampede), ok = application:stop(ranch). %% Tests. stampede_tcp(Config) -> doc("Start a TCP listener, establish a hundred connections, " "run stampede, confirm we can still connect."), %% Start a TCP listener. Name = name(), SockOpts = do_get_sockopts(Config), {ok, _} = ranch:start_listener(Name, ranch_tcp, #{socket_opts => SockOpts}, echo_protocol, []), %% Set restart frequency of ranch_sup. do_set_sup_frequencies([ranch_sup], 999999, 1), %% Run stampede. {ok, _} = stampede:start_herd(ranch_stampede, {application, ranch}, #{interval => {100, 100}, before_kill => fun do_log/1}), %% Establish a hundred connections. ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000), ok = stampede:activate(ranch_stampede), timer:sleep(10000), ok = stampede:stop_herd(ranch_stampede), timer:sleep(1000), %% Confirm we can still connect. ok = do_connect(1, ranch_tcp, ranch:get_port(Name), 1000), ok = ranch:stop_listener(Name). stampede_ssl(_) -> doc("Start a SSL listener, establish a hundred connections, " "run stampede, confirm we can still connect."), %% Start a TCP listener. Name = name(), {ok, _} = ranch:start_listener(Name, ranch_ssl, ct_helper:get_certs_from_ets(), echo_protocol, []), %% Set restart frequencies of ranch_sup and ssl_sup. do_set_sup_frequencies([ranch_sup, ssl_sup], 999999, 1), %% Run stampede. {ok, _} = stampede:start_herd(ranch_stampede, {application, ranch}, #{interval => {100, 100}, before_kill => fun do_log/1}), {ok, _} = stampede:start_herd(ssl_stampede, {application, ssl}, #{interval => {100, 100}, before_kill => fun do_log/1}), %% Establish a hundred connections. ok = do_connect(100, ranch_ssl, ranch:get_port(Name), 1000), ok = stampede:activate(ranch_stampede), ok = stampede:activate(ssl_stampede), timer:sleep(10000), ok = stampede:stop_herd(ssl_stampede), ok = stampede:stop_herd(ranch_stampede), timer:sleep(1000), %% Confirm we can still connect. ok = do_connect(1, ranch_ssl, ranch:get_port(Name), 1000), ok = ranch:stop_listener(Name). stampede_embedded(Config) -> doc("Start an embedded TCP listener, establish a hundred connections, " "run stampede, confirm we can still connect."), %% Start embedded listener. Name = name(), SockOpts = do_get_sockopts(Config), {ok, SupPid} = embedded_sup:start_link(), {ok, _} = embedded_sup:start_listener(SupPid, Name, ranch_tcp, #{socket_opts => SockOpts}, echo_protocol, []), %% Set restart frequency of ranch_sup and embedded_sup. do_set_sup_frequencies([ranch_sup, SupPid], 999999, 1), %% Run stampede. {ok, _} = stampede:start_herd(ranch_stampede, {application, ranch}, #{interval => {100, 100}, before_kill => fun do_log/1}), {ok, _} = stampede:start_herd(embedded_stampede, {supervisor, SupPid}, #{interval => {100, 100}, before_kill => fun do_log/1}), %% Establish a hundred connections. ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000), ok = stampede:activate(ranch_stampede), ok = stampede:activate(embedded_stampede), timer:sleep(10000), ok = stampede:stop_herd(ranch_stampede), ok = stampede:stop_herd(embedded_stampede), timer:sleep(1000), %% Confirm we can still connect. ok = do_connect(1, ranch_tcp, ranch:get_port(Name), 1000), ok = embedded_sup:stop_listener(SupPid, Name), embedded_sup:stop(SupPid), ok. do_set_sup_frequencies(Sups, Intensity, Period) -> StateFun = fun (S) -> setelement(7, setelement(6, S, Intensity), Period) end, _ = [sys:replace_state(Sup, StateFun) || Sup <- Sups], ok. do_connect(0, _, _, _) -> ok; do_connect(N, Transport, Port, Timeout) -> {ok, _} = Transport:connect("localhost", Port, [{active, false}], Timeout), do_connect(N - 1, Transport, Port, Timeout). do_log(Pid) when is_pid(Pid) -> ct:log(info, "~p: ~p~n", [Pid, erlang:process_info(Pid)]), true; do_log(Port) when is_port(Port) -> ct:log(info, "~p: ~p~n", [Port, erlang:port_info(Port)]), true. do_get_sockopts(Config) -> proplists:get_value(socket_opts, Config, []). ranch-2.2.0/test/supervisor_separate.erl000066400000000000000000000007031475363377500204220ustar00rootroot00000000000000-module(supervisor_separate). -behavior(supervisor). -behavior(ranch_protocol). -export([start_link/3]). -export([init/1]). start_link(Ref, Transport, Opts) -> {ok, SupPid} = supervisor:start_link(?MODULE, []), {ok, ConnPid} = supervisor:start_child(SupPid, {echo_protocol, {echo_protocol, start_link, [Ref, Transport, Opts]}, temporary, 5000, worker, [echo_protocol]}), {ok, SupPid, ConnPid}. init([]) -> {ok, {{one_for_one, 1, 1}, []}}. ranch-2.2.0/test/transport_capabilities_protocol.erl000066400000000000000000000020021475363377500227750ustar00rootroot00000000000000-module(transport_capabilities_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} -> Reply = case check(Socket, Transport, Data) of ok -> <<"OK">>; error -> <<"ERROR">> end, Transport:send(Socket, Reply), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. check(Socket, Transport, <<"getopts/2">>) -> case catch Transport:getopts(Socket, []) of {ok, _} -> ok; _ -> error end; check(Socket, Transport, <<"getstat/1">>) -> case catch Transport:getstat(Socket) of {ok, _} -> ok; _ -> error end; check(Socket, Transport, <<"getstat/2">>) -> case catch Transport:getstat(Socket, []) of {ok, _} -> ok; _ -> error end. ranch-2.2.0/test/trap_exit_protocol.erl000066400000000000000000000010311475363377500202300ustar00rootroot00000000000000-module(trap_exit_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 = []) -> process_flag(trap_exit, true), {ok, Socket} = ranch:handshake(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, infinity) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. ranch-2.2.0/test/upgrade_SUITE.erl000066400000000000000000000200711475363377500167150ustar00rootroot00000000000000%% Copyright (c) Loïc Hoguin %% Copyright (c) Maria Scott %% %% 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(upgrade_SUITE). -compile(export_all). -compile(nowarn_export_all). -import(ct_helper, [doc/1]). %% ct. all() -> ct_helper:all(?MODULE). init_per_suite(Config) -> %% Remove environment variables inherited from Erlang.mk. os:unsetenv("ERLANG_MK_TMP"), os:unsetenv("APPS_DIR"), os:unsetenv("DEPS_DIR"), os:unsetenv("ERL_LIBS"), os:unsetenv("CI_ERLANG_MK"), %% Ensure we are using the C locale for all os:cmd calls. os:putenv("LC_ALL", "C"), Config. end_per_suite(_Config) -> ok. %% Find GNU Make. do_find_make_cmd() -> case os:getenv("MAKE") of false -> case os:find_executable("gmake") of false -> "make"; Cmd -> Cmd end; Cmd -> Cmd end. %% Manipulate the release. do_copy(Example0) -> Example = atom_to_list(Example0), {ok, CWD} = file:get_cwd(), _ = do_exec_log("cp -R " ++ CWD ++ "/../../examples/" ++ Example ++ " " ++ CWD), Dir = CWD ++ "/" ++ Example, _ = do_exec_log("sed -i.bak s/\"include \\.\\.\\/\\.\\.\\/erlang.mk\"/\"include ..\\/..\\/..\\/erlang.mk\"/ " ++ Dir ++ "/Makefile"), ok. do_remove(Example0) -> Example = atom_to_list(Example0), {ok, CWD} = file:get_cwd(), _ = do_exec_log("rm -rf " ++ CWD ++ "/" ++ Example), ok. do_get_paths(Example0) -> Example = atom_to_list(Example0), {ok, CWD} = file:get_cwd(), Dir = CWD ++ "/" ++ Example, Rel = Dir ++ "/_rel/" ++ Example ++ "_example/bin/" ++ Example ++ "_example", Log = Dir ++ "/_rel/" ++ Example ++ "_example/log/erlang.log.1", {Dir, Rel, Log}. do_compile_and_start(Example) -> Make = do_find_make_cmd(), {Dir, Rel, _} = do_get_paths(Example), _ = do_exec_log(Make ++ " -C " ++ Dir ++ " distclean"), %% TERM=dumb disables relx coloring. _ = do_exec_log(Make ++ " -C " ++ Dir ++ " TERM=dumb"), %% For some reason the release has ExampleStr.boot %% while the downgrade expects start.boot? ExampleStr = atom_to_list(Example), _ = do_exec_log("cp " ++ Dir ++ "/_rel/" ++ ExampleStr ++ "_example/releases/1/" ++ ExampleStr ++ "_example.boot " ++ Dir ++ "/_rel/" ++ ExampleStr ++ "_example/releases/1/start.boot"), _ = do_exec_log(Rel ++ " stop"), _ = do_exec_log(Rel ++ " daemon"), timer:sleep(2000), _ = do_exec_log(Rel ++ " eval 'application:info()'"), ok. do_stop(Example) -> {Dir, Rel, Log} = do_get_paths(Example), _ = do_exec_log("sed -i.bak s/\"2\"/\"1\"/ " ++ Dir ++ "/relx.config"), _ = do_exec_log(Rel ++ " stop"), ct:log("~s~n", [element(2, file:read_file(Log))]). %% When we are on a tag (git describe --exact-match succeeds), %% we use the tag before that as a starting point. Otherwise %% we use the most recent tag. do_use_ranch_previous(Example) -> _ = do_exec_log("git fetch --tags"), %% Ensure we have all tags. TagsOutput = do_exec_log("git tag | tr - \\~ | sort -V | tr \\~ -"), Tags = string:lexemes(TagsOutput, "\n"), DescribeOutput = do_exec_log("git describe --exact-match"), {CommitOrTag, Prev} = case DescribeOutput of "fatal: no tag exactly matches " ++ _ -> {commit, hd(lists:reverse(Tags))}; _ -> {tag, hd(tl(lists:reverse(Tags)))} end, do_use_ranch_commit(Example, Prev), CommitOrTag. %% Replace the current Ranch commit with the one given as argument. do_use_ranch_commit(Example, Commit) -> {Dir, _, _} = do_get_paths(Example), _ = do_exec_log( "sed -i.bak s/\"dep_ranch_commit = .*\"/\"dep_ranch_commit = " ++ Commit ++ "\"/ " ++ Dir ++ "/Makefile" ), ok. %% Remove Ranch and rebuild, this time generating a relup. do_build_relup(Example, CommitOrTag) -> Make = do_find_make_cmd(), {Dir, _, _} = do_get_paths(Example), _ = do_exec_log("rm -rf " ++ Dir ++ "/deps/ranch/*"), _ = do_exec_log("sed -i.bak s/\"1\"/\"2\"/ " ++ Dir ++ "/relx.config"), %% We need Ranch to be fetched first in order to copy the current appup %% and optionally update its version when we are not on a tag. _ = do_exec_log("cp -R " ++ Dir ++ "/../../../Makefile " ++ Dir ++ "/../../../erlang.mk " ++ Dir ++ "/../../../src " ++ Dir ++ "/deps/ranch/"), _ = do_exec_log(Make ++ " -C " ++ Dir ++ " deps"), _ = case CommitOrTag of tag -> ok; commit -> %% Force the rebuild of Ranch. _ = do_exec_log(Make ++ " -C " ++ Dir ++ "/deps/ranch clean"), %% Update the Ranch version so that the upgrade can be applied. ProjectVersion = do_exec_log("grep \"PROJECT_VERSION = \" " ++ Dir ++ "/deps/ranch/Makefile"), ["PROJECT_VERSION = " ++ Vsn0|_] = string:lexemes(ProjectVersion, "\n"), [A, B|Tail] = string:lexemes(Vsn0, "."), Vsn = binary_to_list(iolist_to_binary([A, $., B, ".9", lists:join($., Tail)])), ct:log("Changing Ranch version from ~s to ~s~n", [Vsn0, Vsn]), _ = do_exec_log( "sed -i.bak s/\"PROJECT_VERSION = .*\"/\"PROJECT_VERSION = " ++ Vsn ++ "\"/ " ++ Dir ++ "/deps/ranch/Makefile" ), %% The version in the appup must be the same as PROJECT_VERSION. _ = do_exec_log( "sed -i.bak s/\"" ++ Vsn0 ++ "\"/\"" ++ Vsn ++ "\"/ " ++ Dir ++ "/deps/ranch/src/ranch.appup" ) end, _ = do_exec_log(Make ++ " -C " ++ Dir ++ " relup"), ok. %% Copy the tarball in the correct location and upgrade. do_upgrade(Example) -> ExampleStr = atom_to_list(Example), {Dir, Rel, _} = do_get_paths(Example), _ = do_exec_log("cp " ++ Dir ++ "/_rel/" ++ ExampleStr ++ "_example/" ++ ExampleStr ++ "_example-2.tar.gz " ++ Dir ++ "/_rel/" ++ ExampleStr ++ "_example/releases/2/" ++ ExampleStr ++ "_example.tar.gz"), _ = do_exec_log(Rel ++ " upgrade \"2\""), _ = do_exec_log(Rel ++ " eval 'application:info()'"), ok. do_downgrade(Example) -> {_, Rel, _} = do_get_paths(Example), _ = do_exec_log(Rel ++ " downgrade \"1\""), _ = do_exec_log(Rel ++ " eval 'application:info()'"), ok. %% Tests. upgrade_ranch_one_conn(_) -> case os:type() of {win32, nt} -> {skip, "This test suite is not currently supported on Windows."}; _ -> do_upgrade_ranch_one_conn() end. do_upgrade_ranch_one_conn() -> Example = tcp_echo, % ExampleStr = atom_to_list(Example), Port = 5555, % {_, Rel, _} = do_get_paths(Example), try %% Copy the example. do_copy(Example), %% Build and start the example release using the previous Ranch version. CommitOrTag = do_use_ranch_previous(Example), do_compile_and_start(Example), %% Establish a connection and check that it works. {ok, S1} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), ok = gen_tcp:send(S1, "Hello!"), {ok, <<"Hello!">>} = gen_tcp:recv(S1, 0, 1000), %% Update Ranch to master then build a release upgrade. do_build_relup(Example, CommitOrTag), %% Perform the upgrade, then check that our connection is still up. do_upgrade(Example), ok = gen_tcp:send(S1, "Hello!"), {ok, <<"Hello!">>} = gen_tcp:recv(S1, 0, 1000), %% Check that new connections are still accepted. {ok, S2} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), %% Perform the downgrade, then check that our connections are still up. do_downgrade(Example), ok = gen_tcp:send(S1, "Hello!"), {ok, <<"Hello!">>} = gen_tcp:recv(S1, 0, 1000), ok = gen_tcp:send(S2, "Hello!"), {ok, <<"Hello!">>} = gen_tcp:recv(S2, 0, 1000), %% Check that new connections are still accepted. {ok, _} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), ok after do_stop(tcp_echo), do_remove(Example) end. %% @todo upgrade_ranch_max_conn do_exec_log(Cmd) -> ct:log("Command: ~s~n", [Cmd]), Out=os:cmd(Cmd), ct:log("Output:~n~n~s~n", [Out]), Out.