diff options
-rw-r--r-- | README.md | 219 | ||||
-rw-r--r-- | test/http_SUITE.erl | 12 |
2 files changed, 191 insertions, 40 deletions
@@ -8,9 +8,10 @@ Goals Cowboy aims to provide the following advantages: -* **Small** codebase. +* **Small** code base. * Damn **fast**. -* **Modular**: transport, protocol and handlers are replaceable. (see below) +* **Modular**: transport and protocol handlers are replaceable. +* **Binary HTTP** for greater speed and lower memory usage. * Easy to **embed** inside another application. * Selectively **dispatch** requests to handlers, allowing you to send some requests to your embedded code and others to a FastCGI application in @@ -19,69 +20,211 @@ Cowboy aims to provide the following advantages: The server is currently in early development stage. Comments, suggestions are more than welcome. To contribute, either open bug reports, or fork the project -and send us pull requests with new or improved functionality. Of course you -might want to discuss your plans with us before you do any serious work so -we can share ideas and save everyone time. +and send us pull requests with new or improved functionality. You should +discuss your plans with us before doing any serious work, though, to avoid +duplicating efforts. -Embedding Cowboy ----------------- +Quick start +----------- * Add Cowboy as a rebar or agner dependency to your application. * Start Cowboy and add one or more listeners. -* Write handlers. +* Write handlers for your application. Getting Started --------------- -Cowboy can be started and stopped like any other application. However, the -Cowboy application does not start any listener, those must be started manually. +At heart, Cowboy is nothing more than an TCP acceptor pool. All it does is +accept connections received on a given port and using a given transport, +like TCP or SSL, and forward them to a request handler for the given +protocol. Acceptors and request handlers are of course supervised +automatically. -A listener is a special kind of supervisor that handles a pool of acceptor -processes. It also manages all its associated request processes. This allows -you to shutdown all processes related to a listener by stopping the supervisor. +It just so happens that Cowboy also includes an HTTP protocol handler. +But Cowboy does nothing by default. You need to explicitly ask Cowboy +to listen on a port with your chosen transport and protocol handlers. +To do so, you must start a listener. -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. +A listener is a special kind of supervisor that manages both the +acceptor pool and the request processes. It is named and can thus be +started and stopped at will. -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. +An acceptor pool is a pool of processes whose only role is to accept +new connections. It's good practice to have many of these processes +as they are very cheap and allow much quicker response when you get +many connections. Of course, as with everything else, you should +**benchmark** before you decide what's best for you. -You can start and stop listeners by calling cowboy:start_listener and -cowboy:stop_listener respectively. It is your responsability to give each -listener a unique name. +Cowboy includes a TCP transport handler for HTTP and an SSL transport +handler for HTTPS. The transport handlers can of course be reused for +other protocols like FTP or IRC. + +The HTTP protocol requires one last thing to continue: dispatching rules. +Don't worry about it right now though and continue reading, it'll all +be explained. -Code speaks more than words: +You can start and stop listeners by calling cowboy:start_listener and +cowboy:stop_listener respectively, as demonstrated in the following +example. ``` erlang -application:start(cowboy), -Dispatch = [ - %% {Host, list({Path, Handler, Opts})} - {'_', [{'_', my_handler, []}]} -], -%% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts -cowboy:start_listener(http, 100, - cowboy_tcp_transport, [{port, 8080}], - cowboy_http_protocol, [{dispatch, Dispatch}] -). +-module(my_app). +-behaviour(application). +-export([start/2, stop/1]). + +start(_Type, _Args) -> + application:start(cowboy), + Dispatch = [ + %% {Host, list({Path, Handler, Opts})} + {'_', [{'_', my_handler, []}]} + ], + %% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts + cowboy:start_listener(http, 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 -handler could be written like this: +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 +want to write your own handlers for your application. + +Following is an example of an "Hello World!" HTTP handler. ``` erlang -module(my_handler). +-behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/2]). init({tcp, http}, Req, Opts) -> - {ok, Req, undefined}. + {ok, Req, undefined_state}. handle(Req, State) -> - {ok, Req2} = cowboy_http_req:reply(200, [], "Hello World!", Req), + {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello World!">>, Req), {ok, Req2, State}. terminate(Req, State) -> ok. ``` + +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 "dev-extend.eu" domain name, you must specify +[<<"dev-extend">>, <<"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 +"dev-extend.eu" and "dev-extend.fr" domains, you can use the match spec +[<<"dev-extend">>, '_'] to match any top level extension. + +Any other atom used as a token will bind the value to this atom when +matching. To follow on our hostnames example, [<<"dev-extend">>, ext] +would bind the values <<"eu">> and <<"fr">> to the ext atom, that you +can later retrieve in your handler by calling `cowboy_http_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_http_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_http_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). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). +-export([init/3, handle/2, terminate/2]). +-export([websocket_init/3, websocket_handle/3, websocket_terminate/3]). + +init({tcp, http}, Req, Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +handle(Req, State) -> + error(foo). %% Will never be called. + +terminate(Req, State) -> + error(foo). %% Same for that one. + +websocket_init(TransportName, Req, _Opts) -> + erlang:start_timer(1000, self(), <<"Hello!">>), + {ok, Req, undefined_state}. + +websocket_handle({timeout, _Ref, Msg}, Req, State) -> + erlang:start_timer(1000, self(), <<"How' you doin'?">>), + {reply, Msg, Req, State}; +websocket_handle({websocket, Msg}, Req, State) -> + {reply, <<"That's what she said! ", Msg/binary >>, 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. + +Using Cowboy with other protocols +--------------------------------- + +One of the strength of Cowboy is of course that you can use it with any +protocol you want. The only downside is that if it's not HTTP, you'll +probably have to write the protocol handler yourself. + +The only exported function a protocol handler needs is the start_link/3 +function, with arguments Socket, Transport and Opts. Socket is of course +the client socket; Transport is the module name of the chosen transport +handler and Opts is protocol options defined when starting the listener. +Anything you do past this point is up to you! + +You should definitely look at the cowboy_http_protocol module for a great +example of fast requests handling if you need to. Otherwise it's probably +safe to use {active, once} mode and handle everything as it comes. + +Note that while you technically can run a protocol handler directly as a +gen_server or a gen_fsm, it's probably not a good idea, as the only call +you'll ever receive from Cowboy is the start_link/3 call. On the other +hand, feel free to write a very basic protocol handler which then forwards +requests to a gen_server or gen_fsm. By doing so however you must take +care to supervise their processes as Cowboy only know about the protocol +handler itself. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index c04c3d8..6b1a70f 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -18,7 +18,8 @@ -export([all/0, groups/0, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2]). %% ct. --export([headers_dupe/1, nc_rand/1, pipeline/1, raw/1]). %% http. +-export([headers_dupe/1, headers_huge/1, + nc_rand/1, pipeline/1, raw/1]). %% http. -export([http_200/1, http_404/1, websocket/1]). %% http and https. %% ct. @@ -28,7 +29,8 @@ all() -> groups() -> BaseTests = [http_200, http_404], - [{http, [], [headers_dupe, nc_rand, pipeline, raw, websocket] ++ BaseTests}, + [{http, [], [headers_dupe, headers_huge, + nc_rand, pipeline, raw, websocket] ++ BaseTests}, {https, [], BaseTests}]. init_per_suite(Config) -> @@ -100,6 +102,12 @@ headers_dupe(Config) -> nomatch = binary:match(Data, <<"Connection: keep-alive">>), ok = gen_tcp:close(Socket). +headers_huge(Config) -> + Cookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77=" + "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _N <- lists:seq(1, 1000)]), + {_Packet, 200} = raw_req(["GET / HTTP/1.0\r\nHost: localhost\r\n" + "Set-Cookie: ", Cookie, "\r\n\r\n"], Config). + nc_rand(Config) -> Cat = os:find_executable("cat"), Nc = os:find_executable("nc"), |