From 108a491f5515fdc2a7fe3ce8c310a261d6be3230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 6 Jul 2011 17:42:20 +0200 Subject: Add documentation for the public interface. This is probably not perfect yet but it should be better than nothing. We'll improve things with feedback received from the many users. --- .gitignore | 4 +++ Makefile | 3 ++ doc/overview.edoc | 4 +++ src/cowboy.erl | 25 +++++++++++++-- src/cowboy_acceptor.erl | 2 ++ src/cowboy_acceptors_sup.erl | 1 + src/cowboy_app.erl | 1 + src/cowboy_clock.erl | 17 +++++++++++ src/cowboy_dispatcher.erl | 33 +++++++++++++++++++- src/cowboy_http_handler.erl | 25 +++++++++++++++ src/cowboy_http_protocol.erl | 20 ++++++++++++ src/cowboy_http_req.erl | 53 ++++++++++++++++++++++++++++++-- src/cowboy_http_websocket.erl | 16 ++++++++++ src/cowboy_http_websocket_handler.erl | 26 ++++++++++++++++ src/cowboy_listener_sup.erl | 1 + src/cowboy_requests_sup.erl | 1 + src/cowboy_ssl_transport.erl | 57 +++++++++++++++++++++++++++++++++-- src/cowboy_sup.erl | 1 + src/cowboy_tcp_transport.erl | 41 +++++++++++++++++++++++-- 19 files changed, 319 insertions(+), 12 deletions(-) create mode 100644 doc/overview.edoc diff --git a/.gitignore b/.gitignore index d38fc51..64a028f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ .cowboy_dialyzer.plt .eunit +doc/*.css +doc/*.html +doc/*.png +doc/edoc-info ebin logs test/*.beam diff --git a/Makefile b/Makefile index 04f12d7..e96d5de 100644 --- a/Makefile +++ b/Makefile @@ -29,3 +29,6 @@ dialyze: @$(DIALYZER) --src src --plt .cowboy_dialyzer.plt \ -Wbehaviours -Werror_handling \ -Wrace_conditions -Wunmatched_returns # -Wunderspecs + +docs: + @$(REBAR) doc diff --git a/doc/overview.edoc b/doc/overview.edoc new file mode 100644 index 0000000..56648c4 --- /dev/null +++ b/doc/overview.edoc @@ -0,0 +1,4 @@ +@author Loïc Hoguin +@copyright 2011 Loïc Hoguin +@version HEAD +@title Small, fast, modular HTTP server. diff --git a/src/cowboy.erl b/src/cowboy.erl index a1e8063..dbc64d0 100644 --- a/src/cowboy.erl +++ b/src/cowboy.erl @@ -12,11 +12,30 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Cowboy API to start and stop listeners. -module(cowboy). --export([start_listener/6, stop_listener/1]). %% API. -%% API. +-export([start_listener/6, stop_listener/1]). +%% @doc Start a listener for the given transport and protocol. +%% +%% A listener is effectively a pool of NbAcceptors acceptors. +%% Acceptors accept connections on the given Transport and forward +%% requests to the given Protocol handler. Both transport and protocol +%% modules can be given options through the TransOpts and the +%% ProtoOpts arguments. Available options are documented in the +%% listen transport function and in the protocol module of your choice. +%% +%% All acceptor and request processes are supervised by the listener. +%% +%% It is recommended to set a large enough number of acceptors to improve +%% performance. The exact number depends of course on your hardware, on the +%% protocol used and on the number of expected simultaneous connections. +%% +%% Although Cowboy includes a cowboy_http_protocol handler, other +%% handlers can be created for different protocols like IRC, FTP and more. +%% +%% Ref can be used to stop the listener later on. -spec start_listener(any(), non_neg_integer(), module(), any(), module(), any()) -> {ok, pid()}. start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> @@ -26,6 +45,8 @@ start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> ]}, permanent, 5000, supervisor, [cowboy_listener_sup]}). +%% @doc Stop a listener identified by Ref. +%% @todo Currently request processes aren't terminated with the listener. -spec stop_listener(any()) -> ok | {error, not_found}. stop_listener(Ref) -> case supervisor:terminate_child(cowboy_sup, {cowboy_listener_sup, Ref}) of diff --git a/src/cowboy_acceptor.erl b/src/cowboy_acceptor.erl index 830828e..cc8dfa3 100644 --- a/src/cowboy_acceptor.erl +++ b/src/cowboy_acceptor.erl @@ -12,7 +12,9 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_acceptor). + -export([start_link/6]). %% API. -export([acceptor/6]). %% Internal. diff --git a/src/cowboy_acceptors_sup.erl b/src/cowboy_acceptors_sup.erl index d0ab77e..c12aeb5 100644 --- a/src/cowboy_acceptors_sup.erl +++ b/src/cowboy_acceptors_sup.erl @@ -12,6 +12,7 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_acceptors_sup). -behaviour(supervisor). diff --git a/src/cowboy_app.erl b/src/cowboy_app.erl index 114eb9a..0ff08f0 100644 --- a/src/cowboy_app.erl +++ b/src/cowboy_app.erl @@ -12,6 +12,7 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_app). -behaviour(application). diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl index cc824ed..e028559 100644 --- a/src/cowboy_clock.erl +++ b/src/cowboy_clock.erl @@ -12,6 +12,12 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Date and time related functions. +%% +%% While a gen_server process runs in the background to update +%% the cache of formatted dates every second, all API calls are +%% local and directly read from the ETS cache table, providing +%% fast time and date computations. -module(cowboy_clock). -behaviour(gen_server). @@ -46,20 +52,26 @@ %% API. +%% @private -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). +%% @private -spec stop() -> stopped. stop() -> gen_server:call(?SERVER, stop). +%% @doc Return the current date and time formatted according to RFC-1123. +%% +%% This format is used in the 'Date' header sent with HTTP responses. -spec rfc1123() -> binary(). rfc1123() -> ets:lookup_element(?TABLE, rfc1123, 2). %% gen_server. +%% @private -spec init([]) -> {ok, #state{}}. init([]) -> ?TABLE = ets:new(?TABLE, [set, protected, @@ -70,6 +82,7 @@ init([]) -> ets:insert(?TABLE, {rfc1123, B}), {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}. +%% @private -spec handle_call(_, _, State) -> {reply, ignored, State} | {stop, normal, stopped, State}. handle_call(stop, _From, State=#state{tref=TRef}) -> @@ -78,10 +91,12 @@ handle_call(stop, _From, State=#state{tref=TRef}) -> handle_call(_Request, _From, State) -> {reply, ignored, State}. +%% @private -spec handle_cast(_, State) -> {noreply, State}. handle_cast(_Msg, State) -> {noreply, State}. +%% @private -spec handle_info(_, State) -> {noreply, State}. handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) -> T = erlang:universaltime(), @@ -91,10 +106,12 @@ handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) -> handle_info(_Info, State) -> {noreply, State}. +%% @private -spec terminate(_, _) -> ok. terminate(_Reason, _State) -> ok. +%% @private -spec code_change(_, State, _) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl index e34a4eb..718b13e 100644 --- a/src/cowboy_dispatcher.erl +++ b/src/cowboy_dispatcher.erl @@ -13,12 +13,14 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Dispatch requests according to a hostname and path. -module(cowboy_dispatcher). + -export([split_host/1, split_path/1, match/3]). %% API. -type bindings() :: list({atom(), binary()}). -type path_tokens() :: list(binary()). --type match_rule() :: '_' | '*' | list(binary() | '_' | atom()). +-type match_rule() :: '_' | '*' | list(binary() | '_' | '...' | atom()). -type dispatch_path() :: list({match_rule(), module(), any()}). -type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}. -type dispatch_rules() :: list(dispatch_rule()). @@ -29,6 +31,7 @@ %% API. +%% @doc Split a hostname into a list of tokens. -spec split_host(binary()) -> {path_tokens(), binary(), undefined | inet:ip_port()}. split_host(<<>>) -> @@ -42,6 +45,7 @@ split_host(Host) -> list_to_integer(binary_to_list(Port))} end. +%% @doc Split a path into a list of tokens. -spec split_path(binary()) -> {path_tokens(), binary(), binary()}. split_path(Path) -> case binary:split(Path, <<"?">>) of @@ -57,6 +61,33 @@ do_split_path(RawPath, Separator) -> Path -> Path end. +%% @doc Match hostname tokens and path tokens against dispatch rules. +%% +%% It is typically used for matching tokens for the hostname and path of +%% the request against a global dispatch rule for your listener. +%% +%% Dispatch rules are a list of {Hostname, PathRules} tuples, with +%% PathRules being a list of {Path, HandlerMod, HandlerOpts}. +%% +%% Hostname and Path are match rules and can be either the +%% atom '_', which matches everything for a single token, the atom +%% '*', which matches everything for the rest of the tokens, or a +%% list of tokens. Each token can be either a binary, the atom '_', +%% the atom '...' or a named atom. A binary token must match exactly, +%% '_' matches everything for a single token, '...' matches +%% everything for the rest of the tokens and a named atom will bind the +%% corresponding token value and return it. +%% +%% The list of hostname tokens is reversed before matching. For example, if +%% we were to match "www.dev-extend.eu", we would first match "eu", then +%% "dev-extend", then "www". This means that in the context of hostnames, +%% the '...' atom matches properly the lower levels of the domain +%% as would be expected. +%% +%% When a result is found, this function will return the handler module and +%% options found in the dispatch list, a key-value list of bindings and +%% the tokens that were matched by the '...' atom for both the +%% hostname and path. -spec match(Host::path_tokens(), Path::path_tokens(), dispatch_rules()) -> {ok, module(), any(), bindings(), HostInfo::undefined | path_tokens(), diff --git a/src/cowboy_http_handler.erl b/src/cowboy_http_handler.erl index 67f07bc..b220b09 100644 --- a/src/cowboy_http_handler.erl +++ b/src/cowboy_http_handler.erl @@ -12,9 +12,34 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Handler for HTTP requests. +%% +%% HTTP handlers must implement three callbacks: init/3, +%% handle/2 and terminate/2, called one after another in +%% that order. +%% +%% init/3 is meant for initialization. It receives information about +%% the transport and protocol used, along with the handler options from the +%% dispatch list, and allows you to upgrade the protocol if needed. You can +%% define a request-wide state here. +%% +%% handle/2 is meant for handling the request. It receives the +%% request and the state previously defined. +%% +%% terminate/2 is meant for cleaning up. It also receives the +%% request and the state previously defined. +%% +%% You do not have to read the request body or even send a reply if you do +%% not need to. Cowboy will properly handle these cases and clean-up afterwards. +%% In doubt it'll simply close the connection. +%% +%% Note that when upgrading the connection to WebSocket you do not need to +%% define the handle/2 and terminate/2 callbacks. -module(cowboy_http_handler). + -export([behaviour_info/1]). +%% @private -spec behaviour_info(_) -> undefined | [{handle, 2} | {init, 3} | {terminate, 2}, ...]. behaviour_info(callbacks) -> diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl index 51be028..779866a 100644 --- a/src/cowboy_http_protocol.erl +++ b/src/cowboy_http_protocol.erl @@ -13,7 +13,24 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc HTTP protocol handler. +%% +%% The available options are: +%%
+%%
dispatch
The dispatch list for this protocol.
+%%
max_empty_lines
Max number of empty lines before a request. +%% Defaults to 5.
+%%
timeout
Time in milliseconds before an idle keep-alive +%% connection is closed. Defaults to 5000 milliseconds.
+%%
+%% +%% Note that there is no need to monitor these processes when using Cowboy as +%% an application as it already supervises them under the listener supervisor. +%% +%% @see cowboy_dispatcher +%% @see cowboy_http_handler -module(cowboy_http_protocol). + -export([start_link/3]). %% API. -export([init/3, parse_request/1]). %% FSM. @@ -33,6 +50,7 @@ %% API. +%% @doc Start an HTTP protocol process. -spec start_link(inet:socket(), module(), any()) -> {ok, pid()}. start_link(Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Socket, Transport, Opts]), @@ -40,6 +58,7 @@ start_link(Socket, Transport, Opts) -> %% FSM. +%% @private -spec init(inet:socket(), module(), any()) -> ok. init(Socket, Transport, Opts) -> Dispatch = proplists:get_value(dispatch, Opts, []), @@ -48,6 +67,7 @@ init(Socket, Transport, Opts) -> wait_request(#state{socket=Socket, transport=Transport, dispatch=Dispatch, max_empty_lines=MaxEmptyLines, timeout=Timeout}). +%% @private -spec parse_request(#state{}) -> ok. %% @todo Use decode_packet options to limit length? parse_request(State=#state{buffer=Buffer}) -> diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl index c9aa5c7..601f839 100644 --- a/src/cowboy_http_req.erl +++ b/src/cowboy_http_req.erl @@ -13,6 +13,12 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc HTTP request manipulation API. +%% +%% Almost all functions in this module return a new Req variable. +%% It should always be used instead of the one used in your function call +%% because it keeps the state of the request. It also allows Cowboy to do +%% some lazy evaluation and cache results where possible. -module(cowboy_http_req). -export([ @@ -42,14 +48,17 @@ %% Request API. +%% @doc Return the HTTP method of the request. -spec method(#http_req{}) -> {http_method(), #http_req{}}. method(Req) -> {Req#http_req.method, Req}. +%% @doc Return the HTTP version used for the request. -spec version(#http_req{}) -> {http_version(), #http_req{}}. version(Req) -> {Req#http_req.version, Req}. +%% @doc Return the peer address and port number of the remote host. -spec peer(#http_req{}) -> {{inet:ip_address(), inet:ip_port()}, #http_req{}}. peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> {ok, Peer} = Transport:peername(Socket), @@ -57,42 +66,53 @@ peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> peer(Req) -> {Req#http_req.peer, Req}. +%% @doc Return the tokens for the hostname requested. -spec host(#http_req{}) -> {cowboy_dispatcher:path_tokens(), #http_req{}}. host(Req) -> {Req#http_req.host, Req}. +%% @doc Return the extra host information obtained from partially matching +%% the hostname using '...'. -spec host_info(#http_req{}) -> {cowboy_dispatcher:path_tokens() | undefined, #http_req{}}. host_info(Req) -> {Req#http_req.host_info, Req}. +%% @doc Return the raw host directly taken from the request. -spec raw_host(#http_req{}) -> {binary(), #http_req{}}. raw_host(Req) -> {Req#http_req.raw_host, Req}. +%% @doc Return the port used for this request. -spec port(#http_req{}) -> {inet:ip_port(), #http_req{}}. port(Req) -> {Req#http_req.port, Req}. +%% @doc Return the tokens for the path requested. -spec path(#http_req{}) -> {cowboy_dispatcher:path_tokens(), #http_req{}}. path(Req) -> {Req#http_req.path, Req}. +%% @doc Return the extra path information obtained from partially matching +%% the patch using '...'. -spec path_info(#http_req{}) -> {cowboy_dispatcher:path_tokens() | undefined, #http_req{}}. path_info(Req) -> {Req#http_req.path_info, Req}. +%% @doc Return the raw path directly taken from the request. -spec raw_path(#http_req{}) -> {binary(), #http_req{}}. raw_path(Req) -> {Req#http_req.raw_path, Req}. +%% @equiv qs_val(Name, Req, undefined) -spec qs_val(binary(), #http_req{}) -> {binary() | true | undefined, #http_req{}}. -%% @equiv qs_val(Name, Req, undefined) qs_val(Name, Req) -> qs_val(Name, Req, undefined). +%% @doc Return the query string value for the given key, or a default if +%% missing. -spec qs_val(binary(), #http_req{}, Default) -> {binary() | true | Default, #http_req{}} when Default::any(). qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}, Default) -> @@ -104,6 +124,7 @@ qs_val(Name, Req, Default) -> false -> {Default, Req} end. +%% @doc Return the full list of query string values. -spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) -> QsVals = parse_qs(RawQs), @@ -111,15 +132,18 @@ qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) -> qs_vals(Req=#http_req{qs_vals=QsVals}) -> {QsVals, Req}. +%% @doc Return the raw query string directly taken from the request. -spec raw_qs(#http_req{}) -> {binary(), #http_req{}}. raw_qs(Req) -> {Req#http_req.raw_qs, Req}. --spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. %% @equiv binding(Name, Req, undefined) +-spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. binding(Name, Req) -> binding(Name, Req, undefined). +%% @doc Return the binding value for the given key obtained when matching +%% the host and path against the dispatch list, or a default if missing. -spec binding(atom(), #http_req{}, Default) -> {binary() | Default, #http_req{}} when Default::any(). binding(Name, Req, Default) -> @@ -128,16 +152,18 @@ binding(Name, Req, Default) -> false -> {Default, Req} end. +%% @doc Return the full list of binding values. -spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}. bindings(Req) -> {Req#http_req.bindings, Req}. +%% @equiv header(Name, Req, undefined) -spec header(atom() | binary(), #http_req{}) -> {binary() | undefined, #http_req{}}. -%% @equiv header(Name, Req, undefined) header(Name, Req) -> header(Name, Req, undefined). +%% @doc Return the header value for the given key, or a default if missing. -spec header(atom() | binary(), #http_req{}, Default) -> {binary() | Default, #http_req{}} when Default::any(). header(Name, Req, Default) -> @@ -146,12 +172,15 @@ header(Name, Req, Default) -> false -> {Default, Req} end. +%% @doc Return the full list of headers. -spec headers(#http_req{}) -> {http_headers(), #http_req{}}. headers(Req) -> {Req#http_req.headers, Req}. %% Request Body API. +%% @doc Return the full body sent with the request, or {error, badarg} +%% if no Content-Length is available. %% @todo We probably want to allow a max length. -spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. body(Req) -> @@ -163,6 +192,11 @@ body(Req) -> body(Length2, Req2) end. +%% @doc Return Length bytes of the request body. +%% +%% You probably shouldn't be calling this function directly, as it expects the +%% Length argument to be the full size of the body, and will consider +%% the body to be fully read from the socket. %% @todo We probably want to configure the timeout. -spec body(non_neg_integer(), #http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. @@ -177,6 +211,8 @@ body(Length, Req=#http_req{socket=Socket, transport=Transport, {error, Reason} -> {error, Reason} end. +%% @doc Return the full body sent with the reqest, parsed as an +%% application/x-www-form-urlencoded string. Essentially a POST query string. -spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. body_qs(Req) -> {ok, Body, Req2} = body(Req), @@ -184,6 +220,7 @@ body_qs(Req) -> %% Response API. +%% @doc Send a reply to the client. -spec reply(http_status(), http_headers(), iodata(), #http_req{}) -> {ok, #http_req{}}. reply(Code, Headers, Body, Req=#http_req{socket=Socket, @@ -199,6 +236,8 @@ reply(Code, Headers, Body, Req=#http_req{socket=Socket, Transport:send(Socket, [Head, Body]), {ok, Req#http_req{resp_state=done}}. +%% @doc Initiate the sending of a chunked reply to the client. +%% @see cowboy_http_req:chunk/2 -spec chunked_reply(http_status(), http_headers(), #http_req{}) -> {ok, #http_req{}}. chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, @@ -212,6 +251,9 @@ chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, Transport:send(Socket, Head), {ok, Req#http_req{resp_state=chunks}}. +%% @doc Send a chunk of data. +%% +%% A chunked reply must have been initiated before calling this function. -spec chunk(iodata(), #http_req{}) -> ok. chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), @@ -219,6 +261,11 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> %% Misc API. +%% @doc Compact the request data by removing all non-system information. +%% +%% This essentially removes the host, path, query string, bindings and headers. +%% Use it when you really need to save up memory, for example when having +%% many concurrent long-running connections. -spec compact(#http_req{}) -> #http_req{}. compact(Req) -> Req#http_req{host=undefined, host_info=undefined, path=undefined, diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl index 8463ff5..da1622c 100644 --- a/src/cowboy_http_websocket.erl +++ b/src/cowboy_http_websocket.erl @@ -12,7 +12,17 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc WebSocket protocol draft hixie-76 implementation. +%% +%% Known to work with the following browsers: +%% -module(cowboy_http_websocket). + -export([upgrade/3]). %% API. -export([handler_loop/4]). %% Internal. @@ -30,6 +40,11 @@ hibernate = false :: boolean() }). +%% @doc Upgrade a HTTP request to the WebSocket protocol. +%% +%% You do not need to call this function manually. To upgrade to the WebSocket +%% protocol, you simply need to return {upgrade, protocol, {@module}} +%% in your cowboy_http_handler:init/3 handler function. -spec upgrade(module(), any(), #http_req{}) -> ok. upgrade(Handler, Opts, Req) -> EOP = binary:compile_pattern(<< 255 >>), @@ -128,6 +143,7 @@ handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport}, Transport:setopts(Socket, [{active, once}]), handler_loop(State, Req, HandlerState, SoFar). +%% @private -spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok. handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout}, Req=#http_req{socket=Socket}, HandlerState, SoFar) -> diff --git a/src/cowboy_http_websocket_handler.erl b/src/cowboy_http_websocket_handler.erl index 30cb2e1..b02c28d 100644 --- a/src/cowboy_http_websocket_handler.erl +++ b/src/cowboy_http_websocket_handler.erl @@ -12,9 +12,35 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Handler for HTTP WebSocket requests. +%% +%% WebSocket handlers must implement three callbacks: websocket_init/3, +%% websocket_handle/3 and websocket_terminate/3. These +%% callbacks will only be called if the connection is upgraded to WebSocket +%% in the HTTP handler's init/3 callback. They are then called in that +%% order, although websocket_handle/3 will be called multiple time, +%% one time for each message or packet received. +%% +%% websocket_init/3 is meant for initialization. It receives +%% information about the transport and protocol used, along with the handler +%% options from the dispatch list. You can define a request-wide state here. +%% If you are going to want to compact the request, you should probably do it +%% here. +%% +%% websocket_handle/3 receives messages sent to the process and +%% also the data sent to the socket. In the later case the information is +%% given as a tuple {websocket, Data}. It can reply something, do +%% nothing or close the connection. You can choose to hibernate the process +%% by returning hibernate to save memory and CPU. +%% +%% websocket_terminate/3 is meant for cleaning up. It also receives +%% the request and the state previously defined, along with a reason for +%% termination. -module(cowboy_http_websocket_handler). + -export([behaviour_info/1]). +%% @private -spec behaviour_info(_) -> undefined | [{websocket_handle, 3} | {websocket_init, 3} | {websocket_terminate, 3}, ...]. behaviour_info(callbacks) -> diff --git a/src/cowboy_listener_sup.erl b/src/cowboy_listener_sup.erl index 248d7df..0ed662f 100644 --- a/src/cowboy_listener_sup.erl +++ b/src/cowboy_listener_sup.erl @@ -12,6 +12,7 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_listener_sup). -behaviour(supervisor). diff --git a/src/cowboy_requests_sup.erl b/src/cowboy_requests_sup.erl index 1ad3a52..a50ee8a 100644 --- a/src/cowboy_requests_sup.erl +++ b/src/cowboy_requests_sup.erl @@ -12,6 +12,7 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_requests_sup). -behaviour(supervisor). diff --git a/src/cowboy_ssl_transport.erl b/src/cowboy_ssl_transport.erl index 8e569ec..098d409 100644 --- a/src/cowboy_ssl_transport.erl +++ b/src/cowboy_ssl_transport.erl @@ -12,18 +12,50 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc SSL transport API. +%% +%% Wrapper around ssl implementing the Cowboy transport API. +%% +%% This transport requires the crypto, public_key +%% and ssl applications to be started. If they aren't started, +%% it will try to start them itself before opening a port to listen. +%% Applications aren't stopped when the listening socket is closed, though. +%% +%% @see ssl -module(cowboy_ssl_transport). -export([name/0, messages/0, listen/1, accept/2, recv/3, send/2, setopts/2, - controlling_process/2, peername/1, close/1]). %% API. - -%% API. + controlling_process/2, peername/1, close/1]). +%% @doc Name of this transport API, ssl. -spec name() -> ssl. name() -> ssl. +%% @doc Atoms used in the process messages sent by this API. +%% +%% They identify incoming data, closed connection and errors when receiving +%% data in active mode. -spec messages() -> {ssl, ssl_closed, ssl_error}. messages() -> {ssl, ssl_closed, ssl_error}. +%% @doc Setup a socket to listen on the given port on the local host. +%% +%% The available options are: +%%
+%%
port
Mandatory. TCP port number to open.
+%%
backlog
Maximum length of the pending connections queue. +%% Defaults to 1024.
+%%
ip
Interface to listen on. Listen on all interfaces +%% by default.
+%%
certfile
Mandatory. Path to a file containing the user's +%% certificate.
+%%
keyfile
Mandatory. Path to the file containing the user's +%% private PEM encoded key.
+%%
password
Mandatory. String containing the user's password. +%% All private keyfiles must be password protected currently.
+%%
+%% +%% @see ssl:listen/2 +%% @todo The password option shouldn't be mandatory. -spec listen([{port, inet:ip_port()} | {certfile, string()} | {keyfile, string()} | {password, string()} | {ip, inet:ip_address()}]) @@ -45,6 +77,13 @@ listen(Opts) -> end, ssl:listen(Port, ListenOpts). +%% @doc Accept an incoming connection on a listen socket. +%% +%% Note that this function does both the transport accept and +%% the SSL handshake. +%% +%% @see ssl:transport_accept/2 +%% @see ssl:ssl_accept/2 -spec accept(ssl:sslsocket(), timeout()) -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> @@ -55,29 +94,41 @@ accept(LSocket, Timeout) -> {error, Reason} end. +%% @doc Receive a packet from a socket in passive mode. +%% @see ssl:recv/3 -spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> ssl:recv(Socket, Length, Timeout). +%% @doc Send a packet on a socket. +%% @see ssl:send/2 -spec send(ssl:sslsocket(), iolist()) -> ok | {error, atom()}. send(Socket, Packet) -> ssl:send(Socket, Packet). +%% @doc Set one or more options for a socket. +%% @see ssl:setopts/2 -spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> ssl:setopts(Socket, Opts). +%% @doc Assign a new controlling process Pid to Socket. +%% @see ssl:controlling_process/2 -spec controlling_process(ssl:sslsocket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> ssl:controlling_process(Socket, Pid). +%% @doc Return the address and port for the other end of a connection. +%% @see ssl:peername/1 -spec peername(ssl:sslsocket()) -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. peername(Socket) -> ssl:peername(Socket). +%% @doc Close a TCP socket. +%% @see ssl:close/1 -spec close(ssl:sslsocket()) -> ok. close(Socket) -> ssl:close(Socket). diff --git a/src/cowboy_sup.erl b/src/cowboy_sup.erl index 13977e7..9c52486 100644 --- a/src/cowboy_sup.erl +++ b/src/cowboy_sup.erl @@ -12,6 +12,7 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @private -module(cowboy_sup). -behaviour(supervisor). diff --git a/src/cowboy_tcp_transport.erl b/src/cowboy_tcp_transport.erl index 1597b88..c1dad62 100644 --- a/src/cowboy_tcp_transport.erl +++ b/src/cowboy_tcp_transport.erl @@ -12,18 +12,39 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc TCP transport API. +%% +%% Wrapper around gen_tcp implementing the Cowboy transport API. +%% +%% @see gen_tcp -module(cowboy_tcp_transport). --export([name/0, messages/0, listen/1, accept/2, recv/3, send/2, setopts/2, - controlling_process/2, peername/1, close/1]). %% API. -%% API. +-export([name/0, messages/0, listen/1, accept/2, recv/3, send/2, setopts/2, + controlling_process/2, peername/1, close/1]). +%% @doc Name of this transport API, tcp. -spec name() -> tcp. name() -> tcp. +%% @doc Atoms used in the process messages sent by this API. +%% +%% They identify incoming data, closed connection and errors when receiving +%% data in active mode. -spec messages() -> {tcp, tcp_closed, tcp_error}. messages() -> {tcp, tcp_closed, tcp_error}. +%% @doc Setup a socket to listen on the given port on the local host. +%% +%% The available options are: +%%
+%%
port
Mandatory. TCP port number to open.
+%%
backlog
Maximum length of the pending connections queue. +%% Defaults to 1024.
+%%
ip
Interface to listen on. Listen on all interfaces +%% by default.
+%%
+%% +%% @see gen_tcp:listen/2 -spec listen([{port, inet:ip_port()} | {ip, inet:ip_address()}]) -> {ok, inet:socket()} | {error, atom()}. listen(Opts) -> @@ -38,34 +59,48 @@ listen(Opts) -> end, gen_tcp:listen(Port, ListenOpts). +%% @doc Accept an incoming connection on a listen socket. +%% @see gen_tcp:accept/2 -spec accept(inet:socket(), timeout()) -> {ok, inet:socket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> gen_tcp:accept(LSocket, Timeout). +%% @doc Receive a packet from a socket in passive mode. +%% @see gen_tcp:recv/3 -spec recv(inet:socket(), non_neg_integer(), timeout()) -> {ok, any()} | {error, closed | atom()}. recv(Socket, Length, Timeout) -> gen_tcp:recv(Socket, Length, Timeout). +%% @doc Send a packet on a socket. +%% @see gen_tcp:send/2 -spec send(inet:socket(), iolist()) -> ok | {error, atom()}. send(Socket, Packet) -> gen_tcp:send(Socket, Packet). +%% @doc Set one or more options for a socket. +%% @see inet:setopts/2 -spec setopts(inet:socket(), list()) -> ok | {error, atom()}. setopts(Socket, Opts) -> inet:setopts(Socket, Opts). +%% @doc Assign a new controlling process Pid to Socket. +%% @see gen_tcp:controlling_process/2 -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid). +%% @doc Return the address and port for the other end of a connection. +%% @see inet:peername/1 -spec peername(inet:socket()) -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. peername(Socket) -> inet:peername(Socket). +%% @doc Close a TCP socket. +%% @see gen_tcp:close/1 -spec close(inet:socket()) -> ok. close(Socket) -> gen_tcp:close(Socket). -- cgit v1.2.3