pax_global_header00006660000000000000000000000064122411564220014511gustar00rootroot0000000000000052 comment=5df1f222f94e08abdcab7084f5e13027143cc222 erlang-ranch-0.9.0/000077500000000000000000000000001224115642200140605ustar00rootroot00000000000000erlang-ranch-0.9.0/.gitignore000066400000000000000000000001321224115642200160440ustar00rootroot00000000000000.ranch.plt .eunit deps doc/*.css doc/*.html doc/*.png doc/edoc-info ebin logs test/*.beam erlang-ranch-0.9.0/AUTHORS000066400000000000000000000006701224115642200151330ustar00rootroot00000000000000Ranch is available thanks to the work of: Loïc Hoguin Andrew Majorov James Fish Fred Hebert 0x00F6 Ali Sabil Andrew Thompson Geoff Cant Ransom Richardson The Ranch code was initially part of Cowboy. Before it was split into a separate project, the following people worked on the code that then became Ranch: Loïc Hoguin Ali Sabil Andrew Thompson DeadZen Hunter Morris Jesper Louis Andersen Paul Oliver Roberto Ostinelli Steven Gravell erlang-ranch-0.9.0/LICENSE000066400000000000000000000013651224115642200150720ustar00rootroot00000000000000Copyright (c) 2011-2012, 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. erlang-ranch-0.9.0/Makefile000066400000000000000000000004651224115642200155250ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = ranch # Dependencies. TEST_DEPS = ct_helper dep_ct_helper = https://github.com/extend/ct_helper.git master # Options. COMPILE_FIRST = ranch_transport CT_SUITES = acceptor sendfile PLT_APPS = crypto public_key ssl # Standard targets. include erlang.mk erlang-ranch-0.9.0/README.md000066400000000000000000000022011224115642200153320ustar00rootroot00000000000000Ranch ===== 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. Getting started --------------- * [Read the guide](http://ninenines.eu/docs/en/ranch/HEAD/guide/introduction) * Look at the examples in the `examples/` directory * Build API documentation with `make docs`; open `doc/index.html` Support ------- * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) erlang-ranch-0.9.0/ROADMAP.md000066400000000000000000000023241224115642200154660ustar00rootroot00000000000000ROADMAP ======= This document explains in as much details as possible the list of planned changes and work to be done on the Ranch project. It is non-exhaustive and subject to change. Items are not ordered. * Write examples. Ideally we would have one complete example per folder. Examples should be commented. They may or may not be used for writing the user guides. * Continuous performance testing. Initially dubbed the Horse project, Ranch could benefit from a continuous performance testing tool that would allow us to easily compare the impact of the changes we are introducing, similar to what the Phoronix test suite allows. * Transport upgrades. Some protocols allow an upgrade from TCP to SSL without closing the connection. This is currently not possible through the Ranch API. * Resizing the acceptor pool. We should be able to add more acceptors to a pool but also to remove some of them as needed. * Add Transport:secure/0. Currently Ranch checks if a connection is secure by checking if its name is 'ssl'. This isn't a very modular solution, adding an API function that returns whether a connection is secure would fix that issue. erlang-ranch-0.9.0/doc/000077500000000000000000000000001224115642200146255ustar00rootroot00000000000000erlang-ranch-0.9.0/doc/overview.edoc000066400000000000000000000002071224115642200173260ustar00rootroot00000000000000@author Loïc Hoguin @copyright 2011-2012 Loïc Hoguin @version HEAD @title Socket acceptor pool for TCP protocols. erlang-ranch-0.9.0/erlang.mk000066400000000000000000000144371224115642200156720ustar00rootroot00000000000000# Copyright (c) 2013, 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. # Project. PROJECT ?= $(notdir $(CURDIR)) # Packages database file. PKG_FILE ?= $(CURDIR)/.erlang.mk.packages.v1 export PKG_FILE PKG_FILE_URL ?= https://raw.github.com/extend/erlang.mk/master/packages.v1.tsv define get_pkg_file wget -O $(PKG_FILE) $(PKG_FILE_URL) endef # Verbosity and tweaks. V ?= 0 appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose = $(appsrc_verbose_$(V)) erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F)); erlc_verbose = $(erlc_verbose_$(V)) xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); xyrl_verbose = $(xyrl_verbose_$(V)) dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose = $(dtl_verbose_$(V)) gen_verbose_0 = @echo " GEN " $@; gen_verbose = $(gen_verbose_$(V)) .PHONY: all clean-all app clean deps clean-deps docs clean-docs \ build-tests tests build-plt dialyze # Deps directory. DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) # Application. ERL_LIBS ?= $(DEPS_DIR) export ERL_LIBS ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec COMPILE_FIRST ?= COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) all: deps app clean-all: clean clean-deps clean-docs $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs app: ebin/$(PROJECT).app $(eval MODULES := $(shell find ebin -type f -name \*.beam \ | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//')) $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \ > ebin/$(PROJECT).app define compile_erl $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \ -pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1) endef define compile_xyrl $(xyrl_verbose) erlc -v -o ebin/ $(1) $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl @rm ebin/*.erl endef define compile_dtl $(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \ Compile = fun(F) -> \ Module = list_to_atom( \ string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ erlydtl_compiler:compile(F, Module, [{out_dir, "ebin/"}]) \ end, \ _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \ init:stop()' endef ebin/$(PROJECT).app: $(shell find src -type f -name \*.erl) \ $(shell find src -type f -name \*.core) \ $(shell find src -type f -name \*.xrl) \ $(shell find src -type f -name \*.yrl) \ $(shell find templates -type f -name \*.dtl 2>/dev/null) @mkdir -p ebin/ $(if $(strip $(filter %.erl %.core,$?)), \ $(call compile_erl,$(filter %.erl %.core,$?))) $(if $(strip $(filter %.xrl %.yrl,$?)), \ $(call compile_xyrl,$(filter %.xrl %.yrl,$?))) $(if $(strip $(filter %.dtl,$?)), \ $(call compile_dtl,$(filter %.dtl,$?))) clean: $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump # Dependencies. define get_dep @mkdir -p $(DEPS_DIR) ifeq (,$(findstring pkg://,$(word 1,$(dep_$(1))))) git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1) else @if [ ! -f $(PKG_FILE) ]; then $(call get_pkg_file); fi git clone -n -- `awk 'BEGIN { FS = "\t" }; \ $$$$1 == "$(subst pkg://,,$(word 1,$(dep_$(1))))" { print $$$$2 }' \ $(PKG_FILE)` $(DEPS_DIR)/$(1) endif cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1))) endef define dep_target $(DEPS_DIR)/$(1): $(call get_dep,$(1)) endef $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) deps: $(ALL_DEPS_DIRS) @for dep in $(ALL_DEPS_DIRS) ; do \ if [ -f $$dep/Makefile ] ; then \ $(MAKE) -C $$dep ; \ else \ echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep ; \ fi ; \ done clean-deps: @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep clean; done # Documentation. docs: clean-docs $(gen_verbose) erl -noshell \ -eval 'edoc:application($(PROJECT), ".", []), init:stop().' clean-docs: $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info # Tests. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) build-test-deps: $(ALL_TEST_DEPS_DIRS) @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done build-tests: build-test-deps $(gen_verbose) erlc -v $(ERLC_OPTS) -o test/ \ $(wildcard test/*.erl test/*/*.erl) -pa ebin/ CT_RUN = ct_run \ -no_auto_compile \ -noshell \ -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ -dir test \ -logdir logs # -cover test/cover.spec CT_SUITES ?= CT_SUITES_FULL = $(addsuffix _SUITE,$(CT_SUITES)) tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}' tests: clean deps app build-tests @if [ -d "test" ] ; \ then \ mkdir -p logs/ ; \ $(CT_RUN) -suite $(CT_SUITES_FULL) ; \ fi $(gen_verbose) rm -f test/*.beam # Dialyzer. PLT_APPS ?= DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \ -Wunmatched_returns # -Wunderspecs build-plt: deps app @dialyzer --build_plt --output_plt .$(PROJECT).plt \ --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS) dialyze: @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS) # Packages. $(PKG_FILE): @$(call get_pkg_file) pkg-list: $(PKG_FILE) @cat $(PKG_FILE) | awk 'BEGIN { FS = "\t" }; { print \ "Name:\t\t" $$1 "\n" \ "Repository:\t" $$2 "\n" \ "Website:\t" $$3 "\n" \ "Description:\t" $$4 "\n" }' ifdef q pkg-search: $(PKG_FILE) @cat $(PKG_FILE) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \ "Name:\t\t" $$1 "\n" \ "Repository:\t" $$2 "\n" \ "Website:\t" $$3 "\n" \ "Description:\t" $$4 "\n" }' else pkg-search: @echo "Usage: make pkg-search q=STRING" endif erlang-ranch-0.9.0/examples/000077500000000000000000000000001224115642200156765ustar00rootroot00000000000000erlang-ranch-0.9.0/examples/tcp_echo/000077500000000000000000000000001224115642200174625ustar00rootroot00000000000000erlang-ranch-0.9.0/examples/tcp_echo/README.md000066400000000000000000000006271224115642200207460ustar00rootroot00000000000000Ranch TCP Echo ============== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then start telnet as indicated and type in a few lines. Be aware that there is a timeout of 5 seconds without receiving data before the example server disconnects your session. erlang-ranch-0.9.0/examples/tcp_echo/rebar.config000066400000000000000000000001241224115642200217410ustar00rootroot00000000000000{deps, [ {ranch, ".*", {git, "git://github.com/extend/ranch.git", "master"}} ]}. erlang-ranch-0.9.0/examples/tcp_echo/src/000077500000000000000000000000001224115642200202515ustar00rootroot00000000000000erlang-ranch-0.9.0/examples/tcp_echo/src/echo_protocol.erl000066400000000000000000000010241224115642200236110ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(echo_protocol). -export([start_link/4, init/4]). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. init(Ref, Socket, Transport, _Opts = []) -> ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, 5000) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. erlang-ranch-0.9.0/examples/tcp_echo/src/tcp_echo.app.src000066400000000000000000000004201224115642200233210ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, tcp_echo, [ {description, "Ranch TCP Echo example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, ranch ]}, {mod, {tcp_echo_app, []}}, {env, []} ]}. erlang-ranch-0.9.0/examples/tcp_echo/src/tcp_echo.erl000066400000000000000000000003051224115642200225370ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(tcp_echo). %% API. -export([start/0]). %% API. start() -> ok = application:start(ranch), ok = application:start(tcp_echo). erlang-ranch-0.9.0/examples/tcp_echo/src/tcp_echo_app.erl000066400000000000000000000005321224115642200234010ustar00rootroot00000000000000%% 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, 1, ranch_tcp, [{port, 5555}], echo_protocol, []), tcp_echo_sup:start_link(). stop(_State) -> ok. erlang-ranch-0.9.0/examples/tcp_echo/src/tcp_echo_sup.erl000066400000000000000000000005621224115642200234330ustar00rootroot00000000000000%% 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}, []}}. erlang-ranch-0.9.0/examples/tcp_echo/start.sh000077500000000000000000000001471224115642200211600ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s tcp_echo \ -eval "io:format(\"Run: telnet localhost 5555~n\")." erlang-ranch-0.9.0/guide/000077500000000000000000000000001224115642200151555ustar00rootroot00000000000000erlang-ranch-0.9.0/guide/embedded.md000066400000000000000000000033221224115642200172300ustar00rootroot00000000000000Embedded mode ============= Purpose ------- 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. 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 requires at the minimum two kinds of child specs for embedding. First, you need to add `ranch_sup` to your supervision tree, only once, regardless of the number of listeners you will use. Then you need to add the child specs for each listener. Ranch has a convenience function for getting the listeners child specs called `ranch:child_spec/6`, that works like `ranch:start_listener/6`, except that it doesn't start anything, it only returns child specs. As for `ranch_sup`, the child spec is simple enough to not require a convenience function. The following example adds both `ranch_sup` and one listener to another application's supervision tree. ``` erlang init([]) -> RanchSupSpec = {ranch_sup, {ranch_sup, start_link, []}, permanent, 5000, supervisor, [ranch_sup]}, ListenerSpec = ranch:child_spec(echo, 100, ranch_tcp, [{port, 5555}], echo_protocol, [] ), {ok, {{one_for_one, 10, 10}, [RanchSupSpec, ListenerSpec]}}. ``` Remember, you can add as many listener child specs as needed, but only one `ranch_sup` spec! It is recommended that your architecture makes sure that all listeners are restarted if `ranch_sup` fails. See the Ranch internals chapter for more details on how Ranch does it. erlang-ranch-0.9.0/guide/internals.md000066400000000000000000000107101224115642200174750ustar00rootroot00000000000000Internals ========= 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. 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 the central process keeping track of the listeners, the acceptors and the connection processes. It does so through the use of a public ets table called `ranch_server` too. This allows some operations to be sequential by going through the gen_server, while others just query the ets table directly, ensuring there is no bottleneck for the most common operations. Because the most common operation is keeping track of the number of connections currently being used for each listener, the ets table has `write_concurrency` enabled, allowing us to perform all these operations concurrently using `ets:update_counter/3`. To read the number of connections we simply increment the counter by 0, which allows us to stay in a write context and still receive the counter's value. For increased fault tolerance, the owner of the ets table is `ranch_sup` and not `ranch_server` as you could expect. 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. Note that this usage is not recommended by OTP. 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/6` 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. ``` erlang {raw, 6, 9, << 30:32/native >>} ``` It means go on layer 6, turn on option 9 with the given integer parameter. erlang-ranch-0.9.0/guide/introduction.md000066400000000000000000000014131224115642200202170ustar00rootroot00000000000000Introduction ============ Purpose ------- 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. In order to run the examples available in this user guide, you will need Erlang and rebar installed and in your $PATH. Please see the [rebar repository](https://github.com/basho/rebar) for downloading and building instructions. Please look up the environment variables documentation of your system for details on how to update the $PATH information. erlang-ranch-0.9.0/guide/listeners.md000066400000000000000000000155661224115642200175240ustar00rootroot00000000000000Listeners ========= Purpose ------- 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 user of transport handlers. The listener takes care of supervising all the acceptor and connection processes, allowing developers to focus on building their application. Starting and stopping --------------------- 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. * The number of acceptors in the pool. * 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/6` function. Before doing so however, you must ensure that the `ranch` application is started. To start the `ranch` application: ``` 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. ``` erlang {ok, _} = ranch:start_listener(tcp_echo, 100, ranch_tcp, [{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 commands: ``` bash % rebar get-deps compile % ./start.sh Listening on port 5555 ``` 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. ``` % telnet localhost 5555 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello! Hello! It works! It works! ^] telnet> quit Connection closed. ``` 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 overriden when starting the listener, but they can be overriden using `Transport:setopts/2` in the protocol. It will also set `{backlog, 1024}` and `{nodelay, true}`, which can be overriden 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/6`. ``` erlang {ok, _} = ranch:start_listener(tcp_echo, 100, ranch_tcp, [{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. Accepting connections on an existing socket ------------------------------------------- If you want to accept connections on an existing socket, you can use the `socket` transport option, which should just be the relevant data returned from the connect function for the transport or the underlying socket library (`gen_tcp:connect`, `ssl:connect`). The accept function will then be called on the passed in socket. You should connect the socket in `{active, false}` mode, as well. Note, however, that because of a bug in SSL, you cannot change ownership of an SSL listen socket prior to R16. Ranch will catch the error thrown, but the owner of the SSL socket will remain as whatever process created the socket. However, this will not affect accept behaviour unless the owner process dies, in which case the socket is closed. Therefore, to use this feature with SSL with an erlang release prior to R16, ensure that the SSL socket is opened in a persistant process. Limiting the number of concurrent connections --------------------------------------------- The `max_connections` transport option allows you to limit the number of concurrent connections. It defaults to 1024. Its purpose is to prevent your system from being overloaded and ensuring all the connections are handled optimally. ``` erlang {ok, _} = ranch:start_listener(tcp_echo, 100, ranch_tcp, [{port, 5555}, {max_connections, 100}], echo_protocol, [] ). ``` You can disable this limit by setting its value to the atom `infinity`. ``` erlang {ok, _} = ranch:start_listener(tcp_echo, 100, ranch_tcp, [{port, 5555}, {max_connections, infinity}], echo_protocol, [] ). ``` 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. ``` erlang ranch:remove_connection(Ref). ``` As seen in the chapter covering protocols, this pid is received as the first argument of the protocol's `start_link/4` 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. ``` erlang ranch:set_max_connections(tcp_echo, MaxConns). ``` The change will occur immediately. 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. ``` 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`. ``` erlang Opts = ranch:get_protocol_options(tcp_echo). ``` erlang-ranch-0.9.0/guide/protocols.md000066400000000000000000000072621224115642200175320ustar00rootroot00000000000000Protocols ========= Purpose ------- 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/4`. This callback is responsible for spawning a new process for handling the connection. It receives four arguments: the name of the listener, the socket, the transport handler being used and the protocol options defined in the call to `ranch:start_listener/6`. This callback must return `{ok, Pid}`, with `Pid` the pid of the new process. The newly started process can then freely initialize itself. However, it must call `ranch:accept_ack/1` before doing any socket operation. This will ensure the connection process is the owner of the socket. It expects the listener's name as argument. ``` erlang ok = ranch:accept_ack(Ref). ``` If your protocol code requires specific socket options, you should set them while initializing your connection process and before starting `ranch:accept_ack/1`. You can use `Transport:setopts/2` for that purpose. Following is the complete protocol code for the example found in `examples/tcp_echo/`. ``` erlang -module(echo_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. init(Ref, Socket, Transport, _Opts = []) -> ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, 5000) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. ``` Using gen_server ---------------- Special processes like the ones that use the `gen_server` or `gen_fsm` behaviours have the particularity of having their `start_link` call not return until the `init` function returns. This is problematic, because you won't be able to call `ranch:accept_ack/1` from the `init` callback as this would cause a deadlock to happen. There are two ways of solving this problem. The first, and probably the most elegant one, is to make use of the `gen_server:enter_loop/3` function. It allows you to start your process normally (although it must be started with `proc_lib` like all special processes), then perform any needed operations before falling back into the normal `gen_server` execution loop. ``` erlang -module(my_protocol). -behaviour(gen_server). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). %% Exports of other gen_server callbacks here. start_link(Ref, Socket, Transport, Opts) -> proc_lib:start_link(?MODULE, init, [Ref, Socket, Transport, Opts]). init(Ref, Socket, Transport, _Opts = []) -> ok = proc_lib:init_ack({ok, self()}), %% Perform any required state initialization here. ok = ranch:accept_ack(Ref), ok = Transport:setopts(Socket, [{active, once}]), gen_server:enter_loop(?MODULE, [], {state, Socket, Transport}). %% Other gen_server callbacks here. ``` The second method involves triggering a timeout just after `gen_server:init` ends. If you return a timeout value of `0` then the `gen_server` will call `handle_info(timeout, _, _)` right away. ``` erlang -module(my_protocol). -behaviour(gen_server). -behaviour(ranch_protocol). %% Exports go here. init([Ref, Socket, Transport]) -> {ok, {state, Ref, Socket, Transport}, 0}. handle_info(timeout, State={state, Ref, Socket, Transport}) -> ok = ranch:accept_ack(Ref), ok = Transport:setopts(Socket, [{active, once}]), {noreply, State}; %% ... ``` erlang-ranch-0.9.0/guide/ssl_auth.md000066400000000000000000000100711224115642200173200ustar00rootroot00000000000000SSL 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 certicate. 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 [CAcert.org](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. ``` erlang {ok, _} = ranch:start_listener(my_ssl, 100, ranch_ssl, [ {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. ``` 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. ``` 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. erlang-ranch-0.9.0/guide/toc.md000066400000000000000000000017121224115642200162650ustar00rootroot00000000000000Ranch User Guide ================ * [Introduction](introduction.md) * Purpose * Prerequisites * [Listeners](listeners.md) * Purpose * Starting and stopping * Default transport options * Listening on a random port * Listening on privileged ports * Accepting connections on an existing socket * Limiting the number of concurrent connections * Upgrading * [Transports](transports.md) * Purpose * TCP transport * SSL transport * Sending and receiving data * Writing a transport handler * [Protocols](protocols.md) * Purpose * Writing a protocol handler * Using gen_server * [SSL client authentication](ssl_auth.md) * Purpose * Obtaining client certificates * Transport configuration * Authentication * [Embedded mode](embedded.md) * Purpose * Embedding * [Internals](internals.md) * Architecture * Number of acceptors * Platform-specific TCP features erlang-ranch-0.9.0/guide/transports.md000066400000000000000000000134261224115642200177240ustar00rootroot00000000000000Transports ========== Purpose ------- 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`. It requires the `crypto`, `asn1`, `public_key` and `ssl` applications to be started. When starting an SSL listener, Ranch will attempt to automatically start them. It will not try to stop them when the listener is removed, however. ``` erlang ssl:start(). ``` In a proper OTP setting, you will need to make your application depend on the `crypto`, `public_key` and `ssl` applications. They will be started automatically when starting your release. The SSL transport `accept/2` function performs both transport and SSL accepts. Errors occurring during the SSL accept phase are returned as `{error, {ssl_accept, atom()}}` to differentiate on which socket the problem occurred. 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: ``` 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/2` 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. ``` 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 two kinds of active modes: `{active, once}` and `{active, true}`. The first will send a single message before going back to passive mode; the second 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. Three different messages can be received: * `{OK, Socket, Data}` * `{Closed, Socket}` * `{Error, Socket, Reason}` The value of `OK`, `Closed` and `Error` 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. ``` erlang {OK, Closed, Error} = 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. ``` erlang {OK, Closed, Error} = 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: ```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`: ```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: ```erlang {ok, RawFile} = file:open(Filename, [raw, read, binary]), {ok, SentBytes} = Transport:sendfile(Socket, RawFile, Offset, Bytes, Opts). ``` 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. erlang-ranch-0.9.0/src/000077500000000000000000000000001224115642200146475ustar00rootroot00000000000000erlang-ranch-0.9.0/src/ranch.app.src000066400000000000000000000020121224115642200172250ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. {application, ranch, [ {description, "Socket acceptor pool for TCP protocols."}, {vsn, "0.9.0"}, {modules, []}, {registered, [ranch_sup, ranch_server]}, {applications, [ kernel, stdlib ]}, {mod, {ranch_app, []}}, {env, []} ]}. erlang-ranch-0.9.0/src/ranch.erl000066400000000000000000000202421224115642200164460ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @doc Ranch API to start and stop listeners. -module(ranch). -export([start_listener/6]). -export([stop_listener/1]). -export([child_spec/6]). -export([accept_ack/1]). -export([remove_connection/1]). -export([get_port/1]). -export([get_max_connections/1]). -export([set_max_connections/2]). -export([get_protocol_options/1]). -export([set_protocol_options/2]). -export([filter_options/3]). -export([set_option_default/3]). -export([require/1]). -type max_conns() :: non_neg_integer() | infinity. -export_type([max_conns/0]). -type ref() :: any(). -export_type([ref/0]). %% @doc Start a listener for the given transport and protocol. %% %% A listener is effectively a pool of NbAcceptors acceptors. %% Acceptors accept connections on the given Transport and forward %% connections to the given Protocol handler. Both transport and %% protocol modules can be given options through the TransOpts and %% the ProtoOpts arguments. Available options are documented in the %% listen transport function and in the protocol module of your choice. %% %% All acceptor and connection processes are supervised by the listener. %% %% It is recommended to set a large enough number of acceptors to improve %% performance. The exact number depends of course on your hardware, on the %% protocol used and on the number of expected simultaneous connections. %% %% The Transport option max_connections allows you to define %% the maximum number of simultaneous connections for this listener. It defaults %% to 1024. See ranch_listener for more details on limiting the number %% of connections. %% %% Ref can be used to stop the listener later on. %% %% This function will return `{error, badarg}` if and only if the transport %% module given doesn't appear to be correct. -spec start_listener(ref(), non_neg_integer(), module(), any(), module(), any()) -> {ok, pid()} | {error, badarg}. start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) when is_integer(NbAcceptors) andalso is_atom(Transport) andalso is_atom(Protocol) -> _ = code:ensure_loaded(Transport), case erlang:function_exported(Transport, name, 0) of false -> {error, badarg}; true -> Res = supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)), Socket = proplists:get_value(socket, TransOpts), case Res of {ok, Pid} when Socket =/= undefined -> %% Give ownership of the socket to ranch_acceptors_sup %% to make sure the socket stays open as long as the %% listener is alive. If the socket closes however there %% will be no way to recover because we don't know how %% to open it again. Children = supervisor:which_children(Pid), {_, AcceptorsSup, _, _} = lists:keyfind(ranch_acceptors_sup, 1, Children), %%% Note: the catch is here because SSL crashes when you change %%% the controlling process of a listen socket because of a bug. %%% The bug will be fixed in R16. catch Transport:controlling_process(Socket, AcceptorsSup); _ -> ok end, Res end. %% @doc Stop a listener identified by Ref. %% %% Note that stopping the listener will close all currently running %% connections abruptly. -spec stop_listener(ref()) -> ok | {error, not_found}. stop_listener(Ref) -> case supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}) of ok -> _ = supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref}), ranch_server:cleanup_listener_opts(Ref); {error, Reason} -> {error, Reason} end. %% @doc Return a child spec suitable for embedding. %% %% When you want to embed Ranch in another application, you can use this %% function to create a ChildSpec suitable for use in a supervisor. %% The parameters are the same as in start_listener/6 but rather %% than hooking the listener to the Ranch internal supervisor, it just returns %% the spec. -spec child_spec(ref(), non_neg_integer(), module(), any(), module(), any()) -> supervisor:child_spec(). child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) when is_integer(NbAcceptors) andalso is_atom(Transport) andalso is_atom(Protocol) -> {{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [ Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts ]}, permanent, 5000, supervisor, [ranch_listener_sup]}. %% @doc Acknowledge the accepted connection. %% %% Effectively used to make sure the socket control has been given to %% the protocol process before starting to use it. -spec accept_ack(ref()) -> ok. accept_ack(Ref) -> receive {shoot, Ref} -> ok end. %% @doc Remove the calling process' connection from the pool. %% %% Useful if you have long-lived connections that aren't taking up %% resources and shouldn't be counted in the limited number of running %% connections. -spec remove_connection(ref()) -> ok. remove_connection(Ref) -> ConnsSup = ranch_server:get_connections_sup(Ref), ConnsSup ! {remove_connection, Ref}, ok. %% @doc Return the listener's port. -spec get_port(ref()) -> inet:port_number(). get_port(Ref) -> ranch_server:get_port(Ref). %% @doc Return the max number of connections allowed concurrently. -spec get_max_connections(ref()) -> max_conns(). get_max_connections(Ref) -> ranch_server:get_max_connections(Ref). %% @doc Set the max number of connections allowed concurrently. -spec set_max_connections(ref(), max_conns()) -> ok. set_max_connections(Ref, MaxConnections) -> ranch_server:set_max_connections(Ref, MaxConnections). %% @doc Return the current protocol options for the given listener. -spec get_protocol_options(ref()) -> any(). get_protocol_options(Ref) -> ranch_server:get_protocol_options(Ref). %% @doc Upgrade the protocol options for the given listener. %% %% The upgrade takes place at the acceptor level, meaning that only the %% newly accepted connections receive the new protocol options. This has %% no effect on the currently opened connections. -spec set_protocol_options(ref(), any()) -> ok. set_protocol_options(Ref, Opts) -> ranch_server:set_protocol_options(Ref, Opts). %% @doc Filter a list of options and remove all unwanted values. %% %% It takes a list of options, a list of allowed keys and an accumulator. %% This accumulator can be used to set default options that should never %% be overriden. -spec filter_options([{atom(), any()} | {atom(), any(), any(), any()}], [atom()], Acc) -> Acc when Acc :: [any()]. filter_options([], _, Acc) -> Acc; filter_options([Opt = {Key, _}|Tail], AllowedKeys, Acc) -> case lists:member(Key, AllowedKeys) of true -> filter_options(Tail, AllowedKeys, [Opt|Acc]); false -> filter_options(Tail, AllowedKeys, Acc) end; filter_options([Opt = {raw, _, _, _}|Tail], AllowedKeys, Acc) -> case lists:member(raw, AllowedKeys) of true -> filter_options(Tail, AllowedKeys, [Opt|Acc]); false -> filter_options(Tail, AllowedKeys, Acc) end. %% @doc Add an option to a list, but only if it wasn't previously set. -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. %% @doc Start the given applications if they were not already started. -spec require(list(module())) -> ok. require([]) -> ok; require([App|Tail]) -> case application:start(App) of ok -> ok; {error, {already_started, App}} -> ok end, require(Tail). erlang-ranch-0.9.0/src/ranch_acceptor.erl000066400000000000000000000040101224115642200203210ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private -module(ranch_acceptor). %% API. -export([start_link/3]). %% Internal. -export([loop/3]). %% API. -spec start_link(inet:socket(), module(), pid()) -> {ok, pid()}. start_link(LSocket, Transport, ConnsSup) -> Pid = spawn_link(?MODULE, loop, [LSocket, Transport, ConnsSup]), {ok, Pid}. %% Internal. -spec loop(inet:socket(), module(), pid()) -> no_return(). loop(LSocket, Transport, ConnsSup) -> _ = case Transport:accept(LSocket, infinity) of {ok, CSocket} -> Transport:controlling_process(CSocket, ConnsSup), %% 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, CSocket); %% 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} -> receive after 100 -> ok end; %% We want to crash if the listening socket got closed. {error, Reason} when Reason =/= closed -> ok end, flush(), ?MODULE:loop(LSocket, Transport, ConnsSup). flush() -> receive Msg -> error_logger:error_msg( "Ranch acceptor received unexpected message: ~p~n", [Msg]), flush() after 0 -> ok end. erlang-ranch-0.9.0/src/ranch_acceptors_sup.erl000066400000000000000000000033041224115642200214000ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private -module(ranch_acceptors_sup). -behaviour(supervisor). %% API. -export([start_link/4]). %% supervisor. -export([init/1]). %% API. -spec start_link(ranch:ref(), non_neg_integer(), module(), any()) -> {ok, pid()}. start_link(Ref, NbAcceptors, Transport, TransOpts) -> supervisor:start_link(?MODULE, [Ref, NbAcceptors, Transport, TransOpts]). %% supervisor. init([Ref, NbAcceptors, Transport, TransOpts]) -> ConnsSup = ranch_server:get_connections_sup(Ref), LSocket = case proplists:get_value(socket, TransOpts) of undefined -> {ok, Socket} = Transport:listen(TransOpts), Socket; Socket -> Socket end, {ok, {_, Port}} = Transport:sockname(LSocket), ranch_server:set_port(Ref, Port), Procs = [ {{acceptor, self(), N}, {ranch_acceptor, start_link, [ LSocket, Transport, ConnsSup ]}, permanent, brutal_kill, worker, []} || N <- lists:seq(1, NbAcceptors)], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-ranch-0.9.0/src/ranch_app.erl000066400000000000000000000026461224115642200173160ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private -module(ranch_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). -export([profile_output/0]). %% API. start(_, _) -> consider_profiling(), ranch_sup:start_link(). 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). %% Internal. -spec consider_profiling() -> profiling | not_profiling. consider_profiling() -> case application:get_env(profile) of {ok, true} -> {ok, _Pid} = eprof:start(), eprof:start_profiling([self()]); _ -> not_profiling end. erlang-ranch-0.9.0/src/ranch_conns_sup.erl000066400000000000000000000165301224115642200205420ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private %% %% 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/4]). -export([start_protocol/2]). -export([active_connections/1]). %% Supervisor internals. -export([init/5]). -export([system_continue/3]). -export([system_terminate/4]). -export([system_code_change/4]). -type conn_type() :: worker | supervisor. -record(state, { parent = undefined :: pid(), ref :: ranch:ref(), conn_type :: conn_type(), transport = undefined :: module(), protocol = undefined :: module(), opts :: any(), max_conns = undefined :: non_neg_integer() | infinity }). %% API. -spec start_link(ranch:ref(), conn_type(), module(), module()) -> {ok, pid()}. start_link(Ref, ConnType, Transport, Protocol) -> proc_lib:start_link(?MODULE, init, [self(), Ref, ConnType, Transport, Protocol]). %% 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(), inet:socket()) -> ok. start_protocol(SupPid, Socket) -> SupPid ! {?MODULE, start_protocol, self(), Socket}, receive SupPid -> ok 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(), conn_type(), module(), module()) -> no_return(). init(Parent, Ref, ConnType, Transport, Protocol) -> process_flag(trap_exit, true), ok = ranch_server:set_connections_sup(Ref, self()), MaxConns = ranch_server:get_max_connections(Ref), Opts = ranch_server:get_protocol_options(Ref), ok = proc_lib:init_ack(Parent, {ok, self()}), loop(#state{parent=Parent, ref=Ref, conn_type=ConnType, transport=Transport, protocol=Protocol, opts=Opts, max_conns=MaxConns}, 0, 0, []). loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType, transport=Transport, protocol=Protocol, opts=Opts, max_conns=MaxConns}, CurConns, NbChildren, Sleepers) -> receive {?MODULE, start_protocol, To, Socket} -> case Protocol:start_link(Ref, Socket, Transport, Opts) of {ok, Pid} -> Transport:controlling_process(Socket, Pid), Pid ! {shoot, Ref}, put(Pid, true), CurConns2 = CurConns + 1, if CurConns2 < MaxConns -> To ! self(), loop(State, CurConns2, NbChildren + 1, Sleepers); true -> loop(State, CurConns2, NbChildren + 1, [To|Sleepers]) end; _ -> To ! self(), 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} -> loop(State, CurConns - 1, NbChildren, Sleepers); %% 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 protocol options. {set_opts, Opts2} -> loop(State#state{opts=Opts2}, CurConns, NbChildren, Sleepers); {'EXIT', Parent, Reason} -> exit(Reason); {'EXIT', Pid, Reason} when Sleepers =:= [] -> report_error(Ref, Protocol, Pid, Reason), erase(Pid), loop(State, CurConns - 1, NbChildren - 1, Sleepers); %% Resume a sleeping acceptor if needed. {'EXIT', Pid, Reason} -> report_error(Ref, Protocol, Pid, Reason), erase(Pid), [To|Sleepers2] = Sleepers, To ! self(), loop(State, CurConns - 1, NbChildren - 1, Sleepers2); {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} -> Pids = get_keys(true), Children = [{Protocol, Pid, ConnType, [Protocol]} || Pid <- Pids, is_pid(Pid)], 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 -> error_logger:error_msg( "Ranch listener ~p received unexpected message ~p~n", [Ref, Msg]) end. system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) -> loop(State, CurConns, NbChildren, Sleepers). -spec system_terminate(any(), _, _, _) -> no_return(). system_terminate(Reason, _, _, _) -> exit(Reason). system_code_change(Misc, _, _, _) -> {ok, Misc}. %% We use ~999999p here instead of ~w because the latter doesn't %% support printable strings. report_error(_, _, _, normal) -> ok; report_error(_, _, _, shutdown) -> ok; report_error(_, _, _, {shutdown, _}) -> ok; report_error(Ref, Protocol, Pid, Reason) -> error_logger:error_msg( "Ranch listener ~p had connection process started with " "~p:start_link/4 at ~p exit with reason: ~999999p~n", [Ref, Protocol, Pid, Reason]). erlang-ranch-0.9.0/src/ranch_listener_sup.erl000066400000000000000000000035451224115642200212510ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private -module(ranch_listener_sup). -behaviour(supervisor). %% API. -export([start_link/6]). %% supervisor. -export([init/1]). %% API. -spec start_link(ranch:ref(), non_neg_integer(), module(), any(), module(), any()) -> {ok, pid()}. start_link(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> MaxConns = proplists:get_value(max_connections, TransOpts, 1024), ranch_server:set_new_listener_opts(Ref, MaxConns, ProtoOpts), supervisor:start_link(?MODULE, { Ref, NbAcceptors, Transport, TransOpts, Protocol }). %% supervisor. init({Ref, NbAcceptors, Transport, TransOpts, Protocol}) -> ConnType = proplists:get_value(connection_type, TransOpts, worker), ChildSpecs = [ %% conns_sup {ranch_conns_sup, {ranch_conns_sup, start_link, [Ref, ConnType, Transport, Protocol]}, permanent, infinity, supervisor, [ranch_conns_sup]}, %% acceptors_sup {ranch_acceptors_sup, {ranch_acceptors_sup, start_link, [Ref, NbAcceptors, Transport, TransOpts] }, permanent, infinity, supervisor, [ranch_acceptors_sup]} ], {ok, {{rest_for_one, 10, 10}, ChildSpecs}}. erlang-ranch-0.9.0/src/ranch_protocol.erl000066400000000000000000000020001224115642200203570ustar00rootroot00000000000000%% Copyright (c) 2012, 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. %% @private -module(ranch_protocol). %% Start a new connection process for the given socket. -callback start_link( Ref::ranch:ref(), Socket::any(), Transport::module(), ProtocolOptions::any()) -> {ok, ConnectionPid::pid()}. erlang-ranch-0.9.0/src/ranch_server.erl000066400000000000000000000130721224115642200200370ustar00rootroot00000000000000%% Copyright (c) 2012, 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. %% @private -module(ranch_server). -behaviour(gen_server). %% API. -export([start_link/0]). -export([set_new_listener_opts/3]). -export([cleanup_listener_opts/1]). -export([set_connections_sup/2]). -export([get_connections_sup/1]). -export([set_port/2]). -export([get_port/1]). -export([set_max_connections/2]). -export([get_max_connections/1]). -export([set_protocol_options/2]). -export([get_protocol_options/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. %% @doc Start the ranch_server gen_server. -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% @private -spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any()) -> ok. set_new_listener_opts(Ref, MaxConns, Opts) -> gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, Opts}). %% @doc Cleanup listener options after it has been stopped. -spec cleanup_listener_opts(ranch:ref()) -> ok. cleanup_listener_opts(Ref) -> _ = ets:delete(?TAB, {port, Ref}), _ = ets:delete(?TAB, {max_conns, Ref}), _ = ets:delete(?TAB, {opts, Ref}), ok. %% @doc Set a connection supervisor associated with specific listener. -spec set_connections_sup(ranch:ref(), pid()) -> ok. set_connections_sup(Ref, Pid) -> true = gen_server:call(?MODULE, {set_connections_sup, Ref, Pid}), ok. %% @doc Return the connection supervisor used by specific listener. -spec get_connections_sup(ranch:ref()) -> pid(). get_connections_sup(Ref) -> ets:lookup_element(?TAB, {conns_sup, Ref}, 2). %% @private -spec set_port(ranch:ref(), inet:port_number()) -> ok. set_port(Ref, Port) -> gen_server:call(?MODULE, {set_port, Ref, Port}). %% @doc Return the listener's port. -spec get_port(ranch:ref()) -> inet:port_number(). get_port(Ref) -> ets:lookup_element(?TAB, {port, Ref}, 2). %% @doc Set the max number of connections allowed concurrently. -spec set_max_connections(ranch:ref(), ranch:max_conns()) -> ok. set_max_connections(Ref, MaxConnections) -> gen_server:call(?MODULE, {set_max_conns, Ref, MaxConnections}). %% @doc Return the max number of connections allowed concurrently. -spec get_max_connections(ranch:ref()) -> ranch:max_conns(). get_max_connections(Ref) -> ets:lookup_element(?TAB, {max_conns, Ref}, 2). %% @doc Upgrade the protocol options. -spec set_protocol_options(ranch:ref(), any()) -> ok. set_protocol_options(Ref, ProtoOpts) -> gen_server:call(?MODULE, {set_opts, Ref, ProtoOpts}). %% @doc Return the current protocol options. -spec get_protocol_options(ranch:ref()) -> any(). get_protocol_options(Ref) -> ets:lookup_element(?TAB, {opts, Ref}, 2). %% @doc Count the number of connections in the connection pool. -spec count_connections(ranch:ref()) -> non_neg_integer(). count_connections(Ref) -> ranch_conns_sup:active_connections(get_connections_sup(Ref)). %% gen_server. %% @private init([]) -> Monitors = [{{erlang:monitor(process, Pid), Pid}, Ref} || [Ref, Pid] <- ets:match(?TAB, {{conns_sup, '$1'}, '$2'})], {ok, #state{monitors=Monitors}}. %% @private handle_call({set_new_listener_opts, Ref, MaxConns, Opts}, _, State) -> ets:insert(?TAB, {{max_conns, Ref}, MaxConns}), ets:insert(?TAB, {{opts, Ref}, Opts}), {reply, ok, State}; handle_call({set_connections_sup, Ref, Pid}, _, State=#state{monitors=Monitors}) -> case ets:insert_new(?TAB, {{conns_sup, Ref}, Pid}) of true -> MonitorRef = erlang:monitor(process, Pid), {reply, true, State#state{monitors=[{{MonitorRef, Pid}, Ref}|Monitors]}}; false -> {reply, false, State} end; handle_call({set_port, Ref, Port}, _, State) -> true = ets:insert(?TAB, {{port, Ref}, Port}), {reply, ok, State}; handle_call({set_max_conns, Ref, MaxConns}, _, State) -> ets:insert(?TAB, {{max_conns, Ref}, MaxConns}), ConnsSup = get_connections_sup(Ref), ConnsSup ! {set_max_conns, MaxConns}, {reply, ok, State}; handle_call({set_opts, Ref, Opts}, _, State) -> ets:insert(?TAB, {{opts, Ref}, Opts}), ConnsSup = get_connections_sup(Ref), ConnsSup ! {set_opts, Opts}, {reply, ok, State}; handle_call(_Request, _From, State) -> {reply, ignore, State}. %% @private handle_cast(_Request, State) -> {noreply, State}. %% @private handle_info({'DOWN', MonitorRef, process, Pid, _}, State=#state{monitors=Monitors}) -> {_, Ref} = lists:keyfind({MonitorRef, Pid}, 1, Monitors), true = ets:delete(?TAB, {conns_sup, Ref}), Monitors2 = lists:keydelete({MonitorRef, Pid}, 1, Monitors), {noreply, State#state{monitors=Monitors2}}; handle_info(_Info, State) -> {noreply, State}. %% @private terminate(_Reason, _State) -> ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. erlang-ranch-0.9.0/src/ranch_ssl.erl000066400000000000000000000276351224115642200173440ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @doc SSL transport API. %% %% Wrapper around ssl implementing the Ranch transport API. %% %% This transport requires the crypto, asn1, %% public_key and ssl applications to be started. %% If they aren't started, it will try to start them itself before %% opening a port to listen. Applications aren't stopped when the %% listening socket is closed, though. %% %% @see ssl -module(ranch_ssl). -behaviour(ranch_transport). -export([name/0]). -export([messages/0]). -export([listen/1]). -export([accept/2]). -export([connect/3]). -export([recv/3]). -export([send/2]). -export([sendfile/2]). -export([sendfile/4]). -export([sendfile/5]). -export([setopts/2]). -export([controlling_process/2]). -export([peername/1]). -export([sockname/1]). -export([close/1]). -type opts() :: [{backlog, non_neg_integer()} | {cacertfile, string()} | {cacerts, [Der::binary()]} | {cert, Der::binary()} | {certfile, string()} | {ciphers, [ssl:erl_cipher_suite()] | string()} | {fail_if_no_peer_cert, boolean()} | {hibernate_after, integer() | undefined} | {ip, inet:ip_address()} | {key, Der::binary()} | {keyfile, string()} | {next_protocols_advertised, [binary()]} | {nodelay, boolean()} | {password, string()} | {port, inet:port_number()} | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()} | {reuse_session, fun()} | {reuse_sessions, boolean()} | {secure_renegotiate, boolean()} | {verify, ssl:verify_type()} | {verify_fun, {fun(), InitialUserState::term()}}]. -export_type([opts/0]). %% @doc Name of this transport, ssl. name() -> ssl. %% @doc Atoms used to identify messages in {active, once | true} mode. messages() -> {ssl, ssl_closed, ssl_error}. %% @doc Listen for connections on the given port number. %% %% Calling this function returns a listening socket that can then %% The available options are: %% %%
%%
backlog
Maximum length of the pending connections queue. %% Defaults to 1024.
%%
cacertfile
Optional. Path to file containing PEM encoded %% CA certificates (trusted certificates used for verifying a peer %% certificate).
%%
cert
Optional. The DER encoded users certificate. If this %% option is supplied it will override the certfile option.
%%
certfile
Mandatory. Path to a file containing the user's %% certificate.
%%
ciphers
Optional. The cipher suites that should be supported. %% The function ssl:cipher_suites/0 can be used to find all available %% ciphers.
%%
fail_if_no_peer_cert
Optional. Used together with {verify, verify_peer}. %% If set to true, the server will fail if the client does not have a certificate %% to send, i.e. sends a empty certificate, if set to false (that is by default) %% it will only fail if the client sends an invalid certificate (an empty %% certificate is considered valid).
%%
hibernate_after
When an integer-value is specified, the ssl_connection %% will go into hibernation after the specified number of milliseconds of inactivity, %% thus reducing its memory footprint. When undefined is specified (this is the %% default), the process will never go into hibernation.
%%
ip
Interface to listen on. Listen on all interfaces %% by default.
%%
key
Optional. The DER encoded users private key. If this option %% is supplied it will override the keyfile option.
%%
keyfile
Optional. Path to the file containing the user's %% private PEM encoded key.
%%
next_protocols_advertised
Optional. Erlang R16B+ required. %% List of protocols advertised by TLS Next Protocol Negotiation %% extension.
%%
nodelay
Optional. Enable TCP_NODELAY. Enabled by default.
%%
password
Optional. String containing the user's password. %% All private keyfiles must be password protected currently.
%%
port
TCP port number to open. Defaults to 0 (see below)
%%
reuse_session
Optional. Enables the ssl server to have a local %% policy for deciding if a session should be reused or not, only meaningful %% if reuse_sessions is set to true.
%%
reuse_sessions
Optional. Specifies if the server should agree %% to reuse sessions when the clients request to do so.
%%
secure_renegotiate
Optional. Specifies if to reject renegotiation %% attempt that does not live up to RFC 5746. By default secure_renegotiate is %% set to false i.e. secure renegotiation will be used if possible but it will %% fallback to unsecure renegotiation if the peer does not support RFC 5746.
%%
verify
Optional. If set to verify_peer, performs an x509-path %% validation and request the client for a certificate.
%%
verify_fun
Optional. The verify fun will be called during the %% X509-path validation when an error or an extension unknown to the ssl %% application is encountered. Additionally it will be called when a certificate %% is considered valid by the path validation to allow access to each certificate %% in the path to the user application.
%%
%% %% You can listen to a random port by setting the port option to 0. %% It is then possible to retrieve this port number by calling %% sockname/1 on the listening socket. If you are using Ranch's %% listener API, then this port number can obtained through %% ranch:get_port/1 instead. %% %% @see ssl:listen/2 -spec listen(opts()) -> {ok, ssl:sslsocket()} | {error, atom()}. listen(Opts) -> ranch:require([crypto, asn1, public_key, ssl]), true = lists:keymember(cert, 1, Opts) orelse lists:keymember(certfile, 1, Opts), Opts2 = ranch:set_option_default(Opts, backlog, 1024), Opts3 = ranch:set_option_default(Opts2, ciphers, unbroken_cipher_suites()), %% 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(Opts3, [backlog, cacertfile, cacerts, cert, certfile, ciphers, fail_if_no_peer_cert, hibernate_after, ip, key, keyfile, next_protocols_advertised, nodelay, password, port, raw, reuse_session, reuse_sessions, secure_renegotiate, verify, verify_fun], [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {nodelay, true}])). %% @doc Accept connections with the given listening socket. %% %% Note that this function does both the transport accept and %% the SSL handshake. The returned socket is thus fully connected. %% %% @see ssl:transport_accept/2 %% @see ssl:ssl_accept/2 -spec accept(ssl:sslsocket(), timeout()) -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom() | tuple()}. accept(LSocket, Timeout) -> case ssl:transport_accept(LSocket, Timeout) of {ok, CSocket} -> ssl_accept(CSocket, Timeout); {error, Reason} -> {error, Reason} end. %% @private Experimental. Open a connection to the given host and port number. %% @see ssl:connect/3 %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts) when is_integer(Port) -> ssl:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}]). %% @doc Receive data from a socket in passive mode. %% @see ssl:recv/3 -spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> ssl:recv(Socket, Length, Timeout). %% @doc Send data on a socket. %% @see ssl:send/2 -spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. send(Socket, Packet) -> ssl:send(Socket, Packet). %% @equiv sendfile(Socket, Filename, 0, 0, []) -spec sendfile(ssl:sslsocket(), file:name_all()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename) -> sendfile(Socket, Filename, 0, 0, []). %% @equiv sendfile(Socket, File, Offset, Bytes, []) -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, []). %% @doc Send part of a file on a socket. %% %% 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. %% %% @see ranch_transport:sendfile/6 %% @see file:sendfile/5 -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). %% @doc Set options on the given socket. %% @see ssl:setopts/2 %% @todo Probably filter Opts? -spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> ssl:setopts(Socket, Opts). %% @doc Give control of the socket to a new process. %% %% Must be called from the process currently controlling the socket, %% otherwise an {error, not_owner} tuple will be returned. %% %% @see ssl:controlling_process/2 -spec controlling_process(ssl:sslsocket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> ssl:controlling_process(Socket, Pid). %% @doc Return the remote address and port of the connection. %% @see ssl:peername/1 -spec peername(ssl:sslsocket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> ssl:peername(Socket). %% @doc Return the local address and port of the connection. %% @see ssl:sockname/1 -spec sockname(ssl:sslsocket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. sockname(Socket) -> ssl:sockname(Socket). %% @doc Close the given socket. %% @see ssl:close/1 -spec close(ssl:sslsocket()) -> ok. close(Socket) -> ssl:close(Socket). %% Internal. %% This call always times out, either because a numeric timeout value %% was given, or because we've decided to use 5000ms instead of infinity. %% This value should be reasonable enough for the moment. -spec ssl_accept(ssl:sslsocket(), timeout()) -> {ok, ssl:sslsocket()} | {error, {ssl_accept, atom()}}. ssl_accept(Socket, infinity) -> ssl_accept(Socket, 5000); ssl_accept(Socket, Timeout) -> case ssl:ssl_accept(Socket, Timeout) of ok -> {ok, Socket}; {error, Reason} -> ok = close(Socket), {error, {ssl_accept, Reason}} end. %% Unfortunately the implementation of elliptic-curve ciphers that has %% been introduced in R16B01 is incomplete. Depending on the particular %% client, this can cause the TLS handshake to break during key %% agreement. Depending on the ssl application version, this function %% returns a list of all cipher suites that are supported by default, %% minus the elliptic-curve ones. -spec unbroken_cipher_suites() -> [ssl:erl_cipher_suite()]. unbroken_cipher_suites() -> case proplists:get_value(ssl_app, ssl:versions()) of Version when Version =:= "5.3"; Version =:= "5.3.1" -> lists:filter(fun(Suite) -> string:left(atom_to_list(element(1, Suite)), 4) =/= "ecdh" end, ssl:cipher_suites()); _ -> ssl:cipher_suites() end. erlang-ranch-0.9.0/src/ranch_sup.erl000066400000000000000000000024551224115642200173430ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @private -module(ranch_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). -define(SUPERVISOR, ?MODULE). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). %% supervisor. init([]) -> ranch_server = ets:new(ranch_server, [ ordered_set, public, named_table]), Procs = [ {ranch_server, {ranch_server, start_link, []}, permanent, 5000, worker, [ranch_server]} ], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-ranch-0.9.0/src/ranch_tcp.erl000066400000000000000000000157471224115642200173320ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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. %% @doc TCP transport API. %% %% Wrapper around gen_tcp implementing the Ranch transport API. %% %% @see gen_tcp -module(ranch_tcp). -behaviour(ranch_transport). -export([name/0]). -export([messages/0]). -export([listen/1]). -export([accept/2]). -export([connect/3]). -export([recv/3]). -export([send/2]). -export([sendfile/2]). -export([sendfile/4]). -export([sendfile/5]). -export([setopts/2]). -export([controlling_process/2]). -export([peername/1]). -export([sockname/1]). -export([close/1]). -type opts() :: [{backlog, non_neg_integer()} | {ip, inet:ip_address()} | {nodelay, boolean()} | {port, inet:port_number()} | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()}]. -export_type([opts/0]). %% @doc Name of this transport, tcp. name() -> tcp. %% @doc Atoms used to identify messages in {active, once | true} mode. messages() -> {tcp, tcp_closed, tcp_error}. %% @doc Listen for connections on the given port number. %% %% Calling this function returns a listening socket that can then %% be passed to accept/2 to accept connections. %% %% The available options are: %%
%%
backlog
Maximum length of the pending connections queue. %% Defaults to 1024.
%%
ip
Interface to listen on. Listen on all interfaces %% by default.
%%
nodelay
Optional. Enable TCP_NODELAY. Enabled by default.
%%
port
TCP port number to open. Defaults to 0 (see below).
%%
%% %% You can listen to a random port by setting the port option to 0. %% It is then possible to retrieve this port number by calling %% sockname/1 on the listening socket. If you are using Ranch's %% listener API, then this port number can obtained through %% ranch:get_port/1 instead. %% %% @see gen_tcp:listen/2 -spec listen(opts()) -> {ok, inet:socket()} | {error, atom()}. listen(Opts) -> Opts2 = ranch:set_option_default(Opts, backlog, 1024), %% 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, ranch:filter_options(Opts2, [backlog, ip, nodelay, port, raw], [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {nodelay, true}])). %% @doc Accept connections with the given listening socket. %% @see gen_tcp:accept/2 -spec accept(inet:socket(), timeout()) -> {ok, inet:socket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> gen_tcp:accept(LSocket, Timeout). %% @private Experimental. Open a connection to the given host and port number. %% @see gen_tcp:connect/3 %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts) when is_integer(Port) -> gen_tcp:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}]). %% @doc Receive data from a socket in passive mode. %% @see gen_tcp:recv/3 -spec recv(inet:socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> gen_tcp:recv(Socket, Length, Timeout). %% @doc Send data on a socket. %% @see gen_tcp:send/2 -spec send(inet:socket(), iodata()) -> ok | {error, atom()}. send(Socket, Packet) -> gen_tcp:send(Socket, Packet). %% @equiv sendfile(Socket, File, Offset, Bytes, []) -spec sendfile(inet:socket(), file:name_all()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename) -> sendfile(Socket, Filename, 0, 0, []). %% @equiv sendfile(Socket, File, Offset, Bytes, []) -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, []). %% @doc Send part of a file on a socket. %% %% As with sendfile/2 this is the optimal way to send (parts) of files using %% TCP. Note that unlike file:sendfile/5 this function accepts either a raw file %% or a file name and the ordering of arguments is different. %% %% @see file:sendfile/5 -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 internal implementation fails with a badmatch in %% prim_file:sendfile/10 if the socket is not connected. {error, closed} end. %% @doc Set options on the given socket. %% @see inet:setopts/2 %% @todo Probably filter Opts? -spec setopts(inet:socket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> inet:setopts(Socket, Opts). %% @doc Give control of the socket to a new process. %% %% Must be called from the process currently controlling the socket, %% otherwise an {error, not_owner} tuple will be returned. %% %% @see gen_tcp:controlling_process/2 -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid). %% @doc Return the remote address and port of the connection. %% @see inet:peername/1 -spec peername(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> inet:peername(Socket). %% @doc Return the local address and port of the connection. %% @see inet:sockname/1 -spec sockname(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. sockname(Socket) -> inet:sockname(Socket). %% @doc Close the given socket. %% @see gen_tcp:close/1 -spec close(inet:socket()) -> ok. close(Socket) -> gen_tcp:close(Socket). erlang-ranch-0.9.0/src/ranch_transport.erl000066400000000000000000000143201224115642200205620ustar00rootroot00000000000000%% Copyright (c) 2012, 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. %% @private -module(ranch_transport). -export([sendfile/6]). -type socket() :: any(). -type opts() :: any(). -type sendfile_opts() :: [{chunk_size, non_neg_integer()}]. -export_type([sendfile_opts/0]). %% Name of the transport. -callback name() -> atom(). %% @todo -callback caps(secure | sendfile) -> boolean(). %% Atoms used to identify messages in {active, once | true} mode. -callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}. %% Listen for connections on the given port number. %% %% Calling this function returns a listening socket that can then %% be passed to accept/2 to accept connections. %% %% Available options may vary between transports. %% %% You can listen to a random port by setting the port option to 0. %% It is then possible to retrieve this port number by calling %% sockname/1 on the listening socket. If you are using Ranch's %% listener API, then this port number can obtained through %% ranch:get_port/1 instead. -callback listen(opts()) -> {ok, socket()} | {error, atom()}. %% Accept connections with the given listening socket. -callback accept(socket(), timeout()) -> {ok, socket()} | {error, closed | timeout | atom() | tuple()}. %% Experimental. Open a connection to the given host and port number. -callback connect(string(), inet:port_number(), opts()) -> {ok, socket()} | {error, atom()}. %% Receive data from a socket in passive mode. -callback recv(socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | timeout | atom()}. %% Send data on a socket. -callback send(socket(), iodata()) -> ok | {error, atom()}. %% Send a file on a socket. -callback sendfile(socket(), file:name()) -> {ok, non_neg_integer()} | {error, atom()}. %% Send part of a file on a socket. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. %% Send part of a file on a socket. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), non_neg_integer(), sendfile_opts()) -> {ok, non_neg_integer()} | {error, atom()}. %% Set options on the given socket. -callback setopts(socket(), opts()) -> ok | {error, atom()}. %% Give control of the socket to a new process. %% %% Must be called from the process currently controlling the socket, %% otherwise an {error, not_owner} tuple will be returned. -callback controlling_process(socket(), pid()) -> ok | {error, closed | not_owner | atom()}. %% Return the remote address and port of the connection. -callback peername(socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. %% Return the local address and port of the connection. -callback sockname(socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. %% Close the given socket. -callback close(socket()) -> ok. %% @doc Send part of a file on a socket. %% %% 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. %% %% @see file:sendfile/5 -spec sendfile(module(), socket(), file:filename_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, term()}. 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). erlang-ranch-0.9.0/test/000077500000000000000000000000001224115642200150375ustar00rootroot00000000000000erlang-ranch-0.9.0/test/acceptor_SUITE.erl000066400000000000000000000441141224115642200203200ustar00rootroot00000000000000%% Copyright (c) 2011-2012, 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(acceptor_SUITE). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). -export([init_per_group/2]). -export([end_per_group/2]). %% misc. -export([misc_bad_transport/1]). %% ssl. -export([ssl_accept_error/1]). -export([ssl_accept_socket/1]). -export([ssl_active_echo/1]). -export([ssl_echo/1]). %% tcp. -export([tcp_accept_socket/1]). -export([tcp_active_echo/1]). -export([tcp_echo/1]). -export([tcp_max_connections/1]). -export([tcp_max_connections_and_beyond/1]). -export([tcp_set_max_connections/1]). -export([tcp_infinity_max_connections/1]). -export([tcp_clean_set_max_connections/1]). -export([tcp_upgrade/1]). %% supervisor. -export([supervisor_clean_restart/1]). -export([supervisor_clean_child_restart/1]). -export([supervisor_conns_alive/1]). -export([supervisor_server_recover_state/1]). -export([supervisor_clean_conns_sup_restart/1]). %% ct. all() -> [{group, tcp}, {group, ssl}, {group, misc}, {group, supervisor}]. groups() -> [{tcp, [ tcp_accept_socket, tcp_active_echo, tcp_echo, tcp_max_connections, tcp_infinity_max_connections, tcp_max_connections_and_beyond, tcp_set_max_connections, tcp_clean_set_max_connections, tcp_upgrade ]}, {ssl, [ ssl_accept_error, ssl_accept_socket, ssl_active_echo, ssl_echo ]}, {misc, [ misc_bad_transport ]}, {supervisor, [ supervisor_clean_restart, supervisor_clean_child_restart, supervisor_conns_alive, supervisor_server_recover_state, supervisor_clean_conns_sup_restart ]}]. init_per_suite(Config) -> ok = application:start(ranch), Config. end_per_suite(_) -> application:stop(ranch), ok. init_per_group(ssl, Config) -> application:start(crypto), application:start(asn1), application:start(public_key), application:start(ssl), Config; init_per_group(_, Config) -> Config. end_per_group(ssl, _) -> application:stop(ssl), application:stop(public_key), application:stop(asn1), application:stop(crypto), ok; end_per_group(_, _) -> ok. %% misc. misc_bad_transport(_) -> {error, badarg} = ranch:start_listener(misc_bad_transport, 1, bad_transport, [{port, 0}], echo_protocol, []), ok. %% ssl. ssl_accept_error(_) -> Name = ssl_accept_error, {_, Cert, Key} = ct_helper:make_certs(), {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_ssl, [{port, 0}, {cert, Cert}, {key, Key}], 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), ranch:stop_listener(Name). ssl_accept_socket(_) -> %%% XXX we can't do the spawn to test the controlling process change %%% because of the bug in ssl Name = ssl_accept_socket, {_, Cert, Key} = ct_helper:make_certs(), {ok, S} = ssl:listen(0, [{cert, Cert}, {key, Key}, binary, {active, false}, {packet, raw}, {reuseaddr, true}]), {ok, _} = ranch:start_listener(Name, 1, ranch_ssl, [{socket, S}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [binary, {active, false}, {packet, raw}, {cert, Cert}, {key, Key}]), ok = ssl:send(Socket, <<"TCP Ranch is working!">>), {ok, <<"TCP 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_echo(_) -> Name = ssl_active_echo, {_, Cert, Key} = ct_helper:make_certs(), {ok, _} = ranch:start_listener(Name, 1, ranch_ssl, [{port, 0}, {cert, Cert}, {key, Key}], active_echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [binary, {active, false}, {packet, raw}, {cert, Cert}, {key, Key}]), 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_echo(_) -> Name = ssl_echo, {_, Cert, Key} = ct_helper:make_certs(), {ok, _} = ranch:start_listener(Name, 1, ranch_ssl, [{port, 0}, {cert, Cert}, {key, Key}], echo_protocol, []), Port = ranch:get_port(Name), {ok, Socket} = ssl:connect("localhost", Port, [binary, {active, false}, {packet, raw}, {cert, Cert}, {key, Key}]), 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. %% tcp. tcp_accept_socket(_) -> Name = tcp_accept_socket, Ref = make_ref(), Parent = self(), spawn(fun() -> {ok, S} = gen_tcp:listen(0, [binary, {active, false}, {packet, raw}, {reuseaddr, true}]), {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{socket, S}], echo_protocol, []), Parent ! Ref end), receive Ref -> ok end, 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_echo(_) -> Name = tcp_active_echo, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], 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_echo(_) -> Name = tcp_echo, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], 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_max_connections(_) -> Name = tcp_max_connections, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {max_connections, 10}], notify_and_wait_protocol, [{msg, connected}, {pid, self()}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 11, 150), 10 = ranch_server:count_connections(Name), 10 = receive_loop(connected, 400), 1 = receive_loop(connected, 1000), ranch:stop_listener(Name). tcp_max_connections_and_beyond(_) -> Name = tcp_max_connections_and_beyond, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {max_connections, 10}], remove_conn_and_wait_protocol, [{remove, true}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 10, 0), receive after 250 -> ok end, 0 = ranch_server:count_connections(Name), 10 = length(supervisor:which_children( ranch_server:get_connections_sup(Name))), Counts = supervisor:count_children( ranch_server:get_connections_sup(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}]), receive after 250 -> ok end, ok = connect_loop(Port, 10, 0), receive after 250 -> ok end, 10 = ranch_server:count_connections(Name), 20 = length(supervisor:which_children( ranch_server:get_connections_sup(Name))), Counts2 = supervisor:count_children( ranch_server:get_connections_sup(Name)), {_, 20} = lists:keyfind(active, 1, Counts2), {_, 20} = lists:keyfind(workers, 1, Counts2), ranch:stop_listener(Name). tcp_set_max_connections(_) -> Name = tcp_set_max_connections, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {max_connections, 10}], notify_and_wait_protocol, [{msg, connected}, {pid, self()}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 20, 0), 10 = ranch_server:count_connections(Name), 10 = receive_loop(connected, 1000), 10 = ranch:get_max_connections(Name), ranch:set_max_connections(Name, 20), 10 = receive_loop(connected, 1000), 20 = ranch:get_max_connections(Name), ranch:stop_listener(Name). tcp_infinity_max_connections(_) -> Name = tcp_infinity_max_connections, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {max_connections, 10}], notify_and_wait_protocol, [{msg, connected}, {pid, self()}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 20, 0), 10 = ranch_server:count_connections(Name), 10 = 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 = receive_loop(connected, 1000), ranch:stop_listener(Name). tcp_clean_set_max_connections(_) -> %% This is a regression test to check that setting max connections does not %% cause any processes to crash. Name = tcp_clean_set_max_connections, {ok, ListSupPid} = ranch:start_listener(Name, 4, ranch_tcp, [{port, 0}, {max_connections, 4}], notify_and_wait_protocol, [{msg, connected}, {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(tcp_clean_set_max_connections), N = 20, ok = connect_loop(Port, N*5, 0), %% Randomly set max connections. [spawn(ranch, set_max_connections, [tcp_clean_set_max_connections, 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(), ranch:stop_listener(Name). tcp_upgrade(_) -> Name = tcp_upgrade, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], notify_and_wait_protocol, [{msg, connected}, {pid, self()}]), Port = ranch:get_port(Name), ok = connect_loop(Port, 1, 0), receive connected -> ok after 1000 -> error(timeout) end, ranch:set_protocol_options(Name, [{msg, upgraded}, {pid, self()}]), ok = connect_loop(Port, 1, 0), receive upgraded -> ok after 1000 -> error(timeout) end, ranch:stop_listener(Name). %% Supervisor tests supervisor_clean_restart(_) -> %% There we verify that mature listener death will not let %% whole supervisor down and also the supervisor itself will %% restart everything properly. Name = supervisor_clean_restart, NbAcc = 4, {ok, Pid} = ranch:start_listener(Name, NbAcc, ranch_tcp, [{port, 0}], echo_protocol, []), %% Trace supervisor spawns. 1 = erlang:trace(Pid, true, [procs, set_on_spawn]), ConnsSup0 = ranch_server:get_connections_sup(Name), erlang:exit(ConnsSup0, kill), receive after 1000 -> ok end, %% Verify that supervisor is alive true = is_process_alive(Pid), %% ...but children are dead. false = is_process_alive(ConnsSup0), %% Receive traces from newly started children ConnsSup = receive {trace, Pid, spawn, Pid2, _} -> Pid2 end, AccSupPid = receive {trace, Pid, spawn, Pid3, _} -> Pid3 end, %% ...and its acceptors. [receive {trace, AccSupPid, spawn, _Pid, _} -> ok end || _ <- lists:seq(1, NbAcc)], %% 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. ConnsSup = ranch_server:get_connections_sup(Name), _ = erlang:trace(all, false, [all]), ok = clean_traces(), ranch:stop_listener(Name). supervisor_clean_child_restart(_) -> %% Then we verify that only parts of the supervision tree %% restarted in the case of failure. Name = supervisor_clean_child_restart, %% Trace socket allocations. _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_tcp, listen, 1}, [{'_', [], [{return_trace}]}], [global]), {ok, Pid} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], echo_protocol, []), %% Trace supervisor spawns. 1 = erlang:trace(Pid, true, [procs, set_on_spawn]), ConnsSup = ranch_server:get_connections_sup(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 = is_process_alive(ConnsSup), %% 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. ConnsSup = ranch_server:get_connections_sup(Name), _ = erlang:trace_pattern({ranch_tcp, listen, 1}, false, []), _ = erlang:trace(all, false, [all]), ok = clean_traces(), ranch:stop_listener(Name). supervisor_conns_alive(_) -> %% And finally we make sure that in the case of partial failure %% live connections are not being killed. Name = supervisor_conns_alive, _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_tcp, listen, 1}, [{'_', [], [{return_trace}]}], [global]), {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], remove_conn_and_wait_protocol, [{remove, false}]), %% Get the listener socket LSocket = receive {trace, _, return_from, {ranch_tcp, listen, 1}, {ok, S}} -> S after 0 -> error(lsocket_unknown) end, TcpPort = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", TcpPort, [binary, {active, true}, {packet, raw}]), %% 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(), ranch:stop_listener(Name). supervisor_server_recover_state(_) -> %% Verify that if ranch_server crashes it regains its state and monitors %% ranch_conns_sup that were previously registered. Name = supervisor_server_recover_state, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], echo_protocol, []), _ = erlang:trace(new, true, [call]), 1 = erlang:trace_pattern({ranch_server, init, 1}, [{'_', [], [{return_trace}]}], [global]), ConnsSup = ranch_server:get_connections_sup(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, ConnsSup = ranch_server:get_connections_sup(Name), ranch:stop_listener(Name), %% Check ranch_server has removed the ranch_conns_sup. {'EXIT', {badarg, _}} = (catch ranch_server:get_connections_sup(Name)), _ = erlang:trace(all, false, [all]), ok = clean_traces(). supervisor_clean_conns_sup_restart(_) -> %% Verify that a conns_sup can not register with the same Name as an already %% registered conns_sup that is still alive. Make sure this does not crash %% the ranch_server. Name = supervisor_clean_conns_sup_restart, {ok, _} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}], echo_protocol, []), Server = erlang:whereis(ranch_server), ServerMonRef = erlang:monitor(process, Server), %% Exit because Name already registered and is alive. {'EXIT', _} = (catch ranch_server:set_connections_sup(Name, self())), receive {'DOWN', ServerMonRef, process, Server, _} -> error(ranch_server_down) after 1000 -> ok end, ranch:stop_listener(Name). %% 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) -> receive Message -> receive_loop(Message, Timeout, N + 1) after Timeout -> N end. clean_traces() -> receive {trace, _, _, _} -> clean_traces(); {trace, _, _, _, _} -> clean_traces() after 0 -> ok end. erlang-ranch-0.9.0/test/active_echo_protocol.erl000066400000000000000000000011671224115642200217420ustar00rootroot00000000000000-module(active_echo_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. init(Ref, Socket, Transport, _Opts = []) -> ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> {OK, Closed, Error} = 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. erlang-ranch-0.9.0/test/cover.spec000066400000000000000000000000341224115642200170260ustar00rootroot00000000000000{incl_app, ranch, details}. erlang-ranch-0.9.0/test/echo_protocol.erl000066400000000000000000000007771224115642200204150ustar00rootroot00000000000000-module(echo_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. init(Ref, Socket, Transport, _Opts = []) -> ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, 5000) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end. erlang-ranch-0.9.0/test/notify_and_wait_protocol.erl000066400000000000000000000004361224115642200226450ustar00rootroot00000000000000-module(notify_and_wait_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/2]). start_link(_, _, _, [{msg, Msg}, {pid, TestPid}]) -> Pid = spawn_link(?MODULE, init, [Msg, TestPid]), {ok, Pid}. init(Msg, Pid) -> Pid ! Msg, receive after 2500 -> ok end. erlang-ranch-0.9.0/test/remove_conn_and_wait_protocol.erl000066400000000000000000000006171224115642200236500ustar00rootroot00000000000000-module(remove_conn_and_wait_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/2]). start_link(Ref, _, _, [{remove, MaybeRemove}]) -> Pid = spawn_link(?MODULE, init, [Ref, MaybeRemove]), {ok, Pid}. init(Ref, MaybeRemove) -> ranch:accept_ack(Ref), case MaybeRemove of true -> ranch:remove_connection(Ref); false -> ok end, receive after 2500 -> ok end. erlang-ranch-0.9.0/test/sendfile_SUITE.erl000066400000000000000000000262551224115642200203170ustar00rootroot00000000000000%% Copyright (c) 2013, James Fish %% %% 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). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). -export([suite/0]). -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). -export([init_per_group/2]). -export([end_per_group/2]). %% Tests. -export([filename/1]). -export([rawfile/1]). -export([rawfile_bytes_large/1]). -export([rawfile_bytes_zero/1]). -export([rawfile_chunk_size_large/1]). -export([rawfile_offset_large/1]). -export([rawfile_range_large/1]). -export([rawfile_range_medium/1]). -export([rawfile_range_small/1]). -export([ssl_chunk_size/1]). all() -> [{group, tcp}, {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}, {ssl, [parallel], Tests ++ [ssl_chunk_size]}]. init_per_suite(Config) -> ok = application:start(ranch), ok = application:start(crypto), Filename = filename:join(?config(priv_dir, Config), "sendfile"), Binary = crypto:rand_bytes(20 * 1024 * 1024), ok = file:write_file(Filename, Binary), [{filename, Filename} | Config]. end_per_suite(Config) -> application:stop(ranch), application:stop(crypto), Filename = ?config(filename, Config), ok = file:delete(Filename), ok. init_per_group(ssl, Config) -> application:start(asn1), application:start(public_key), application:start(ssl), {_, Cert, Key} = ct_helper:make_certs(), SslOpts = [{cert, Cert}, {key, Key}], [{transport, ranch_ssl}, {transport_opts, SslOpts} | Config]; init_per_group(tcp, Config) -> [{transport, ranch_tcp}, {transport_opts, []} | Config]. end_per_group(ssl, _) -> application:stop(ssl), application:stop(public_key), application:stop(asn1), ok; end_per_group(_, _) -> ok. %% Check can send a whole file given with filename. filename(Config) -> 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). %% Check can send a whole file with rawfile. rawfile(Config) -> 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). %% Check can send a file where Bytes is larger than file size. rawfile_bytes_large(Config) -> 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). %% Check can send whole file when Bytes =:= 0. rawfile_bytes_zero(Config) -> 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). %% Check can send file where chunk_size is greater than file size. rawfile_chunk_size_large(Config) -> 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). %% Check send file where offset is larger than file size sends no bytes and %% returns {ok, 0}. rawfile_offset_large(Config) -> 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). %% Check can send file with positive Offset and Offset + Bytes larger than file %% size. rawfile_range_large(Config) -> 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). %% Check can send file with positive Offset and Offset + Bytes less than file %% size. rawfile_range_medium(Config) -> 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). %% Check can send file with positive Offset, Offset + Bytes less than file %% size and Bytes less than chunk_size. rawfile_range_small(Config) -> 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). %% Check ssl obeys chunk_size. ssl_chunk_size(Config) -> 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 1000 -> 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(). sockets(Config) -> Transport = ?config(transport, Config), TransportOpts = ?config(transport_opts, Config), {ok, LSocket} = Transport:listen(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, 500), 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.