From e4da6956fceea1af8e2cdf9fece0e014576ab43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Sat, 2 Apr 2011 20:27:16 +0200 Subject: [PATCH] Anonymize and improve the cowboy supervision tree. * Cowboy isn't an OTP application anymore; just a supervisor. * All processes started by Cowboy are now anonymous. * All processes related to a listener are now part of its supervision tree. --- README.md | 42 ++++++++++++------- src/cowboy.app.src | 4 +- src/cowboy_acceptor.erl | 19 +++++---- ...owboy_sup.erl => cowboy_acceptors_sup.erl} | 27 ++++++------ src/cowboy_app.erl | 30 ------------- src/cowboy_listener_sup.erl | 28 +++++++++---- ...tocols_sup.erl => cowboy_requests_sup.erl} | 14 +++---- 7 files changed, 78 insertions(+), 86 deletions(-) rename src/{cowboy_sup.erl => cowboy_acceptors_sup.erl} (53%) delete mode 100644 src/cowboy_app.erl rename src/{cowboy_protocols_sup.erl => cowboy_requests_sup.erl} (76%) diff --git a/README.md b/README.md index c47974cb..c8e9f9f8 100644 --- a/README.md +++ b/README.md @@ -33,30 +33,40 @@ Embedding Cowboy Getting Started --------------- -Cowboy can be started and stopped like any other application. However the -Cowboy application do not start any listener, those must be started manually. +Cowboy provides an anonymous listener supervisor that you can directly embed +in your application's supervision tree. A listener is a special kind of supervisor that handles a pool of acceptor -processes. An acceptor simply accept connections and forward them to a -protocol module, for example HTTP. You must thus define the transport and -protocol module to use for the listener, their options and the number of -acceptors in the pool before you can start a listener supervisor. +processes. It also manages all its associated request processes. This allows +you to shutdown all processes related to a listener by stopping the supervisor. + +An acceptor simply accepts connections and forwards them to a protocol module, +for example HTTP. You must thus define the transport and protocol module to +use for the listener, their options and the number of acceptors in the pool +before you can start a listener supervisor. For HTTP applications the transport can be either TCP or SSL for HTTP and HTTPS respectively. On the other hand, the protocol is of course HTTP. Code speaks more than words: - application:start(cowboy), - Dispatch = [ - %% {Host, list({Path, Handler, Opts})} - {'_', [{'_', my_handler, []}]} - ], - %% NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts - cowboy_listener_sup:start_link(100, - cowboy_tcp_transport, [{port, 8080}], - cowboy_http_protocol, [{dispatch, Dispatch}] - ). + -module(my_app). + -behaviour(application). + -export([start/2, stop/1]). + + start(_Type, _Args) -> + Dispatch = [ + %% {Host, list({Path, Handler, Opts})} + {'_', [{'_', my_handler, []}]} + ], + %% NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts + cowboy_listener_sup:start_link(100, + cowboy_tcp_transport, [{port, 8080}], + cowboy_http_protocol, [{dispatch, Dispatch}] + ). + + stop(_State) -> + ok. You must also write the `my_handler` module to process requests. You can use one of the predefined handlers or write your own. An hello world HTTP diff --git a/src/cowboy.app.src b/src/cowboy.app.src index d3dd9589..69724968 100644 --- a/src/cowboy.app.src +++ b/src/cowboy.app.src @@ -20,7 +20,5 @@ {applications, [ kernel, stdlib - ]}, - {mod, {cowboy_app, []}}, - {env, []} + ]} ]}. diff --git a/src/cowboy_acceptor.erl b/src/cowboy_acceptor.erl index d4d7f5cc..0dbd9b0c 100644 --- a/src/cowboy_acceptor.erl +++ b/src/cowboy_acceptor.erl @@ -13,27 +13,28 @@ %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(cowboy_acceptor). --export([start_link/4]). %% API. --export([acceptor/4]). %% Internal. +-export([start_link/5]). %% API. +-export([acceptor/5]). %% Internal. -include("include/types.hrl"). %% API. -spec start_link(LSocket::socket(), Transport::module(), - Protocol::module(), Opts::term()) -> {ok, Pid::pid()}. -start_link(LSocket, Transport, Protocol, Opts) -> - Pid = spawn_link(?MODULE, acceptor, [LSocket, Transport, Protocol, Opts]), + Protocol::module(), Opts::term(), ReqsSup::pid()) -> {ok, Pid::pid()}. +start_link(LSocket, Transport, Protocol, Opts, ReqsSup) -> + Pid = spawn_link(?MODULE, acceptor, + [LSocket, Transport, Protocol, Opts, ReqsSup]), {ok, Pid}. %% Internal. -spec acceptor(LSocket::socket(), Transport::module(), - Protocol::module(), Opts::term()) -> no_return(). -acceptor(LSocket, Transport, Protocol, Opts) -> + Protocol::module(), Opts::term(), ReqsSup::pid()) -> no_return(). +acceptor(LSocket, Transport, Protocol, Opts, ReqsSup) -> case Transport:accept(LSocket) of {ok, CSocket} -> - {ok, Pid} = supervisor:start_child(cowboy_protocols_sup, + {ok, Pid} = supervisor:start_child(ReqsSup, [CSocket, Transport, Protocol, Opts]), Transport:controlling_process(CSocket, Pid); {error, _Reason} -> @@ -41,4 +42,4 @@ acceptor(LSocket, Transport, Protocol, Opts) -> %% we may want to try and listen again on the port? ignore end, - ?MODULE:acceptor(LSocket, Transport, Protocol, Opts). + ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, ReqsSup). diff --git a/src/cowboy_sup.erl b/src/cowboy_acceptors_sup.erl similarity index 53% rename from src/cowboy_sup.erl rename to src/cowboy_acceptors_sup.erl index 2cb9c10f..c5e7db41 100644 --- a/src/cowboy_sup.erl +++ b/src/cowboy_acceptors_sup.erl @@ -12,26 +12,29 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --module(cowboy_sup). +-module(cowboy_acceptors_sup). -behaviour(supervisor). --export([start_link/0]). %% API. +-export([start_link/6]). %% API. -export([init/1]). %% supervisor. --define(SUPERVISOR, ?MODULE). +-include("include/types.hrl"). %% API. --spec start_link() -> {ok, Pid::pid()}. -start_link() -> - supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). +-spec start_link(NbAcceptors::non_neg_integer(), Transport::module(), + TransOpts::term(), Protocol::module(), ProtoOpts::term(), ReqsPid::pid()) + -> {ok, Pid::pid()}. +start_link(LSocket, NbAcceptors, Transport, Protocol, ProtoOpts, ReqsPid) -> + supervisor:start_link(?MODULE, [LSocket, NbAcceptors, + Transport, Protocol, ProtoOpts, ReqsPid]). %% supervisor. --spec init([]) -> term(). %% @todo These specs should be improved. -init([]) -> - Procs = [ - {cowboy_protocols_sup, {cowboy_protocols_sup, start_link, []}, - permanent, 5000, supervisor, [cowboy_protocols_sup]} - ], +-spec init(list(term())) -> term(). %% @todo These specs should be improved. +init([LSocket, NbAcceptors, Transport, Protocol, ProtoOpts, ReqsPid]) -> + Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [ + LSocket, Transport, Protocol, ProtoOpts, ReqsPid + ]}, permanent, brutal_kill, worker, dynamic} + || N <- lists:seq(1, NbAcceptors)], {ok, {{one_for_one, 10, 10}, Procs}}. diff --git a/src/cowboy_app.erl b/src/cowboy_app.erl deleted file mode 100644 index 4f81ff64..00000000 --- a/src/cowboy_app.erl +++ /dev/null @@ -1,30 +0,0 @@ -%% Copyright (c) 2011, 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(cowboy_app). --behaviour(application). - --export([start/2, stop/1]). %% API. - --include("include/types.hrl"). - -%% API. - --spec start(Type::application_start_type(), Args::term()) -> {ok, Pid::pid()}. -start(_Type, _Args) -> - cowboy_sup:start_link(). - --spec stop(State::term()) -> ok. -stop(_State) -> - ok. diff --git a/src/cowboy_listener_sup.erl b/src/cowboy_listener_sup.erl index 3c8b3c2c..85b17efb 100644 --- a/src/cowboy_listener_sup.erl +++ b/src/cowboy_listener_sup.erl @@ -26,8 +26,7 @@ start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> case Transport:listen(TransOpts) of {ok, LSocket} -> - supervisor:start_link(?MODULE, [LSocket, - NbAcceptors, Transport, Protocol, ProtoOpts]); + start_sup(LSocket, NbAcceptors, Transport, Protocol, ProtoOpts); {error, Reason} -> {error, Reason} end. @@ -35,9 +34,22 @@ start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> %% supervisor. %% @todo These specs should be improved. --spec init(list(term())) -> term(). -init([LSocket, NbAcceptors, Transport, Protocol, ProtoOpts]) -> - Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, - [LSocket, Transport, Protocol, ProtoOpts]}, permanent, - brutal_kill, worker, dynamic} || N <- lists:seq(1, NbAcceptors)], - {ok, {{one_for_one, 10, 10}, Procs}}. +-spec init([]) -> term(). +init([]) -> + {ok, {{one_for_one, 0, 1}, []}}. + +%% Internal. + +-spec start_sup(NbAcceptors::non_neg_integer(), Transport::module(), + TransOpts::term(), Protocol::module(), ProtoOpts::term()) + -> {ok, Pid::pid()}. +start_sup(LSocket, NbAcceptors, Transport, Protocol, ProtoOpts) -> + {ok, SupPid} = supervisor:start_link(?MODULE, []), + {ok, ReqsPid} = supervisor:start_child(SupPid, + {cowboy_requests_sup, {cowboy_requests_sup, start_link, []}, + permanent, 5000, supervisor, [cowboy_requests_sup]}), + {ok, _PoolPid} = supervisor:start_child(SupPid, + {cowboy_acceptors_sup, {cowboy_acceptors_sup, start_link, [ + LSocket, NbAcceptors, Transport, Protocol, ProtoOpts, ReqsPid + ]}, permanent, 5000, supervisor, [cowboy_acceptors_sup]}), + {ok, SupPid}. diff --git a/src/cowboy_protocols_sup.erl b/src/cowboy_requests_sup.erl similarity index 76% rename from src/cowboy_protocols_sup.erl rename to src/cowboy_requests_sup.erl index 04f662e5..af6db7e7 100644 --- a/src/cowboy_protocols_sup.erl +++ b/src/cowboy_requests_sup.erl @@ -12,30 +12,28 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --module(cowboy_protocols_sup). +-module(cowboy_requests_sup). -behaviour(supervisor). --export([start_link/0, start_protocol/4]). %% API. +-export([start_link/0, start_request/4]). %% API. -export([init/1]). %% supervisor. -include("include/types.hrl"). --define(SUPERVISOR, ?MODULE). - %% API. -spec start_link() -> {ok, Pid::pid()}. start_link() -> - supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). + supervisor:start_link(?MODULE, []). --spec start_protocol(Socket::socket(), Transport::module(), +-spec start_request(Socket::socket(), Transport::module(), Protocol::module(), Opts::term()) -> {ok, Pid::pid()}. -start_protocol(Socket, Transport, Protocol, Opts) -> +start_request(Socket, Transport, Protocol, Opts) -> Protocol:start_link(Socket, Transport, Opts). %% supervisor. -spec init([]) -> term(). %% @todo These specs should be improved. init([]) -> - {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_protocol, []}, + {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_request, []}, temporary, brutal_kill, worker, [?MODULE]}]}}.