diff options
-rw-r--r-- | README.md | 204 | ||||
-rw-r--r-- | guide/loop_handlers.md | 31 | ||||
-rw-r--r-- | guide/ws_handlers.md | 43 |
3 files changed, 73 insertions, 205 deletions
@@ -31,207 +31,3 @@ Support * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) - - - -Old README (deprecated) ------------------------ - -This and all following sections will be removed as soon as their -equivalent appear in the Cowboy guide. - -Cowboy does nothing by default. - -Cowboy uses Ranch for handling connections, and provides convenience -functions to start and stop Ranch listeners. The Ranch application -must always be started before Cowboy. The crypto application must -also be started. - -The `cowboy:start_http/4` function will handle HTTP connections -using the TCP transport. Similarly, `cowboy:start_https/4` will -handle HTTP connections using the SSL transport. - -You can start as many listeners as you need to. To allow this, you -are required to give a name to your listeners. It is the first -argument to the start functions. The name can be of any type. - -You can stop listeners using `cowboy:stop_listener/1`, giving it -the name of the listener to be stopped. - -The following example demonstrates the startup of a very simple -HTTP listener. It redirects all requests to the `my_handler` -module. - -``` erlang -application:start(crypto), -application:start(ranch), -application:start(cowboy), -Dispatch = [ - %% {URIHost, list({URIPath, Handler, Opts})} - {'_', [{'_', my_handler, []}]} -], -%% Name, NbAcceptors, TransOpts, ProtoOpts -cowboy:start_http(my_http_listener, 100, [{port, 8080}], - [{dispatch, Dispatch}] -). -``` - -This is not enough though, you must also write the `my_handler` -module to process the incoming HTTP requests. Of course Cowboy -comes with predefined handlers for specific tasks but most of -the time you'll need to write the handlers appropriate for your -application. - -Following is an example of a "Hello World!" HTTP handler. - -``` erlang --module(my_handler). --export([init/3, handle/2, terminate/2]). - -init({tcp, http}, Req, Opts) -> - {ok, Req, undefined_state}. - -handle(Req, State) -> - {ok, Req2} = cowboy_req:reply(200, [], <<"Hello World!">>, Req), - {ok, Req2, State}. - -terminate(Req, State) -> - ok. -``` - -You can also write handlers that do not reply directly. Instead, such handlers -will wait for an Erlang message from another process and only reply when -receiving such message, or timeout if it didn't arrive in time. - -This is especially useful for long-polling functionality, as Cowboy will handle -process hibernation and timeouts properly, preventing mistakes if you were to -write the code yourself. A handler of that kind can be defined like this: - -``` erlang --module(my_loop_handler). --export([init/3, info/3, terminate/2]). - --define(TIMEOUT, 60000). - -init({tcp, http}, Req, Opts) -> - {loop, Req, undefined_state, ?TIMEOUT, hibernate}. - -info({reply, Body}, Req, State) -> - {ok, Req2} = cowboy_req:reply(200, [], Body, Req), - {ok, Req2, State}; -info(Message, Req, State) -> - {loop, Req, State, hibernate}. - -terminate(Req, State) -> - ok. -``` - -It is of course possible to combine both type of handlers together as long as -you return the proper tuple from init/3. - -Continue reading to learn how to dispatch rules and handle requests. - -Dispatch rules --------------- - -Cowboy allows you to dispatch HTTP requests directly to a specific handler -based on the hostname and path information from the request. It also lets -you define static options for the handler directly in the rules. - -To match the hostname and path, Cowboy requires a list of tokens. For -example, to match the "ninenines.eu" domain name, you must specify -`[<<"ninenines">>, <<"eu">>]`. Or, to match the "/path/to/my/resource" -you must use `[<<"path">>, <<"to">>, <<"my">>, <<"resource">>]`. All the -tokens must be given as binary. - -You can use the special token `'_'` (the atom underscore) to indicate that -you accept anything in that position. For example if you have both -"ninenines.eu" and "ninenines.fr" domains, you can use the match spec -`[<<"ninenines">>, '_']` to match any top level extension. - -Finally, you can also match multiple leading segments of the domain name and -multiple trailing segments of the request path using the atom `'...'` (the atom -ellipsis) respectively as the first host token or the last path token. For -example, host rule `['...', <<"ninenines">>, <<"eu">>]` can match both -"cowboy.bugs.ninenines.eu" and "ninenines.eu" and path rule -`[<<"projects">>, '...']` can match both "/projects" and -"/projects/cowboy/issues/42". The host leading segments and the path trailing -segments can later be retrieved through `cowboy_req:host_info/1` and -`cowboy_req:path_info/1`. - -Any other atom used as a token will bind the value to this atom when -matching. To follow on our hostnames example, `[<<"ninenines">>, ext]` -would bind the values `<<"eu">>` and `<<"fr">>` to the ext atom, that you -can later retrieve in your handler by calling `cowboy_req:binding/{2,3}`. - -You can also accept any match spec by using the atom `'_'` directly instead of -a list of tokens. Our hello world example above uses this to forward all -requests to a single handler. - -There is currently no way to match multiple tokens at once. - -Requests handling ------------------ - -Requests are passed around in the Request variable. Although they are -defined as a record, it is recommended to access them only through the -cowboy_req module API. - -You can retrieve the HTTP method, HTTP version, peer address and port, -host tokens, raw host, used port, path tokens, raw path, query string -values, bound values from the dispatch step, header values from the -request. You can also read the request body, if any, optionally parsing -it as a query string. Finally, the request allows you to send a response -to the client. - -See the cowboy_req module for more information. - -Websockets ----------- - -The Websocket protocol is built upon the HTTP protocol. It first sends -an HTTP request for an handshake, performs it and then switches -to Websocket. Therefore you need to write a standard HTTP handler to -confirm the handshake should be completed and then the Websocket-specific -callbacks. - -A simple handler doing nothing but sending a repetitive message using -Websocket would look like this: - -``` erlang --module(my_ws_handler). --export([init/3]). --export([websocket_init/3, websocket_handle/3, - websocket_info/3, websocket_terminate/3]). - -init({tcp, http}, Req, Opts) -> - {upgrade, protocol, cowboy_websocket}. - -websocket_init(TransportName, Req, _Opts) -> - erlang:start_timer(1000, self(), <<"Hello!">>), - {ok, Req, undefined_state}. - -websocket_handle({text, Msg}, Req, State) -> - {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; -websocket_handle(_Data, Req, State) -> - {ok, Req, State}. - -websocket_info({timeout, _Ref, Msg}, Req, State) -> - erlang:start_timer(1000, self(), <<"How' you doin'?">>), - {reply, {text, Msg}, Req, State}; -websocket_info(_Info, Req, State) -> - {ok, Req, State}. - -websocket_terminate(_Reason, _Req, _State) -> - ok. -``` - -Of course you can have an HTTP handler doing both HTTP and Websocket -handling, but for the sake of this example we're ignoring the HTTP -part entirely. - -As the Websocket protocol is still a draft the API is subject to change -regularly when support to the most recent drafts gets added. Features may -be added, changed or removed before the protocol gets finalized. Cowboy -tries to implement all drafts transparently and give a single interface to -handle them all, however. diff --git a/guide/loop_handlers.md b/guide/loop_handlers.md index 67f8ec9..6d67c62 100644 --- a/guide/loop_handlers.md +++ b/guide/loop_handlers.md @@ -12,6 +12,10 @@ a response. They are most useful when performing long-polling operations or when using server-sent events. +While the same can be accomplished using plain HTTP handlers, +it is recommended to use loop handlers because they are well-tested +and allow using built-in features like hibernation and timeouts. + Callbacks --------- @@ -21,3 +25,30 @@ Usage ----- @todo Explain how to use them. + +The following handler waits for a message `{reply, Body}` before +sending a response. If this message doesn't arrive within 60 +seconds, it gives up and a `204 No Content` will be replied. +It also hibernates the process to save memory while waiting for +this message. + +``` erlang +-module(my_loop_handler). +-behaviour(cowboy_loop_handler). + +-export([init/3]). +-export([info/3]). +-export([terminate/2]). + +init({tcp, http}, Req, Opts) -> + {loop, Req, undefined_state, 60000, hibernate}. + +info({reply, Body}, Req, State) -> + {ok, Req2} = cowboy_req:reply(200, [], Body, Req), + {ok, Req2, State}; +info(Message, Req, State) -> + {loop, Req, State, hibernate}. + +terminate(Req, State) -> + ok. +``` diff --git a/guide/ws_handlers.md b/guide/ws_handlers.md index fc5d953..226ada0 100644 --- a/guide/ws_handlers.md +++ b/guide/ws_handlers.md @@ -4,12 +4,18 @@ Websocket handlers Purpose ------- -Websockets are an extension to HTTP to emulate plain TCP connections +Websocket is an extension to HTTP to emulate plain TCP connections between the user's browser and the server. Requests that are upgraded are then handled by websocket handlers. Both sides of the socket can send data at any time asynchronously. +Websocket is an IETF standard. Cowboy supports the standard and all +the drafts that were previously implemented by browsers. Websocket +is implemented by most browsers today, although for backward +compatibility reasons a solution like [Bullet](https://github.com/extend/bullet) +might be preferred. + Callbacks --------- @@ -19,3 +25,38 @@ Usage ----- @todo Explain how to use them. + +The following handler sends a message every second. It also echoes +back what it receives. + +``` erlang +-module(my_ws_handler). +-behaviour(cowboy_websocket_handler). + +-export([init/3]). +-export([websocket_init/3]). +-export([websocket_handle/3]). +-export([websocket_info/3]). +-export([websocket_terminate/3]). + +init({tcp, http}, Req, Opts) -> + {upgrade, protocol, cowboy_websocket}. + +websocket_init(TransportName, Req, _Opts) -> + erlang:start_timer(1000, self(), <<"Hello!">>), + {ok, Req, undefined_state}. + +websocket_handle({text, Msg}, Req, State) -> + {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; +websocket_handle(_Data, Req, State) -> + {ok, Req, State}. + +websocket_info({timeout, _Ref, Msg}, Req, State) -> + erlang:start_timer(1000, self(), <<"How' you doin'?">>), + {reply, {text, Msg}, Req, State}; +websocket_info(_Info, Req, State) -> + {ok, Req, State}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. +``` |