diff options
-rw-r--r-- | README.md | 47 | ||||
-rw-r--r-- | guide/introduction.md | 114 | ||||
-rw-r--r-- | guide/toc.md | 47 | ||||
-rw-r--r-- | src/cowboy.app.src | 3 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 44 | ||||
-rw-r--r-- | src/cowboy_static.erl | 18 |
6 files changed, 221 insertions, 52 deletions
@@ -6,35 +6,40 @@ Cowboy is a small, fast and modular HTTP server written in Erlang. Goals ----- -Cowboy aims to provide the following advantages: - -* **Small** code base. -* Damn **fast**. -* **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 - PHP or Ruby. -* No parameterized module. No process dictionary. **Clean** Erlang code. - -The server is currently in early development. Comments and 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. You should -discuss your plans with us before doing any serious work, though, to avoid -duplicating efforts. +Cowboy aims to provide a **complete** HTTP stack in a **small** code base. +It is optimized for **low latency** and **low memory usage**, in parts +because it uses **binary strings**. + +Cowboy provides **routing** capabilities, selectively dispatching requests +to handlers written in Erlang. + +Because it uses Ranch for managing connections, Cowboy can easily be +**embedded** in any other application. + +No parameterized module. No process dictionary. **Clean** Erlang code. Quick start ----------- -* Add Cowboy as a rebar or agner dependency to your application. -* Start Cowboy and add one or more listeners. -* Write handlers for your application. -* Check out the `examples/` directory! + * Add Cowboy as a rebar dependency to your application. + * Start Cowboy and add one or more listeners. + * Write handlers for your application. Getting Started --------------- + * [Read the guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide/introduction) + * Look at the examples in the `examples/` directory + * Build API documentation with `make docs`; open `doc/index.html` + + + +Old README +---------- + +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 diff --git a/guide/introduction.md b/guide/introduction.md new file mode 100644 index 0000000..871e243 --- /dev/null +++ b/guide/introduction.md @@ -0,0 +1,114 @@ +Introduction +============ + +Purpose +------- + +Cowboy is a small, fast and modular HTTP server written in Erlang. + +Cowboy aims to provide a complete HTTP stack, including its derivatives +SPDY, Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1, +Websocket (all implemented drafts + standard) and Webmachine-based REST. + +Cowboy is a high quality project. It has a small code base, is very +efficient (both in latency and memory use) and can easily be embedded +in another application. + +Cowboy is clean Erlang code. It bans the use of parameterized modules +and the process dictionary. It includes documentation and typespecs +for all public interfaces. + +Prerequisites +------------- + +It is assumed the developer already knows Erlang and has basic knowledge +about the HTTP protocol. + +In order to run the examples available in this user guide, you will need +Erlang and rebar installed and in your $PATH. + +Please see the [rebar repository](https://github.com/basho/rebar) for +downloading and building instructions. Please look up the environment +variables documentation of your system for details on how to update the +$PATH information. + +Conventions +----------- + +In the HTTP protocol, the method name is case sensitive. All standard +method names are uppercase. + +Header names are case insensitive. Cowboy converts all the request +header names to lowercase, and expects your application to provide +lowercase header names in the response. + +Getting started +--------------- + +Cowboy does nothing by default. + +Cowboy requires the `crypto` and `ranch` applications to be started. + +``` erlang +ok = application:start(crypto). +ok = application:start(ranch). +ok = application:start(cowboy). +``` + +Cowboy uses Ranch for handling the connections and provides convenience +functions to start Ranch listeners. + +The `cowboy:start_http/4` function starts a listener for HTTP connections +using the TCP transport. The `cowboy:start_https/4` function starts a +listener for HTTPS connections using the SSL transport. + +Listeners are named. They spawn a given number of acceptors, listen for +connections using the given transport options and pass along the protocol +options to the connection processes. The protocol options must include +the dispatch list for routing requests to handlers. + +The dispatch list is explained in greater details in the Routing section +of the guide. + +``` erlang +Dispatch = [ + %% {URIHost, list({URIPath, Handler, Opts})} + {'_', [{'_', my_handler, []}]} +], +%% Name, NbAcceptors, TransOpts, ProtoOpts +cowboy:start_http(my_http_listener, 100, + [{port, 8080}], + [{dispatch, Dispatch}] +). +``` + +Cowboy features many kinds of handlers. It has plain HTTP handlers, loop +handlers, Websocket handlers, REST handlers and static handlers. Their +usage is documented in the respective sections of the guide. + +Most applications use the plain HTTP handler, which has three callback +functions: init/3, handle/2 and terminate/2. Following is an example of +a simple handler module. + +``` erlang +-module(my_handler). +-behaviour(cowboy_http_handler). + +-export([init/3]). +-export([handle/2]). +-export([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. +``` + +The `Req` variable above is the Req object, which allows the developer +to obtain informations about the request and to perform a reply. Its usage +is explained in its respective section of the guide. diff --git a/guide/toc.md b/guide/toc.md new file mode 100644 index 0000000..ea543d1 --- /dev/null +++ b/guide/toc.md @@ -0,0 +1,47 @@ +Cowboy User Guide +================= + + * [Introduction](introduction.md) + * Purpose + * Prerequisites + * Conventions + * Getting started + * Routing + * Purpose + * Dispatch rule + * Match rules + * Bindings + * Handlers + * Purpose + * Protocol upgrades + * HTTP handlers + * Purpose + * Callbacks + * Usage + * Loop handlers + * Purpose + * Callbacks + * Usage + * Websocket handlers + * Purpose + * Callbacks + * Usage + * REST handlers + * Purpose + * Flow diagram + * Callbacks + * Usage + * Static handlers + * Purpose + * Usage + * Request object + * Purpose + * Request + * Request body + * Reply + * Hooks + * On request + * On response + * Internals + * Architecture + * Efficiency considerations diff --git a/src/cowboy.app.src b/src/cowboy.app.src index b68ef14..d32262e 100644 --- a/src/cowboy.app.src +++ b/src/cowboy.app.src @@ -13,7 +13,10 @@ %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. {application, cowboy, [ + {id, "Cowboy"}, {description, "Small, fast, modular HTTP server."}, + {sub_description, "Cowboy is also a socket acceptor pool, " + "able to accept connections for any kind of TCP protocol."}, {vsn, "0.7.0"}, {modules, []}, {registered, [cowboy_clock, cowboy_sup]}, diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index c0decbd..1c0554a 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -73,7 +73,7 @@ upgrade(_ListenerPid, Handler, Opts, Req) -> catch Class:Reason -> PLReq = cowboy_req:to_list(Req), error_logger:error_msg( - "** Handler ~p terminating in rest_init/3~n" + "** Handler ~p terminating in rest_init/2~n" " for the reason ~p:~p~n** Options were ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]), @@ -84,7 +84,7 @@ upgrade(_ListenerPid, Handler, Opts, Req) -> service_available(Req, State) -> expect(Req, State, service_available, true, fun known_methods/2, 503). -%% known_methods/2 should return a list of atoms or binary methods. +%% known_methods/2 should return a list of binary methods. known_methods(Req, State=#state{method=Method}) -> case call(Req, State, known_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>; @@ -107,7 +107,7 @@ known_methods(Req, State=#state{method=Method}) -> uri_too_long(Req, State) -> expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). -%% allowed_methods/2 should return a list of atoms or binary methods. +%% allowed_methods/2 should return a list of binary methods. allowed_methods(Req, State=#state{method=Method}) -> case call(Req, State, allowed_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> @@ -126,7 +126,7 @@ allowed_methods(Req, State=#state{method=Method}) -> method_not_allowed(Req, State, Methods) -> Req2 = cowboy_req:set_resp_header( - <<"Allow">>, method_not_allowed_build(Methods, []), Req), + <<"allow">>, method_not_allowed_build(Methods, []), Req), respond(Req2, State, 405). method_not_allowed_build([], []) -> @@ -153,7 +153,7 @@ is_authorized(Req, State) -> forbidden(Req2, State#state{handler_state=HandlerState}); {{false, AuthHead}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( - <<"Www-Authenticate">>, AuthHead, Req2), + <<"www-authenticate">>, AuthHead, Req2), respond(Req3, State#state{handler_state=HandlerState}, 401) end. @@ -166,7 +166,7 @@ valid_content_headers(Req, State) -> known_content_type(Req, State) -> expect(Req, State, known_content_type, true, - fun valid_entity_length/2, 413). + fun valid_entity_length/2, 415). valid_entity_length(Req, State) -> expect(Req, State, valid_entity_length, true, fun options/2, 413). @@ -351,7 +351,7 @@ match_language(Req, State, Accept, [Provided|Tail], end. set_language(Req, State=#state{language_a=Language}) -> - Req2 = cowboy_req:set_resp_header(<<"Content-Language">>, Language, Req), + Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req), charsets_provided(cowboy_req:set_meta(language, Language, Req2), State). %% charsets_provided should return a list of binary values indicating @@ -415,7 +415,7 @@ set_content_type(Req, State=#state{ undefined -> ContentType; Charset -> [ContentType, <<"; charset=">>, Charset] end, - Req2 = cowboy_req:set_resp_header(<<"Content-Type">>, ContentType2, Req), + Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req), encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State). set_content_type_build_params([], []) -> @@ -446,17 +446,17 @@ variances(Req, State=#state{content_types_p=CTP, Variances = case CTP of [] -> []; [_] -> []; - [_|_] -> [<<"Accept">>] + [_|_] -> [<<"accept">>] end, Variances2 = case LP of [] -> Variances; [_] -> Variances; - [_|_] -> [<<"Accept-Language">>|Variances] + [_|_] -> [<<"accept-language">>|Variances] end, Variances3 = case CP of [] -> Variances2; [_] -> Variances2; - [_|_] -> [<<"Accept-Charset">>|Variances2] + [_|_] -> [<<"accept-charset">>|Variances2] end, {Variances4, Req3, State2} = case call(Req, State, variances) of no_call -> @@ -470,13 +470,13 @@ variances(Req, State=#state{content_types_p=CTP, resource_exists(Req3, State2); [[<<", ">>, H]|Variances5] -> Req4 = cowboy_req:set_resp_header( - <<"Vary">>, [H|Variances5], Req3), + <<"vary">>, [H|Variances5], Req3), resource_exists(Req4, State2) end. resource_exists(Req, State) -> expect(Req, State, resource_exists, true, - fun if_match_exists/2, fun if_match_musnt_exist/2). + fun if_match_exists/2, fun if_match_must_not_exist/2). if_match_exists(Req, State) -> case cowboy_req:parse_header(<<"if-match">>, Req) of @@ -496,7 +496,7 @@ if_match(Req, State, EtagsList) -> false -> precondition_failed(Req2, State2) end. -if_match_musnt_exist(Req, State) -> +if_match_must_not_exist(Req, State) -> case cowboy_req:header(<<"if-match">>, Req) of {undefined, Req2} -> is_put_to_missing_resource(Req2, State); {_Any, Req2} -> precondition_failed(Req2, State) @@ -577,7 +577,7 @@ if_modified_since(Req, State, IfModifiedSince) -> end. not_modified(Req, State) -> - Req2 = cowboy_req:delete_resp_header(<<"Content-Type">>, Req), + Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req), {Req3, State2} = set_resp_etag(Req2, State), {Req4, State3} = set_resp_expires(Req3, State2), respond(Req4, State3, 304). @@ -596,7 +596,7 @@ moved_permanently(Req, State, OnFalse) -> case call(Req, State, moved_permanently) of {{true, Location}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( - <<"Location">>, Location, Req2), + <<"location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 301); {false, Req2, HandlerState} -> OnFalse(Req2, State#state{handler_state=HandlerState}); @@ -617,7 +617,7 @@ moved_temporarily(Req, State) -> case call(Req, State, moved_temporarily) of {{true, Location}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( - <<"Location">>, Location, Req2), + <<"location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 307); {false, Req2, HandlerState} -> is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410); @@ -670,7 +670,7 @@ create_path(Req, State) -> {HostURL, Req3} = cowboy_req:host_url(Req2), State2 = State#state{handler_state=HandlerState}, Req4 = cowboy_req:set_resp_header( - <<"Location">>, << HostURL/binary, Path/binary >>, Req3), + <<"location">>, << HostURL/binary, Path/binary >>, Req3), put_resource(cowboy_req:set_meta(put_path, Path, Req4), State2, 303) end. @@ -744,7 +744,7 @@ choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> %% This is easily testable because we would have set the Location %% header by this point if we did so. is_new_resource(Req, State) -> - case cowboy_req:has_resp_header(<<"Location">>, Req) of + case cowboy_req:has_resp_header(<<"location">>, Req) of true -> respond(Req, State, 201); false -> has_resp_body(Req, State) end. @@ -767,7 +767,7 @@ set_resp_body(Req, State=#state{content_type_a={_Type, Fun}}) -> LastModified -> LastModifiedStr = httpd_util:rfc1123_date(LastModified), Req4 = cowboy_req:set_resp_header( - <<"Last-Modified">>, LastModifiedStr, Req3) + <<"last-modified">>, LastModifiedStr, Req3) end, {Req5, State4} = set_resp_expires(Req4, State3), case call(Req5, State4, Fun) of @@ -796,7 +796,7 @@ set_resp_etag(Req, State) -> {Req2, State2}; Etag -> Req3 = cowboy_req:set_resp_header( - <<"ETag">>, encode_etag(Etag), Req2), + <<"etag">>, encode_etag(Etag), Req2), {Req3, State2} end. @@ -812,7 +812,7 @@ set_resp_expires(Req, State) -> Expires -> ExpiresStr = httpd_util:rfc1123_date(Expires), Req3 = cowboy_req:set_resp_header( - <<"Expires">>, ExpiresStr, Req2), + <<"expires">>, ExpiresStr, Req2), {Req3, State2} end. diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index 5450115..724bf33 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -81,9 +81,9 @@ %% {<<".js">>, [<<"application/javascript">>]}]}]} %% %% %% Use the default database in the mimetypes application. -%% {[<<"static">>, '...', cowboy_static, +%% {[<<"static">>, '...'], cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, -%% {mimetypes, {fun mimetypes:path_to_mimes/2, default}}]]} +%% {mimetypes, {fun mimetypes:path_to_mimes/2, default}}]} %% ''' %% %% == ETag Header Function == @@ -110,19 +110,19 @@ %% ==== Examples ==== %% ``` %% %% A value of default is equal to not specifying the option. -%% {[<<"static">>, '...', cowboy_static, +%% {[<<"static">>, '...'], cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, -%% {etag, default}]]} +%% {etag, default}]} %% %% %% Use all avaliable ETag function arguments to generate a header value. -%% {[<<"static">>, '...', cowboy_static, +%% {[<<"static">>, '...'], cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, -%% {etag, {attributes, [filepath, filesize, inode, mtime]}}]]} +%% {etag, {attributes, [filepath, filesize, inode, mtime]}}]} %% %% %% Use a user defined function to generate a strong ETag header value. -%% {[<<"static">>, '...', cowboy_static, +%% {[<<"static">>, '...'], cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, -%% {etag, {fun generate_strong_etag/2, strong_etag_extra}}]]} +%% {etag, {fun generate_strong_etag/2, strong_etag_extra}}]} %% %% generate_strong_etag(Arguments, strong_etag_extra) -> %% {_, Filepath} = lists:keyfind(filepath, 1, Arguments), @@ -451,7 +451,7 @@ path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) -> -spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()]. path_to_mimetypes_(Ext, Extensions) -> - case lists:keyfind(Ext, 1, Extensions) of + case lists:keyfind(cowboy_bstr:to_lower(Ext), 1, Extensions) of {_, MTs} -> MTs; _Unknown -> default_mimetype() end. |