aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile28
-rw-r--r--examples/echo_post/src/toppage_handler.erl6
-rw-r--r--guide/handlers.md22
-rw-r--r--guide/hooks.md69
-rw-r--r--guide/http_handlers.md22
-rw-r--r--guide/internals.md76
-rw-r--r--guide/introduction.md2
-rw-r--r--guide/loop_handlers.md30
-rw-r--r--guide/req.md180
-rw-r--r--guide/routing.md89
-rw-r--r--guide/static_handlers.md18
-rw-r--r--guide/toc.md14
-rw-r--r--guide/ws_handlers.md25
-rw-r--r--src/cowboy_bstr.erl49
-rw-r--r--src/cowboy_protocol.erl4
-rw-r--r--src/cowboy_req.erl22
-rw-r--r--src/cowboy_rest.erl6
-rw-r--r--src/cowboy_websocket.erl8
-rw-r--r--test/http_SUITE.erl40
-rw-r--r--test/http_handler_echo_body.erl4
20 files changed, 621 insertions, 93 deletions
diff --git a/Makefile b/Makefile
index e98b409..fb00e22 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,9 @@ PROJECT = cowboy
RANCH_VSN = 0.6.0
ERLC_OPTS = -Werror +debug_info +warn_export_all # +bin_opt_info +warn_missing_spec
+DEPS_DIR ?= $(CURDIR)/deps
+export DEPS_DIR
+
.PHONY: all clean-all app clean docs clean-docs tests autobahn build-plt dialyze
# Application.
@@ -12,25 +15,26 @@ all: app
clean-all: clean clean-docs
rm -f .$(PROJECT).plt
- rm -rf deps logs
+ rm -rf $(DEPS_DIR) logs
deps/ranch:
- @mkdir -p deps/
- git clone -n -- https://github.com/extend/ranch.git deps/ranch
- cd deps/ranch ; git checkout -q $(RANCH_VSN)
+ @mkdir -p $(DEPS_DIR)
+ git clone -n -- https://github.com/extend/ranch.git $(DEPS_DIR)/ranch
+ cd $(DEPS_DIR)/ranch ; git checkout -q $(RANCH_VSN)
MODULES = $(shell ls src/*.erl | sed 's/src\///;s/\.erl/,/' | sed '$$s/.$$//')
app: deps/ranch
- @cd deps/ranch ; make
+ @$(MAKE) -C $(DEPS_DIR)/ranch
@mkdir -p ebin/
- @cat src/cowboy.app.src \
+ @cat src/$(PROJECT).app.src \
| sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \
- > ebin/cowboy.app
- erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ src/cowboy_middleware.erl src/*.erl
+ > ebin/$(PROJECT).app
+ erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \
+ src/$(PROJECT)_middleware.erl src/*.erl
clean:
- -@cd deps/ranch && make clean
+ -@$(MAKE) -C $(DEPS_DIR)/ranch clean
rm -rf ebin/
rm -f test/*.beam
rm -f erl_crash.dump
@@ -38,7 +42,7 @@ clean:
# Documentation.
docs: clean-docs
- erl -noshell -eval 'edoc:application(cowboy, ".", []), init:stop().'
+ erl -noshell -eval 'edoc:application($(PROJECT), ".", []), init:stop().'
clean-docs:
rm -f doc/*.css
@@ -49,7 +53,7 @@ clean-docs:
# Tests.
CT_RUN = ct_run \
- -pa ebin deps/*/ebin \
+ -pa ebin $(DEPS_DIR)/*/ebin \
-dir test \
-logdir logs \
-cover test/cover.spec
@@ -67,7 +71,7 @@ autobahn: clean app
build-plt: app
@dialyzer --build_plt --output_plt .$(PROJECT).plt \
- --apps erts kernel stdlib sasl inets crypto public_key ssl deps/ranch
+ --apps erts kernel stdlib crypto public_key ssl $(DEPS_DIR)/ranch
dialyze:
@dialyzer --src src --plt .$(PROJECT).plt --no_native \
diff --git a/examples/echo_post/src/toppage_handler.erl b/examples/echo_post/src/toppage_handler.erl
index 808ba8e..21e1dc6 100644
--- a/examples/echo_post/src/toppage_handler.erl
+++ b/examples/echo_post/src/toppage_handler.erl
@@ -12,9 +12,9 @@ init(_Transport, Req, []) ->
handle(Req, State) ->
{Method, Req2} = cowboy_req:method(Req),
- {HasBody, Req3} = cowboy_req:has_body(Req2),
- {ok, Req4} = maybe_echo(Method, HasBody, Req3),
- {ok, Req4, State}.
+ HasBody = cowboy_req:has_body(Req2),
+ {ok, Req3} = maybe_echo(Method, HasBody, Req2),
+ {ok, Req3, State}.
maybe_echo(<<"POST">>, true, Req) ->
{ok, PostVals, Req2} = cowboy_req:body_qs(Req),
diff --git a/guide/handlers.md b/guide/handlers.md
index dac5460..e2c1264 100644
--- a/guide/handlers.md
+++ b/guide/handlers.md
@@ -33,7 +33,23 @@ init(_Any, _Req, _Opts) ->
{upgrade, protocol, my_protocol}.
```
-The `my_protocol` module will be used for further processing of the
-request. It requires only one callback, `upgrade/4`.
+Cowboy comes with two protocol upgrades: `cowboy_rest` and
+`cowboy_websocket`. Use these values in place of `my_protocol`
+to use them.
-@todo Describe `upgrade/4` when the middleware code gets pushed.
+Custom protocol upgrades
+------------------------
+
+The `my_protocol` module above will be used for further processing
+of the request. It requires only one callback, `upgrade/4`.
+
+It receives the request object, the middleware environment, and
+the handler this request has been routed to along with its options.
+
+``` erlang
+upgrade(Req, Env, Handler, HandlerOpts) ->
+ %% ...
+```
+
+This callback is expected to behave like any middleware. Please
+see the corresponding chapter for more information.
diff --git a/guide/hooks.md b/guide/hooks.md
index ba48c4a..d4b520a 100644
--- a/guide/hooks.md
+++ b/guide/hooks.md
@@ -4,9 +4,74 @@ Hooks
On request
----------
-@todo Describe.
+The `onrequest` hook is called as soon as Cowboy finishes fetching
+the request headers. It occurs before any other processing, including
+routing. It can be used to perform any modification needed on the
+request object before continuing with the processing. If a reply is
+sent inside this hook, then Cowboy will move on to the next request,
+skipping any subsequent handling.
+
+This hook is a function that takes a request object as argument,
+and returns a request object. This function MUST NOT crash. Cowboy
+will not send any reply if a crash occurs in this function.
+
+You can specify the `onrequest` hook when creating the listener,
+inside the request options.
+
+``` erlang
+cowboy:start_http(my_http_listener, 100,
+ [{port, 8080}],
+ [
+ {env, [{dispatch, Dispatch}]},
+ {onrequest, fun ?MODULE:debug_hook/1}
+ ]
+).
+```
+
+The following hook function prints the request object everytime a
+request is received. This can be useful for debugging, for example.
+
+``` erlang
+debug_hook(Req) ->
+ erlang:display(Req),
+ Req.
+```
+
+Make sure to always return the last request object obtained.
On response
-----------
-@todo Describe.
+The `onresponse` hook is called right before sending the response
+to the socket. It can be used for the purposes of logging responses,
+or for modifying the response headers or body. The best example is
+providing custom error pages.
+
+Note that like the `onrequest` hook, this function MUST NOT crash.
+Cowboy may or may not send a reply if this function crashes.
+
+You can specify the `onresponse` hook when creating the listener also.
+
+``` erlang
+cowboy:start_http(my_http_listener, 100,
+ [{port, 8080}],
+ [
+ {env, [{dispatch, Dispatch}]},
+ {onresponse, fun ?MODULE:custom_404_hook/4}
+ ]
+).
+```
+
+The following hook function will provide a custom body for 404 errors
+when it has not been provided before, and will let Cowboy proceed with
+the default response otherwise.
+
+``` erlang
+custom_404_hook(404, Headers, <<>>, Req) ->
+ {ok, Req2} = cowboy_req:reply(404, Headers, <<"404 Not Found.">>, Req),
+ Req2;
+custom_404_hook(_, _, _, Req) ->
+ Req.
+```
+
+Again, make sure to always return the last request object obtained.
diff --git a/guide/http_handlers.md b/guide/http_handlers.md
index 0d8886d..ea88c79 100644
--- a/guide/http_handlers.md
+++ b/guide/http_handlers.md
@@ -6,12 +6,22 @@ Purpose
HTTP handlers are the simplest Cowboy module to handle a request.
-Callbacks
----------
-
-@todo Describe the callbacks.
-
Usage
-----
-@todo Explain how to use them.
+You need to implement three callbacks for HTTP handlers. The first,
+`init/3`, is common to all handlers. In the context of HTTP handlers
+this should be used for any initialization needs.
+
+The second callback, `handle/2`, is where most of your code should
+be. As the name explains, this is where you handle the request.
+
+The last callback, `terminate/2`, will be empty most of the time.
+It's used for any needed cleanup. If you used the process dictionary,
+timers, monitors then you most likely want to stop them in this
+callback, as Cowboy might end up reusing this process for subsequent
+requests. Please see the Internals chapter for more information.
+
+Of course the general advice is to not use the process dictionary,
+and that any operation requiring reception of messages should be
+done in a loop handler, documented in its own chapter.
diff --git a/guide/internals.md b/guide/internals.md
index 431ca01..0f8adc2 100644
--- a/guide/internals.md
+++ b/guide/internals.md
@@ -4,10 +4,76 @@ Internals
Architecture
------------
-@todo Describe.
+Cowboy is a lightweight HTTP server.
-Efficiency considerations
--------------------------
+It is built on top of Ranch. Please see the Ranch guide for more
+informations.
-@todo Mention that you need to cleanup in terminate especially if you
-used the process dictionary, started timers, started monitoring...
+It uses only one process per connection. The process where your
+code runs is the process controlling the socket. Using one process
+instead of two allows for lower memory usage.
+
+It uses binaries. Binaries are more efficient than lists for
+representing strings because they take less memory space. Processing
+performance can vary depending on the operation. Binaries are known
+for generally getting a great boost if the code is compiled natively.
+Please see the HiPE documentation for more details.
+
+Because querying for the current date and time can be expensive,
+Cowboy generates one `Date` header value every second, shares it
+to all other processes, which then simply copy it in the response.
+This allows compliance with HTTP/1.1 with no actual performance loss.
+
+One process for many requests
+-----------------------------
+
+As previously mentioned, Cowboy only use one process per connection.
+Because there can be more than one request per connection with the
+keepalive feature of HTTP/1.1, that means the same process will be
+used to handle many requests.
+
+Because of this, you are expected to make sure your process cleans
+up before terminating the handling of the current request. This may
+include cleaning up the process dictionary, timers, monitoring and
+more.
+
+Lowercase header names
+----------------------
+
+For consistency reasons it has been chosen to convert all header names
+to lowercase binary strings. This prevents the programmer from making
+case mistakes, and is possible because header names are case insensitive.
+
+This works fine for the large majority of clients. However, some badly
+implemented clients, especially ones found in corporate code or closed
+source products, may not handle header names in a case insensitive manner.
+This means that when Cowboy returns lowercase header names, these clients
+will not find the headers they are looking for.
+
+A simple way to solve this is to create an `onresponse` hook that will
+format the header names with the expected case.
+
+``` erlang
+capitalize_hook(Status, Headers, Body, Req) ->
+ Headers2 = [{cowboy_bstr:capitalize_token(N), V}
+ || {N, V} <- Headers],
+ {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req),
+ Req2.
+```
+
+Improving performance
+---------------------
+
+By default the maximum number of active connections is set to a
+generally accepted big enough number. This is meant to prevent having
+too many processes performing potentially heavy work and slowing
+everything else down, or taking up all the memory.
+
+Disabling this feature, by setting the `{max_connections, infinity}`
+protocol option, would give you greater performance when you are
+only processing short-lived requests.
+
+Another option is to define platform-specific socket options that
+are known to improve their efficiency.
+
+Please see the Ranch guide for more information.
diff --git a/guide/introduction.md b/guide/introduction.md
index 4d1dacc..f1fd18e 100644
--- a/guide/introduction.md
+++ b/guide/introduction.md
@@ -77,7 +77,7 @@ Dispatch = [
],
%% Name, NbAcceptors, TransOpts, ProtoOpts
cowboy:start_http(my_http_listener, 100,
- [{port, 8080}],
+ [{port, 8080}],
[{env, [{dispatch, Dispatch}]}]
).
```
diff --git a/guide/loop_handlers.md b/guide/loop_handlers.md
index 6d67c62..64cf80a 100644
--- a/guide/loop_handlers.md
+++ b/guide/loop_handlers.md
@@ -16,15 +16,23 @@ 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
----------
-
-@todo Describe the callbacks.
-
Usage
-----
-@todo Explain how to use them.
+Loop handlers are used for requests where a response might not
+be immediately available, but where you would like to keep the
+connection open for a while in case the response arrives. The
+most known example of such practice is known as long-polling.
+
+Loop handlers can also be used for requests where a response is
+partially available and you need to stream the response body
+while the connection is open. The most known example of such
+practice is known as server-sent events.
+
+Loop handlers essentially wait for one or more Erlang messages
+and feed these messages to the `info/3` callback. It also features
+the `init/3` and `terminate/2` callbacks which work the same as
+for plain HTTP handlers.
The following handler waits for a message `{reply, Body}` before
sending a response. If this message doesn't arrive within 60
@@ -41,14 +49,14 @@ this message.
-export([terminate/2]).
init({tcp, http}, Req, Opts) ->
- {loop, Req, undefined_state, 60000, hibernate}.
+ {loop, Req, undefined_state, 60000, hibernate}.
info({reply, Body}, Req, State) ->
- {ok, Req2} = cowboy_req:reply(200, [], Body, Req),
- {ok, Req2, State};
+ {ok, Req2} = cowboy_req:reply(200, [], Body, Req),
+ {ok, Req2, State};
info(Message, Req, State) ->
- {loop, Req, State, hibernate}.
+ {loop, Req, State, hibernate}.
terminate(Req, State) ->
- ok.
+ ok.
```
diff --git a/guide/req.md b/guide/req.md
index 79c59a9..c039658 100644
--- a/guide/req.md
+++ b/guide/req.md
@@ -4,19 +4,187 @@ Request object
Purpose
-------
-@todo Describe.
+The request object is a special variable that can be used
+to interact with a request, extracting information from it
+or modifying it, and sending a response.
+
+It's a special variable because it contains both immutable
+and mutable state. This means that some operations performed
+on the request object will always return the same result,
+while others will not. For example, obtaining request headers
+can be repeated safely. Obtaining the request body can only
+be done once, as it is read directly from the socket.
+
+With few exceptions, all calls to the `cowboy_req` module
+will return an updated request object. You MUST use the new
+request object instead of the old one for all subsequent
+operations.
Request
-------
-@todo Describe.
+Cowboy allows you to retrieve a lot of information about
+the request. All these calls return a `{Value, Req}` tuple,
+with `Value` the requested value and `Req` the updated
+request object.
+
+The following access functions are defined in `cowboy_req`:
+
+ * `method/1`: the request method (`<<"GET">>`, `<<"POST">>`...)
+ * `version/1`: the HTTP version (`{1,0}` or `{1,1}`)
+ * `peer/1`: the peer address and port number
+ * `peer_addr/1`: the peer address guessed using the request headers
+ * `host/1`: the hostname requested
+ * `host_info/1`: the result of the `[...]` match on the host
+ * `port/1`: the port number used for the connection
+ * `path/1`: the path requested
+ * `path_info/1`: the result of the `[...]` match on the path
+ * `qs/1`: the entire query string unmodified
+ * `qs_val/{2,3}`: the value for the requested query string key
+ * `qs_vals/1`: all key/values found in the query string
+ * `fragment/1`: the fragment part of the URL (e.g. `#nav-links`)
+ * `host_url/1`: the requested URL without the path, qs and fragment
+ * `url/1`: the requested URL
+ * `binding/{2,3}`: the value for the requested binding found during routing
+ * `bindings/1`: all key/values found during routing
+ * `header/{2,3}`: the value for the requested header name
+ * `headers/1`: all headers name/value
+ * `cookie/{2,3}`: the value for the requested cookie name
+ * `cookies/1`: all cookies name/value
+ * `meta/{2,3}`: the meta information for the requested key
+
+All the functions above that can take two or three arguments
+take an optional third argument for the default value if
+none is found. Otherwise it will return `undefined`.
+
+In addition, Cowboy allows you to parse headers using the
+`parse_header/{2,3}` function, which takes a header name
+as lowercase binary, the request object, and an optional
+default value. It returns `{ok, ParsedValue, Req}` if it
+could be parsed, `{undefined, RawValue, Req}` if Cowboy
+doesn't know this header, and `{error, badarg}` if Cowboy
+encountered an error while trying to parse it.
+
+Finally, Cowboy allows you to set request meta information
+using the `set_meta/3` function, which takes a name, a value
+and the request object and returns the latter modified.
Request body
------------
-@todo Describe.
+Cowboy will not read the request body until you ask it to.
+If you don't, then Cowboy will simply discard it. It will
+not take extra memory space until you start reading it.
+
+Cowboy has a few utility functions for dealing with the
+request body.
+
+The function `has_body/1` will return whether the request
+contains a body. Note that some clients may not send the
+right headers while still sending a body, but as Cowboy has
+no way of detecting it this function will return `false`.
+
+The function `body_length/1` retrieves the size of the
+request body. If the body is compressed, the value returned
+here is the compressed size. If a `Transfer-Encoding` header
+was passed in the request, then Cowboy will return a size
+of `undefined`, as it has no way of knowing it.
+
+If you know the request contains a body, and that it is
+of appropriate size, then you can read it directly with
+either `body/1` or `body_qs/1`. Otherwise, you will want
+to stream it with `stream_body/1` and `skip_body/1`, with
+the streaming process optionally initialized using `init_stream/4`.
+
+Multipart request body
+----------------------
+
+Cowboy provides facilities for dealing with multipart bodies.
+They are typically used for uploading files. You can use two
+functions to process these bodies, `multipart_data/1` and
+`multipart_skip/1`.
+
+Response
+--------
+
+You can send a response by calling the `reply/{2,3,4}` function.
+It takes the status code for the response (usually `200`),
+an optional list of headers, an optional body and the request
+object.
+
+The following snippet sends a simple response with no headers
+specified but with a body.
+
+``` erlang
+{ok, Req2} = cowboy_req:reply(200, [], "Hello world!", Req).
+```
+
+If this is the only line in your handler then make sure to return
+the `Req2` variable to Cowboy so it can know you replied.
+
+If you want to send HTML you'll need to specify the `Content-Type`
+header so the client can properly interpret it.
+
+``` erlang
+{ok, Req2} = cowboy_req:reply(200,
+ [{<<"content-type">>, <<"text/html">>}],
+ "<html><head>Hello world!</head><body><p>Hats off!</p></body></html>",
+ Req).
+```
+
+You only need to make sure to follow conventions and to use a
+lowercase header name.
+
+Chunked response
+----------------
+
+You can also send chunked responses using `chunked_reply/{2,3}`.
+Chunked responses allow you to send the body in chunks of various
+sizes. It is the recommended way of performing streaming if the
+client supports it.
+
+You must first initiate the response by calling the aforementioned
+function, then you can call `chunk/2` as many times as needed.
+The following snippet sends a body in three chunks.
+
+``` erlang
+{ok, Req2} = cowboy_req:chunked_reply(200, Req),
+ok = cowboy_req:chunk("Hello...", Req2),
+ok = cowboy_req:chunk("chunked...", Req2),
+ok = cowboy_req:chunk("world!!", Req2).
+```
+
+As you can see the call to `chunk/2` does not return a modified
+request object. It may return an error, however, so you should
+make sure that you match the return value on `ok`.
+
+Response preconfiguration
+-------------------------
+
+Cowboy allows you to set response cookies, headers or body
+in advance without having to send the response at the same time.
+Then, when you decide to send it, all these informations will be
+built into the resulting response.
+
+Some of the functions available for this purpose also give you
+additional functionality, like `set_resp_cookie/4` which will build
+the appropriate `Set-Cookie` header, or `set_resp_body_fun/{2,3}`
+which allows you to stream the response body.
+
+Note that any value given directly to `reply/{2,3,4}` will
+override all preset values. This means for example that you
+can set a default body and then override it when you decide
+to send a reply.
+
+Reducing the memory footprint
+-----------------------------
-Reply
------
+When you are done reading information from the request object
+and know you are not going to access it anymore, for example
+when using long-polling or Websocket, you can use the `compact/1`
+function to remove most of the data from the request object and
+free memory.
-@todo Describe.
+``` erlang
+Req2 = cowboy_req:compact(Req).
+```
diff --git a/guide/routing.md b/guide/routing.md
index 7d6fa41..2970b39 100644
--- a/guide/routing.md
+++ b/guide/routing.md
@@ -90,10 +90,12 @@ PathMatch3 = "/path/to/resource/".
```
Hosts with and without a trailing dot are equivalent for routing.
+Similarly, hosts with and without a leading dot are also equivalent.
``` erlang
HostMatch1 = "cowboy.example.org".
HostMatch2 = "cowboy.example.org.".
+HostMatch3 = ".cowboy.example.org".
```
It is possible to extract segments of the host and path and to store
@@ -115,16 +117,93 @@ segment value where they were defined. For example, the URL
`http://test.example.org/hats/wild_cowboy_legendary/prices` will
result in having the value `test` bound to the name `subdomain`
and the value `wild_cowboy_legendary` bound to the name `hat_name`.
-They can later be retrieved using `cowboy_req:binding/{2,3}`.
+They can later be retrieved using `cowboy_req:binding/{2,3}`. The
+binding name must be given as an atom.
-@todo special binding `'_'`
-@todo optional path or segments
-@todo same binding twice (+ optional + host/path)
+There is a special binding name you can use to mimic the underscore
+variable in Erlang. Any match against the `_` binding will succeed
+but the data will be discarded. This is especially useful for
+matching against many domain names in one go.
+
+``` erlang
+HostMatch = "ninenines.:_".
+```
+
+Similarly, it is possible to have optional segments. Anything
+between brackets is optional.
+
+``` erlang
+PathMatch = "/hats/[page/:number]".
+HostMatch = "[www.]ninenines.eu".
+```
+
+You can also have imbricated optional segments.
+
+``` erlang
+PathMatch = "/hats/[page/[:number]]".
+```
+
+You can retrieve the rest of the host or path using `[...]`.
+In the case of hosts it will match anything before, in the case
+of paths anything after the previously matched segments. It is
+a special case of optional segments, in that it can have
+zero, one or many segments. You can then find the segments using
+`cowboy_req:host_info/1` and `cowboy_req:path_info/1` respectively.
+They will be represented as a list of segments.
+
+``` erlang
+PathMatch = "/hats/[...]".
+HostMatch = "[...]ninenines.eu".
+```
+
+Finally, if a binding appears twice in the routing rules, then the
+match will succeed only if they share the same value. This copies
+the Erlang pattern matching behavior.
+
+``` erlang
+PathMatch = "/hats/:name/:name".
+```
+
+This is also true when an optional segment is present. In this
+case the two values must be identical only if the segment is
+available.
+
+``` erlang
+PathMatch = "/hats/:name/[:name]".
+```
+
+If a binding is defined in both the host and path, then they must
+also share the same value.
+
+``` erlang
+PathMatch = "/:user/[...]".
+HostMatch = ":user.github.com".
+```
Constraints
-----------
-@todo Describe constraints.
+After the matching has completed, the resulting bindings can be tested
+against a set of constraints. The match will succeed only if they all
+succeed.
+
+They are always given as a two or three elements tuple, where the first
+element is the name of the binding, the second element is the constraint's
+name, and the optional third element is the constraint's arguments.
+
+The following constraints are currently defined:
+
+ * {Name, int}
+ * {Name, function, (fun(Value) -> true | {true, NewValue} | false)}
+
+The `int` constraint will check if the binding is a binary string
+representing an integer, and if it is, will convert the value to integer.
+
+The `function` constraint will pass the binding value to a user specified
+function that receives the binary value as its only argument and must
+return whether it fulfills the constraint, optionally modifying the value.
+
+Note that constraint functions SHOULD be pure and MUST NOT crash.
Compilation
-----------
diff --git a/guide/static_handlers.md b/guide/static_handlers.md
index 5c897dd..f87515a 100644
--- a/guide/static_handlers.md
+++ b/guide/static_handlers.md
@@ -11,4 +11,20 @@ proper cache handling.
Usage
-----
-@todo Describe.
+Static handlers are pre-written REST handlers. They only need
+to be specified in the routing information with the proper options.
+
+The following example routing serves all files found in the
+`priv_dir/static/` directory of the application. It uses a
+mimetypes library to figure out the files' content types.
+
+``` erlang
+Dispatch = [
+ {'_', [
+ {['...'], cowboy_static, [
+ {directory, {priv_dir, static, []}},
+ {mimetypes, {fun mimetypes:path_to_mimes/2, default}}
+ ]}
+ ]}
+].
+```
diff --git a/guide/toc.md b/guide/toc.md
index 2498b4d..2890172 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -15,17 +15,15 @@ Cowboy User Guide
* [Handlers](handlers.md)
* Purpose
* Protocol upgrades
+ * Custom protocol upgrades
* [HTTP handlers](http_handlers.md)
* Purpose
- * Callbacks
* Usage
* [Loop handlers](loop_handlers.md)
* Purpose
- * Callbacks
* Usage
* [Websocket handlers](ws_handlers.md)
* Purpose
- * Callbacks
* Usage
* [REST handlers](rest_handlers.md)
* Purpose
@@ -39,7 +37,11 @@ Cowboy User Guide
* Purpose
* Request
* Request body
- * Reply
+ * Multipart request body
+ * Response
+ * Chunked response
+ * Response preconfiguration
+ * Reducing the memory footprint
* [Hooks](hooks.md)
* On request
* On response
@@ -51,4 +53,6 @@ Cowboy User Guide
* Handler middleware
* [Internals](internals.md)
* Architecture
- * Efficiency considerations
+ * One process for many requests
+ * Lowercase header names
+ * Improving performance
diff --git a/guide/ws_handlers.md b/guide/ws_handlers.md
index 226ada0..c1e551e 100644
--- a/guide/ws_handlers.md
+++ b/guide/ws_handlers.md
@@ -16,15 +16,28 @@ is implemented by most browsers today, although for backward
compatibility reasons a solution like [Bullet](https://github.com/extend/bullet)
might be preferred.
-Callbacks
----------
-
-@todo Describe the callbacks.
-
Usage
-----
-@todo Explain how to use them.
+Websocket handlers are a bridge between the client and your system.
+They can receive data from the client, through `websocket_handle/3`,
+or from the system, through `websocket_info/3`. It is up to the
+handler to decide to process this data, and optionally send a reply
+to the client.
+
+The first thing to do to be able to handle websockets is to tell
+Cowboy that it should upgrade the connection to use the Websocket
+protocol, as follow.
+
+``` erlang
+init({tcp, http}, Req, Opts) ->
+ {upgrade, protocol, cowboy_websocket}.
+```
+
+Cowboy will then switch the protocol and call `websocket_init`,
+followed by zero or more calls to `websocket_data` and
+`websocket_info`. Then, when the connection is shutting down,
+`websocket_terminate` will be called.
The following handler sends a message every second. It also echoes
back what it receives.
diff --git a/src/cowboy_bstr.erl b/src/cowboy_bstr.erl
index e906de7..bc6818f 100644
--- a/src/cowboy_bstr.erl
+++ b/src/cowboy_bstr.erl
@@ -16,16 +16,40 @@
-module(cowboy_bstr).
%% Binary strings.
+-export([capitalize_token/1]).
-export([to_lower/1]).
%% Characters.
-export([char_to_lower/1]).
-export([char_to_upper/1]).
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%% @doc Capitalize a token.
+%%
+%% The first letter and all letters after a dash are capitalized.
+%% This is the form seen for header names in the HTTP/1.1 RFC and
+%% others. Note that using this form isn't required, as header name
+%% are case insensitive, and it is only provided for use with eventual
+%% badly implemented clients.
+-spec capitalize_token(B) -> B when B::binary().
+capitalize_token(B) ->
+ capitalize_token(B, true, <<>>).
+capitalize_token(<<>>, _, Acc) ->
+ Acc;
+capitalize_token(<< $-, Rest/bits >>, _, Acc) ->
+ capitalize_token(Rest, true, << Acc/binary, $- >>);
+capitalize_token(<< C, Rest/bits >>, true, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);
+capitalize_token(<< C, Rest/bits >>, false, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).
+
%% @doc Convert a binary string to lowercase.
--spec to_lower(binary()) -> binary().
-to_lower(L) ->
- << << (char_to_lower(C)) >> || << C >> <= L >>.
+-spec to_lower(B) -> B when B::binary().
+to_lower(B) ->
+ << << (char_to_lower(C)) >> || << C >> <= B >>.
%% @doc Convert [A-Z] characters to lowercase.
%% @end
@@ -88,3 +112,22 @@ char_to_upper($x) -> $X;
char_to_upper($y) -> $Y;
char_to_upper($z) -> $Z;
char_to_upper(Ch) -> Ch.
+
+%% Tests.
+
+-ifdef(TEST).
+
+capitalize_token_test_() ->
+ %% {Header, Result}
+ Tests = [
+ {<<"heLLo-woRld">>, <<"Hello-World">>},
+ {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
+ {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>},
+ {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
+ ],
+ [{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].
+
+-endif.
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index 0e9982b..a0f571b 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -30,7 +30,7 @@
%% <dt>max_headers</dt><dd>Max number of headers allowed.
%% Defaults to 100.</dd>
%% <dt>max_keepalive</dt><dd>Max number of requests allowed in a single
-%% keep-alive session. Defaults to infinity.</dd>
+%% keep-alive session. Defaults to 100.</dd>
%% <dt>max_request_line_length</dt><dd>Max length allowed for the request
%% line. Defaults to 4096.</dd>
%% <dt>middlewares</dt><dd>The list of middlewares to execute when a
@@ -107,7 +107,7 @@ init(ListenerPid, Socket, Transport, Opts) ->
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
MaxHeaders = get_value(max_headers, Opts, 100),
- MaxKeepalive = get_value(max_keepalive, Opts, infinity),
+ MaxKeepalive = get_value(max_keepalive, Opts, 100),
MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
Env = [{listener, ListenerPid}|get_value(env, Opts, [])],
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 89758dd..7f7ef32 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -163,7 +163,8 @@
| {non_neg_integer(), resp_body_fun()},
%% Functions.
- onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun()
+ onresponse = undefined :: undefined | already_called
+ | cowboy_protocol:onresponse_fun()
}).
-opaque req() :: #http_req{}.
@@ -555,11 +556,10 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
%% Request Body API.
%% @doc Return whether the request message has a body.
--spec has_body(Req) -> {boolean(), Req} when Req::req().
+-spec has_body(cowboy_req:req()) -> boolean().
has_body(Req) ->
- Has = lists:keymember(<<"content-length">>, 1, Req#http_req.headers) orelse
- lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers),
- {Has, Req}.
+ lists:keymember(<<"content-length">>, 1, Req#http_req.headers) orelse
+ lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers).
%% @doc Return the request message body length, if known.
%%
@@ -728,7 +728,6 @@ skip_body(Req) ->
%% @doc Return the full body sent with the request, parsed as an
%% application/x-www-form-urlencoded string. Essentially a POST query string.
-%% @todo We need an option to limit the size of the body for QS too.
-spec body_qs(Req)
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
when Req::req().
@@ -765,7 +764,6 @@ multipart_data(Req=#http_req{multipart={Length, Cont}}) ->
multipart_data(Req=#http_req{body_state=done}) ->
{eof, Req}.
-%% @todo Typespecs.
multipart_data(Req, Length, {headers, Headers, Cont}) ->
{headers, Headers, Req#http_req{multipart={Length, Cont}}};
multipart_data(Req, Length, {body, Data, Cont}) ->
@@ -868,6 +866,8 @@ has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
%% @doc Return whether a body has been set for the response.
-spec has_resp_body(req()) -> boolean().
+has_resp_body(#http_req{resp_body=RespBody}) when is_function(RespBody) ->
+ true;
has_resp_body(#http_req{resp_body={Length, _}}) ->
Length > 0;
has_resp_body(#http_req{resp_body=RespBody}) ->
@@ -1163,13 +1163,17 @@ to_list(Req) ->
response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
pid=ReqPid, onresponse=OnResponse}) ->
- FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders),
+ FullHeaders = case OnResponse of
+ already_called -> Headers;
+ _ -> response_merge_headers(Headers, RespHeaders, DefaultHeaders)
+ end,
Req2 = case OnResponse of
+ already_called -> Req;
undefined -> Req;
OnResponse -> OnResponse(Status, FullHeaders, Body,
%% Don't call 'onresponse' from the hook itself.
Req#http_req{resp_headers=[], resp_body= <<>>,
- onresponse=undefined})
+ onresponse=already_called})
end,
ReplyType = case Req2#http_req.resp_state of
waiting ->
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 963b2f7..f5bc22d 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -846,12 +846,6 @@ generate_etag(Req, State=#state{etag=undefined}) ->
case call(Req, State, generate_etag) of
no_call ->
{undefined, Req, State#state{etag=no_call}};
- %% Previously the return value from the generate_etag/2 callback was set
- %% as the value of the ETag header in the response. Therefore the only
- %% valid return type was `binary()'. If a handler returns a `binary()'
- %% it must be mapped to the expected type or it'll always fail to
- %% compare equal to any entity tags present in the request headers.
- %% @todo Remove support for binary return values after 0.6.
{Etag, Req2, HandlerState} when is_binary(Etag) ->
[Etag2] = cowboy_http:entity_tag_match(Etag),
{Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 4553aef..1b2ad3b 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -24,9 +24,12 @@
%% Internal.
-export([handler_loop/4]).
+-type close_code() :: 1000..4999.
+-export_type([close_code/0]).
+
-type frame() :: close | ping | pong
| {text | binary | close | ping | pong, binary()}
- | {close, 1000..4999, binary()}.
+ | {close, close_code(), binary()}.
-export_type([frame/0]).
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
@@ -645,7 +648,8 @@ websocket_send_many([Frame|Tail], State) ->
Error -> Error
end.
--spec websocket_close(#state{}, Req, any(), {atom(), atom()})
+-spec websocket_close(#state{}, Req, any(),
+ {atom(), atom()} | {remote, close_code(), binary()})
-> {ok, Req, cowboy_middleware:env()}
when Req::cowboy_req:req().
websocket_close(State=#state{socket=Socket, transport=Transport},
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 3200188..4791f36 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -45,6 +45,7 @@
-export([nc_zero/1]).
-export([onrequest/1]).
-export([onrequest_reply/1]).
+-export([onresponse_capitalize/1]).
-export([onresponse_crash/1]).
-export([onresponse_reply/1]).
-export([pipeline/1]).
@@ -84,7 +85,8 @@ all() ->
{group, http_compress},
{group, https_compress},
{group, onrequest},
- {group, onresponse}
+ {group, onresponse},
+ {group, onresponse_capitalize}
].
groups() ->
@@ -146,6 +148,9 @@ groups() ->
{onresponse, [], [
onresponse_crash,
onresponse_reply
+ ]},
+ {onresponse_capitalize, [], [
+ onresponse_capitalize
]}
].
@@ -250,6 +255,18 @@ init_per_group(onresponse, Config) ->
]),
{ok, Client} = cowboy_client:init([]),
[{scheme, <<"http">>}, {port, Port}, {opts, []},
+ {transport, Transport}, {client, Client}|Config];
+init_per_group(onresponse_capitalize, Config) ->
+ Port = 33086,
+ Transport = ranch_tcp,
+ {ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, Port}], [
+ {env, [{dispatch, init_dispatch(Config)}]},
+ {max_keepalive, 50},
+ {onresponse, fun onresponse_capitalize_hook/4},
+ {timeout, 500}
+ ]),
+ {ok, Client} = cowboy_client:init([]),
+ [{scheme, <<"http">>}, {port, Port}, {opts, []},
{transport, Transport}, {client, Client}|Config].
end_per_group(Group, Config) when Group =:= https; Group =:= https_compress ->
@@ -569,7 +586,8 @@ keepalive_max(Config) ->
URL = build_url("/", Config),
ok = keepalive_max_loop(Client, URL, 50).
-keepalive_max_loop(_, _, 0) ->
+keepalive_max_loop(Client, _, 0) ->
+ {error, closed} = cowboy_client:response(Client),
ok;
keepalive_max_loop(Client, URL, N) ->
Headers = [{<<"connection">>, <<"keep-alive">>}],
@@ -588,7 +606,8 @@ keepalive_nl(Config) ->
URL = build_url("/", Config),
ok = keepalive_nl_loop(Client, URL, 10).
-keepalive_nl_loop(_, _, 0) ->
+keepalive_nl_loop(Client, _, 0) ->
+ {error, closed} = cowboy_client:response(Client),
ok;
keepalive_nl_loop(Client, URL, N) ->
Headers = [{<<"connection">>, <<"keep-alive">>}],
@@ -671,6 +690,21 @@ onrequest_hook(Req) ->
Req3
end.
+onresponse_capitalize(Config) ->
+ Client = ?config(client, Config),
+ {ok, Client2} = cowboy_client:request(<<"GET">>,
+ build_url("/", Config), Client),
+ {ok, Transport, Socket} = cowboy_client:transport(Client2),
+ {ok, Data} = Transport:recv(Socket, 0, 1000),
+ false = nomatch =:= binary:match(Data, <<"Content-Length">>).
+
+%% Hook for the above onresponse_capitalize test.
+onresponse_capitalize_hook(Status, Headers, Body, Req) ->
+ Headers2 = [{cowboy_bstr:capitalize_token(N), V}
+ || {N, V} <- Headers],
+ {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req),
+ Req2.
+
onresponse_crash(Config) ->
Client = ?config(client, Config),
{ok, Client2} = cowboy_client:request(<<"GET">>,
diff --git a/test/http_handler_echo_body.erl b/test/http_handler_echo_body.erl
index e4b1ee0..37c2072 100644
--- a/test/http_handler_echo_body.erl
+++ b/test/http_handler_echo_body.erl
@@ -8,8 +8,8 @@ init({_, http}, Req, _) ->
{ok, Req, undefined}.
handle(Req, State) ->
- {true, Req1} = cowboy_req:has_body(Req),
- {ok, Body, Req2} = cowboy_req:body(Req1),
+ true = cowboy_req:has_body(Req),
+ {ok, Body, Req2} = cowboy_req:body(Req),
{Size, Req3} = cowboy_req:body_length(Req2),
Size = byte_size(Body),
{ok, Req4} = cowboy_req:reply(200, [], Body, Req3),