pax_global_header00006660000000000000000000000064124267072100014513gustar00rootroot0000000000000052 comment=741bfb73ca663394ecf7a73410506d66f01985a6 ranch-1.1.0/000077500000000000000000000000001242670721000126055ustar00rootroot00000000000000ranch-1.1.0/.gitignore000066400000000000000000000001321242670721000145710ustar00rootroot00000000000000.ranch.plt .eunit deps doc/*.css doc/*.html doc/*.png doc/edoc-info ebin logs test/*.beam ranch-1.1.0/AUTHORS000066400000000000000000000010441242670721000136540ustar00rootroot00000000000000Ranch is available thanks to the work of: Loïc Hoguin James Fish Andrew Majorov Ransom Richardson Fred Hebert Geoff Cant Klaus Trainer josh rotenberg 0x00F6 Alexander Zhuravlev Ali Sabil Andre Graf Andrew Thompson Jihyun Yu Slava Yurin Stéphane Wirtel Xiao Jia 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 ranch-1.1.0/CHANGELOG.md000066400000000000000000000002761242670721000144230ustar00rootroot00000000000000CHANGELOG ========= 1.1.0 ----- * Add Transport:secure/0 * Add SSL partial_chain option * Stop reporting errors on {error, closed} in accept_ack 1.0.0 ----- * Initial release. ranch-1.1.0/LICENSE000066400000000000000000000013651242670721000136170ustar00rootroot00000000000000Copyright (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. ranch-1.1.0/Makefile000066400000000000000000000004331242670721000142450ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = ranch # Dependencies. TEST_DEPS = ct_helper dep_ct_helper = git https://github.com/extend/ct_helper.git master # Options. COMPILE_FIRST = ranch_transport PLT_APPS = crypto public_key ssl # Standard targets. include erlang.mk ranch-1.1.0/README.md000066400000000000000000000021661242670721000140710ustar00rootroot00000000000000Ranch ===== 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) * [Check the manual](http://ninenines.eu/docs/en/ranch/HEAD/manual) * Look at the examples in the `examples/` directory Support ------- * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) ranch-1.1.0/ROADMAP.md000066400000000000000000000017251242670721000142170ustar00rootroot00000000000000ROADMAP ======= 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. ranch-1.1.0/all.sh000077500000000000000000000006161242670721000137170ustar00rootroot00000000000000#!/bin/sh KERL_INSTALL_PATH=~/erlang KERL_RELEASES="r15b01 r15b02 r15b03 r16b r16b01 r16b02 r16b03-1 17.0 17.1.2" make build-ct-suites for rel in $KERL_RELEASES do echo echo " TESTING $rel" echo . $KERL_INSTALL_PATH/$rel/activate cp ~/.kerl/builds/$rel/otp_src_*/lib/ssl/test/erl_make_certs.erl \ deps/ct_helper/src/ CT_OPTS="-label $rel" make tests done xdg-open logs/all_runs.html ranch-1.1.0/build.config000066400000000000000000000004661242670721000151010ustar00rootroot00000000000000# Core modules. # # Do *not* comment or remove them # unless you know what you are doing! core/core core/deps core/erlc # Plugins. # # Comment to disable, uncomment to enable. plugins/bootstrap #plugins/c_src plugins/ct plugins/dialyzer #plugins/edoc plugins/elvis #plugins/erlydtl plugins/relx plugins/shell ranch-1.1.0/erlang.mk000066400000000000000000000510041242670721000144060ustar00rootroot00000000000000# Copyright (c) 2013-2014, Loïc Hoguin # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .PHONY: all deps app rel docs tests clean distclean help erlang-mk ERLANG_MK_VERSION = 1 # Core configuration. PROJECT ?= $(notdir $(CURDIR)) PROJECT := $(strip $(PROJECT)) # Verbosity. V ?= 0 gen_verbose_0 = @echo " GEN " $@; gen_verbose = $(gen_verbose_$(V)) # Core targets. all:: deps app rel clean:: $(gen_verbose) rm -f erl_crash.dump distclean:: clean help:: @printf "%s\n" \ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ "Copyright (c) 2013-2014 Loïc Hoguin " \ "" \ "Usage: [V=1] make [target]" \ "" \ "Core targets:" \ " all Run deps, app and rel targets in that order" \ " deps Fetch dependencies (if needed) and compile them" \ " app Compile the project" \ " rel Build a release for this project, if applicable" \ " docs Build the documentation for this project" \ " tests Run the tests for this project" \ " clean Delete temporary and output files from most targets" \ " distclean Delete all temporary and output files" \ " help Display this help and exit" \ "" \ "The target clean only removes files that are commonly removed." \ "Dependencies and releases are left untouched." \ "" \ "Setting V=1 when calling make enables verbose mode." # Core functions. ifeq ($(shell which wget 2>/dev/null | wc -l), 1) define core_http_get wget --no-check-certificate -O $(1) $(2)|| rm $(1) endef else define core_http_get erl -noshell -eval 'ssl:start(), inets:start(), case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of {ok, {{_, 200, _}, _, Body}} -> case file:write_file("$(1)", Body) of ok -> ok; {error, R1} -> halt(R1) end; {error, R2} -> halt(R2) end, halt(0).' endef endif # Automated update. ERLANG_MK_BUILD_CONFIG ?= build.config ERLANG_MK_BUILD_DIR ?= .erlang.mk.build erlang-mk: git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR) if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi cd $(ERLANG_MK_BUILD_DIR) && make cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk rm -rf $(ERLANG_MK_BUILD_DIR) # Copyright (c) 2013-2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-deps distclean-pkg pkg-list pkg-search # Configuration. DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) ifeq ($(ERL_LIBS),) ERL_LIBS = $(DEPS_DIR) else ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR) endif endif export ERL_LIBS PKG_FILE2 ?= $(CURDIR)/.erlang.mk.packages.v2 export PKG_FILE2 PKG_FILE_URL ?= https://raw.githubusercontent.com/ninenines/erlang.mk/master/packages.v2.tsv # Core targets. deps:: $(ALL_DEPS_DIRS) @for dep in $(ALL_DEPS_DIRS) ; do \ if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ] ; then \ $(MAKE) -C $$dep ; \ else \ echo "include $(CURDIR)/erlang.mk" | ERLC_OPTS=+debug_info $(MAKE) -f - -C $$dep ; \ fi ; \ done distclean:: distclean-deps distclean-pkg # Deps related targets. define dep_fetch if [ "$$$$VS" = "git" ]; then \ git clone -n -- $$$$REPO $(DEPS_DIR)/$(1); \ cd $(DEPS_DIR)/$(1) && git checkout -q $$$$COMMIT; \ elif [ "$$$$VS" = "hg" ]; then \ hg clone -U $$$$REPO $(DEPS_DIR)/$(1); \ cd $(DEPS_DIR)/$(1) && hg update -q $$$$COMMIT; \ else \ echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \ exit 78; \ fi endef define dep_target $(DEPS_DIR)/$(1): @mkdir -p $(DEPS_DIR) ifeq (,$(dep_$(1))) @if [ ! -f $(PKG_FILE2) ]; then $(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL)); fi @DEPPKG=$$$$(awk 'BEGIN { FS = "\t" }; $$$$1 == "$(1)" { print $$$$2 " " $$$$3 " " $$$$4 }' $(PKG_FILE2);); \ VS=$$$$(echo $$$$DEPPKG | cut -d " " -f1); \ REPO=$$$$(echo $$$$DEPPKG | cut -d " " -f2); \ COMMIT=$$$$(echo $$$$DEPPKG | cut -d " " -f3); \ $(call dep_fetch,$(1)) else @VS=$(word 1,$(dep_$(1))); \ REPO=$(word 2,$(dep_$(1))); \ COMMIT=$(word 3,$(dep_$(1))); \ $(call dep_fetch,$(1)) endif endef $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) distclean-deps: $(gen_verbose) rm -rf $(DEPS_DIR) # Packages related targets. $(PKG_FILE2): @$(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL)) pkg-list: $(PKG_FILE2) @cat $(PKG_FILE2) | awk 'BEGIN { FS = "\t" }; { print \ "Name:\t\t" $$1 "\n" \ "Repository:\t" $$3 "\n" \ "Website:\t" $$5 "\n" \ "Description:\t" $$6 "\n" }' ifdef q pkg-search: $(PKG_FILE2) @cat $(PKG_FILE2) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \ "Name:\t\t" $$1 "\n" \ "Repository:\t" $$3 "\n" \ "Website:\t" $$5 "\n" \ "Description:\t" $$6 "\n" }' else pkg-search: $(error Usage: make pkg-search q=STRING) endif ifeq ($(PKG_FILE2),$(CURDIR)/.erlang.mk.packages.v2) distclean-pkg: $(gen_verbose) rm -f $(PKG_FILE2) endif help:: @printf "%s\n" "" \ "Package-related targets:" \ " pkg-list List all known packages" \ " pkg-search q=STRING Search for STRING in the package index" # Copyright (c) 2013-2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-app # Configuration. ERLC_OPTS ?= -Werror +debug_info +warn_export_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))) # Verbosity. 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)) # Core targets. app:: erlc-include ebin/$(PROJECT).app $(eval MODULES := $(shell find ebin -type f -name \*.beam \ | sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//')) @if [ -z "$$(grep -E '^[^%]*{modules,' src/$(PROJECT).app.src)" ]; then \ echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \ exit 1; \ fi $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true)) $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed "s/{modules,[[:space:]]*\[\]}/{modules, \[$(MODULES)\]}/" \ | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \ > 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 ifneq ($(wildcard src/),) ebin/$(PROJECT).app:: @mkdir -p ebin/ ebin/$(PROJECT).app:: $(shell find src -type f -name \*.erl) \ $(shell find src -type f -name \*.core) $(if $(strip $?),$(call compile_erl,$?)) ebin/$(PROJECT).app:: $(shell find src -type f -name \*.xrl) \ $(shell find src -type f -name \*.yrl) $(if $(strip $?),$(call compile_xyrl,$?)) endif clean:: clean-app # Extra targets. erlc-include: -@if [ -d ebin/ ]; then \ find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \ fi clean-app: $(gen_verbose) rm -rf ebin/ # Copyright (c) 2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates # Core targets. help:: @printf "%s\n" "" \ "Bootstrap targets:" \ " bootstrap Generate a skeleton of an OTP application" \ " bootstrap-lib Generate a skeleton of an OTP library" \ " bootstrap-rel Generate the files needed to build a release" \ " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ " list-templates List available templates" # Bootstrap templates. bs_appsrc = "{application, $(PROJECT), [" \ " {description, \"\"}," \ " {vsn, \"0.1.0\"}," \ " {id, \"git\"}," \ " {modules, []}," \ " {registered, []}," \ " {applications, [" \ " kernel," \ " stdlib" \ " ]}," \ " {mod, {$(PROJECT)_app, []}}," \ " {env, []}" \ "]}." bs_appsrc_lib = "{application, $(PROJECT), [" \ " {description, \"\"}," \ " {vsn, \"0.1.0\"}," \ " {id, \"git\"}," \ " {modules, []}," \ " {registered, []}," \ " {applications, [" \ " kernel," \ " stdlib" \ " ]}" \ "]}." bs_Makefile = "PROJECT = $(PROJECT)" \ "include erlang.mk" bs_app = "-module($(PROJECT)_app)." \ "-behaviour(application)." \ "" \ "-export([start/2])." \ "-export([stop/1])." \ "" \ "start(_Type, _Args) ->" \ " $(PROJECT)_sup:start_link()." \ "" \ "stop(_State) ->" \ " ok." bs_relx_config = "{release, {$(PROJECT)_release, \"1\"}, [$(PROJECT)]}." \ "{extended_start_script, true}." \ "{sys_config, \"rel/sys.config\"}." \ "{vm_args, \"rel/vm.args\"}." bs_sys_config = "[" \ "]." bs_vm_args = "-name $(PROJECT)@127.0.0.1" \ "-setcookie $(PROJECT)" \ "-heart" # Normal templates. tpl_supervisor = "-module($(n))." \ "-behaviour(supervisor)." \ "" \ "-export([start_link/0])." \ "-export([init/1])." \ "" \ "start_link() ->" \ " supervisor:start_link({local, ?MODULE}, ?MODULE, [])." \ "" \ "init([]) ->" \ " Procs = []," \ " {ok, {{one_for_one, 1, 5}, Procs}}." tpl_gen_server = "-module($(n))." \ "-behaviour(gen_server)." \ "" \ "%% API." \ "-export([start_link/0])." \ "" \ "%% gen_server." \ "-export([init/1])." \ "-export([handle_call/3])." \ "-export([handle_cast/2])." \ "-export([handle_info/2])." \ "-export([terminate/2])." \ "-export([code_change/3])." \ "" \ "-record(state, {" \ "})." \ "" \ "%% API." \ "" \ "-spec start_link() -> {ok, pid()}." \ "start_link() ->" \ " gen_server:start_link(?MODULE, [], [])." \ "" \ "%% gen_server." \ "" \ "init([]) ->" \ " {ok, \#state{}}." \ "" \ "handle_call(_Request, _From, State) ->" \ " {reply, ignored, State}." \ "" \ "handle_cast(_Msg, State) ->" \ " {noreply, State}." \ "" \ "handle_info(_Info, State) ->" \ " {noreply, State}." \ "" \ "terminate(_Reason, _State) ->" \ " ok." \ "" \ "code_change(_OldVsn, State, _Extra) ->" \ " {ok, State}." tpl_cowboy_http = "-module($(n))." \ "-behaviour(cowboy_http_handler)." \ "" \ "-export([init/3])." \ "-export([handle/2])." \ "-export([terminate/3])." \ "" \ "-record(state, {" \ "})." \ "" \ "init(_, Req, _Opts) ->" \ " {ok, Req, \#state{}}." \ "" \ "handle(Req, State=\#state{}) ->" \ " {ok, Req2} = cowboy_req:reply(200, Req)," \ " {ok, Req2, State}." \ "" \ "terminate(_Reason, _Req, _State) ->" \ " ok." tpl_cowboy_loop = "-module($(n))." \ "-behaviour(cowboy_loop_handler)." \ "" \ "-export([init/3])." \ "-export([info/3])." \ "-export([terminate/3])." \ "" \ "-record(state, {" \ "})." \ "" \ "init(_, Req, _Opts) ->" \ " {loop, Req, \#state{}, 5000, hibernate}." \ "" \ "info(_Info, Req, State) ->" \ " {loop, Req, State, hibernate}." \ "" \ "terminate(_Reason, _Req, _State) ->" \ " ok." tpl_cowboy_rest = "-module($(n))." \ "" \ "-export([init/3])." \ "-export([content_types_provided/2])." \ "-export([get_html/2])." \ "" \ "init(_, _Req, _Opts) ->" \ " {upgrade, protocol, cowboy_rest}." \ "" \ "content_types_provided(Req, State) ->" \ " {[{{<<\"text\">>, <<\"html\">>, '*'}, get_html}], Req, State}." \ "" \ "get_html(Req, State) ->" \ " {<<\"This is REST!\">>, Req, State}." tpl_cowboy_ws = "-module($(n))." \ "-behaviour(cowboy_websocket_handler)." \ "" \ "-export([init/3])." \ "-export([websocket_init/3])." \ "-export([websocket_handle/3])." \ "-export([websocket_info/3])." \ "-export([websocket_terminate/3])." \ "" \ "-record(state, {" \ "})." \ "" \ "init(_, _, _) ->" \ " {upgrade, protocol, cowboy_websocket}." \ "" \ "websocket_init(_, Req, _Opts) ->" \ " Req2 = cowboy_req:compact(Req)," \ " {ok, Req2, \#state{}}." \ "" \ "websocket_handle({text, Data}, Req, State) ->" \ " {reply, {text, Data}, Req, State};" \ "websocket_handle({binary, Data}, Req, State) ->" \ " {reply, {binary, Data}, Req, State};" \ "websocket_handle(_Frame, Req, State) ->" \ " {ok, Req, State}." \ "" \ "websocket_info(_Info, Req, State) ->" \ " {ok, Req, State}." \ "" \ "websocket_terminate(_Reason, _Req, _State) ->" \ " ok." tpl_ranch_protocol = "-module($(n))." \ "-behaviour(ranch_protocol)." \ "" \ "-export([start_link/4])." \ "-export([init/4])." \ "" \ "-type opts() :: []." \ "-export_type([opts/0])." \ "" \ "-record(state, {" \ " socket :: inet:socket()," \ " transport :: module()" \ "})." \ "" \ "start_link(Ref, Socket, Transport, Opts) ->" \ " Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts])," \ " {ok, Pid}." \ "" \ "-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok." \ "init(Ref, Socket, Transport, _Opts) ->" \ " ok = ranch:accept_ack(Ref)," \ " loop(\#state{socket=Socket, transport=Transport})." \ "" \ "loop(State) ->" \ " loop(State)." # Plugin-specific targets. bootstrap: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif @printf "%s\n" $(bs_Makefile) > Makefile @mkdir src/ @printf "%s\n" $(bs_appsrc) > src/$(PROJECT).app.src @printf "%s\n" $(bs_app) > src/$(PROJECT)_app.erl $(eval n := $(PROJECT)_sup) @printf "%s\n" $(tpl_supervisor) > src/$(PROJECT)_sup.erl bootstrap-lib: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif @printf "%s\n" $(bs_Makefile) > Makefile @mkdir src/ @printf "%s\n" $(bs_appsrc_lib) > src/$(PROJECT).app.src bootstrap-rel: ifneq ($(wildcard relx.config),) $(error Error: relx.config already exists) endif ifneq ($(wildcard rel/),) $(error Error: rel/ directory already exists) endif @printf "%s\n" $(bs_relx_config) > relx.config @mkdir rel/ @printf "%s\n" $(bs_sys_config) > rel/sys.config @printf "%s\n" $(bs_vm_args) > rel/vm.args new: ifeq ($(wildcard src/),) $(error Error: src/ directory does not exist) endif ifndef t $(error Usage: make new t=TEMPLATE n=NAME) endif ifndef tpl_$(t) $(error Unknown template) endif ifndef n $(error Usage: make new t=TEMPLATE n=NAME) endif @printf "%s\n" $(tpl_$(t)) > src/$(n).erl list-templates: @echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) # Copyright (c) 2013-2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: build-ct-deps build-ct-suites tests-ct clean-ct distclean-ct # Configuration. CT_OPTS ?= ifneq ($(wildcard test/),) CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(shell find test -type f -name \*_SUITE.erl -exec basename {} \;))) else CT_SUITES ?= endif TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard TEST_ERLC_OPTS += -DTEST=1 -DEXTRA=1 +'{parse_transform, eunit_autoexport}' # Core targets. tests:: tests-ct clean:: clean-ct distclean:: distclean-ct help:: @printf "%s\n" "" \ "All your common_test suites have their associated targets." \ "A suite named http_SUITE can be ran using the ct-http target." # Plugin-specific targets. ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) CT_RUN = ct_run \ -no_auto_compile \ -noshell \ -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ -dir test \ -logdir logs $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) build-ct-deps: $(ALL_TEST_DEPS_DIRS) @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done build-ct-suites: build-ct-deps $(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o test/ \ $(wildcard test/*.erl test/*/*.erl) -pa ebin/ tests-ct: ERLC_OPTS = $(TEST_ERLC_OPTS) tests-ct: clean deps app build-ct-suites @if [ -d "test" ] ; \ then \ mkdir -p logs/ ; \ $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) ; \ fi $(gen_verbose) rm -f test/*.beam define ct_suite_target ct-$(1): ERLC_OPTS = $(TEST_ERLC_OPTS) ct-$(1): clean deps app build-ct-suites @if [ -d "test" ] ; \ then \ mkdir -p logs/ ; \ $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS) ; \ fi $(gen_verbose) rm -f test/*.beam endef $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) clean-ct: $(gen_verbose) rm -rf test/*.beam distclean-ct: $(gen_verbose) rm -rf logs/ # Copyright (c) 2013-2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: plt distclean-plt dialyze # Configuration. DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt export DIALYZER_PLT PLT_APPS ?= DIALYZER_DIRS ?= --src -r src DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \ -Wunmatched_returns # -Wunderspecs # Core targets. distclean:: distclean-plt help:: @printf "%s\n" "" \ "Dialyzer targets:" \ " plt Build a PLT file for this project" \ " dialyze Analyze the project using Dialyzer" # Plugin-specific targets. $(DIALYZER_PLT): deps app @dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS) plt: $(DIALYZER_PLT) distclean-plt: $(gen_verbose) rm -f $(DIALYZER_PLT) ifneq ($(wildcard $(DIALYZER_PLT)),) dialyze: else dialyze: $(DIALYZER_PLT) endif @dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS) # Copyright (c) 2014, Juan Facorro # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: elvis distclean-elvis # Configuration. ELVIS_CONFIG ?= $(CURDIR)/elvis.config ELVIS ?= $(CURDIR)/elvis export ELVIS ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis.config ELVIS_OPTS ?= # Core targets. help:: @printf "%s\n" "" \ "Elvis targets:" \ " elvis Run Elvis using the local elvis.config or download the default otherwise" ifneq ($(wildcard $(ELVIS_CONFIG)),) rel:: distclean-elvis endif distclean:: distclean-elvis # Plugin-specific targets. $(ELVIS): @$(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL)) @$(call core_http_get,$(ELVIS),$(ELVIS_URL)) @chmod +x $(ELVIS) elvis: $(ELVIS) @$(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS) distclean-elvis: $(gen_verbose) rm -rf $(ELVIS) # Copyright (c) 2013-2014, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: relx-rel distclean-relx-rel distclean-relx # Configuration. RELX_CONFIG ?= $(CURDIR)/relx.config RELX ?= $(CURDIR)/relx export RELX RELX_URL ?= https://github.com/erlware/relx/releases/download/v1.0.2/relx RELX_OPTS ?= RELX_OUTPUT_DIR ?= _rel ifeq ($(firstword $(RELX_OPTS)),-o) RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS)) else RELX_OPTS += -o $(RELX_OUTPUT_DIR) endif # Core targets. ifneq ($(wildcard $(RELX_CONFIG)),) rel:: distclean-relx-rel relx-rel endif distclean:: distclean-relx-rel distclean-relx # Plugin-specific targets. define relx_fetch $(call core_http_get,$(RELX),$(RELX_URL)) chmod +x $(RELX) endef $(RELX): @$(call relx_fetch) relx-rel: $(RELX) @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) distclean-relx-rel: $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) distclean-relx: $(gen_verbose) rm -rf $(RELX) # Copyright (c) 2014, M Robert Martin # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: shell # Configuration. SHELL_PATH ?= -pa ../$(PROJECT)/ebin $(DEPS_DIR)/*/ebin SHELL_OPTS ?= ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) # Core targets help:: @printf "%s\n" "" \ "Shell targets:" \ " shell Run an erlang shell with SHELL_OPTS or reasonable default" # Plugin-specific targets. $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep)))) build-shell-deps: $(ALL_SHELL_DEPS_DIRS) @for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done shell: build-shell-deps $(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS) ranch-1.1.0/examples/000077500000000000000000000000001242670721000144235ustar00rootroot00000000000000ranch-1.1.0/examples/tcp_echo/000077500000000000000000000000001242670721000162075ustar00rootroot00000000000000ranch-1.1.0/examples/tcp_echo/Makefile000066400000000000000000000000701242670721000176440ustar00rootroot00000000000000PROJECT = tcp_echo DEPS = ranch include ../../erlang.mk ranch-1.1.0/examples/tcp_echo/README.md000066400000000000000000000010161242670721000174640ustar00rootroot00000000000000Ranch TCP echo example ====================== To try this example, you need GNU `make` and `git` in your PATH. To build the example, run the following command: ``` bash $ make ``` To start the release in the foreground: ``` bash $ ./_rel/bin/tcp_echo_example console ``` Then start a telnet session to port 5555: ``` bash $ telnet localhost 5555 ``` Type in a few words and see them echoed back. Be aware that there is a timeout of 5 seconds without receiving data before the example server disconnects your session. ranch-1.1.0/examples/tcp_echo/relx.config000066400000000000000000000001171242670721000203470ustar00rootroot00000000000000{release, {tcp_echo_example, "1"}, [tcp_echo]}. {extended_start_script, true}. ranch-1.1.0/examples/tcp_echo/src/000077500000000000000000000000001242670721000167765ustar00rootroot00000000000000ranch-1.1.0/examples/tcp_echo/src/echo_protocol.erl000066400000000000000000000010741242670721000223430ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -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. ranch-1.1.0/examples/tcp_echo/src/tcp_echo.app.src000066400000000000000000000004341242670721000220530ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, tcp_echo, [ {description, "Ranch TCP echo example."}, {vsn, "1"}, {modules, []}, {registered, [tcp_echo_sup]}, {applications, [ kernel, stdlib, ranch ]}, {mod, {tcp_echo_app, []}}, {env, []} ]}. ranch-1.1.0/examples/tcp_echo/src/tcp_echo_app.erl000066400000000000000000000005321242670721000221260ustar00rootroot00000000000000%% 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. ranch-1.1.0/examples/tcp_echo/src/tcp_echo_sup.erl000066400000000000000000000005621242670721000221600ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(tcp_echo_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> {ok, {{one_for_one, 10, 10}, []}}. ranch-1.1.0/examples/tcp_reverse/000077500000000000000000000000001242670721000167445ustar00rootroot00000000000000ranch-1.1.0/examples/tcp_reverse/Makefile000066400000000000000000000000731242670721000204040ustar00rootroot00000000000000PROJECT = tcp_reverse DEPS = ranch include ../../erlang.mk ranch-1.1.0/examples/tcp_reverse/README.md000066400000000000000000000014361242670721000202270ustar00rootroot00000000000000Ranch TCP reverse example ========================= This example uses a `gen_server` to handle a protocol to revese input. See `reverse_protocol.erl` for the implementation. Documentation about this topic can be found in the guide: http://ninenines.eu/docs/en/ranch/HEAD/guide/protocols/#using_gen_server To try this example, you need GNU `make` and `git` in your PATH. To build the example, run the following command: ``` bash $ make ``` To start the release in the foreground: ``` bash $ ./_rel/bin/tcp_reverse_example console ``` Then start a telnet session to port 5555: ``` bash $ telnet localhost 5555 ``` Type in a few words and see them reversed! Amazing! Be aware that there is a timeout of 5 seconds without receiving data before the example server disconnects your session. ranch-1.1.0/examples/tcp_reverse/relx.config000066400000000000000000000001251242670721000211030ustar00rootroot00000000000000{release, {tcp_reverse_example, "1"}, [tcp_reverse]}. {extended_start_script, true}. ranch-1.1.0/examples/tcp_reverse/src/000077500000000000000000000000001242670721000175335ustar00rootroot00000000000000ranch-1.1.0/examples/tcp_reverse/src/reverse_protocol.erl000066400000000000000000000033531242670721000236370ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(reverse_protocol). -behaviour(gen_server). -behaviour(ranch_protocol). %% API. -export([start_link/4]). %% gen_server. -export([init/1]). -export([init/4]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). -define(TIMEOUT, 5000). -record(state, {socket, transport}). %% API. start_link(Ref, Socket, Transport, Opts) -> proc_lib:start_link(?MODULE, init, [Ref, Socket, Transport, Opts]). %% gen_server. %% This function is never called. We only define it so that %% we can use the -behaviour(gen_server) attribute. init([]) -> {ok, undefined}. init(Ref, Socket, Transport, _Opts = []) -> ok = proc_lib:init_ack({ok, self()}), ok = ranch:accept_ack(Ref), ok = Transport:setopts(Socket, [{active, once}]), gen_server:enter_loop(?MODULE, [], #state{socket=Socket, transport=Transport}, ?TIMEOUT). handle_info({tcp, Socket, Data}, State=#state{ socket=Socket, transport=Transport}) -> Transport:setopts(Socket, [{active, once}]), Transport:send(Socket, reverse_binary(Data)), {noreply, State, ?TIMEOUT}; handle_info({tcp_closed, _Socket}, State) -> {stop, normal, State}; handle_info({tcp_error, _, Reason}, State) -> {stop, Reason, State}; handle_info(timeout, State) -> {stop, normal, State}; handle_info(_Info, State) -> {stop, normal, State}. handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% Internal. reverse_binary(B) when is_binary(B) -> [list_to_binary(lists:reverse(binary_to_list( binary:part(B, {0, byte_size(B)-2}) ))), "\r\n"]. ranch-1.1.0/examples/tcp_reverse/src/tcp_reverse.app.src000066400000000000000000000004501242670721000233430ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, tcp_reverse, [ {description, "Ranch TCP reverse example."}, {vsn, "1"}, {modules, []}, {registered, [tcp_reverse_sup]}, {applications, [ kernel, stdlib, ranch ]}, {mod, {tcp_reverse_app, []}}, {env, []} ]}. ranch-1.1.0/examples/tcp_reverse/src/tcp_reverse_app.erl000066400000000000000000000005551242670721000234250ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(tcp_reverse_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> {ok, _} = ranch:start_listener(tcp_reverse, 10, ranch_tcp, [{port, 5555}], reverse_protocol, []), tcp_reverse_sup:start_link(). stop(_State) -> ok. ranch-1.1.0/examples/tcp_reverse/src/tcp_reverse_sup.erl000066400000000000000000000005701242670721000234510ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(tcp_reverse_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> {ok, {{one_for_one, 10, 10}, []}}. ranch-1.1.0/guide/000077500000000000000000000000001242670721000137025ustar00rootroot00000000000000ranch-1.1.0/guide/embedded.md000066400000000000000000000033221242670721000157550ustar00rootroot00000000000000Embedded 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. ranch-1.1.0/guide/internals.md000066400000000000000000000101521242670721000162220ustar00rootroot00000000000000Internals ========= 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 a central process keeping track of the listeners and their acceptors. It does so through the use of a public ets table called `ranch_server`. The table is owned by the top supervisor to improve fault tolerance. This way if the `ranch_server` gen_server fails, it doesn't lose any information and the restarted process can continue as if nothing happened. Ranch uses a custom supervisor for managing connections. This supervisor keeps track of the number of connections and handles connection limits directly. While it is heavily optimized to perform the task of creating connection processes for accepted connections, it is still following the OTP principles and the usual `sys` and `supervisor` calls will work on it as expected. Listeners are grouped into the `ranch_listener_sup` supervisor and consist of three kinds of processes: the listener gen_server, the acceptor processes and the connection processes, both grouped under their own supervisor. All of these processes are registered to the `ranch_server` gen_server with varying amount of information. All socket operations, including listening for connections, go through transport handlers. Accepted connections are given to the protocol handler. Transport handlers are simple callback modules for performing operations on sockets. Protocol handlers start a new process, which receives socket ownership, with no requirements on how the code should be written inside that new process. Number of acceptors ------------------- The second argument to `ranch:start_listener/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. ranch-1.1.0/guide/introduction.md000066400000000000000000000020021242670721000167370ustar00rootroot00000000000000Introduction ============ 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. Supported platforms ------------------- Ranch is tested and supported on Linux. Ranch has been reported to work on other platforms, but we make no guarantee that the experience will be safe and smooth. You are advised to perform the necessary testing and security audits prior to deploying on other platforms. Ranch is developed for Erlang R15B01 and later versions. Ranch may be compiled on earlier Erlang versions with small source code modifications but there is no guarantee that it will work as expected. Versioning ---------- Ranch uses [Semantic Versioning 2.0.0](http://semver.org/). ranch-1.1.0/guide/listeners.md000066400000000000000000000155361242670721000162460ustar00rootroot00000000000000Listeners ========= 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 $ make $ ./_rel/bin/tcp_echo console ``` 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). ``` ranch-1.1.0/guide/parsers.md000066400000000000000000000055131242670721000157070ustar00rootroot00000000000000Writing parsers =============== There are three kinds of protocols: * Text protocols * Schema-less binary protocols * Schema-based binary protocols This chapter introduces the first two kinds. It will not cover more advanced topics such as continuations or parser generators. This chapter isn't specifically about Ranch, we assume here that you know how to read data from the socket. The data you read and the data that hasn't been parsed is saved in a buffer. Every time you read from the socket, the data read is appended to the buffer. What happens next depends on the kind of protocol. We will only cover the first two. Parsing text ------------ Text protocols are generally line based. This means that we can't do anything with them until we receive the full line. A simple way to get a full line is to use `binary:split/{2,3}`. ``` erlang case binary:split(Buffer, <<"\n">>) of [_] -> get_more_data(Buffer); [Line, Rest] -> handle_line(Line, Rest) end. ``` In the above example, we can have two results. Either there was a line break in the buffer and we get it split into two parts, the line and the rest of the buffer; or there was no line break in the buffer and we need to get more data from the socket. Next, we need to parse the line. The simplest way is to again split, here on space. The difference is that we want to split on all spaces character, as we want to tokenize the whole string. ``` erlang case binary:split(Line, <<" ">>, [global]) of [<<"HELLO">>] -> be_polite(); [<<"AUTH">>, User, Password] -> authenticate_user(User, Password); [<<"QUIT">>, Reason] -> quit(Reason) %% ... end. ``` Pretty simple, right? Match on the command name, get the rest of the tokens in variables and call the respective functions. After doing this, you will want to check if there is another line in the buffer, and handle it immediately if any. Otherwise wait for more data. Parsing binary -------------- Binary protocols can be more varied, although most of them are pretty similar. The first four bytes of a frame tend to be the size of the frame, which is followed by a certain number of bytes for the type of frame and then various parameters. Sometimes the size of the frame includes the first four bytes, sometimes not. Other times this size is encoded over two bytes. And even other times little-endian is used instead of big-endian. The general idea stays the same though. ``` erlang << Size:32, _/bits >> = Buffer, case Buffer of << Frame:Size/binary, Rest/bits >> -> handle_frame(Frame, Rest); _ -> get_more_data(Buffer) end. ``` You will then need to parse this frame using binary pattern matching, and handle it. Then you will want to check if there is another frame fully received in the buffer, and handle it immediately if any. Otherwise wait for more data. ranch-1.1.0/guide/protocols.md000066400000000000000000000072551242670721000162610ustar00rootroot00000000000000Protocols ========= 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, after calling `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}; %% ... ``` ranch-1.1.0/guide/ssl_auth.md000066400000000000000000000100711242670721000160450ustar00rootroot00000000000000SSL 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. ranch-1.1.0/guide/toc.md000066400000000000000000000007661242670721000150220ustar00rootroot00000000000000Ranch User Guide ================ The Ranch User Guide explores how to make best use of Ranch for writing powerful TCP applications. Introducing Ranch ----------------- * [Introduction](introduction.md) Using Ranch ----------- * [Listeners](listeners.md) * [Transports](transports.md) * [Protocols](protocols.md) * [Writing parsers](parsers.md) Advanced topics --------------- * [SSL client authentication](ssl_auth.md) * [Embedded mode](embedded.md) * [Internals](internals.md) ranch-1.1.0/guide/transports.md000066400000000000000000000134261242670721000164510ustar00rootroot00000000000000Transports ========== 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/3` call, while active mode means that you will receive the data as a message. By default, all data will be received as binary. It is possible to receive data as strings, although this is not recommended as binaries are a more efficient construct, especially for binary protocols. Receiving data using passive mode requires a single function call. The first argument is the socket, and the third argument is a timeout duration before the call returns with `{error, timeout}`. The second argument is the amount of data in bytes that we want to receive. The function will wait for data until it has received exactly this amount. If you are not expecting a precise size, you can specify 0 which will make this call return as soon as data was read, regardless of its size. ``` 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. ranch-1.1.0/manual/000077500000000000000000000000001242670721000140625ustar00rootroot00000000000000ranch-1.1.0/manual/ranch.md000066400000000000000000000115211242670721000154770ustar00rootroot00000000000000ranch ===== The `ranch` module provides functions for starting and manipulating Ranch listeners. Types ----- ### max_conns() = non_neg_integer() | infinity > Maximum number of connections allowed on this listener. > > This is a soft limit. The actual number of connections > might be slightly above the limit due to concurrency > when accepting new connections. Some connections may > also be removed from this count explicitly by the user > code. ### ref() = any() > Unique name used to refer to a listener. Exports ------- ### accept_ack(Ref) -> ok > Types: > * Ref = ref() > > Acknowledge that the connection is accepted. > > This function MUST be used by a connection process to inform > Ranch that it initialized properly and let it perform any > additional operations before the socket can be safely used. ### child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> supervisor:child_spec() > Types: > * Ref = ref() > * NbAcceptors = non_neg_integer() > * Transport = module() > * TransOpts = any() > * Protocol = module() > * ProtoOpts = any() > > Return child specifications for a new listener. > > This function can be used to embed a listener directly > in an application instead of letting Ranch handle it. ### get_max_connections(Ref) -> MaxConns > Types: > * Ref = ref() > * MaxConns = max_conns() > > Return the max number of connections allowed for the given listener. ### get_port(Ref) -> Port > Types: > * Ref = ref() > * Port = inet:port_number() > > Return the port for the given listener. ### get_protocol_options(Ref) -> ProtoOpts > Types: > * Ref = ref() > * ProtoOpts = any() > > Return the protocol options set for the given listener. ### remove_connection(Ref) -> ok > Types: > * Ref = ref() > > Do not count this connection when limiting the number of connections. > > You can use this function for long-running connection processes > which spend most of their time idling rather than consuming > resources. This allows Ranch to accept a lot more connections > without sacrificing the latency of the system. > > This function may only be called from a connection process. ### set_max_connections(Ref, MaxConns) -> ok > Types: > * Ref = ref() > * MaxConns = max_conns() > > Set the max number of connections for the given listener. > > The change will be applied immediately. If the new value is > smaller than the previous one, Ranch will not kill the extra > connections, but will wait for them to terminate properly. ### set_protocol_options(Ref, ProtoOpts) -> ok > Types: > * Ref = ref() > * ProtoOpts = any() > > Set the protocol options for the given listener. > > The change will be applied immediately for all new connections. > Old connections will not receive the new options. ### start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> {ok, pid()} | {error, badarg} > Types: > * Ref = ref() > * NbAcceptors = non_neg_integer() > * Transport = module() > * TransOpts = any() > * Protocol = module() > * ProtoOpts = any() > > Start listening for connections using the given transport > and protocol. Returns the pid for this listener's supervisor. > > There are five additional transport options that apply > regardless of transport. They allow configuring how the > connections are supervised, rate limited and allow using > an already open listening socket. > > The `ack_timeout` option defines how long post-accept socket > initialization should take at a maximum. It defaults to `5000`. > > The `connection_type` option defines the type of process > that will handle the connection. It can be either `worker` > or `supervisor`. It defaults to `worker`. > > The `max_connections` option determines how many active > connections are allowed before Ranch starts throttling > the accept rate. This is a soft limit. It defaults to `1024`. > Using the value `infinity` will disable this functionality > entirely. > > The `shutdown` option determines the policy used with > regards to connection processes when shutting down the listener. > It can be either a positive integer indicating the max number > of ms the supervisor will wait before forcibly killing the > children, or the atom `brutal_kill`. It defaults to `5000`. > > The `socket` option allow passing an already open listening > socket. In this case, Ranch will not call `Transport:listen/1` > and so none of the transport specific options apply. ### stop_listener(Ref) -> ok | {error, not_found} > Types: > * Ref = ref() > > Stop the given listener. > > The listener is stopped gracefully, first by closing the > listening port, then by stopping the connection processes. > These processes are stopped according to the `shutdown` > transport option, which may be set to brutally kill all > connection processes or give them some time to stop properly. > > This function does not return until the listener is > completely stopped. ranch-1.1.0/manual/ranch_app.md000066400000000000000000000013761242670721000163460ustar00rootroot00000000000000The Ranch Application ===================== Socket acceptor pool for TCP protocols. Dependencies ------------ The `ranch` application has no particular dependency required to start. It has optional dependencies that are only required when listening for SSL connections. The dependencies are `crypto`, `asn1`, `public_key` and `ssl`. They are started automatically if they weren't before. Environment ----------- The `ranch` application defines one application environment configuration parameter. - profile (false) - When enabled, Ranch will start `eprof` profiling automatically. You can use the `ranch_app:profile_output/0` function to stop profiling and output the results to the files `procs.profile` and `total.profile`. Do not use in production. ranch-1.1.0/manual/ranch_protocol.md000066400000000000000000000015621242670721000174240ustar00rootroot00000000000000ranch_protocol ============== The `ranch_protocol` behaviour defines the interface used by Ranch protocols. Types ----- None. Callbacks --------- ### start_link(Ref, Socket, Transport, ProtoOpts) -> {ok, pid()} > Types: > * Ref = ranch:ref() > * Socket = any() > * Transport = module() > * ProtoOpts = any() > > Start a new connection process for the given socket. > > The only purpose of this callback is to start a process that > will handle the socket. It must spawn the process, link and > then return the new pid. This function will always be called > from inside a supervisor. > > If any other value is returned, the supervisor will close the > socket and assume no process has been started. > > Do not perform any operation in this callback, as this would > block the supervisor responsible for starting connection > processes and degrade performance severely. ranch-1.1.0/manual/ranch_ssl.md000066400000000000000000000077211242670721000163670ustar00rootroot00000000000000ranch_ssl ========= The `ranch_ssl` module implements an SSL Ranch transport. Types ----- ### 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} | {honor_cipher_order, boolean()} | {ip, inet:ip_address()} | {key, Der::binary()} | {keyfile, string()} | {linger, {boolean(), non_neg_integer()}} | {log_alert, boolean()} | {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()} | {send_timeout, timeout()} | {send_timeout_close, boolean()} | {verify, ssl:verify_type()} | {verify_fun, {fun(), InitialUserState::term()}}, | {versions, [atom()]}]. > Listen options. > > This does not represent the entirety of the options that can > be set on the socket, but only the options that should be > set independently of protocol implementation. Option descriptions ------------------- Specifying a certificate is mandatory, either through the `cert` or the `certfile` option. None of the other options are required. The default value is given next to the option name. - backlog (1024) - Max length of the queue of pending connections. - cacertfile - Path to PEM encoded trusted certificates file used to verify peer certificates. - cacerts - List of DER encoded trusted certificates. - cert - DER encoded user certificate. - certfile - Path to the PEM encoded user certificate file. May also contain the private key. - ciphers - List of ciphers that clients are allowed to use. - fail_if_no_peer_cert (false) - Whether to refuse the connection if the client sends an empty certificate. - hibernate_after (undefined) - Time in ms after which SSL socket processes go into hibernation to reduce memory usage. - honor_cipher_order (false) - If true, use the server's preference for cipher selection. If false (the default), use the client's preference. - ip - Interface to listen on. Listen on all interfaces by default. - key - DER encoded user private key. - keyfile - Path to the PEM encoded private key file, if different than the certfile. - linger ({false, 0}) - Whether to wait and how long to flush data sent before closing the socket. - log_alert (true) - If false, error reports will not be displayed. - next_protocols_advertised - List of protocols to send to the client if it supports the Next Protocol extension. - nodelay (true) - Whether to enable TCP_NODELAY. - password - Password to the private key file, if password protected. - port (0) - TCP port number to listen on. 0 means a random port will be used. - reuse_session - Custom policy to decide whether a session should be reused. - reuse_sessions (false) - Whether to allow session reuse. - secure_renegotiate (false) - Whether to reject renegotiation attempts that do not conform to RFC5746. - send_timeout (30000) - How long the send call may wait for confirmation before returning. - send_timeout_close (true) - Whether to close the socket when the confirmation wasn't received. - verify (verify_none) - Use `verify_peer` to request a certificate from the client. - verify_fun - Custom policy to decide whether a client certificate is valid. - versions - TLS protocol versions that will be supported. Note that the client will not send a certificate unless the value for the `verify` option is set to `verify_peer`. This means that the `fail_if_no_peer_cert` only apply when combined with the `verify` option. The `verify_fun` option allows greater control over the client certificate validation. The `raw` option is unsupported. Exports ------- None. ranch-1.1.0/manual/ranch_tcp.md000066400000000000000000000030451242670721000163470ustar00rootroot00000000000000ranch_tcp ========= The `ranch_tcp` module implements a TCP Ranch transport. Note that due to bugs in OTP up to at least R16B02, it is recommended to disable async threads when using the `sendfile` function of this transport, as it can make the threads stuck indefinitely. Types ----- ### opts() = [{backlog, non_neg_integer()} | {ip, inet:ip_address()} | {linger, {boolean(), non_neg_integer()}} | {nodelay, boolean()} | {port, inet:port_number()} | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()} | {send_timeout, timeout()} | {send_timeout_close, boolean()}] > Listen options. > > This does not represent the entirety of the options that can > be set on the socket, but only the options that should be > set independently of protocol implementation. Option descriptions ------------------- None of the options are required. The default value is given next to the option name. - backlog (1024) - Max length of the queue of pending connections. - ip - Interface to listen on. Listen on all interfaces by default. - linger ({false, 0}) - Whether to wait and how long to flush data sent before closing the socket. - nodelay (true) - Whether to enable TCP_NODELAY. - port (0) - TCP port number to listen on. 0 means a random port will be used. - send_timeout (30000) - How long the send call may wait for confirmation before returning. - send_timeout_close (true) - Whether to close the socket when the confirmation wasn't received. The `raw` option is unsupported. Exports ------- None. ranch-1.1.0/manual/ranch_transport.md000066400000000000000000000123561242670721000176220ustar00rootroot00000000000000ranch_transport =============== The `ranch_transport` behaviour defines the interface used by Ranch transports. Types ----- ### sendfile_opts() = [{chunk_size, non_neg_integer()}] > Options used by the sendfile function and callbacks. > > Allows configuring the chunk size, in bytes. Defaults to 8191 bytes. Callbacks --------- ### accept(LSocket, Timeout) -> {ok, CSocket} | {error, closed | timeout | atom()} > Types: > * LSocket = CSocket = any() > * Timeout = timeout() > > Accept a connection on the given listening socket. > > The `accept_ack` callback will be used to initialize the socket > after accepting the connection. This is most useful when the > transport is not raw TCP, like with SSL for example. ### accept_ack(CSocket, Timeout) -> ok > Types: > * CSocket = any() > * Timeout = timeout() > > Perform post-accept initialization of the connection. > > This function will be called by connection processes > before performing any socket operation. It allows > transports that require extra initialization to perform > their task and make the socket ready to use. ### close(CSocket) -> ok > Types: > * CSocket = any() > > Close the given socket. ### controlling_process(CSocket, Pid) -> ok | {error, closed | not_owner | atom()} > Types: > * CSocket = any() > * Pid = pid() > > Change the controlling process for the given socket. > > The controlling process is the process that is allowed to > perform operations on the socket, and that will receive > messages from the socket when active mode is used. When > the controlling process dies, the socket is closed. ### listen(TransOpts) -> {ok, LSocket} | {error, atom()} > Types: > * TransOpts = any() > * LSocket = any() > > Listen for connections on the given port. > > The port is given as part of the transport options under > the key `port`. Any other option is transport dependent. > > The socket returned by this call can then be used to > accept connections. It is not possible to send or receive > data from the listening socket. ### messages() -> {OK, Closed, Error} > Types: > * OK = Closed = Error = atom() > > Return the atoms used to identify messages sent in active mode. ### name() -> Name > Types: > * Name = atom() > > Return the name of the transport. ### peername(CSocket) -> {ok, {IP, Port}} | {error, atom()} > Types: > * CSocket = any() > * IP = inet:ip_address() > * Port = inet:port_number() > > Return the IP and port of the remote endpoint. ### recv(CSocket, Length, Timeout) -> {ok, Packet} | {error, closed | timeout | atom()} > Types: > * CSocket = any() > * Length = non_neg_integer() > * Timeout = timeout() > * Packet = iodata() | any() > > Receive data from the given socket when in passive mode. > > Trying to receive data from a socket that is in active mode > will return an error. > > A length of 0 will return any data available on the socket. > > While it is possible to use the timeout value `infinity`, > this is highly discouraged as this could cause your process > to get stuck waiting for data that will never come. This may > happen when a socket becomes half-open due to a crash of the > remote endpoint. Wi-Fi going down is another common culprit > of this issue. ### send(CSocket, Packet) -> ok | {error, atom()} > Types: > * CSocket = any() > * Packet = iodata() > > Send data to the given socket. ### sendfile(CSocket, File) -> sendfile(CSocket, File, 0, 0, []) ### sendfile(CSocket, File, Offset, Bytes) -> sendfile(CSocket, File, Offset, Bytes, []) ### sendfile(CSocket, File, Offset, Bytes, SfOpts) -> {ok, SentBytes} | {error, atom()} > Types: > * CSocket = any() > * File = file:filename_all() | file:fd() > * Offset = non_neg_integer() > * Bytes = SentBytes = non_neg_integer() > * SfOpts = sendfile_opts() > > Send data from a file to the given socket. > > The file may be sent full or in parts, and may be specified > by its filename or by an already open file descriptor. > > Transports that manipulate TCP directly may use the > `file:sendfile/{2,4,5}` function, which calls the sendfile > syscall where applicable (on Linux, for example). Other > transports can use the `sendfile/6` function exported from > this module. ### setopts(CSocket, TransOpts) -> ok | {error, atom()} > Types: > * CSocket = any() > * TransOpts = any() > > Change transport options for the given socket. > > This is mainly useful for switching to active or passive mode. ### shutdown(CSocket, How) -> ok | {error, atom()} > Types: > * CSocket = any() > * How = read | write | read_write > > Immediately close the socket in one or two directions. ### sockname(CSocket) -> {ok, {IP, Port}} | {error, atom()} > Types: > * CSocket = any() > * IP = inet:ip_address() > * Port = inet:port_number() > > Return the IP and port of the local endpoint. Exports ------- ### sendfile(Transport, CSocket, File, Offset, Bytes, SfOpts) -> {ok, SentBytes} | {error, atom()} > Types: > * Transport = module() > * CSocket = any() > * File = file:filename_all() | file:fd() > * Offset = non_neg_integer() > * Bytes = SentBytes = non_neg_integer() > * SfOpts = sendfile_opts() > > Send data from a file to the given socket. > > This function emulates the function `file:sendfile/{2,4,5}` > and may be used when transports are not manipulating TCP > directly. ranch-1.1.0/manual/toc.md000066400000000000000000000005021242670721000151660ustar00rootroot00000000000000Ranch Function Reference ======================== The function reference documents the public interface of Ranch. * [The Ranch Application](ranch_app.md) * [ranch](ranch.md) * [ranch_protocol](ranch_protocol.md) * [ranch_ssl](ranch_ssl.md) * [ranch_tcp](ranch_tcp.md) * [ranch_transport](ranch_transport.md) ranch-1.1.0/src/000077500000000000000000000000001242670721000133745ustar00rootroot00000000000000ranch-1.1.0/src/ranch.app.src000066400000000000000000000020301242670721000157520ustar00rootroot00000000000000%% Copyright (c) 2011-2014, 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, "1.1.0"}, {id, "git"}, {modules, []}, {registered, [ranch_sup, ranch_server]}, {applications, [ kernel, stdlib ]}, {mod, {ranch_app, []}}, {env, []} ]}. ranch-1.1.0/src/ranch.erl000066400000000000000000000130501242670721000151720ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch). -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]). -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. -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. -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, infinity, supervisor, [ranch_listener_sup]}. -spec accept_ack(ref()) -> ok. accept_ack(Ref) -> receive {shoot, Ref, Transport, Socket, AckTimeout} -> Transport:accept_ack(Socket, AckTimeout) end. -spec remove_connection(ref()) -> ok. remove_connection(Ref) -> ConnsSup = ranch_server:get_connections_sup(Ref), ConnsSup ! {remove_connection, Ref}, ok. -spec get_port(ref()) -> inet:port_number(). get_port(Ref) -> ranch_server:get_port(Ref). -spec get_max_connections(ref()) -> max_conns(). get_max_connections(Ref) -> ranch_server:get_max_connections(Ref). -spec set_max_connections(ref(), max_conns()) -> ok. set_max_connections(Ref, MaxConnections) -> ranch_server:set_max_connections(Ref, MaxConnections). -spec get_protocol_options(ref()) -> any(). get_protocol_options(Ref) -> ranch_server:get_protocol_options(Ref). -spec set_protocol_options(ref(), any()) -> ok. set_protocol_options(Ref, Opts) -> ranch_server:set_protocol_options(Ref, Opts). -spec filter_options([{atom(), any()} | {raw, any(), any(), any()}], [atom()], Acc) -> Acc when Acc :: [any()]. filter_options(UserOptions, AllowedKeys, DefaultOptions) -> AllowedOptions = filter_user_options(UserOptions, AllowedKeys), lists:foldl(fun merge_options/2, DefaultOptions, AllowedOptions). filter_user_options([Opt = {Key, _}|Tail], AllowedKeys) -> case lists:member(Key, AllowedKeys) of true -> [Opt|filter_user_options(Tail, AllowedKeys)]; false -> filter_user_options(Tail, AllowedKeys) end; filter_user_options([Opt = {raw, _, _, _}|Tail], AllowedKeys) -> case lists:member(raw, AllowedKeys) of true -> [Opt|filter_user_options(Tail, AllowedKeys)]; false -> filter_user_options(Tail, AllowedKeys) end; filter_user_options([], _) -> []. merge_options({Key, _} = Option, OptionList) -> lists:keystore(Key, 1, OptionList, Option); merge_options(Option, OptionList) -> [Option|OptionList]. -spec set_option_default(Opts, atom(), any()) -> Opts when Opts :: [{atom(), any()}]. set_option_default(Opts, Key, Value) -> case lists:keymember(Key, 1, Opts) of true -> Opts; false -> [{Key, Value}|Opts] end. -spec require([atom()]) -> ok. require([]) -> ok; require([App|Tail]) -> case application:start(App) of ok -> ok; {error, {already_started, App}} -> ok end, require(Tail). ranch-1.1.0/src/ranch_acceptor.erl000066400000000000000000000037171242670721000170630ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_acceptor). -export([start_link/3]). -export([loop/3]). -spec start_link(inet:socket(), module(), pid()) -> {ok, pid()}. start_link(LSocket, Transport, ConnsSup) -> Pid = spawn_link(?MODULE, loop, [LSocket, Transport, ConnsSup]), {ok, Pid}. -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. ranch-1.1.0/src/ranch_acceptors_sup.erl000066400000000000000000000032071242670721000201270ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_acceptors_sup). -behaviour(supervisor). -export([start_link/4]). -export([init/1]). -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]). 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}}. ranch-1.1.0/src/ranch_app.erl000066400000000000000000000025061242670721000160360ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_app). -behaviour(application). -export([start/2]). -export([stop/1]). -export([profile_output/0]). 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). consider_profiling() -> case application:get_env(profile) of {ok, true} -> {ok, _Pid} = eprof:start(), eprof:start_profiling([self()]); _ -> not_profiling end. ranch-1.1.0/src/ranch_conns_sup.erl000066400000000000000000000217571242670721000172760ustar00rootroot00000000000000%% Copyright (c) 2011-2014, 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. %% Make sure to never reload this module outside a release upgrade, %% as calling l(ranch_conns_sup) twice will kill the process and all %% the currently open connections. -module(ranch_conns_sup). %% API. -export([start_link/6]). -export([start_protocol/2]). -export([active_connections/1]). %% Supervisor internals. -export([init/7]). -export([system_continue/3]). -export([system_terminate/4]). -export([system_code_change/4]). -type conn_type() :: worker | supervisor. -type shutdown() :: brutal_kill | timeout(). -record(state, { parent = undefined :: pid(), ref :: ranch:ref(), conn_type :: conn_type(), shutdown :: shutdown(), transport = undefined :: module(), protocol = undefined :: module(), opts :: any(), ack_timeout :: timeout(), max_conns = undefined :: ranch:max_conns() }). %% API. -spec start_link(ranch:ref(), conn_type(), shutdown(), module(), timeout(), module()) -> {ok, pid()}. start_link(Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol) -> proc_lib:start_link(?MODULE, init, [self(), Ref, ConnType, Shutdown, Transport, AckTimeout, 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(), shutdown(), module(), timeout(), module()) -> no_return(). init(Parent, Ref, ConnType, Shutdown, Transport, AckTimeout, 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, shutdown=Shutdown, transport=Transport, protocol=Protocol, opts=Opts, ack_timeout=AckTimeout, max_conns=MaxConns}, 0, 0, []). loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType, transport=Transport, protocol=Protocol, opts=Opts, ack_timeout=AckTimeout, 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, Transport, Socket, AckTimeout}, 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; Ret -> To ! self(), error_logger:error_msg( "Ranch listener ~p connection process start failure; " "~p:start_link/4 returned: ~999999p~n", [Ref, Protocol, Ret]), 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} -> terminate(State, Reason, NbChildren); {'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. -spec terminate(#state{}, any(), non_neg_integer()) -> no_return(). %% Kill all children and then exit. We unlink first to avoid %% getting a message for each child getting killed. terminate(#state{shutdown=brutal_kill}, Reason, _) -> Pids = get_keys(true), _ = [begin unlink(P), exit(P, kill) end || P <- Pids], exit(Reason); %% Attempt to gracefully shutdown all children. terminate(#state{shutdown=Shutdown}, Reason, NbChildren) -> shutdown_children(), _ = if Shutdown =:= infinity -> ok; true -> erlang:send_after(Shutdown, self(), kill) end, wait_children(NbChildren), exit(Reason). %% Monitor processes so we can know which ones have shutdown %% before the timeout. Unlink so we avoid receiving an extra %% message. Then send a shutdown exit signal. shutdown_children() -> Pids = get_keys(true), _ = [begin monitor(process, P), unlink(P), exit(P, shutdown) end || P <- Pids], ok. wait_children(0) -> ok; wait_children(NbChildren) -> receive {'DOWN', _, process, Pid, _} -> _ = erase(Pid), wait_children(NbChildren - 1); kill -> Pids = get_keys(true), _ = [exit(P, kill) || P <- Pids], ok end. system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) -> loop(State, CurConns, NbChildren, Sleepers). -spec system_terminate(any(), _, _, _) -> no_return(). system_terminate(Reason, _, _, {State, _, NbChildren, _}) -> terminate(State, Reason, NbChildren). 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]). ranch-1.1.0/src/ranch_listener_sup.erl000066400000000000000000000036301242670721000177710ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_listener_sup). -behaviour(supervisor). -export([start_link/6]). -export([init/1]). -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 }). init({Ref, NbAcceptors, Transport, TransOpts, Protocol}) -> AckTimeout = proplists:get_value(ack_timeout, TransOpts, 5000), ConnType = proplists:get_value(connection_type, TransOpts, worker), Shutdown = proplists:get_value(shutdown, TransOpts, 5000), ChildSpecs = [ {ranch_conns_sup, {ranch_conns_sup, start_link, [Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]}, permanent, infinity, supervisor, [ranch_conns_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}}. ranch-1.1.0/src/ranch_protocol.erl000066400000000000000000000017711242670721000171220ustar00rootroot00000000000000%% Copyright (c) 2012-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_protocol). %% Start a new connection process for the given socket. -callback start_link( Ref::ranch:ref(), Socket::any(), Transport::module(), ProtocolOptions::any()) -> {ok, ConnectionPid::pid()}. ranch-1.1.0/src/ranch_server.erl000066400000000000000000000116471242670721000165720ustar00rootroot00000000000000%% Copyright (c) 2012-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_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. -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any()) -> ok. set_new_listener_opts(Ref, MaxConns, Opts) -> gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, Opts}). -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. -spec set_connections_sup(ranch:ref(), pid()) -> ok. set_connections_sup(Ref, Pid) -> true = gen_server:call(?MODULE, {set_connections_sup, Ref, Pid}), ok. -spec get_connections_sup(ranch:ref()) -> pid(). get_connections_sup(Ref) -> ets:lookup_element(?TAB, {conns_sup, Ref}, 2). -spec set_port(ranch:ref(), inet:port_number()) -> ok. set_port(Ref, Port) -> gen_server:call(?MODULE, {set_port, Ref, Port}). -spec get_port(ranch:ref()) -> inet:port_number(). get_port(Ref) -> ets:lookup_element(?TAB, {port, Ref}, 2). -spec set_max_connections(ranch:ref(), ranch:max_conns()) -> ok. set_max_connections(Ref, MaxConnections) -> gen_server:call(?MODULE, {set_max_conns, Ref, MaxConnections}). -spec get_max_connections(ranch:ref()) -> ranch:max_conns(). get_max_connections(Ref) -> ets:lookup_element(?TAB, {max_conns, Ref}, 2). -spec set_protocol_options(ranch:ref(), any()) -> ok. set_protocol_options(Ref, ProtoOpts) -> gen_server:call(?MODULE, {set_opts, Ref, ProtoOpts}). -spec get_protocol_options(ranch:ref()) -> any(). get_protocol_options(Ref) -> ets:lookup_element(?TAB, {opts, Ref}, 2). -spec count_connections(ranch:ref()) -> non_neg_integer(). count_connections(Ref) -> ranch_conns_sup:active_connections(get_connections_sup(Ref)). %% gen_server. init([]) -> Monitors = [{{erlang:monitor(process, Pid), Pid}, Ref} || [Ref, Pid] <- ets:match(?TAB, {{conns_sup, '$1'}, '$2'})], {ok, #state{monitors=Monitors}}. 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}. handle_cast(_Request, State) -> {noreply, State}. 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}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. ranch-1.1.0/src/ranch_ssl.erl000066400000000000000000000166751242670721000160730ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_ssl). -behaviour(ranch_transport). -export([name/0]). -export([secure/0]). -export([messages/0]). -export([listen/1]). -export([accept/2]). -export([accept_ack/2]). -export([connect/3]). -export([connect/4]). -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([shutdown/2]). -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} | {honor_cipher_order, boolean()} | {ip, inet:ip_address()} | {key, Der::binary()} | {keyfile, string()} | {linger, {boolean(), non_neg_integer()}} | {log_alert, boolean()} | {next_protocols_advertised, [binary()]} | {nodelay, boolean()} | {partial_chain, fun(([Der::binary()]) -> {trusted_ca, Der::binary()} | unknown_ca)} | {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()} | {send_timeout, timeout()} | {send_timeout_close, boolean()} | {verify, ssl:verify_type()} | {verify_fun, {fun(), InitialUserState::term()}} | {versions, [atom()]}]. -export_type([opts/0]). name() -> ssl. -spec secure() -> boolean(). secure() -> true. messages() -> {ssl, ssl_closed, ssl_error}. -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, send_timeout, 30000), Opts4 = ranch:set_option_default(Opts3, send_timeout_close, true), Opts5 = ranch:set_option_default(Opts4, 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(Opts5, [backlog, cacertfile, cacerts, cert, certfile, ciphers, fail_if_no_peer_cert, hibernate_after, honor_cipher_order, ip, key, keyfile, linger, next_protocols_advertised, nodelay, log_alert, partial_chain, password, port, raw, reuse_session, reuse_sessions, secure_renegotiate, send_timeout, send_timeout_close, verify, verify_fun, versions], [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {nodelay, true}])). -spec accept(ssl:sslsocket(), timeout()) -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> ssl:transport_accept(LSocket, Timeout). -spec accept_ack(ssl:sslsocket(), timeout()) -> ok. accept_ack(CSocket, Timeout) -> case ssl:ssl_accept(CSocket, Timeout) of ok -> ok; %% Garbage was most likely sent to the socket, don't error out. {error, {tls_alert, _}} -> ok = close(CSocket), exit(normal); %% Socket most likely stopped responding, don't error out. {error, Reason} when Reason =:= timeout; Reason =:= closed -> ok = close(CSocket), exit(normal); {error, Reason} -> ok = close(CSocket), error(Reason) end. %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts) when is_integer(Port) -> ssl:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}]). %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any(), timeout()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts, Timeout) when is_integer(Port) -> ssl:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}], Timeout). -spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> ssl:recv(Socket, Length, Timeout). -spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. send(Socket, Packet) -> ssl:send(Socket, Packet). -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename) -> sendfile(Socket, Filename, 0, 0, []). -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), non_neg_integer(), non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, File, Offset, Bytes) -> sendfile(Socket, File, Offset, Bytes, []). %% Unlike with TCP, no syscall can be used here, so sending files %% through SSL will be much slower in comparison. Note that unlike %% file:sendfile/5 this function accepts either a file or a file name. -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, File, Offset, Bytes, Opts) -> ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts). %% @todo Probably filter Opts? -spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> ssl:setopts(Socket, Opts). -spec controlling_process(ssl:sslsocket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> ssl:controlling_process(Socket, Pid). -spec peername(ssl:sslsocket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> ssl:peername(Socket). -spec sockname(ssl:sslsocket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. sockname(Socket) -> ssl:sockname(Socket). -spec shutdown(ssl:sslsocket(), read | write | read_write) -> ok | {error, atom()}. shutdown(Socket, How) -> ssl:shutdown(Socket, How). -spec close(ssl:sslsocket()) -> ok. close(Socket) -> ssl:close(Socket). %% Internal. %% 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. ranch-1.1.0/src/ranch_sup.erl000066400000000000000000000023151242670721000160630ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_sup). -behaviour(supervisor). -export([start_link/0]). -export([init/1]). -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). 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}}. ranch-1.1.0/src/ranch_tcp.erl000066400000000000000000000132021242670721000160370ustar00rootroot00000000000000%% Copyright (c) 2011-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_tcp). -behaviour(ranch_transport). -export([name/0]). -export([secure/0]). -export([messages/0]). -export([listen/1]). -export([accept/2]). -export([accept_ack/2]). -export([connect/3]). -export([connect/4]). -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([shutdown/2]). -export([close/1]). -type opts() :: [{backlog, non_neg_integer()} | {ip, inet:ip_address()} | {linger, {boolean(), non_neg_integer()}} | {nodelay, boolean()} | {port, inet:port_number()} | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()} | {send_timeout, timeout()} | {send_timeout_close, boolean()}]. -export_type([opts/0]). name() -> tcp. -spec secure() -> boolean(). secure() -> false. messages() -> {tcp, tcp_closed, tcp_error}. -spec listen(opts()) -> {ok, inet:socket()} | {error, atom()}. listen(Opts) -> Opts2 = ranch:set_option_default(Opts, backlog, 1024), Opts3 = ranch:set_option_default(Opts2, send_timeout, 30000), Opts4 = ranch:set_option_default(Opts3, send_timeout_close, true), %% 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(Opts4, [backlog, ip, linger, nodelay, port, raw, send_timeout, send_timeout_close], [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {nodelay, true}])). -spec accept(inet:socket(), timeout()) -> {ok, inet:socket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> gen_tcp:accept(LSocket, Timeout). -spec accept_ack(inet:socket(), timeout()) -> ok. accept_ack(_, _) -> ok. %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts) when is_integer(Port) -> gen_tcp:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}]). %% @todo Probably filter Opts? -spec connect(inet:ip_address() | inet:hostname(), inet:port_number(), any(), timeout()) -> {ok, inet:socket()} | {error, atom()}. connect(Host, Port, Opts, Timeout) when is_integer(Port) -> gen_tcp:connect(Host, Port, Opts ++ [binary, {active, false}, {packet, raw}], Timeout). -spec recv(inet:socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> gen_tcp:recv(Socket, Length, Timeout). -spec send(inet:socket(), iodata()) -> ok | {error, atom()}. send(Socket, Packet) -> gen_tcp:send(Socket, Packet). -spec sendfile(inet:socket(), file:name_all() | file:fd()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename) -> sendfile(Socket, Filename, 0, 0, []). -spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(), non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, File, Offset, Bytes) -> sendfile(Socket, File, Offset, Bytes, []). -spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(), non_neg_integer(), [{chunk_size, non_neg_integer()}]) -> {ok, non_neg_integer()} | {error, atom()}. sendfile(Socket, Filename, Offset, Bytes, Opts) when is_list(Filename) orelse is_atom(Filename) orelse is_binary(Filename) -> case file:open(Filename, [read, raw, binary]) of {ok, RawFile} -> try sendfile(Socket, RawFile, Offset, Bytes, Opts) of Result -> Result after ok = file:close(RawFile) end; {error, _} = Error -> Error end; sendfile(Socket, RawFile, Offset, Bytes, Opts) -> Opts2 = case Opts of [] -> [{chunk_size, 16#1FFF}]; _ -> Opts end, try file:sendfile(RawFile, Socket, Offset, Bytes, Opts2) of Result -> Result catch error:{badmatch, {error, enotconn}} -> %% file:sendfile/5 might fail by throwing a %% {badmatch, {error, enotconn}}. This is because its %% implementation fails with a badmatch in %% prim_file:sendfile/10 if the socket is not connected. {error, closed} end. %% @todo Probably filter Opts? -spec setopts(inet:socket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> inet:setopts(Socket, Opts). -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid). -spec peername(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> inet:peername(Socket). -spec sockname(inet:socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. sockname(Socket) -> inet:sockname(Socket). -spec shutdown(inet:socket(), read | write | read_write) -> ok | {error, atom()}. shutdown(Socket, How) -> gen_tcp:shutdown(Socket, How). -spec close(inet:socket()) -> ok. close(Socket) -> gen_tcp:close(Socket). ranch-1.1.0/src/ranch_transport.erl000066400000000000000000000121431242670721000173100ustar00rootroot00000000000000%% Copyright (c) 2012-2014, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ranch_transport). -export([sendfile/6]). -type socket() :: any(). -type opts() :: any(). -type sendfile_opts() :: [{chunk_size, non_neg_integer()}]. -export_type([sendfile_opts/0]). -callback name() -> atom(). -callback secure() -> boolean(). -callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}. -callback listen(opts()) -> {ok, socket()} | {error, atom()}. -callback accept(socket(), timeout()) -> {ok, socket()} | {error, closed | timeout | atom()}. -callback accept_ack(socket(), timeout()) -> ok. -callback connect(string(), inet:port_number(), opts()) -> {ok, socket()} | {error, atom()}. -callback connect(string(), inet:port_number(), opts(), timeout()) -> {ok, socket()} | {error, atom()}. -callback recv(socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | timeout | atom()}. -callback send(socket(), iodata()) -> ok | {error, atom()}. -callback sendfile(socket(), file:name() | file:fd()) -> {ok, non_neg_integer()} | {error, atom()}. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), non_neg_integer(), sendfile_opts()) -> {ok, non_neg_integer()} | {error, atom()}. -callback setopts(socket(), opts()) -> ok | {error, atom()}. -callback controlling_process(socket(), pid()) -> ok | {error, closed | not_owner | atom()}. -callback peername(socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. -callback sockname(socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. -callback shutdown(socket(), read | write | read_write) -> ok | {error, atom()}. -callback close(socket()) -> ok. %% A fallback for transports that don't have a native sendfile implementation. %% Note that the ordering of arguments is different from file:sendfile/5 and %% that this function accepts either a raw file or a file name. -spec sendfile(module(), socket(), file: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). ranch-1.1.0/test/000077500000000000000000000000001242670721000135645ustar00rootroot00000000000000ranch-1.1.0/test/acceptor_SUITE.erl000066400000000000000000000451621242670721000170510ustar00rootroot00000000000000%% 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_inherit_options/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, tcp_inherit_options ]}, {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). tcp_inherit_options(_) -> Name = tcp_inherit_options, TcpOptions = [{nodelay, false}, {send_timeout_close, false}], {ok, _} = ranch:start_listener(Name, 4, ranch_tcp, [{port, 0} | TcpOptions], check_tcp_options, [{pid, self()} | TcpOptions]), Port = ranch:get_port(Name), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, true}, {packet, raw}]), receive checked -> ok after 1000 -> error(timeout) end, ok = gen_tcp:close(Socket), 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. ranch-1.1.0/test/active_echo_protocol.erl000066400000000000000000000011671242670721000204670ustar00rootroot00000000000000-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. ranch-1.1.0/test/check_tcp_options.erl000066400000000000000000000006321242670721000177670ustar00rootroot00000000000000-module(check_tcp_options). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/3]). start_link(_, Socket, _, [{pid, TestPid}|TcpOptions]) -> {ok, RealTcpOptions} = inet:getopts(Socket, [Key || {Key, _} <- TcpOptions]), Pid = spawn_link(?MODULE, init, [TestPid, RealTcpOptions, TcpOptions]), {ok, Pid}. init(Pid, TcpOptions, TcpOptions) -> Pid ! checked, receive after 2500 -> ok end. ranch-1.1.0/test/cover.spec000066400000000000000000000000341242670721000155530ustar00rootroot00000000000000{incl_app, ranch, details}. ranch-1.1.0/test/echo_protocol.erl000066400000000000000000000007771242670721000171420ustar00rootroot00000000000000-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. ranch-1.1.0/test/notify_and_wait_protocol.erl000066400000000000000000000004361242670721000213720ustar00rootroot00000000000000-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. ranch-1.1.0/test/remove_conn_and_wait_protocol.erl000066400000000000000000000006171242670721000223750ustar00rootroot00000000000000-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. ranch-1.1.0/test/sendfile_SUITE.erl000066400000000000000000000263261242670721000170430ustar00rootroot00000000000000%% 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), ok = Transport:accept_ack(Server, 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. ranch-1.1.0/test/shutdown_SUITE.erl000066400000000000000000000130541242670721000171170ustar00rootroot00000000000000%% 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. -module(shutdown_SUITE). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). %% Tests. -export([brutal_kill/1]). -export([infinity/1]). -export([infinity_trap_exit/1]). -export([timeout/1]). -export([timeout_trap_exit/1]). %% ct. all() -> [brutal_kill, infinity, infinity_trap_exit, timeout, timeout_trap_exit]. init_per_suite(Config) -> ok = application:start(ranch), Config. end_per_suite(_) -> application:stop(ranch), ok. %% Tests. brutal_kill(_) -> Name = brutal_kill, {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {shutdown, brutal_kill}], echo_protocol, []), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, []), receive after 100 -> ok end, ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSup, _, _} = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren), [{_, Pid, _, _}] = supervisor:which_children(ConnsSup), true = is_process_alive(Pid), ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. infinity(_) -> Name = infinity, {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {shutdown, infinity}], echo_protocol, []), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, []), receive after 100 -> ok end, ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSup, _, _} = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren), [{_, Pid, _, _}] = supervisor:which_children(ConnsSup), true = is_process_alive(Pid), ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. infinity_trap_exit(_) -> Name = infinity_trap_exit, {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {shutdown, infinity}], trap_exit_protocol, []), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, []), receive after 100 -> ok end, ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSup, _, _} = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren), [{_, Pid, _, _}] = supervisor:which_children(ConnsSup), true = is_process_alive(Pid), %% This call will block infinitely. SpawnPid = spawn(fun() -> ranch:stop_listener(Name) end), receive after 100 -> ok end, %% The protocol traps exit signals, and ignore them, so it won't die. true = is_process_alive(Pid), %% The listener will stay up forever too. true = is_process_alive(ListenerSup), %% We can't connect, though. {error, _} = gen_tcp:connect("localhost", Port, []), %% Killing the process unblocks everything. exit(Pid, kill), receive after 100 -> ok end, false = is_process_alive(ListenerSup), false = is_process_alive(SpawnPid), ok. %% Same as infinity because the protocol doesn't trap exits. timeout(_) -> Name = timeout, {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {shutdown, 500}], echo_protocol, []), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, []), receive after 100 -> ok end, ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSup, _, _} = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren), [{_, Pid, _, _}] = supervisor:which_children(ConnsSup), true = is_process_alive(Pid), ranch:stop_listener(Name), receive after 100 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), {error, _} = gen_tcp:connect("localhost", Port, []), ok. timeout_trap_exit(_) -> Name = timeout_trap_exit, {ok, ListenerSup} = ranch:start_listener(Name, 1, ranch_tcp, [{port, 0}, {shutdown, 500}], trap_exit_protocol, []), Port = ranch:get_port(Name), {ok, _} = gen_tcp:connect("localhost", Port, []), receive after 100 -> ok end, ListenerSupChildren = supervisor:which_children(ListenerSup), {_, ConnsSup, _, _} = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren), [{_, Pid, _, _}] = supervisor:which_children(ConnsSup), true = is_process_alive(Pid), %% This call will block for the duration of the shutdown. SpawnPid = spawn(fun() -> ranch:stop_listener(Name) end), receive after 100 -> ok end, %% The protocol traps exit signals, and ignore them, so it won't die. true = is_process_alive(Pid), %% The listener will stay up for now too. true = is_process_alive(ListenerSup), %% We can't connect, though. {error, _} = gen_tcp:connect("localhost", Port, []), %% Wait for the timeout to finish and see that everything is killed. receive after 500 -> ok end, false = is_process_alive(Pid), false = is_process_alive(ListenerSup), false = is_process_alive(SpawnPid), ok. ranch-1.1.0/test/trap_exit_protocol.erl000066400000000000000000000010501242670721000202040ustar00rootroot00000000000000-module(trap_exit_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 = []) -> process_flag(trap_exit, true), ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, infinity) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end.