aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--CHANGELOG.md50
-rw-r--r--LICENSE2
-rw-r--r--Makefile101
-rw-r--r--README.md4
-rw-r--r--ROADMAP.md53
-rw-r--r--erlang.mk135
-rw-r--r--examples/elixir_hello_world/mix.exs2
-rw-r--r--guide/req.md5
-rw-r--r--manual/cowboy.md89
-rw-r--r--manual/cowboy_app.md25
-rw-r--r--manual/cowboy_handler.md25
-rw-r--r--manual/cowboy_http_handler.md57
-rw-r--r--manual/cowboy_loop_handler.md91
-rw-r--r--manual/cowboy_middleware.md56
-rw-r--r--manual/cowboy_protocol.md65
-rw-r--r--manual/cowboy_req.md597
-rw-r--r--manual/cowboy_rest.md552
-rw-r--r--manual/cowboy_router.md68
-rw-r--r--manual/cowboy_sub_protocol.md34
-rw-r--r--manual/cowboy_websocket.md34
-rw-r--r--manual/cowboy_websocket_handler.md131
-rw-r--r--manual/toc.md18
-rw-r--r--rebar.config2
-rw-r--r--src/cowboy.app.src2
-rw-r--r--src/cowboy.erl39
-rw-r--r--src/cowboy_client.erl18
-rw-r--r--src/cowboy_handler.erl2
-rw-r--r--src/cowboy_http.erl22
-rw-r--r--src/cowboy_http_handler.erl2
-rw-r--r--src/cowboy_loop_handler.erl2
-rw-r--r--src/cowboy_middleware.erl4
-rw-r--r--src/cowboy_protocol.erl205
-rw-r--r--src/cowboy_req.erl253
-rw-r--r--src/cowboy_rest.erl15
-rw-r--r--src/cowboy_spdy.erl544
-rw-r--r--src/cowboy_spdy.hrl181
-rw-r--r--src/cowboy_sub_protocol.erl4
-rw-r--r--src/cowboy_websocket.erl4
-rw-r--r--test/http_SUITE.erl14
-rw-r--r--test/http_SUITE_data/rest_forbidden_resource.erl2
-rw-r--r--test/http_SUITE_data/ssl/cert.pem14
-rw-r--r--test/http_SUITE_data/ssl/key.pem18
-rw-r--r--test/spdy_SUITE.erl163
44 files changed, 3274 insertions, 432 deletions
diff --git a/AUTHORS b/AUTHORS
index bb2cbde..4803afd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,8 +6,8 @@ Anthony Ramine
Adam Cammack
Tom Burdick
Ali Sabil
-Paul Oliver
James Fish
+Paul Oliver
Slava Yurin
Yurii Rashkovskii
Andrew Majorov
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d9b92b..478a9a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,56 @@
CHANGELOG
=========
+0.8.5
+-----
+
+ * Add the Cowboy Function Reference
+
+ Everything documented in the function reference is the API
+ that will make it to Cowboy 1.0.
+
+ * Use erlang.mk
+
+ The project is of course still compatible with rebar
+ and can be used as a dependency just fine.
+
+ * Update Ranch to 0.8.3
+
+ * Remove cowboy_req:fragment/1
+
+ No well-written client is sending the fragment with the URL.
+
+ * Add cowboy_req:set_resp_body_fun(chunked, Fun, Req)
+
+ * Improve various typespecs
+
+ * Change the return value of cowboy_req:version/1
+
+ We now have 'HTTP/1.1' instead of {1, 1} and 'HTTP/1.0'
+ instead of {1, 0}.
+
+ * Change the return value of REST accept callbacks
+
+ The Path return value becomes {true, Path}.
+
+ * Change the return value of REST charsets_provided/2
+
+ It was incorrectly expecting a list of tuples instead of
+ a list of charsets.
+
+ * Move various types to the cowboy module
+ * cowboy_http:version() to cowboy:http_version()
+ * cowboy_http:headers() to cowboy:http_headers()
+ * cowboy_http:status() to cowboy:http_status()
+ * cowboy_protocol:onrequest_fun() to cowboy:onrequest_fun()
+ * cowboy_protocol:onresponse_fun() to cowboy:onresponse_fun()
+
+ * Add type cowboy_protocol:opts()
+
+ * Fix a REST bug with the OPTIONS method
+
+ * Fix a REST bug where iso-8859-1 would be incoditionally selected
+
0.8.4
-----
diff --git a/LICENSE b/LICENSE
index f79b460..6bc2b91 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/Makefile b/Makefile
index 08adaa8..9810deb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,105 +1,28 @@
# See LICENSE for licensing information.
PROJECT = cowboy
-RANCH_VSN = 0.8.1
-ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
- +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec
-DEPS_DIR ?= $(CURDIR)/deps
-export DEPS_DIR
+# Options.
-# Makefile tweaks.
-
-V ?= 0
-
-appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
-appsrc_verbose = $(appsrc_verbose_$(V))
-
-erlc_verbose_0 = @echo " ERLC " $(?F);
-erlc_verbose = $(erlc_verbose_$(V))
-
-gen_verbose_0 = @echo " GEN " $@;
-gen_verbose = $(gen_verbose_$(V))
-
-.PHONY: all clean-all app clean deps clean-deps docs clean-docs \
- build-tests tests autobahn build-plt dialyze
-
-# Application.
-
-all: deps app
-
-clean-all: clean clean-deps clean-docs
- $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs
-
-MODULES = $(shell ls src/*.erl | sed 's/src\///;s/\.erl/,/' | sed '$$s/.$$//')
-
-app: ebin/$(PROJECT).app
- $(appsrc_verbose) cat src/$(PROJECT).app.src \
- | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \
- > ebin/$(PROJECT).app
-
-COMPILE_FIRST = src/cowboy_middleware.erl src/cowboy_sub_protocol.erl
-
-ebin/$(PROJECT).app: src/*.erl
- @mkdir -p ebin/
- $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \
- $(COMPILE_FIRST) $?
-
-clean:
- $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump
+COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol
+CT_SUITES = eunit http spdy ws
+PLT_APPS = crypto public_key ssl
# Dependencies.
-$(DEPS_DIR)/ranch:
- @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)
-
-deps: $(DEPS_DIR)/ranch
- @$(MAKE) -C $(DEPS_DIR)/ranch
-
-clean-deps:
- -@$(MAKE) -C $(DEPS_DIR)/ranch clean
-
-# Documentation.
-
-docs: clean-docs
- $(gen_verbose) erl -noshell \
- -eval 'edoc:application($(PROJECT), ".", []), init:stop().'
+DEPS = ranch
+TEST_DEPS = ct_helper
+dep_ranch = https://github.com/extend/ranch.git 0.8.3
+dep_ct_helper = https://github.com/extend/ct_helper.git master
-clean-docs:
- $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
+# Standard targets.
-# Tests.
+include erlang.mk
-build-tests:
- $(gen_verbose) erlc -v $(ERLC_OPTS) \
- -o test/ test/*.erl test/*/*.erl -pa ebin/
+# Extra targets.
-CT_RUN = ct_run \
- -no_auto_compile \
- -noshell \
- -pa ebin $(DEPS_DIR)/*/ebin \
- -dir test \
- -logdir logs
-# -cover test/cover.spec
-
-tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
-tests: clean clean-deps deps app build-tests
- @mkdir -p logs/
- @$(CT_RUN) -suite eunit_SUITE http_SUITE ws_SUITE
- $(gen_verbose) rm -f test/*.beam
+.PHONY: autobahn
autobahn: clean clean-deps deps app build-tests
@mkdir -p logs/
@$(CT_RUN) -suite autobahn_SUITE
-
-# Dialyzer.
-
-build-plt: deps app
- @dialyzer --build_plt --output_plt .$(PROJECT).plt \
- --apps erts kernel stdlib crypto public_key ssl $(DEPS_DIR)/ranch
-
-dialyze:
- @dialyzer --src src --plt .$(PROJECT).plt --no_native \
- -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
diff --git a/README.md b/README.md
index 730995b..a543ede 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,9 @@ No parameterized module. No process dictionary. **Clean** Erlang code.
Getting Started
---------------
- * [Read the guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide/introduction)
+ * [Read the guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide)
+ * [Check the manual](http://ninenines.eu/docs/en/cowboy/HEAD/manual)
* Look at the examples in the `examples/` directory
- * Build API documentation with `make docs`; open `doc/index.html`
Support
-------
diff --git a/ROADMAP.md b/ROADMAP.md
index 96b3f9c..d21fe00 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -3,58 +3,39 @@ ROADMAP
This document explains in as much details as possible the
list of planned changes and work to be done on the Cowboy
-server. It is non-exhaustive and subject to change. Items
-are not ordered.
+server. It is intended to be exhaustive but some elements
+might still be missing.
- * Add and improve examples
+All the following items must be done before Cowboy 1.0 is
+released.
- * Improve user guide
+ * Parse support for all standard HTTP/1.1 headers
- We need feedback to improve the guide.
+ * Support for multipart requests and responses
- * Add and improve tests
+ * Convenience API for extracting query string and body
+ information, similar to PHP's $_GET, $_POST and $_FILES
- Amongst the areas less tested there is protocol upgrades
- and the REST handler.
+ * Add Range support to REST
- While eunit and ct tests are fine, some parts of the
- code could benefit from PropEr tests.
-
- * Continuous performance testing
-
- Initially dubbed the Horse project, Cowboy could benefit
- from a continuous performance testing tool that would
- allow us to easily compare the impact of the changes we
- are introducing, similar to what the Phoronix test suite
- allows.
-
- Depending on the test it may be interesting to compare
- Cowboy to other servers and eventually take ideas from
- the servers that outperform Cowboy for the task being tested.
-
- * Full HTTP/1.1 support
-
- * Improved HTTP/1.0 support
-
- Most of the work on Cowboy has been done with HTTP/1.1
- in mind. But there is still a need for HTTP/1.0 code in
- Cowboy. The server code should be reviewed and tested
- to ensure compatibility with remaining HTTP/1.0 products.
+ * SPDY support
- * Continue improving the REST API
+ We are only interested in supporting existing
+ implementations, not the full protocol, as this
+ protocol has been abandoned in favor of HTTP/2.0
- * SPDY support
+ * Complete the user guide
-The following items pertain to Ranch.
+The following items pertain to Ranch, but are equally important.
* Resizing the acceptor pool
We should be able to add more acceptors to a pool but also
- to remove some of them as needed.
+ to remove some of them as needed
* Add Transport:secure/0
Currently Cowboy checks if a connection is secure by
checking if its name is 'ssl'. This isn't a very modular
solution, adding an API function that returns whether
- a connection is secure would fix that issue.
+ a connection is secure would fix that issue
diff --git a/erlang.mk b/erlang.mk
new file mode 100644
index 0000000..ecd4d54
--- /dev/null
+++ b/erlang.mk
@@ -0,0 +1,135 @@
+# Copyright (c) 2013, Loïc Hoguin <[email protected]>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Verbosity and tweaks.
+
+V ?= 0
+
+appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
+appsrc_verbose = $(appsrc_verbose_$(V))
+
+erlc_verbose_0 = @echo " ERLC " $(?F);
+erlc_verbose = $(erlc_verbose_$(V))
+
+gen_verbose_0 = @echo " GEN " $@;
+gen_verbose = $(gen_verbose_$(V))
+
+.PHONY: all clean-all app clean deps clean-deps docs clean-docs \
+ build-tests tests build-plt dialyze
+
+# Deps directory.
+
+DEPS_DIR ?= $(CURDIR)/deps
+export DEPS_DIR
+
+ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS))
+ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
+
+# Application.
+
+ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
+ +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec
+COMPILE_FIRST ?=
+COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
+
+all: deps app
+
+clean-all: clean clean-deps clean-docs
+ $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs
+
+MODULES = $(shell ls src/*.erl | sed 's/src\///;s/\.erl/,/' | sed '$$s/.$$//')
+
+app: ebin/$(PROJECT).app
+ $(appsrc_verbose) cat src/$(PROJECT).app.src \
+ | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \
+ > ebin/$(PROJECT).app
+
+ebin/$(PROJECT).app: src/*.erl
+ @mkdir -p ebin/
+ $(erlc_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \
+ $(COMPILE_FIRST_PATHS) $?
+
+clean:
+ $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump
+
+# Dependencies.
+
+define get_dep =
+ @mkdir -p $(DEPS_DIR)
+ git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1)
+ cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1)))
+endef
+
+define dep_target =
+$(DEPS_DIR)/$(1):
+ $(call get_dep,$(1))
+endef
+
+$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
+
+deps: $(ALL_DEPS_DIRS)
+ @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
+
+clean-deps:
+ @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep clean; done
+
+# Documentation.
+
+docs: clean-docs
+ $(gen_verbose) erl -noshell \
+ -eval 'edoc:application($(PROJECT), ".", []), init:stop().'
+
+clean-docs:
+ $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
+
+# Tests.
+
+$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
+
+build-test-deps: $(ALL_TEST_DEPS_DIRS)
+ @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
+
+build-tests: build-test-deps
+ $(gen_verbose) erlc -v $(ERLC_OPTS) -o test/ \
+ $(wildcard test/*.erl test/*/*.erl) -pa ebin/
+
+CT_RUN = ct_run \
+ -no_auto_compile \
+ -noshell \
+ -pa ebin $(DEPS_DIR)/*/ebin \
+ -dir test \
+ -logdir logs
+# -cover test/cover.spec
+
+CT_SUITES ?=
+CT_SUITES_FULL = $(addsuffix _SUITE,$(CT_SUITES))
+
+tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
+tests: clean deps app build-tests
+ @mkdir -p logs/
+ @$(CT_RUN) -suite $(CT_SUITES_FULL)
+ $(gen_verbose) rm -f test/*.beam
+
+# Dialyzer.
+
+PLT_APPS ?=
+DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
+ -Wunmatched_returns # -Wunderspecs
+
+build-plt: deps app
+ @dialyzer --build_plt --output_plt .$(PROJECT).plt \
+ --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS)
+
+dialyze:
+ @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS)
diff --git a/examples/elixir_hello_world/mix.exs b/examples/elixir_hello_world/mix.exs
index aab48e8..b2e3dd3 100644
--- a/examples/elixir_hello_world/mix.exs
+++ b/examples/elixir_hello_world/mix.exs
@@ -14,7 +14,7 @@ defmodule ElixirHelloWorld.Mixfile do
end
defp deps do
- [ {:ranch, github: "extend/ranch", tag: "0.8.1"},
+ [ {:ranch, github: "extend/ranch", tag: "0.8.3"},
{:cowboy, github: "extend/cowboy"} ]
end
end
diff --git a/guide/req.md b/guide/req.md
index aa3bf4b..96f72b9 100644
--- a/guide/req.md
+++ b/guide/req.md
@@ -31,7 +31,7 @@ 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}`)
+ * `version/1`: the HTTP version (`'HTTP/1.0'` or `'HTTP/1.1'`)
* `peer/1`: the peer address and port number
* `host/1`: the hostname requested
* `host_info/1`: the result of the `[...]` match on the host
@@ -41,8 +41,7 @@ The following access functions are defined in `cowboy_req`:
* `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
+ * `host_url/1`: the requested URL without the path and query string
* `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
diff --git a/manual/cowboy.md b/manual/cowboy.md
new file mode 100644
index 0000000..ebfe615
--- /dev/null
+++ b/manual/cowboy.md
@@ -0,0 +1,89 @@
+cowboy
+======
+
+The `cowboy` module provides convenience functions for
+manipulating Ranch listeners.
+
+Types
+-----
+
+### http_headers() = [{binary(), iodata()}]
+
+> HTTP headers as a list of key/values.
+
+### http_status() = non_neg_integer() | binary()
+
+> HTTP status.
+>
+> A binary status can be used to set a custom message.
+
+### http_version() = 'HTTP/1.1' | 'HTTP/1.0'
+
+> HTTP version.
+
+### onrequest_fun() = fun((cowboy_req:req()) -> cowboy_req:req())
+
+> Fun called immediately after receiving a request.
+>
+> It can perform any operation on the `Req` object, including
+> reading the request body or replying. If a reply is sent,
+> the processing of the request ends here, before any middleware
+> is executed.
+
+### onresponse_fun() = fun((http_status(), http_headers(),
+ iodata(), cowboy_req:req()) -> cowboy_req:req())
+
+> Fun called immediately before sending the response.
+>
+> It can perform any operation on the `Req` object, including
+> reading the request body or replying. If a reply is sent, it
+> overrides the reply initially sent. The callback will not be
+> called again for the new reply.
+
+Exports
+-------
+
+### start_http(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()}
+
+> Types:
+> * Ref = ranch:ref()
+> * NbAcceptors = non_neg_integer()
+> * TransOpts = ranch_tcp:opts()
+> * ProtoOpts = cowboy_protocol:opts()
+>
+> Start listening for HTTP connections. Returns the pid for this
+> listener's supervisor.
+
+### start_https(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()}
+
+> Types:
+> * Ref = ranch:ref()
+> * NbAcceptors = non_neg_integer()
+> * TransOpts = ranch_ssl:opts()
+> * ProtoOpts = cowboy_protocol:opts()
+>
+> Start listening for HTTPS connections. Returns the pid for this
+> listener's supervisor.
+
+### stop_listener(Ref) -> ok
+
+> Types:
+> * Ref = ranch:ref()
+>
+> Stop a previously started listener.
+
+### set_env(Ref, Name, Value) -> ok
+
+> Types:
+> * Ref = ranch:ref()
+> * Name = atom()
+> * Value = any()
+>
+> Set or update an environment value for an already running listener.
+> This will take effect on all subsequent connections.
+
+See also
+--------
+
+The [Ranch guide](http://ninenines.eu/docs/en/ranch/HEAD/guide)
+provides detailed information about how listeners work.
diff --git a/manual/cowboy_app.md b/manual/cowboy_app.md
new file mode 100644
index 0000000..5311109
--- /dev/null
+++ b/manual/cowboy_app.md
@@ -0,0 +1,25 @@
+The Cowboy Application
+======================
+
+Small, fast, modular HTTP server.
+
+Dependencies
+------------
+
+The `cowboy` application uses the Erlang applications `ranch`
+for listening and accepting TCP connections, and `crypto`
+for establishing Websocket connections. These dependencies must
+be loaded for the `cowboy` application to work. In an embedded
+environment this means that they need to be started with the
+`application:start/{1,2}` function before the `cowboy`
+application is started.
+
+The `cowboy` application also uses the Erlang applications
+`public_key` and `ssl` when listening for HTTPS connections.
+These are started automatically if they weren't before.
+
+Environment
+-----------
+
+The `cowboy` application does not define any application
+environment configuration parameters.
diff --git a/manual/cowboy_handler.md b/manual/cowboy_handler.md
new file mode 100644
index 0000000..8d13492
--- /dev/null
+++ b/manual/cowboy_handler.md
@@ -0,0 +1,25 @@
+cowboy_handler
+==============
+
+The `cowboy_handler` middleware executes the handler passed
+through the environment values `handler` and `handler_opts`,
+and add the result of this execution to the environment as
+the value `result`, indicating that the request has been
+handled and received a response.
+
+Environment input:
+ * handler = module()
+ * handler_opts = any()
+
+Environment output:
+ * result = ok
+
+Types
+-----
+
+None.
+
+Exports
+-------
+
+None.
diff --git a/manual/cowboy_http_handler.md b/manual/cowboy_http_handler.md
new file mode 100644
index 0000000..9d283e7
--- /dev/null
+++ b/manual/cowboy_http_handler.md
@@ -0,0 +1,57 @@
+cowboy_http_handler
+===================
+
+The `cowboy_http_handler` behaviour defines the interface used
+by plain HTTP handlers.
+
+Unless noted otherwise, the callbacks will be executed sequentially.
+
+Types
+-----
+
+None.
+
+Callbacks
+---------
+
+### init({TransportName, ProtocolName}, Req, Opts)
+ -> {ok, Req, State} | {shutdown, Req, State}
+
+> Types:
+> * TransportName = tcp | ssl | atom()
+> * ProtocolName = http | atom()
+> * Req = cowboy_req:req()
+> * Opts = any()
+> * State = any()
+>
+> Initialize the state for this request.
+>
+> The `shutdown` return value can be used to skip the `handle/2`
+> call entirely.
+
+### handle(Req, State) -> {ok, Req, State}
+
+> Types:
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Handle the request.
+>
+> This callback is where the request is handled and a response
+> should be sent. If a response is not sent, Cowboy will send
+> a `204 No Content` response automatically.
+
+### terminate(Reason, Req, State) -> ok
+
+> Types:
+> * Reason = {normal, shutdown} | {error, atom()}
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Perform any necessary cleanup of the state.
+>
+> This callback should release any resource currently in use,
+> clear any active timer and reset the process to its original
+> state, as it might be reused for future requests sent on the
+> same connection. Typical plain HTTP handlers rarely need to
+> use it.
diff --git a/manual/cowboy_loop_handler.md b/manual/cowboy_loop_handler.md
new file mode 100644
index 0000000..ccbb9b0
--- /dev/null
+++ b/manual/cowboy_loop_handler.md
@@ -0,0 +1,91 @@
+cowboy_loop_handler
+===================
+
+The `cowboy_loop_handler` behaviour defines the interface used
+by HTTP handlers that do not send a response directly, instead
+requiring a receive loop to process Erlang messages.
+
+This interface is best fit for long-polling types of requests.
+
+The `init/3` callback will always be called, followed by zero
+or more calls to `info/3`. The `terminate/3` will always be
+called last.
+
+Types
+-----
+
+None.
+
+Callbacks
+---------
+
+### init({TransportName, ProtocolName}, Req, Opts)
+ -> {loop, Req, State}
+ | {loop, Req, State, hibernate}
+ | {loop, Req, State, Timeout}
+ | {loop, Req, State, Timeout, hibernate}
+ | {shutdown, Req, State}
+
+> Types:
+> * TransportName = tcp | ssl | atom()
+> * ProtocolName = http | atom()
+> * Req = cowboy_req:req()
+> * Opts = any()
+> * State = any()
+> * Timeout = timeout()
+>
+> Initialize the state for this request.
+>
+> This callback will typically be used to register this process
+> to an event manager or a message queue in order to receive
+> the messages the handler wants to process.
+>
+> The receive loop will run for a duration of up to `Timeout`
+> milliseconds after it last received data from the socket,
+> at which point it will stop and send a `204 No Content` reply.
+> By default this value is set to `infinity`. It is recommended
+> to either set this value or ensure by any other mechanism
+> that the handler will be closed after a certain period of
+> inactivity.
+>
+> The `hibernate` option will hibernate the process until it
+> starts receiving messages.
+>
+> The `shutdown` return value can be used to skip the receive
+> loop entirely.
+
+### info(Info, Req, State) -> {ok, Req, State} | {loop, Req, State}
+ | {loop, Req, State, hibernate}
+
+> Types:
+> * Info = any()
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Handle the Erlang message received.
+>
+> This function will be called every time an Erlang message
+> has been received. The message can be any Erlang term.
+>
+> The `ok` return value can be used to stop the receive loop,
+> typically because a response has been sent.
+>
+> The `hibernate` option will hibernate the process until
+> it receives another message.
+
+### terminate(Reason, Req, State) -> ok
+
+> Types:
+> * Reason = {normal, shutdown} | {normal, timeout} | {error, closed} | {error, overflow} | {error, atom()}
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Perform any necessary cleanup of the state.
+>
+> This callback will typically unregister from any event manager
+> or message queue it registered to in `init/3`.
+>
+> This callback should release any resource currently in use,
+> clear any active timer and reset the process to its original
+> state, as it might be reused for future requests sent on the
+> same connection.
diff --git a/manual/cowboy_middleware.md b/manual/cowboy_middleware.md
new file mode 100644
index 0000000..dd28ff8
--- /dev/null
+++ b/manual/cowboy_middleware.md
@@ -0,0 +1,56 @@
+cowboy_middleware
+=================
+
+The `cowboy_middleware` behaviour defines the interface used
+by Cowboy middleware modules.
+
+Middlewares process the request sequentially in the order they
+are configured.
+
+Types
+-----
+
+### env() = [{atom(), any()}]
+
+> The environment variable.
+>
+> One is created for every request. It is passed to each
+> middleware module executed and subsequently returned,
+> optionally with its contents modified.
+
+Callbacks
+---------
+
+### execute(Req, Env)
+ -> {ok, Req, Env}
+ | {suspend, Module, Function, Args}
+ | {halt, Req}
+ | {error, StatusCode, Req}
+
+> Types:
+> * Req = cowboy_req:req()
+> * Env = env()
+> * Module = module()
+> * Function = atom()
+> * Args = [any()]
+> * StatusCode = cowboy:http_status()
+>
+> Execute the middleware.
+>
+> The `ok` return value indicates that everything went well
+> and that Cowboy should continue processing the request. A
+> response may or may not have been sent.
+>
+> The `suspend` return value will hibernate the process until
+> an Erlang message is received. Note that when resuming, any
+> previous stacktrace information will be gone.
+>
+> The `halt` return value stops Cowboy from doing any further
+> processing of the request, even if there are middlewares
+> that haven't been executed yet. The connection may be left
+> open to receive more requests from the client.
+>
+> The `error` return value sends an error response identified
+> by the `StatusCode` and then proceeds to terminate the
+> connection. Middlewares that haven't been executed yet
+> will not be called.
diff --git a/manual/cowboy_protocol.md b/manual/cowboy_protocol.md
new file mode 100644
index 0000000..86aee9d
--- /dev/null
+++ b/manual/cowboy_protocol.md
@@ -0,0 +1,65 @@
+cowboy_protocol
+===============
+
+The `cowboy_protocol` module implements HTTP/1.1 and HTTP/1.0
+as a Ranch protocol.
+
+Types
+-----
+
+### opts() = [{compress, boolean()}
+ | {env, cowboy_middleware:env()}
+ | {max_empty_lines, non_neg_integer()}
+ | {max_header_name_length, non_neg_integer()}
+ | {max_header_value_length, non_neg_integer()}
+ | {max_headers, non_neg_integer()}
+ | {max_keepalive, non_neg_integer()}
+ | {max_request_line_length, non_neg_integer()}
+ | {middlewares, [module()]}
+ | {onrequest, cowboy:onrequest_fun()}
+ | {onresponse, cowboy:onresponse_fun()}
+ | {timeout, timeout()}]
+
+> Configuration for the HTTP protocol handler.
+>
+> This configuration is passed to Cowboy when starting listeners
+> using `cowboy:start_http/4` or `cowboy:start_https/4` functions.
+>
+> It can be updated without restarting listeners using the
+> Ranch functions `ranch:get_protocol_options/1` and
+> `ranch:set_protocol_options/2`.
+
+Option descriptions
+-------------------
+
+The default value is given next to the option name.
+
+ - compress (false)
+ - When enabled, Cowboy will attempt to compress the response body.
+ - env ([{listener, Ref}])
+ - Initial middleware environment.
+ - max_empty_lines (5)
+ - Maximum number of empty lines before a request.
+ - max_header_name_length (64)
+ - Maximum length of header names.
+ - max_header_value_length (4096)
+ - Maximum length of header values.
+ - max_headers (100)
+ - Maximum number of headers allowed per request.
+ - max_keepalive (100)
+ - Maximum number of requests allowed per connection.
+ - max_request_line_length (4096)
+ - Maximum length of the request line.
+ - middlewares ([cowboy_router, cowboy_handler])
+ - List of middlewares to execute for every requests.
+ - onrequest (undefined)
+ - Fun called every time a request is received.
+ - onresponse (undefined)
+ - Fun called every time a response is sent.
+ - timeout (5000)
+ - Time in ms with no requests before Cowboy closes the connection.
+
+Exports
+-------
+
+None.
diff --git a/manual/cowboy_req.md b/manual/cowboy_req.md
new file mode 100644
index 0000000..f10120a
--- /dev/null
+++ b/manual/cowboy_req.md
@@ -0,0 +1,597 @@
+cowboy_req
+==========
+
+The `cowboy_req` module provides functions to access, manipulate
+and respond to requests.
+
+The functions in this module follow patterns for their return types,
+based on the kind of function.
+
+ * access: `{Value, Req}`
+ * action: `{Result, Req} | {Result, Value, Req} | {error, atom()}`
+ * modification: `Req`
+ * question: `boolean()`
+
+The only exception is the `chunk/2` function which may return `ok`.
+
+Whenever `Req` is returned, you must use this returned value and
+ignore any previous you may have had. This value contains various
+state informations which are necessary for Cowboy to do some lazy
+evaluation or cache results where appropriate.
+
+Types
+-----
+
+### cookie_opts() = [{max_age, non_neg_integer()}
+ | {domain, binary()} | {path, binary()}
+ | {secure, boolean()} | {http_only, boolean()}]
+
+> Cookie options.
+
+### req() - opaque to the user
+
+> The `Req` object.
+>
+> All functions in this module receive a `Req` as argument,
+> and most of them return a new object labelled `Req2` in
+> the function descriptions below.
+
+Request related exports
+-----------------------
+
+### binding(Name, Req) -> binding(Name, Req, undefined)
+### binding(Name, Req, Default) -> {Value, Req2}
+
+> Types:
+> * Name = atom()
+> * Default = any()
+> * Value = binary() | Default
+>
+> Return the value for the given binding.
+
+### bindings(Req) -> {[{Name, Value}], Req2}
+
+> Types:
+> * Name = atom()
+> * Value = binary()
+>
+> Return all bindings.
+
+### cookie(Name, Req) -> cookie(Name, Req, undefined)
+### cookie(Name, Req, Default) -> {Value, Req2}
+
+> Types:
+> * Name = binary()
+> * Default = any()
+> * Value = binary() | Default
+>
+> Return the value for the given cookie.
+>
+> Cookie names are case sensitive.
+
+### cookies(Req) -> {[{Name, Value}], Req2}
+
+> Types:
+> * Name = binary()
+> * Value = binary()
+>
+> Return all cookies.
+
+### header(Name, Req) -> header(Name, Req, undefined)
+### header(Name, Req, Default) -> {Value, Req2}
+
+> Types:
+> * Name = binary()
+> * Default = any()
+> * Value = binary() | Default
+>
+> Return the value for the given header.
+>
+> While header names are case insensitive, this function expects
+> the name to be a lowercase binary.
+
+### headers(Req) -> {Headers, Req2}
+
+> Types:
+> * Headers = cowboy:http_headers()
+>
+> Return all headers.
+
+### host(Req) -> {Host, Req2}
+
+> Types:
+> * Host = binary()
+>
+> Return the requested host.
+
+### host_info(Req) -> {HostInfo, Req2}
+
+> Types:
+> * HostInfo = cowboy_router:tokens() | undefined
+>
+> Return the extra tokens from matching against `...` during routing.
+
+### host_url(Req) -> {HostURL, Req2}
+
+> Types:
+> * HostURL = binary() | undefined
+>
+> Return the requested URL excluding the path component.
+>
+> This function will always return `undefined` until the
+> `cowboy_router` middleware has been executed. This includes
+> the `onrequest` hook.
+
+### meta(Name, Req) -> meta(Name, Req, undefined)
+### meta(Name, Req, Default) -> {Value, Req2}
+
+> Types:
+> * Name = atom()
+> * Default = any()
+> * Value = any()
+>
+> Return metadata about the request.
+
+### method(Req) -> {Method, Req2}
+
+> Types:
+> * Method = binary()
+>
+> Return the method.
+>
+> Methods are case sensitive. Standard methods are always uppercase.
+
+### parse_header(Name, Req) ->
+### parse_header(Name, Req, Default) -> {ok, ParsedValue, Req2}
+ | {undefined, Value, Req2} | {error, badarg}
+
+> Types:
+> * Name = binary()
+> * Default = any()
+> * ParsedValue - see below
+> * Value = any()
+>
+> Parse the given header.
+>
+> While header names are case insensitive, this function expects
+> the name to be a lowercase binary.
+>
+> The `parse_header/2` function will call `parser_header/3` with a
+> different default value depending on the header being parsed. The
+> following table summarizes the default values used.
+>
+> | Header name | Default value |
+> | ----------------- | ------------------ |
+> | transfer-encoding | `[<<"identity">>]` |
+> | Any other header | `undefined` |
+>
+> The parsed value differs depending on the header being parsed. The
+> following table summarizes the different types returned.
+>
+> | Header name | Type |
+> | ---------------------- | ------------------------------------------------- |
+> | accept | `[{{Type, SubType, Params}, Quality, AcceptExt}]` |
+> | accept-charset | `[{Charset, Quality}]` |
+> | accept-encoding | `[{Encoding, Quality}]` |
+> | accept-language | `[{LanguageTag, Quality}]` |
+> | authorization | `{AuthType, Credentials}` |
+> | content-length | `non_neg_integer()` |
+> | content-type | `{Type, SubType, Params}` |
+> | cookie | `[{binary(), binary()}]` |
+> | expect | `[Expect | {Expect, ExpectValue, Params}]` |
+> | if-match | `'*' | [{weak | strong, OpaqueTag}]` |
+> | if-modified-since | `calendar:datetime()` |
+> | if-none-match | `'*' | [{weak | strong, OpaqueTag}]` |
+> | if-unmodified-since | `calendar:datetime()` |
+> | range | `{Unit, [Range]}` |
+> | sec-websocket-protocol | `[binary()]` |
+> | transfer-encoding | `[binary()]` |
+> | upgrade | `[binary()]` |
+> | x-forwarded-for | `[binary()]` |
+>
+> Types for the above table:
+> * Type = SubType = Charset = Encoding = LanguageTag = binary()
+> * AuthType = Expect = OpaqueTag = Unit = binary()
+> * Params = [{binary(), binary()}]
+> * Quality = 0..1000
+> * AcceptExt = [{binary(), binary()} | binary()]
+> * Credentials - see below
+> * Range = {non_neg_integer(), non_neg_integer() | infinity} | neg_integer()
+>
+> The cookie names and values, the values of the sec-websocket-protocol
+> and x-forwarded-for headers, the values in `AcceptExt` and `Params`,
+> the authorization `Credentials`, the `ExpectValue` and `OpaqueTag`
+> are case sensitive. All other values are case insensitive and
+> will be returned as lowercase.
+>
+> The headers accept, accept-encoding and cookie headers can return
+> an empty list. Others will return `{error, badarg}` if the header
+> value is empty.
+>
+> The authorization header parsing code currently only supports basic
+> HTTP authentication. The `Credentials` type is thus `{Username, Password}`
+> with `Username` and `Password` being `binary()`.
+>
+> The range header value `Range` can take three forms:
+> * `{From, To}`: from `From` to `To` units
+> * `{From, infinity}`: everything after `From` units
+> * `-Final`: the final `Final` units
+>
+> An `undefined` tuple will be returned if Cowboy doesn't know how
+> to parse the requested header.
+
+### path(Req) -> {Path, Req2}
+
+> Types:
+> * Path = binary()
+>
+> Return the requested path.
+
+### path_info(Req) -> {PathInfo, Req2}
+
+> Types:
+> * PathInfo = cowboy_router:tokens() | undefined
+>
+> Return the extra tokens from matching against `...` during routing.
+
+### peer(Req) -> {Peer, Req2}
+
+> Types:
+> * Peer = {inet:ip_address(), inet:port_number()}
+>
+> Return the client's IP address and port number.
+
+### port(Req) -> {Port, Req2}
+
+> Types:
+> * Port = inet:port_number()
+>
+> Return the request's port.
+>
+> The port returned by this function is obtained by parsing
+> the host header. It may be different than the actual port
+> the client used to connect to the Cowboy server.
+
+### qs(Req) -> {QueryString, Req2}
+
+> Types:
+> * QueryString = binary()
+>
+> Return the request's query string.
+
+### qs_val(Name, Req) -> qs_val(Name, Req, undefined)
+### qs_val(Name, Req, Default) -> {Value, Req2}
+
+> Types:
+> * Name = binary()
+> * Default = any()
+> * Value = binary() | true
+>
+> Return a value from the request's query string.
+>
+> The value `true` will be returned when the name was found
+> in the query string without an associated value.
+
+### qs_vals(Req) -> {[{Name, Value}], Req2}
+
+> Types:
+> * Name = binary()
+> * Value = binary() | true
+>
+> Return the request's query string as a list of tuples.
+>
+> The value `true` will be returned when the name was found
+> in the query string without an associated value.
+
+### set_meta(Name, Value, Req) -> Req2
+
+> Types:
+> * Name = atom()
+> * Value = any()
+>
+> Set metadata about the request.
+>
+> An existing value will be overwritten.
+
+### url(Req) -> {URL, Req2}
+
+> Types:
+> * URL = binary() | undefined
+>
+> Return the requested URL.
+>
+> This function will always return `undefined` until the
+> `cowboy_router` middleware has been executed. This includes
+> the `onrequest` hook.
+
+### version(Req) -> {Version, Req2}
+
+> Types:
+> * Version = cowboy:http_version()
+>
+> Return the HTTP version used for this request.
+
+Request body related exports
+----------------------------
+
+### body(Req) -> body(8000000, Req)
+### body(MaxLength, Req) -> {ok, Data, Req2} | {error, Reason}
+
+> Types:
+> * MaxLength = non_neg_integer() | infinity
+> * Data = binary()
+> * Reason = chunked | badlength | atom()
+>
+> Return the request body.
+>
+> This function will return `{error, chunked}` if the request
+> body was sent using the chunked transfer-encoding. It will
+> also return `{error, badlength}` if the length of the body
+> exceeds the given `MaxLength`, which is 8MB by default.
+
+### body_length(Req) -> {Length, Req2}
+
+> Types:
+> * Length = non_neg_integer() | undefined
+>
+> Return the length of the request body.
+>
+> The length will only be returned if the request does not
+> use any transfer-encoding and if the content-length header
+> is present.
+
+### body_qs(Req) -> body_qs(16000, Req)
+### body_qs(MaxLength, Req) -> {ok, [{Name, Value}], Req2} | {error, Reason}
+
+> Types:
+> * MaxLength = non_neg_integer() | infinity
+> * Name = binary()
+> * Value = binary() | true
+> * Reason = chunked | badlength | atom()
+>
+> Return the request body as a list of tuples.
+>
+> This function will parse the body assuming the content-type
+> application/x-www-form-urlencoded, commonly used for the
+> query string.
+>
+> This function will return `{error, chunked}` if the request
+> body was sent using the chunked transfer-encoding. It will
+> also return `{error, badlength}` if the length of the body
+> exceeds the given `MaxLength`, which is 16KB by default.
+
+### has_body(Req) -> boolean()
+
+> Return whether the request has a body.
+
+### init_stream(TransferDecode, TransferState, ContentDecode, Req) -> {ok, Req2}
+
+> Types:
+> * TransferDecode = fun((Encoded, TransferState) -> OK | More | Done | {error, Reason})
+> * Encoded = Decoded = Rest = binary()
+> * TransferState = any()
+> * OK = {ok, Decoded, Rest, TransferState}
+> * More = more | {more, Length, Decoded, TransferState}
+> * Done = {done, TotalLength, Rest} | {done, Decoded, TotalLength, Rest}
+> * Length = TotalLength = non_neg_integer()
+> * ContentDecode = fun((Encoded) -> {ok, Decoded} | {error, Reason})
+> * Reason = atom()
+>
+> Initialize streaming of the request body.
+>
+> This function can be used to specify what function to use
+> for decoding the request body, generally specified in the
+> transfer-encoding and content-encoding request headers.
+>
+> Cowboy will properly handle chunked transfer-encoding by
+> default. You do not need to call this function if you do
+> not need to decode other encodings, `stream_body/{1,2}`
+> will perform all the required initialization when it is
+> called the first time.
+
+### skip_body(Req) -> {ok, Req2} | {error, Reason}
+
+> Types:
+> * Reason = atom()
+>
+> Skip the request body.
+>
+> This function will skip the body even if it was partially
+> read before.
+
+### stream_body(Req) -> stream_body(1000000, Req)
+### stream_body(MaxSegmentSize, Req) -> {ok, Data, Req2}
+ | {done, Req2} | {error, Reason}
+
+> Types:
+> * MaxSegmentSize = non_neg_integer()
+> * Data = binary()
+> * Reason = atom()
+>
+> Stream the request body.
+>
+> This function will return a segment of the request body
+> with a size of up to `MaxSegmentSize`, or 1MB by default.
+> This function can be called repeatedly until a `done` tuple
+> is returned, indicating the body has been fully received.
+>
+> Cowboy will properly handle chunked transfer-encoding by
+> default. If any other transfer-encoding or content-encoding
+> has been used for the request, custom decoding functions
+> can be used. They must be specified using `init_stream/4`.
+>
+> After the body has been streamed fully, Cowboy will remove
+> the transfer-encoding header from the `Req` object, and add
+> the content-length header if it wasn't already there.
+
+Response related exports
+------------------------
+
+### chunk(Data, Req) -> ok | {error, Reason}
+
+> Types:
+> * Data = iodata()
+> * Reason = atom()
+>
+> Send a chunk of data.
+>
+> This function should be called as many times as needed
+> to send data chunks after calling `chunked_reply/{2,3}`.
+>
+> When the method is HEAD, no data will actually be sent.
+>
+> If the request uses HTTP/1.0, the data is sent directly
+> without wrapping it in an HTTP/1.1 chunk, providing
+> compatibility with older clients.
+
+### chunked_reply(StatusCode, Req) -> chunked_reply(StatusCode, [], Req)
+### chunked_reply(StatusCode, Headers, Req) -> {ok, Req2}
+
+> Types:
+> * StatusCode = cowboy:http_status()
+> * Headers = cowboy:http_headers()
+>
+> Send a response using chunked transfer-encoding.
+>
+> This function effectively sends the response status line
+> and headers to the client.
+>
+> This function will not send any body set previously. After
+> this call the handler must use the `chunk/2` function
+> repeatedly to send the body in as many chunks as needed.
+>
+> If the request uses HTTP/1.0, the data is sent directly
+> without wrapping it in an HTTP/1.1 chunk, providing
+> compatibility with older clients.
+
+### delete_resp_header(Name, Req) -> Req2
+
+> Types:
+> * Name = binary()
+>
+> Delete the given response header.
+>
+> While header names are case insensitive, this function expects
+> the name to be a lowercase binary.
+
+### has_resp_body(Req) -> boolean()
+
+> Return whether a response body has been set.
+>
+> This function will return false if a response body has
+> been set with a length of 0.
+
+### has_resp_header(Name, Req) -> boolean()
+
+> Types:
+> * Name = binary()
+>
+> Return whether the given response header has been set.
+>
+> While header names are case insensitive, this function expects
+> the name to be a lowercase binary.
+
+### reply(StatusCode, Req) -> reply(StatusCode, [], Req)
+### reply(StatusCode, Headers, Req) - see below
+### reply(StatusCode, Headers, Body, Req) -> {ok, Req2}
+
+> Types:
+> * StatusCode = cowboy:http_status()
+> * Headers = cowboy:http_headers()
+> * Body = iodata()
+>
+> Send a response.
+>
+> This function effectively sends the response status line,
+> headers and body to the client, in a single send function
+> call.
+>
+> The `reply/2` and `reply/3` functions will send the body
+> set previously, if any. The `reply/4` function overrides
+> any body set previously and sends `Body` instead.
+>
+> If a body function was set, and `reply/2` or `reply/3` was
+> used, it will be called before returning.
+>
+> No more data can be sent to the client after this function
+> returns.
+
+### set_resp_body(Body, Req) -> Req2
+
+> Types:
+> * Body = iodata()
+>
+> Set a response body.
+>
+> This body will not be sent if `chunked_reply/{2,3}` or
+> `reply/4` is used, as they override it.
+
+### set_resp_body_fun(Fun, Req) -> Req2
+### set_resp_body_fun(Length, Fun, Req) -> Req2
+
+> Types:
+> * Fun = fun((Socket, Transport) -> ok)
+> * Socket = inet:socket()
+> * Transport = module()
+> * Length = non_neg_integer()
+>
+> Set a fun for sending the response body.
+>
+> If a `Length` is provided, it will be sent in the
+> content-length header in the response. It is recommended
+> to set the length if it can be known in advance.
+>
+> This function will only be called if the response is sent
+> using the `reply/2` or `reply/3` function.
+>
+> The fun will receive the Ranch `Socket` and `Transport` as
+> arguments. Only send and sendfile operations are supported.
+
+### set_resp_body_fun(chunked, Fun, Req) -> Req2
+
+> Types:
+> * Fun = fun((ChunkFun) -> ok)
+> * ChunkFun = fun((iodata()) -> ok | {error, atom()})
+>
+> Set a fun for sending the response body using chunked transfer-encoding.
+>
+> This function will only be called if the response is sent
+> using the `reply/2` or `reply/3` function.
+>
+> The fun will receive another fun as argument. This fun is to
+> be used to send chunks in a similar way to the `chunk/2` function,
+> except the fun only takes one argument, the data to be sent in
+> the chunk.
+
+### set_resp_cookie(Name, Value, Opts, Req) -> Req2
+
+> Types:
+> * Name = iodata()
+> * Value = iodata()
+> * Opts = cookie_opts()
+>
+> Set a cookie in the response.
+>
+> Cookie names are case sensitive.
+
+### set_resp_header(Name, Value, Req) -> Req2
+
+> Types:
+> * Name = binary()
+> * Value = iodata()
+>
+> Set a response header.
+>
+> You should use `set_resp_cookie/4` instead of this function
+> to set cookies.
+
+Misc. exports
+-------------
+
+### compact(Req) -> Req2
+
+> Remove any non-essential data from the `Req` object.
+>
+> Long-lived connections usually only need to manipulate the
+> `Req` object at initialization. Compacting allows saving up
+> memory by discarding extraneous information.
diff --git a/manual/cowboy_rest.md b/manual/cowboy_rest.md
new file mode 100644
index 0000000..4d5862a
--- /dev/null
+++ b/manual/cowboy_rest.md
@@ -0,0 +1,552 @@
+cowboy_rest
+===========
+
+The `cowboy_rest` module implements REST semantics on top of
+the HTTP protocol.
+
+This module cannot be described as a behaviour due to most of
+the callbacks it defines being optional. It has the same
+semantics as a behaviour otherwise.
+
+The only mandatory callback is `init/3`, needed to perform
+the protocol upgrade.
+
+Types
+-----
+
+None.
+
+Meta values
+-----------
+
+### charset
+
+> Type: binary()
+>
+> Negotiated charset.
+>
+> This value may not be defined if no charset was negotiated.
+
+### language
+
+> Type: binary()
+>
+> Negotiated language.
+>
+> This value may not be defined if no language was negotiated.
+
+### media_type
+
+> Type: {binary(), binary(), '*' | [{binary(), binary()}]}
+>
+> Negotiated media-type.
+>
+> The media-type is the content-type, excluding the charset.
+>
+> This value is always defined after the call to
+> `content_types_provided/2`.
+
+Callbacks
+---------
+
+### init({TransportName, ProtocolName}, Req, Opts)
+ -> {upgrade, protocol, cowboy_rest}
+ | {upgrade, protocol, cowboy_rest, Req, Opts}
+
+> Types:
+> * TransportName = tcp | ssl | atom()
+> * ProtocolName = http | atom()
+> * Req = cowboy_req:req()
+> * Opts = any()
+>
+> Upgrade the protocol to `cowboy_rest`.
+>
+> This is the only mandatory callback.
+
+### rest_init(Req, Opts) -> {ok, Req, State}
+
+> Types:
+> * Req = cowboy_req:req()
+> * Opts = any()
+> * State = any()
+>
+> Initialize the state for this request.
+
+### rest_terminate(Req, State) -> ok
+
+> Types:
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Perform any necessary cleanup of the state.
+>
+> This callback should release any resource currently in use,
+> clear any active timer and reset the process to its original
+> state, as it might be reused for future requests sent on the
+> same connection.
+
+### Callback(Req, State) -> {Value, Req, State} | {halt, Req, State}
+
+> Types:
+> * Callback - one of the REST callbacks described below
+> * Req = cowboy_req:req()
+> * State = any()
+> * Value - see the REST callbacks description below
+>
+> Please see the REST callbacks description below for details
+> on the `Value` type, the default value if the callback is
+> not defined, and more general information on when the
+> callback is called and what its intended use is.
+>
+> The `halt` tuple can be returned to stop REST processing.
+> It is up to the resource code to send a reply before that,
+> otherwise a `204 No Content` will be sent.
+
+REST callbacks description
+--------------------------
+
+### allowed_methods
+
+> * Methods: all
+> * Value type: [binary()]
+> * Default value: [<<"GET">>, <<"HEAD">>, <<"OPTIONS">>]
+>
+> Return the list of allowed methods.
+>
+> Methods are case sensitive. Standard methods are always uppercase.
+
+### allow_missing_post
+
+> * Methods: POST
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether POST is allowed when the resource doesn't exist.
+>
+> Returning `true` here means that a new resource will be
+> created. The URL to the created resource should also be
+> returned from the `AcceptResource` callback.
+
+### charsets_provided
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: [binary()]
+> * Skip to the next step if undefined
+>
+> Return the list of charsets the resource provides.
+>
+> The list must be ordered in order of preference.
+>
+> If the accept-charset header was not sent, the first charset
+> in the list will be selected. Otherwise Cowboy will select
+> the most appropriate charset from the list.
+>
+> The chosen charset will be set in the `Req` object as the meta
+> value `charset`.
+>
+> While charsets are case insensitive, this callback is expected
+> to return them as lowercase binary.
+
+### content_types_accepted
+
+> * Methods: POST, PUT, PATCH
+> * No default
+>
+> Types:
+> * Value = [{binary() | {Type, SubType, Params}, AcceptResource}]
+> * Type = SubType = binary()
+> * Params = '*' | [{binary(), binary()}]
+> * AcceptResource = atom()
+>
+> Return the list of content-types the resource accepts.
+>
+> The list must be ordered in order of preference.
+>
+> Each content-type can be given either as a binary string or as
+> a tuple containing the type, subtype and parameters.
+>
+> Cowboy will select the most appropriate content-type from the list.
+> If any parameter is acceptable, then the tuple form should be used
+> with parameters set to `'*'`. If the parameters value is set to `[]`
+> only content-type values with no parameters will be accepted.
+>
+> This function will be called for POST, PUT and PATCH requests.
+> It is entirely possible to define different callbacks for different
+> methods if the handling of the request differs. Simply verify
+> what the method is with `cowboy_req:method/1` and return a
+> different list for each methods.
+>
+> The `AcceptResource` value is the name of the callback that will
+> be called if the content-type matches. It is defined as follow.
+>
+> * Value type: true | {true, URL} | false
+> * No default
+>
+> Process the request body.
+>
+> This function should create or update the resource with the
+> information contained in the request body. This information
+> may be full or partial depending on the request method.
+>
+> If the request body was processed successfully, `true` or
+> `{true, URL}` may be returned. If an URL is provided, the
+> response will redirect the client to the location of the
+> resource.
+>
+> If a response body must be sent, the appropriate media-type, charset
+> and language can be retrieved using the `cowboy_req:meta/{2,3}`
+> functions. The respective keys are `media_type`, `charset`
+> and `language`. The body can be set using `cowboy_req:set_resp_body/2`.
+
+### content_types_provided
+
+> * Methods: GET, HEAD
+> * Default value: [{{<<"text">>, <<"html">>, '*'}, to_html}]
+>
+> Types:
+> * Value = [{binary() | {Type, SubType, Params}, ProvideResource}]
+> * Type = SubType = binary()
+> * Params = '*' | [{binary(), binary()}]
+> * ProvideResource = atom()
+>
+> Return the list of content-types the resource provides.
+>
+> The list must be ordered in order of preference.
+>
+> Each content-type can be given either as a binary string or as
+> a tuple containing the type, subtype and parameters.
+>
+> Cowboy will select the most appropriate content-type from the list.
+> If any parameter is acceptable, then the tuple form should be used
+> with parameters set to `'*'`. If the parameters value is set to `[]`
+> only content-type values with no parameters will be accepted.
+>
+> The `ProvideResource` value is the name of the callback that will
+> be called if the content-type matches. It is defined as follow.
+>
+> * Value type: iodata() | {stream, Fun} | {stream, Len, Fun} | {chunked, ChunkedFun}
+> * No default
+>
+> Return the response body.
+>
+> The response body may be provided directly or through a fun.
+> If a fun tuple is returned, the appropriate `set_resp_body_fun`
+> function will be called. Please refer to the documentation for
+> these functions for more information about the types.
+>
+> The call to this callback happens a good time after the call to
+> `content_types_provided/2`, when it is time to start rendering
+> the response body.
+
+### delete_completed
+
+> * Methods: DELETE
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the delete action has been completed.
+>
+> This function should return `false` if there is no guarantee
+> that the resource gets deleted immediately from the system,
+> including from any internal cache.
+>
+> When this function returns `false`, a `202 Accepted`
+> response will be sent instead of a `200 OK` or `204 No Content`.
+
+### delete_resource
+
+> * Methods: DELETE
+> * Value type: boolean()
+> * Default value: false
+>
+> Delete the resource.
+>
+> The value returned indicates if the action was successful,
+> regardless of whether the resource is immediately deleted
+> from the system.
+
+### expires
+
+> * Methods: GET, HEAD
+> * Value type: calendar:datetime() | undefined
+> * Default value: undefined
+>
+> Return the date of expiration of the resource.
+>
+> This date will be sent as the value of the expires header.
+
+### forbidden
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether access to the resource is forbidden.
+>
+> A `403 Forbidden` response will be sent if this
+> function returns `true`. This status code means that
+> access is forbidden regardless of authentication,
+> and that the request shouldn't be repeated.
+
+### generate_etag
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: binary() | {weak | strong, binary()}
+> * Default value: undefined
+>
+> Return the entity tag of the resource.
+>
+> This value will be sent as the value of the etag header.
+>
+> If a binary is returned, then the value will be parsed
+> to the tuple form automatically.
+
+### is_authorized
+
+> * Methods: all
+> * Value type: true | {false, AuthHeader}
+> * Default value: true
+>
+> Return whether the user is authorized to perform the action.
+>
+> This function should be used to perform any necessary
+> authentication of the user before attempting to perform
+> any action on the resource.
+>
+> If the authentication fails, the value returned will be sent
+> as the value for the www-authenticate header in the
+> `401 Unauthorized` response.
+
+### is_conflict
+
+> * Methods: PUT
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether the put action results in a conflict.
+>
+> A `409 Conflict` response will be sent if this function
+> returns `true`.
+
+### known_content_type
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the content-type is known.
+>
+> This function determines if the server understands the
+> content-type, regardless of its use by the resource.
+
+### known_methods
+
+> * Methods: all
+> * Value type: [binary()]
+> * Default value: [<<"GET">>, <<"HEAD">>, <<"POST">>, <<"PUT">>, <<"PATCH">>, <<"DELETE">>, <<"OPTIONS">>]
+>
+> Return the list of known methods.
+>
+> The full list of methods known by the server should be
+> returned, regardless of their use in the resource.
+>
+> The default value lists the methods Cowboy knows and
+> implement in `cowboy_rest`.
+>
+> Methods are case sensitive. Standard methods are always uppercase.
+
+### languages_provided
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: [binary()]
+> * Skip to the next step if undefined
+>
+> Return the list of languages the resource provides.
+>
+> The list must be ordered in order of preference.
+>
+> If the accept-language header was not sent, the first language
+> in the list will be selected. Otherwise Cowboy will select
+> the most appropriate language from the list.
+>
+> The chosen language will be set in the `Req` object as the meta
+> value `language`.
+>
+> While languages are case insensitive, this callback is expected
+> to return them as lowercase binary.
+
+### last_modified
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: calendar:datetime()
+> * Default value: undefined
+>
+> Return the date of last modification of the resource.
+>
+> This date will be used to test against the if-modified-since
+> and if-unmodified-since headers, and sent as the last-modified
+> header in the response of GET and HEAD requests.
+
+### malformed_request
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether the request is malformed.
+>
+> Cowboy has already performed all the necessary checks
+> by the time this function is called, so few resources
+> are expected to implement it.
+>
+> The check is to be done on the request itself, not on
+> the request body, which is processed later.
+
+### moved_permanently
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: {true, URL} | false
+> * Default value: false
+>
+> Return whether the resource was permanently moved.
+>
+> If it was, its new URL is also returned and sent in the
+> location header in the response.
+
+### moved_temporarily
+
+> * Methods: GET, HEAD, POST, PATCH, DELETE
+> * Value type: {true, URL} | false
+> * Default value: false
+>
+> Return whether the resource was temporarily moved.
+>
+> If it was, its new URL is also returned and sent in the
+> location header in the response.
+
+### multiple_choices
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether there are multiple representations of the resource.
+>
+> This function should be used to inform the client if there
+> are different representations of the resource, for example
+> different content-type. If this function returns `true`,
+> the response body should include information about these
+> different representations using `cowboy_req:set_resp_body/2`.
+> The content-type of the response should be the one previously
+> negociated and that can be obtained by calling
+> `cowboy_req:meta(media_type, Req)`.
+
+### options
+
+> * Methods: OPTIONS
+> * Value type: ok
+> * Default value: ok
+>
+> Handle a request for information.
+>
+> The response should inform the client the communication
+> options available for this resource.
+>
+> By default, Cowboy will send a `200 OK` response with the
+> allow header set.
+
+### previously_existed
+
+> * Methods: GET, HEAD, POST, PATCH, DELETE
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether the resource existed previously.
+
+### resource_exists
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the resource exists.
+>
+> If it exists, conditional headers will be tested before
+> attempting to perform the action. Otherwise, Cowboy will
+> check if the resource previously existed first.
+
+### service_available
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the service is available.
+>
+> This function can be used to test that all relevant backend
+> systems are up and able to handle requests.
+>
+> A `503 Service Unavailable` response will be sent if this
+> function returns `false`.
+
+### uri_too_long
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: false
+>
+> Return whether the requested URI is too long.
+>
+> Cowboy has already performed all the necessary checks
+> by the time this function is called, so few resources
+> are expected to implement it.
+>
+> A `414 Request-URI Too Long` response will be sent if this
+> function returns `true`.
+
+### valid_content_headers
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the content-* headers are valid.
+>
+> This also applies to the transfer-encoding header. This
+> function must return `false` for any unknown content-*
+> headers, or if the headers can't be understood. The
+> function `cowboy_req:parse_header/2` can be used to
+> quickly check the headers can be parsed.
+>
+> A `501 Not Implemented` response will be sent if this
+> function returns `false`.
+
+### valid_entity_length
+
+> * Methods: all
+> * Value type: boolean()
+> * Default value: true
+>
+> Return whether the request body length is within acceptable boundaries.
+>
+> A `413 Request Entity Too Large` response will be sent if this
+> function returns `false`.
+
+### variances
+
+> * Methods: GET, HEAD, POST, PUT, PATCH, DELETE
+> * Value type: [binary()]
+> * Default value: []
+>
+> Return the list of headers that affect the representation of the resource.
+>
+> These request headers return the same resource but with different
+> parameters, like another language or a different content-type.
+>
+> Cowboy will automatically add the accept, accept-language and
+> accept-charset headers to the list if the respective functions
+> were defined in the resource.
+>
+> This operation is performed right before the `resource_exists/2`
+> callback. All responses past that point will contain the vary
+> header which holds this list.
diff --git a/manual/cowboy_router.md b/manual/cowboy_router.md
new file mode 100644
index 0000000..1c6dc04
--- /dev/null
+++ b/manual/cowboy_router.md
@@ -0,0 +1,68 @@
+cowboy_router
+=============
+
+The `cowboy_router` middleware maps the requested host and
+path to the handler to be used for processing the request.
+It uses the dispatch rules compiled from the routes given
+to the `compile/1` function for this purpose. It adds the
+handler name and options to the environment as the values
+`handler` and `handler_opts` respectively.
+
+Environment input:
+ * dispatch = dispatch_rules()
+
+Environment output:
+ * handler = module()
+ * handler_opts = any()
+
+Types
+-----
+
+### bindings() = [{atom(), binary()}]
+
+> List of bindings found during routing.
+
+### constraints() = [IntConstraint | FunConstraint]
+
+> Types:
+> * IntConstraint = {atom(), int}
+> * FunConstraint = {atom(), function, Fun}
+> * Fun = fun((binary()) -> true | {true, any()} | false)
+>
+> List of constraints to apply to the bindings.
+>
+> The int constraint will convert the binding to an integer.
+> The fun constraint allows writing custom code for checking
+> the bindings. Returning a new value from that fun allows
+> replacing the current binding with a new value.
+
+### dispatch_rules() - opaque to the user
+
+> Rules for dispatching request used by Cowboy.
+
+### routes() = [{Host, Paths} | {Host, constraints(), Paths}]
+
+> Types:
+> * Host = Path = '_' | iodata()
+> * Paths = [{Path, Handler, Opts} | {Path, constraints(), Handler, Opts}]
+> * Handler = module()
+> * Opts = any()
+>
+> Human readable list of routes mapping hosts and paths to handlers.
+>
+> The syntax for routes is defined in the user guide.
+
+### tokens() = [binary()]
+
+> List of host_info and path_info tokens found during routing.
+
+Exports
+-------
+
+### compile(Routes) -> Dispatch
+
+> Types:
+> * Routes = routes()
+> * Dispatch = dispatch_rules()
+>
+> Compile the routes for use by Cowboy.
diff --git a/manual/cowboy_sub_protocol.md b/manual/cowboy_sub_protocol.md
new file mode 100644
index 0000000..a8ecae1
--- /dev/null
+++ b/manual/cowboy_sub_protocol.md
@@ -0,0 +1,34 @@
+cowboy_sub_protocol
+===================
+
+The `cowboy_sub_protocol` behaviour defines the interface used
+by modules that implement a protocol on top of HTTP.
+
+Types
+-----
+
+None.
+
+Callbacks
+---------
+
+### upgrade(Req, Env, Handler, Opts)
+ -> {ok, Req, Env}
+ | {suspend, Module, Function, Args}
+ | {halt, Req}
+ | {error, StatusCode, Req}
+
+> Types:
+> * Req = cowboy_req:req()
+> * Env = env()
+> * Handler = module()
+> * Opts = any()
+> * Module = module()
+> * Function = atom()
+> * Args = [any()]
+> * StatusCode = cowboy:http_status()
+>
+> Upgrade the protocol.
+>
+> Please refer to the `cowboy_middleware` manual for a
+> description of the return values.
diff --git a/manual/cowboy_websocket.md b/manual/cowboy_websocket.md
new file mode 100644
index 0000000..ae3ca1b
--- /dev/null
+++ b/manual/cowboy_websocket.md
@@ -0,0 +1,34 @@
+cowboy_websocket
+================
+
+The `cowboy_websocket` module implements the Websocket protocol.
+
+The callbacks for websocket handlers are defined in the manual
+for the `cowboy_websocket_handler` behaviour.
+
+Types
+-----
+
+### close_code() = 1000..4999
+
+> Reason for closing the connection.
+
+### frame() = close | ping | pong
+ | {text | binary | close | ping | pong, iodata()}
+ | {close, close_code(), iodata()}
+
+> Frames that can be sent to the client.
+
+Meta values
+-----------
+
+### websocket_version
+
+> Type: 7 | 8 | 13
+>
+> The version of the Websocket protocol being used.
+
+Exports
+-------
+
+None.
diff --git a/manual/cowboy_websocket_handler.md b/manual/cowboy_websocket_handler.md
new file mode 100644
index 0000000..f0480b1
--- /dev/null
+++ b/manual/cowboy_websocket_handler.md
@@ -0,0 +1,131 @@
+cowboy_websocket_handler
+========================
+
+The `cowboy_websocket_handler` behaviour defines the interface used
+by Websocket handlers.
+
+The `init/3` and `websocket_init/3` callbacks will always be called,
+followed by zero or more calls to `websocket_handle/3` and
+`websocket_info/3`. The `websocket_terminate/3` will always
+be called last.
+
+Types
+-----
+
+None.
+
+Callbacks
+---------
+
+### init({TransportName, ProtocolName}, Req, Opts)
+ -> {upgrade, protocol, cowboy_websocket}
+ | {upgrade, protocol, cowboy_websocket, Req, Opts}
+
+> Types:
+> * TransportName = tcp | ssl | atom()
+> * ProtocolName = http | atom()
+> * Req = cowboy_req:req()
+> * Opts = any()
+>
+> Upgrade the protocol to `cowboy_websocket`.
+
+### websocket_init(TransportName, Req, Opts)
+ -> {ok, Req, State}
+ | {ok, Req, State, hibernate}
+ | {ok, Req, State, Timeout}
+ | {ok, Req, State, Timeout, hibernate}
+ | {shutdown, Req}
+
+> Types:
+> * TransportName = tcp | ssl | atom()
+> * Req = cowboy_req:req()
+> * Opts = any()
+> * State = any()
+> * Timeout = timeout()
+>
+> Initialize the state for this session.
+>
+> This function is called before the upgrade to Websocket occurs.
+> It can be used to negotiate Websocket protocol extensions
+> with the client. It will typically be used to register this process
+> to an event manager or a message queue in order to receive
+> the messages the handler wants to process.
+>
+> The connection will stay up for a duration of up to `Timeout`
+> milliseconds after it last received data from the socket,
+> at which point it will stop and close the connection.
+> By default this value is set to `infinity`. It is recommended
+> to either set this value or ensure by any other mechanism
+> that the handler will be closed after a certain period of
+> inactivity.
+>
+> The `hibernate` option will hibernate the process until it
+> starts receiving either data from the Websocket connection
+> or Erlang messages.
+>
+> The `shutdown` return value can be used to close the connection
+> before upgrading to Websocket.
+
+### websocket_handle(InFrame, Req, State)
+ -> {ok, Req, State}
+ | {ok, Req, State, hibernate}
+ | {reply, OutFrame | [OutFrame], Req, State}
+ | {reply, OutFrame | [OutFrame], Req, State, hibernate}
+ | {shutdown, Req, State}
+
+> Types:
+> * InFrame = {text | binary | ping | pong, binary()}
+> * Req = cowboy_req:req()
+> * State = any()
+> * OutFrame = cowboy_websocket:frame()
+>
+> Handle the data received from the Websocket connection.
+>
+> This function will be called every time data is received
+> from the Websocket connection.
+>
+> The `shutdown` return value can be used to close the
+> connection. A close reply will also result in the connection
+> being closed.
+>
+> The `hibernate` option will hibernate the process until
+> it receives new data from the Websocket connection or an
+> Erlang message.
+
+### websocket_info(Info, Req, State)
+ -> {ok, Req, State}
+ | {ok, Req, State, hibernate}
+ | {reply, OutFrame | [OutFrame], Req, State}
+ | {reply, OutFrame | [OutFrame], Req, State, hibernate}
+ | {shutdown, Req, State}
+
+> Types:
+> * Info = any()
+> * Req = cowboy_req:req()
+> * State = any()
+> * OutFrame = cowboy_websocket:frame()
+>
+> Handle the Erlang message received.
+>
+> This function will be called every time an Erlang message
+> has been received. The message can be any Erlang term.
+>
+> The `shutdown` return value can be used to close the
+> connection. A close reply will also result in the connection
+> being closed.
+>
+> The `hibernate` option will hibernate the process until
+> it receives another message or new data from the Websocket
+> connection.
+
+### websocket_terminate(Reason, Req, State) -> ok
+
+> Types:
+> * Reason = {normal, shutdown | timeout} | {remote, closed} | {remote, cowboy_websocket:close_code(), binary()} | {error, badencoding | badframe | closed | atom()}
+> * Req = cowboy_req:req()
+> * State = any()
+>
+> Perform any necessary cleanup of the state.
+>
+> The connection will be closed and the process stopped right
+> after this call.
diff --git a/manual/toc.md b/manual/toc.md
new file mode 100644
index 0000000..d05696e
--- /dev/null
+++ b/manual/toc.md
@@ -0,0 +1,18 @@
+Cowboy Function Reference
+=========================
+
+The function reference documents the public interface of Cowboy.
+
+ * [The Cowboy Application](cowboy_app.md)
+ * [cowboy](cowboy.md)
+ * [cowboy_handler](cowboy_handler.md)
+ * [cowboy_http_handler](cowboy_http_handler.md)
+ * [cowboy_loop_handler](cowboy_loop_handler.md)
+ * [cowboy_middleware](cowboy_middleware.md)
+ * [cowboy_protocol](cowboy_protocol.md)
+ * [cowboy_req](cowboy_req.md)
+ * [cowboy_rest](cowboy_rest.md)
+ * [cowboy_router](cowboy_router.md)
+ * [cowboy_sub_protocol](cowboy_sub_protocol.md)
+ * [cowboy_websocket](cowboy_websocket.md)
+ * [cowboy_websocket_handler](cowboy_websocket_handler.md)
diff --git a/rebar.config b/rebar.config
index 7367e6d..bab6fa3 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,3 +1,3 @@
{deps, [
- {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.1"}}
+ {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.3"}}
]}.
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index 92dd124..e9cfcb8 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -17,7 +17,7 @@
{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.8.4"},
+ {vsn, "0.8.5"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
{applications, [
diff --git a/src/cowboy.erl b/src/cowboy.erl
index 257172d..16445e1 100644
--- a/src/cowboy.erl
+++ b/src/cowboy.erl
@@ -17,25 +17,56 @@
-export([start_http/4]).
-export([start_https/4]).
+-export([start_spdy/4]).
-export([stop_listener/1]).
-export([set_env/3]).
+-type http_headers() :: [{binary(), iodata()}].
+-export_type([http_headers/0]).
+
+-type http_status() :: non_neg_integer() | binary().
+-export_type([http_status/0]).
+
+-type http_version() :: 'HTTP/1.1' | 'HTTP/1.0'.
+-export_type([http_version/0]).
+
+-type onrequest_fun() :: fun((Req) -> Req).
+-export_type([onrequest_fun/0]).
+
+-type onresponse_fun() ::
+ fun((http_status(), http_headers(), iodata(), Req) -> Req).
+-export_type([onresponse_fun/0]).
+
%% @doc Start an HTTP listener.
--spec start_http(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+-spec start_http(ranch:ref(), non_neg_integer(), ranch_tcp:opts(),
+ cowboy_protocol:opts()) -> {ok, pid()}.
start_http(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_tcp, TransOpts, cowboy_protocol, ProtoOpts).
%% @doc Start an HTTPS listener.
--spec start_https(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+-spec start_https(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
+ cowboy_protocol:opts()) -> {ok, pid()}.
start_https(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_ssl, TransOpts, cowboy_protocol, ProtoOpts).
+%% @doc Start a SPDY listener.
+-spec start_spdy(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts)
+ when is_integer(NbAcceptors), NbAcceptors > 0 ->
+ TransOpts2 = [
+ {connection_type, supervisor},
+ {next_protocols_advertised,
+ [<<"spdy/3">>, <<"http/1.1">>, <<"http/1.0">>]}
+ |TransOpts],
+ ranch:start_listener(Ref, NbAcceptors,
+ ranch_ssl, TransOpts2, cowboy_spdy, ProtoOpts).
+
%% @doc Stop a listener.
--spec stop_listener(any()) -> ok.
+-spec stop_listener(ranch:ref()) -> ok.
stop_listener(Ref) ->
ranch:stop_listener(Ref).
@@ -44,7 +75,7 @@ stop_listener(Ref) ->
%% Allows you to update live an environment value used by middlewares.
%% This function is primarily intended to simplify updating the dispatch
%% list used for routing.
--spec set_env(any(), atom(), any()) -> ok.
+-spec set_env(ranch:ref(), atom(), any()) -> ok.
set_env(Ref, Name, Value) ->
Opts = ranch:get_protocol_options(Ref),
{_, Env} = lists:keyfind(env, 1, Opts),
diff --git a/src/cowboy_client.erl b/src/cowboy_client.erl
index 4d958b1..b5f96b3 100644
--- a/src/cowboy_client.erl
+++ b/src/cowboy_client.erl
@@ -40,7 +40,7 @@
timeout = 5000 :: timeout(), %% @todo Configurable.
buffer = <<>> :: binary(),
connection = keepalive :: keepalive | close,
- version = {1, 1} :: cowboy_http:version(),
+ version = 'HTTP/1.1' :: cowboy:http_version(),
response_body = undefined :: undefined | non_neg_integer()
}).
@@ -91,7 +91,7 @@ request(Method, URL, Headers, Body, Client=#client{
wait -> connect(Transport, Host, Port, Client);
request -> {ok, Client}
end,
- VersionBin = cowboy_http:version_to_binary(Version),
+ VersionBin = atom_to_binary(Version, latin1),
%% @todo do keepalive too, allow override...
Headers2 = [
{<<"host">>, FullHost},
@@ -173,7 +173,7 @@ stream_status(Client=#client{state=State, buffer=Buffer})
when State =:= request ->
case binary:split(Buffer, <<"\r\n">>) of
[Line, Rest] ->
- parse_status(Client#client{state=response, buffer=Rest}, Line);
+ parse_version(Client#client{state=response, buffer=Rest}, Line);
_ ->
case recv(Client) of
{ok, Data} ->
@@ -184,11 +184,13 @@ stream_status(Client=#client{state=State, buffer=Buffer})
end
end.
-parse_status(Client, << "HTTP/", High, ".", Low, " ",
- S3, S2, S1, " ", StatusStr/binary >>)
- when High >= $0, High =< $9, Low >= $0, Low =< $9,
- S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 ->
- Version = {High - $0, Low - $0},
+parse_version(Client, << "HTTP/1.1 ", Rest/binary >>) ->
+ parse_status(Client, Rest, 'HTTP/1.1');
+parse_version(Client, << "HTTP/1.0 ", Rest/binary >>) ->
+ parse_status(Client, Rest, 'HTTP/1.0').
+
+parse_status(Client, << S3, S2, S1, " ", StatusStr/binary >>, Version)
+ when S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 ->
Status = (S3 - $0) * 100 + (S2 - $0) * 10 + S1 - $0,
{ok, Status, StatusStr, Client#client{version=Version}}.
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl
index e040554..2074b4e 100644
--- a/src/cowboy_handler.erl
+++ b/src/cowboy_handler.erl
@@ -105,7 +105,7 @@ handler_init(Req, State, Handler, HandlerOpts) ->
-> {ok, Req, Env}
| {suspend, module(), atom(), any()}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade_protocol(Req, #state{env=Env},
Handler, HandlerOpts, Module) ->
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index f889b52..af60dd9 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -46,21 +46,12 @@
%% Interpretation.
-export([cookie_to_iodata/3]).
--export([version_to_binary/1]).
-export([urldecode/1]).
-export([urldecode/2]).
-export([urlencode/1]).
-export([urlencode/2]).
-export([x_www_form_urlencoded/1]).
--type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
--type headers() :: [{binary(), iodata()}].
--type status() :: non_neg_integer() | binary().
-
--export_type([version/0]).
--export_type([headers/0]).
--export_type([status/0]).
-
%% Parsing.
%% @doc Parse a non-empty list of the given type.
@@ -802,7 +793,7 @@ qvalue(Data, Fun, Q, _M) ->
%% Only Basic authorization is supported so far.
-spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}.
authorization(UserPass, Type = <<"basic">>) ->
- cowboy_http:whitespace(UserPass,
+ whitespace(UserPass,
fun(D) ->
authorization_basic_userid(base64:mime_decode(D),
fun(Rest, Userid) ->
@@ -813,7 +804,7 @@ authorization(UserPass, Type = <<"basic">>) ->
end)
end);
authorization(String, Type) ->
- cowboy_http:whitespace(String, fun(Rest) -> {Type, Rest} end).
+ whitespace(String, fun(Rest) -> {Type, Rest} end).
%% @doc Parse user credentials.
-spec authorization_basic_userid(binary(), fun()) -> any().
@@ -849,12 +840,12 @@ authorization_basic_password(<<C, Rest/binary>>, Fun, Acc) ->
Unit :: binary(),
Range :: {non_neg_integer(), non_neg_integer() | infinity} | neg_integer().
range(Data) ->
- cowboy_http:token_ci(Data, fun range/2).
+ token_ci(Data, fun range/2).
range(Data, Token) ->
whitespace(Data,
fun(<<"=", Rest/binary>>) ->
- case cowboy_http:list(Rest, fun range_beginning/2) of
+ case list(Rest, fun range_beginning/2) of
{error, badarg} ->
{error, badarg};
Ranges ->
@@ -1001,11 +992,6 @@ cookie_to_iodata(Name, Value, Opts) ->
[Name, <<"=">>, Value, <<"; Version=1">>,
MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin].
-%% @doc Convert an HTTP version tuple to its binary form.
--spec version_to_binary(version()) -> binary().
-version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
-version_to_binary({1, 0}) -> <<"HTTP/1.0">>.
-
%% @doc Decode a URL encoded binary.
%% @equiv urldecode(Bin, crash)
-spec urldecode(binary()) -> binary().
diff --git a/src/cowboy_http_handler.erl b/src/cowboy_http_handler.erl
index 0393153..3ad8f88 100644
--- a/src/cowboy_http_handler.erl
+++ b/src/cowboy_http_handler.erl
@@ -35,6 +35,8 @@
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout} %% Only occurs in loop handlers.
+ | {error, closed} %% Only occurs in loop handlers.
+ | {error, overflow} %% Only occurs in loop handlers.
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
diff --git a/src/cowboy_loop_handler.erl b/src/cowboy_loop_handler.erl
index f8d008f..af49e57 100644
--- a/src/cowboy_loop_handler.erl
+++ b/src/cowboy_loop_handler.erl
@@ -41,6 +41,8 @@
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout}
+ | {error, closed}
+ | {error, overflow}
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
diff --git a/src/cowboy_middleware.erl b/src/cowboy_middleware.erl
index 0c1ca77..40c9407 100644
--- a/src/cowboy_middleware.erl
+++ b/src/cowboy_middleware.erl
@@ -30,7 +30,7 @@
-callback execute(Req, Env)
-> {ok, Req, Env}
- | {suspend, module(), atom(), any()}
+ | {suspend, module(), atom(), [any()]}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::env().
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index be351b7..b42f524 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -54,13 +54,22 @@
%% Internal.
-export([init/4]).
-export([parse_request/3]).
+-export([parse_host/2]).
-export([resume/6]).
--type onrequest_fun() :: fun((Req) -> Req).
--type onresponse_fun() ::
- fun((cowboy_http:status(), cowboy_http:headers(), iodata(), Req) -> Req).
--export_type([onrequest_fun/0]).
--export_type([onresponse_fun/0]).
+-type opts() :: [{compress, boolean()}
+ | {env, cowboy_middleware:env()}
+ | {max_empty_lines, non_neg_integer()}
+ | {max_header_name_length, non_neg_integer()}
+ | {max_header_value_length, non_neg_integer()}
+ | {max_headers, non_neg_integer()}
+ | {max_keepalive, non_neg_integer()}
+ | {max_request_line_length, non_neg_integer()}
+ | {middlewares, [module()]}
+ | {onrequest, cowboy:onrequest_fun()}
+ | {onresponse, cowboy:onresponse_fun()}
+ | {timeout, timeout()}].
+-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
@@ -68,8 +77,8 @@
middlewares :: [module()],
compress :: boolean(),
env :: cowboy_middleware:env(),
- onrequest :: undefined | onrequest_fun(),
- onresponse = undefined :: undefined | onresponse_fun(),
+ onrequest :: undefined | cowboy:onrequest_fun(),
+ onresponse = undefined :: undefined | cowboy:onresponse_fun(),
max_empty_lines :: non_neg_integer(),
req_keepalive = 1 :: non_neg_integer(),
max_keepalive :: non_neg_integer(),
@@ -84,7 +93,7 @@
%% API.
%% @doc Start an HTTP protocol process.
--spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
+-spec start_link(ranch:ref(), inet:socket(), module(), opts()) -> {ok, pid()}.
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
@@ -100,7 +109,7 @@ get_value(Key, Opts, Default) ->
end.
%% @private
--spec init(any(), inet:socket(), module(), any()) -> ok.
+-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, Opts) ->
Compress = get_value(compress, Opts, false),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
@@ -202,7 +211,7 @@ parse_method(<< C, Rest/bits >>, State, SoFar) ->
parse_uri(<< $\r, _/bits >>, State, _) ->
error_terminate(400, State);
parse_uri(<< "* ", Rest/bits >>, State, Method) ->
- parse_version(Rest, State, Method, <<"*">>, <<>>, <<>>);
+ parse_version(Rest, State, Method, <<"*">>, <<>>);
parse_uri(<< "http://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "https://", Rest/bits >>, State, Method) ->
@@ -220,61 +229,61 @@ parse_uri_skip_host(<< C, Rest/bits >>, State, Method) ->
parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) ->
case C of
$\r -> error_terminate(400, State);
- $\s -> parse_version(Rest, State, Method, SoFar, <<>>, <<>>);
+ $\s -> parse_version(Rest, State, Method, SoFar, <<>>);
$? -> parse_uri_query(Rest, State, Method, SoFar, <<>>);
- $# -> parse_uri_fragment(Rest, State, Method, SoFar, <<>>, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<>>);
_ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>)
end.
parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) ->
case C of
$\r -> error_terminate(400, S);
- $\s -> parse_version(Rest, S, M, P, SoFar, <<>>);
- $# -> parse_uri_fragment(Rest, S, M, P, SoFar, <<>>);
+ $\s -> parse_version(Rest, S, M, P, SoFar);
+ $# -> skip_uri_fragment(Rest, S, M, P, SoFar);
_ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>)
end.
-parse_uri_fragment(<< C, Rest/bits >>, S, M, P, Q, SoFar) ->
+skip_uri_fragment(<< C, Rest/bits >>, S, M, P, Q) ->
case C of
$\r -> error_terminate(400, S);
- $\s -> parse_version(Rest, S, M, P, Q, SoFar);
- _ -> parse_uri_fragment(Rest, S, M, P, Q, << SoFar/binary, C >>)
+ $\s -> parse_version(Rest, S, M, P, Q);
+ _ -> skip_uri_fragment(Rest, S, M, P, Q)
end.
-parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q, F) ->
- parse_header(Rest, S, M, P, Q, F, {1, 1}, []);
-parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q, F) ->
- parse_header(Rest, S, M, P, Q, F, {1, 0}, []);
-parse_version(_, State, _, _, _, _) ->
+parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q) ->
+ parse_header(Rest, S, M, P, Q, 'HTTP/1.1', []);
+parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q) ->
+ parse_header(Rest, S, M, P, Q, 'HTTP/1.0', []);
+parse_version(_, State, _, _, _) ->
error_terminate(505, State).
%% Stop receiving data if we have more than allowed number of headers.
-wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, _, Headers)
+wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, Headers)
when length(Headers) >= MaxHeaders ->
error_terminate(400, State);
wait_header(Buffer, State=#state{socket=Socket, transport=Transport,
- until=Until}, M, P, Q, F, V, H) ->
+ until=Until}, M, P, Q, V, H) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_header(<< Buffer/binary, Data/binary >>,
- State, M, P, Q, F, V, H);
+ State, M, P, Q, V, H);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, F, V, Headers) ->
- request(Rest, S, M, P, Q, F, V, lists:reverse(Headers));
+parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, V, Headers) ->
+ request(Rest, S, M, P, Q, V, lists:reverse(Headers));
parse_header(Buffer, State=#state{max_header_name_length=MaxLength},
- M, P, Q, F, V, H) ->
+ M, P, Q, V, H) ->
case match_colon(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
- wait_header(Buffer, State, M, P, Q, F, V, H);
+ wait_header(Buffer, State, M, P, Q, V, H);
_ ->
- parse_hd_name(Buffer, State, M, P, Q, F, V, H, <<>>)
+ parse_hd_name(Buffer, State, M, P, Q, V, H, <<>>)
end.
match_colon(<< $:, _/bits >>, N) ->
@@ -290,73 +299,73 @@ match_colon(_, _) ->
%% ... Sorry for your eyes.
%%
%% But let's be honest, that's still pretty readable.
-parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, F, V, H, SoFar) ->
+parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, V, H, SoFar) ->
case C of
- $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, SoFar);
- $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar);
- $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar);
- $A -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $a >>);
- $B -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $b >>);
- $C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $c >>);
- $D -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $d >>);
- $E -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $e >>);
- $F -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $f >>);
- $G -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $g >>);
- $H -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $h >>);
- $I -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $i >>);
- $J -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $j >>);
- $K -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $k >>);
- $L -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $l >>);
- $M -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $m >>);
- $N -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $n >>);
- $O -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $o >>);
- $P -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $p >>);
- $Q -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $q >>);
- $R -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $r >>);
- $S -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $s >>);
- $T -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $t >>);
- $U -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $u >>);
- $V -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $v >>);
- $W -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $w >>);
- $X -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $x >>);
- $Y -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $y >>);
- $Z -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $z >>);
- C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, C >>)
+ $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar);
+ $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
+ $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
+ $A -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $a >>);
+ $B -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $b >>);
+ $C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $c >>);
+ $D -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $d >>);
+ $E -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $e >>);
+ $F -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $f >>);
+ $G -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $g >>);
+ $H -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $h >>);
+ $I -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $i >>);
+ $J -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $j >>);
+ $K -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $k >>);
+ $L -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $l >>);
+ $M -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $m >>);
+ $N -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $n >>);
+ $O -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $o >>);
+ $P -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $p >>);
+ $Q -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $q >>);
+ $R -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $r >>);
+ $S -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $s >>);
+ $T -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $t >>);
+ $U -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $u >>);
+ $V -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $v >>);
+ $W -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $w >>);
+ $X -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $x >>);
+ $Y -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $y >>);
+ $Z -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $z >>);
+ C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, C >>)
end.
-parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, F, V, H, Name) ->
+parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, V, H, Name) ->
case C of
- $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name);
- $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name);
- $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, Name)
+ $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
+ $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
+ $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, Name)
end.
wait_hd_before_value(Buffer, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, H, N) ->
+ M, P, Q, V, H, N) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_hd_before_value(<< Buffer/binary, Data/binary >>,
- State, M, P, Q, F, V, H, N);
+ State, M, P, Q, V, H, N);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, F, V, H, N) ->
- parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N);
-parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, F, V, H, N) ->
- parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N);
+parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, V, H, N) ->
+ parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, V, H, N) ->
+ parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
parse_hd_before_value(Buffer, State=#state{
- max_header_value_length=MaxLength}, M, P, Q, F, V, H, N) ->
+ max_header_value_length=MaxLength}, M, P, Q, V, H, N) ->
case match_eol(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
- wait_hd_before_value(Buffer, State, M, P, Q, F, V, H, N);
+ wait_hd_before_value(Buffer, State, M, P, Q, V, H, N);
_ ->
- parse_hd_value(Buffer, State, M, P, Q, F, V, H, N, <<>>)
+ parse_hd_value(Buffer, State, M, P, Q, V, H, N, <<>>)
end.
%% We completely ignore the first argument which is always
@@ -365,10 +374,10 @@ parse_hd_before_value(Buffer, State=#state{
%% operations for no reasons.
wait_hd_value(_, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, H, N, SoFar) ->
+ M, P, Q, V, H, N, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
- parse_hd_value(Data, State, M, P, Q, F, V, H, N, SoFar);
+ parse_hd_value(Data, State, M, P, Q, V, H, N, SoFar);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
@@ -380,51 +389,51 @@ wait_hd_value(_, State=#state{
%% the critical path, but forces us to have a special function.
wait_hd_value_nl(_, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, Headers, Name, SoFar) ->
+ M, P, Q, V, Headers, Name, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, << C, Data/bits >>} when C =:= $\s; C =:= $\t ->
- parse_hd_value(Data, State, M, P, Q, F, V, Headers, Name, SoFar);
+ parse_hd_value(Data, State, M, P, Q, V, Headers, Name, SoFar);
{ok, Data} ->
- parse_header(Data, State, M, P, Q, F, V, [{Name, SoFar}|Headers]);
+ parse_header(Data, State, M, P, Q, V, [{Name, SoFar}|Headers]);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, F, V, Headers, Name, SoFar) ->
+parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, V, Headers, Name, SoFar) ->
case Rest of
<< $\n >> ->
- wait_hd_value_nl(<<>>, S, M, P, Q, F, V, Headers, Name, SoFar);
+ wait_hd_value_nl(<<>>, S, M, P, Q, V, Headers, Name, SoFar);
<< $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
- parse_hd_value(Rest2, S, M, P, Q, F, V, Headers, Name, SoFar);
+ parse_hd_value(Rest2, S, M, P, Q, V, Headers, Name, SoFar);
<< $\n, Rest2/bits >> ->
- parse_header(Rest2, S, M, P, Q, F, V, [{Name, SoFar}|Headers])
+ parse_header(Rest2, S, M, P, Q, V, [{Name, SoFar}|Headers])
end;
-parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, F, V, H, N, SoFar) ->
- parse_hd_value(Rest, S, M, P, Q, F, V, H, N, << SoFar/binary, C >>);
+parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, V, H, N, SoFar) ->
+ parse_hd_value(Rest, S, M, P, Q, V, H, N, << SoFar/binary, C >>);
parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength},
- _, _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength ->
+ _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength ->
error_terminate(400, State);
-parse_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar) ->
- wait_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar).
+parse_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar) ->
+ wait_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar).
-request(B, State=#state{transport=Transport}, M, P, Q, F, Version, Headers) ->
+request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) ->
case lists:keyfind(<<"host">>, 1, Headers) of
- false when Version =:= {1, 1} ->
+ false when Version =:= 'HTTP/1.1' ->
error_terminate(400, State);
false ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
<<>>, default_port(Transport:name()));
{_, RawHost} ->
case catch parse_host(RawHost, <<>>) of
{'EXIT', _} ->
error_terminate(400, State);
{Host, undefined} ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
Host, default_port(Transport:name()));
{Host, Port} ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
Host, Port)
end
end.
@@ -476,11 +485,11 @@ parse_host(<< C, Rest/bits >>, Acc) ->
request(Buffer, State=#state{socket=Socket, transport=Transport,
req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive,
compress=Compress, onresponse=OnResponse},
- Method, Path, Query, Fragment, Version, Headers, Host, Port) ->
+ Method, Path, Query, Version, Headers, Host, Port) ->
case Transport:peername(Socket) of
{ok, Peer} ->
Req = cowboy_req:new(Socket, Transport, Peer, Method, Path,
- Query, Fragment, Version, Headers, Host, Port, Buffer,
+ Query, Version, Headers, Host, Port, Buffer,
ReqKeepalive < MaxKeepalive, Compress, OnResponse),
onrequest(Req, State);
{error, _} ->
@@ -565,7 +574,7 @@ next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout},
end.
%% Only send an error reply if there is no resp_sent message.
--spec error_terminate(cowboy_http:status(), cowboy_req:req(), #state{}) -> ok.
+-spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok.
error_terminate(Code, Req, State) ->
receive
{cowboy_req, resp_sent} -> ok
@@ -576,14 +585,14 @@ error_terminate(Code, Req, State) ->
terminate(State).
%% Only send an error reply if there is no resp_sent message.
--spec error_terminate(cowboy_http:status(), #state{}) -> ok.
+-spec error_terminate(cowboy:http_status(), #state{}) -> ok.
error_terminate(Code, State=#state{socket=Socket, transport=Transport,
compress=Compress, onresponse=OnResponse}) ->
receive
{cowboy_req, resp_sent} -> ok
after 0 ->
_ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport,
- undefined, <<"GET">>, <<>>, <<>>, <<>>, {1, 1}, [], <<>>,
+ undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>,
undefined, <<>>, false, Compress, OnResponse)),
ok
end,
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 5b8157d..093663c 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -41,7 +41,7 @@
-module(cowboy_req).
%% Request API.
--export([new/15]).
+-export([new/14]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
@@ -54,7 +54,6 @@
-export([qs_val/2]).
-export([qs_val/3]).
-export([qs_vals/1]).
--export([fragment/1]).
-export([host_url/1]).
-export([url/1]).
-export([binding/2]).
@@ -121,20 +120,30 @@
-type cookie_opts() :: [cookie_option()].
-export_type([cookie_opts/0]).
--type resp_body_fun() :: fun((inet:socket(), module()) -> ok).
+-type content_decode_fun() :: fun((binary())
+ -> {ok, binary()}
+ | {error, atom()}).
+-type transfer_decode_fun() :: fun((binary(), any())
+ -> {ok, binary(), binary(), any()}
+ | more | {more, non_neg_integer(), binary(), any()}
+ | {done, non_neg_integer(), binary()}
+ | {done, binary(), non_neg_integer(), binary()}
+ | {error, atom()}).
+
+-type resp_body_fun() :: fun((any(), module()) -> ok).
-type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}).
-type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok).
-record(http_req, {
%% Transport.
- socket = undefined :: undefined | inet:socket(),
+ socket = undefined :: any(),
transport = undefined :: undefined | module(),
connection = keepalive :: keepalive | close,
%% Request.
pid = undefined :: pid(),
method = <<"GET">> :: binary(),
- version = {1, 1} :: cowboy_http:version(),
+ version = 'HTTP/1.1' :: cowboy:http_version(),
peer = undefined :: undefined | {inet:ip_address(), inet:port_number()},
host = undefined :: undefined | binary(),
host_info = undefined :: undefined | cowboy_router:tokens(),
@@ -143,30 +152,29 @@
path_info = undefined :: undefined | cowboy_router:tokens(),
qs = undefined :: binary(),
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
- fragment = undefined :: binary(),
bindings = undefined :: undefined | cowboy_router:bindings(),
- headers = [] :: cowboy_http:headers(),
+ headers = [] :: cowboy:http_headers(),
p_headers = [] :: [any()], %% @todo Improve those specs.
cookies = undefined :: undefined | [{binary(), binary()}],
meta = [] :: [{atom(), any()}],
%% Request body.
- body_state = waiting :: waiting | done
- | {stream, non_neg_integer(), fun(), any(), fun()},
+ body_state = waiting :: waiting | done | {stream, non_neg_integer(),
+ transfer_decode_fun(), any(), content_decode_fun()},
multipart = undefined :: undefined | {non_neg_integer(), fun()},
buffer = <<>> :: binary(),
%% Response.
resp_compress = false :: boolean(),
resp_state = waiting :: locked | waiting | chunks | done,
- resp_headers = [] :: cowboy_http:headers(),
+ resp_headers = [] :: cowboy:http_headers(),
resp_body = <<>> :: iodata() | resp_body_fun()
| {non_neg_integer(), resp_body_fun()}
| {chunked, resp_chunked_fun()},
%% Functions.
onresponse = undefined :: undefined | already_called
- | cowboy_protocol:onresponse_fun()
+ | cowboy:onresponse_fun()
}).
-opaque req() :: #http_req{}.
@@ -181,21 +189,21 @@
%%
%% Since we always need to parse the Connection header, we do it
%% in an optimized way and add the parsed value to p_headers' cache.
--spec new(inet:socket(), module(),
+-spec new(any(), module(),
undefined | {inet:ip_address(), inet:port_number()},
- binary(), binary(), binary(), binary(),
- cowboy_http:version(), cowboy_http:headers(), binary(),
+ binary(), binary(), binary(),
+ cowboy:http_version(), cowboy:http_headers(), binary(),
inet:port_number() | undefined, binary(), boolean(), boolean(),
- undefined | cowboy_protocol:onresponse_fun())
+ undefined | cowboy:onresponse_fun())
-> req().
-new(Socket, Transport, Peer, Method, Path, Query, Fragment,
+new(Socket, Transport, Peer, Method, Path, Query,
Version, Headers, Host, Port, Buffer, CanKeepalive,
Compress, OnResponse) ->
Req = #http_req{socket=Socket, transport=Transport, pid=self(), peer=Peer,
- method=Method, path=Path, qs=Query, fragment=Fragment, version=Version,
+ method=Method, path=Path, qs=Query, version=Version,
headers=Headers, host=Host, port=Port, buffer=Buffer,
resp_compress=Compress, onresponse=OnResponse},
- case CanKeepalive and (Version =:= {1, 1}) of
+ case CanKeepalive and (Version =:= 'HTTP/1.1') of
false ->
Req#http_req{connection=close};
true ->
@@ -216,13 +224,13 @@ method(Req) ->
{Req#http_req.method, Req}.
%% @doc Return the HTTP version used for the request.
--spec version(Req) -> {cowboy_http:version(), Req} when Req::req().
+-spec version(Req) -> {cowboy:http_version(), Req} when Req::req().
version(Req) ->
{Req#http_req.version, Req}.
%% @doc Return the peer address and port number of the remote host.
-spec peer(Req)
- -> {undefined | {inet:ip_address(), inet:port_number()}, Req}
+ -> {{inet:ip_address(), inet:port_number()}, Req}
when Req::req().
peer(Req) ->
{Req#http_req.peer, Req}.
@@ -289,11 +297,6 @@ qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
{QsVals, Req}.
-%% @doc Return the raw fragment directly taken from the request.
--spec fragment(Req) -> {binary(), Req} when Req::req().
-fragment(Req) ->
- {Req#http_req.fragment, Req}.
-
%% @doc Return the request URL as a binary without the path and query string.
%%
%% The URL includes the scheme, host and port only.
@@ -316,7 +319,7 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
%% @doc Return the full request URL as a binary.
%%
-%% The URL includes the scheme, host, port, path, query string and fragment.
+%% The URL includes the scheme, host, port, path and query string.
-spec url(Req) -> {undefined | binary(), Req} when Req::req().
url(Req=#http_req{}) ->
{HostURL, Req2} = host_url(Req),
@@ -324,16 +327,12 @@ url(Req=#http_req{}) ->
url(undefined, Req=#http_req{}) ->
{undefined, Req};
-url(HostURL, Req=#http_req{path=Path, qs=QS, fragment=Fragment}) ->
+url(HostURL, Req=#http_req{path=Path, qs=QS}) ->
QS2 = case QS of
<<>> -> <<>>;
_ -> << "?", QS/binary >>
end,
- Fragment2 = case Fragment of
- <<>> -> <<>>;
- _ -> << "#", Fragment/binary >>
- end,
- {<< HostURL/binary, Path/binary, QS2/binary, Fragment2/binary >>, Req}.
+ {<< HostURL/binary, Path/binary, QS2/binary >>, Req}.
%% @equiv binding(Name, Req, undefined)
-spec binding(atom(), Req) -> {binary() | undefined, Req} when Req::req().
@@ -371,7 +370,7 @@ header(Name, Req, Default) ->
end.
%% @doc Return the full list of headers.
--spec headers(Req) -> {cowboy_http:headers(), Req} when Req::req().
+-spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req().
headers(Req) ->
{Req#http_req.headers, Req}.
@@ -480,14 +479,14 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
%% @equiv cookie(Name, Req, undefined)
-spec cookie(binary(), Req)
- -> {binary() | true | undefined, Req} when Req::req().
+ -> {binary() | undefined, Req} when Req::req().
cookie(Name, Req) when is_binary(Name) ->
cookie(Name, Req, undefined).
%% @doc Return the cookie value for the given key, or a default if
%% missing.
-spec cookie(binary(), Req, Default)
- -> {binary() | true | Default, Req} when Req::req(), Default::any().
+ -> {binary() | Default, Req} when Req::req(), Default::any().
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
@@ -502,7 +501,7 @@ cookie(Name, Req, Default) ->
end.
%% @doc Return the full list of cookie values.
--spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
+-spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req().
cookies(Req=#http_req{cookies=undefined}) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
@@ -583,7 +582,7 @@ body_length(Req) ->
%% Content encoding is generally used for compression.
%%
%% Standard encodings can be found in cowboy_http.
--spec init_stream(fun(), any(), fun(), Req)
+-spec init_stream(transfer_decode_fun(), any(), content_decode_fun(), Req)
-> {ok, Req} when Req::req().
init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
{ok, Req#http_req{body_state=
@@ -608,7 +607,7 @@ stream_body(Req) ->
%% for each streamed part, and {done, Req} when it's finished streaming.
%%
%% You can limit the size of the chunks being returned by using the
-%% second argument which is the size in bytes. It defaults to 1000000 bytes.
+%% first argument which is the size in bytes. It defaults to 1000000 bytes.
-spec stream_body(non_neg_integer(), Req) -> {ok, binary(), Req}
| {done, Req} | {error, atom()} when Req::req().
stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version,
@@ -616,7 +615,7 @@ stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version,
{ok, ExpectHeader, Req1} = parse_header(<<"expect">>, Req),
case ExpectHeader of
[<<"100-continue">>] ->
- HTTPVer = cowboy_http:version_to_binary(Version),
+ HTTPVer = atom_to_binary(Version, latin1),
Transport:send(Socket,
<< HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>);
undefined ->
@@ -702,7 +701,7 @@ transfer_decode_done(Length, Rest, Req=#http_req{
headers=Headers3, p_headers=PHeaders3}.
%% @todo Probably needs a Rest.
--spec content_decode(fun(), binary(), Req)
+-spec content_decode(content_decode_fun(), binary(), Req)
-> {ok, binary(), Req} | {error, atom()} when Req::req().
content_decode(ContentDecode, Data, Req) ->
case ContentDecode(Data) of
@@ -787,11 +786,8 @@ body_qs(MaxBodyLength, Req) ->
%% this function returns <em>{headers, Headers}</em> followed by a sequence of
%% <em>{body, Data}</em> tuples and finally <em>end_of_part</em>. When there
%% is no part to parse anymore, <em>eof</em> is returned.
-%%
-%% If the request Content-Type is not a multipart one, <em>{error, badarg}</em>
-%% is returned.
-spec multipart_data(Req)
- -> {headers, cowboy_http:headers(), Req} | {body, binary(), Req}
+ -> {headers, cowboy:http_headers(), Req} | {body, binary(), Req}
| {end_of_part | eof, Req} when Req::req().
multipart_data(Req=#http_req{body_state=waiting}) ->
{ok, {<<"multipart">>, _SubType, Params}, Req2} =
@@ -921,7 +917,7 @@ has_resp_body(#http_req{resp_body={Length, _}}) ->
has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
-%% Remove a header previously set for the response.
+%% @doc Remove a header previously set for the response.
-spec delete_resp_header(binary(), Req)
-> Req when Req::req().
delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
@@ -929,18 +925,18 @@ delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
Req#http_req{resp_headers=RespHeaders2}.
%% @equiv reply(Status, [], [], Req)
--spec reply(cowboy_http:status(), Req) -> {ok, Req} when Req::req().
+-spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
reply(Status, Req=#http_req{resp_body=Body}) ->
reply(Status, [], Body, Req).
%% @equiv reply(Status, Headers, [], Req)
--spec reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
reply(Status, Headers, Body, Req).
%% @doc Send a reply to the client.
--spec reply(cowboy_http:status(), cowboy_http:headers(),
+-spec reply(cowboy:http_status(), cowboy:http_headers(),
iodata() | {non_neg_integer() | resp_body_fun()}, Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Body, Req=#http_req{
@@ -948,20 +944,30 @@ reply(Status, Headers, Body, Req=#http_req{
version=Version, connection=Connection,
method=Method, resp_compress=Compress,
resp_state=waiting, resp_headers=RespHeaders}) ->
- HTTP11Headers = case Version of
- {1, 1} -> [{<<"connection">>, atom_to_connection(Connection)}];
- _ -> []
+ HTTP11Headers = if
+ Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' ->
+ [{<<"connection">>, atom_to_connection(Connection)}];
+ true ->
+ []
end,
Req3 = case Body of
BodyFun when is_function(BodyFun) ->
%% We stream the response body until we close the connection.
RespConn = close,
- {RespType, Req2} = response(Status, Headers, RespHeaders, [
- {<<"connection">>, <<"close">>},
- {<<"date">>, cowboy_clock:rfc1123()},
- {<<"server">>, <<"Cowboy">>},
- {<<"transfer-encoding">>, <<"identity">>}
- ], <<>>, Req),
+ {RespType, Req2} = if
+ Transport =:= cowboy_spdy ->
+ response(Status, Headers, RespHeaders, [
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ ], stream, Req);
+ true ->
+ response(Status, Headers, RespHeaders, [
+ {<<"connection">>, <<"close">>},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>},
+ {<<"transfer-encoding">>, <<"identity">>}
+ ], <<>>, Req)
+ end,
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
@@ -974,13 +980,12 @@ reply(Status, Headers, Body, Req=#http_req{
ChunkFun = fun(IoData) -> chunk(IoData, Req2) end,
BodyFun(ChunkFun),
%% Terminate the chunked body for HTTP/1.1 only.
- _ = case Version of
- {1, 0} -> ok;
- _ -> Transport:send(Socket, <<"0\r\n\r\n">>)
+ case Version of
+ 'HTTP/1.0' -> Req2;
+ _ -> last_chunk(Req2)
end;
- true -> ok
- end,
- Req2;
+ true -> Req2
+ end;
{ContentLength, BodyFun} ->
%% We stream the response body for ContentLength bytes.
RespConn = response_connection(Headers, Connection),
@@ -988,7 +993,7 @@ reply(Status, Headers, Body, Req=#http_req{
{<<"content-length">>, integer_to_list(ContentLength)},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
- |HTTP11Headers], <<>>, Req),
+ |HTTP11Headers], stream, Req),
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
@@ -1005,7 +1010,7 @@ reply(Status, Headers, Body, Req=#http_req{
RespHeaders, HTTP11Headers, Method, iolist_size(Body)),
Req2#http_req{connection=RespConn}
end,
- {ok, Req3#http_req{resp_state=done,resp_headers=[], resp_body= <<>>}}.
+ {ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method) ->
@@ -1051,13 +1056,13 @@ reply_no_compress(Status, Headers, Body, Req,
Req2.
%% @equiv chunked_reply(Status, [], Req)
--spec chunked_reply(cowboy_http:status(), Req) -> {ok, Req} when Req::req().
+-spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
chunked_reply(Status, Req) ->
chunked_reply(Status, [], Req).
%% @doc Initiate the sending of a chunked reply to the client.
%% @see cowboy_req:chunk/2
--spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
chunked_reply(Status, Headers, Req) ->
{_, Req2} = chunked_response(Status, Headers, Req),
@@ -1069,18 +1074,34 @@ chunked_reply(Status, Headers, Req) ->
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
chunk(_Data, #http_req{method= <<"HEAD">>}) ->
ok;
-chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
+chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
+ resp_state=chunks}) ->
+ cowboy_spdy:stream_data(Socket, Data);
+chunk(Data, #http_req{socket=Socket, transport=Transport,
+ resp_state=chunks, version='HTTP/1.0'}) ->
Transport:send(Socket, Data);
-chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
+chunk(Data, #http_req{socket=Socket, transport=Transport,
+ resp_state=chunks}) ->
Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
<<"\r\n">>, Data, <<"\r\n">>]).
+%% @doc Finish the chunked reply.
+%% @todo If ever made public, need to send nothing if HEAD.
+-spec last_chunk(Req) -> Req when Req::req().
+last_chunk(Req=#http_req{socket=Socket, transport=cowboy_spdy}) ->
+ _ = cowboy_spdy:stream_close(Socket),
+ Req#http_req{resp_state=done};
+last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
+ _ = Transport:send(Socket, <<"0\r\n\r\n">>),
+ Req#http_req{resp_state=done}.
+
%% @doc Send an upgrade reply.
%% @private
--spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
-upgrade_reply(Status, Headers, Req=#http_req{
- resp_state=waiting, resp_headers=RespHeaders}) ->
+upgrade_reply(Status, Headers, Req=#http_req{transport=Transport,
+ resp_state=waiting, resp_headers=RespHeaders})
+ when Transport =/= cowboy_spdy ->
{_, Req2} = response(Status, Headers, RespHeaders, [
{<<"connection">>, <<"Upgrade">>}
], <<>>, Req),
@@ -1088,7 +1109,7 @@ upgrade_reply(Status, Headers, Req=#http_req{
%% @doc Ensure the response has been sent fully.
%% @private
--spec ensure_response(req(), cowboy_http:status()) -> ok.
+-spec ensure_response(req(), cowboy:http_status()) -> ok.
%% The response has already been fully sent to the client.
ensure_response(#http_req{resp_state=done}, _) ->
ok;
@@ -1100,11 +1121,10 @@ ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
%% Terminate the chunked body for HTTP/1.1 only.
ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
ok;
-ensure_response(#http_req{version={1, 0}, resp_state=chunks}, _) ->
+ensure_response(#http_req{version='HTTP/1.0', resp_state=chunks}, _) ->
ok;
-ensure_response(#http_req{socket=Socket, transport=Transport,
- resp_state=chunks}, _) ->
- Transport:send(Socket, <<"0\r\n\r\n">>),
+ensure_response(Req=#http_req{resp_state=chunks}, _) ->
+ _ = last_chunk(Req),
ok.
%% Private setter/getter API.
@@ -1126,7 +1146,6 @@ g(body_state, #http_req{body_state=Ret}) -> Ret;
g(buffer, #http_req{buffer=Ret}) -> Ret;
g(connection, #http_req{connection=Ret}) -> Ret;
g(cookies, #http_req{cookies=Ret}) -> Ret;
-g(fragment, #http_req{fragment=Ret}) -> Ret;
g(headers, #http_req{headers=Ret}) -> Ret;
g(host, #http_req{host=Ret}) -> Ret;
g(host_info, #http_req{host_info=Ret}) -> Ret;
@@ -1157,7 +1176,6 @@ set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
-set([{fragment, Val}|Tail], Req) -> set(Tail, Req#http_req{fragment=Val});
set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
@@ -1216,14 +1234,23 @@ to_list(Req) ->
%% Internal.
--spec chunked_response(cowboy_http:status(), cowboy_http:headers(), Req) ->
+-spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) ->
{normal | hook, Req} when Req::req().
chunked_response(Status, Headers, Req=#http_req{
+ transport=cowboy_spdy, resp_state=waiting,
+ resp_headers=RespHeaders}) ->
+ {RespType, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ ], stream, Req),
+ {RespType, Req2#http_req{resp_state=chunks,
+ resp_headers=[], resp_body= <<>>}};
+chunked_response(Status, Headers, Req=#http_req{
version=Version, connection=Connection,
resp_state=waiting, resp_headers=RespHeaders}) ->
RespConn = response_connection(Headers, Connection),
HTTP11Headers = case Version of
- {1, 1} -> [
+ 'HTTP/1.1' -> [
{<<"connection">>, atom_to_connection(Connection)},
{<<"transfer-encoding">>, <<"chunked">>}];
_ -> []
@@ -1235,8 +1262,8 @@ chunked_response(Status, Headers, Req=#http_req{
{RespType, Req2#http_req{connection=RespConn, resp_state=chunks,
resp_headers=[], resp_body= <<>>}}.
--spec response(cowboy_http:status(), cowboy_http:headers(),
- cowboy_http:headers(), cowboy_http:headers(), iodata(), Req)
+-spec response(cowboy:http_status(), cowboy:http_headers(),
+ cowboy:http_headers(), cowboy:http_headers(), stream | iodata(), Req)
-> {normal | hook, Req} when Req::req().
response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
@@ -1245,22 +1272,32 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
already_called -> Headers;
_ -> response_merge_headers(Headers, RespHeaders, DefaultHeaders)
end,
+ Body2 = case Body of stream -> <<>>; _ -> Body 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=already_called})
+ OnResponse ->
+ OnResponse(Status, FullHeaders, Body2,
+ %% Don't call 'onresponse' from the hook itself.
+ Req#http_req{resp_headers=[], resp_body= <<>>,
+ onresponse=already_called})
end,
ReplyType = case Req2#http_req.resp_state of
+ waiting when Transport =:= cowboy_spdy, Body =:= stream ->
+ cowboy_spdy:stream_reply(Socket, status(Status), FullHeaders),
+ ReqPid ! {?MODULE, resp_sent},
+ normal;
+ waiting when Transport =:= cowboy_spdy ->
+ cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body),
+ ReqPid ! {?MODULE, resp_sent},
+ normal;
waiting ->
- HTTPVer = cowboy_http:version_to_binary(Version),
+ HTTPVer = atom_to_binary(Version, latin1),
StatusLine = << HTTPVer/binary, " ",
(status(Status))/binary, "\r\n" >>,
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|| {Key, Value} <- FullHeaders],
- Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body]),
+ Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]),
ReqPid ! {?MODULE, resp_sent},
normal;
_ ->
@@ -1268,7 +1305,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
end,
{ReplyType, Req2}.
--spec response_connection(cowboy_http:headers(), keepalive | close)
+-spec response_connection(cowboy:http_headers(), keepalive | close)
-> keepalive | close.
response_connection([], Connection) ->
Connection;
@@ -1281,16 +1318,16 @@ response_connection([{Name, Value}|Tail], Connection) ->
response_connection(Tail, Connection)
end.
--spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
- cowboy_http:headers()) -> cowboy_http:headers().
+-spec response_merge_headers(cowboy:http_headers(), cowboy:http_headers(),
+ cowboy:http_headers()) -> cowboy:http_headers().
response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
Headers2 = [{Key, Value} || {Key, Value} <- Headers],
merge_headers(
merge_headers(Headers2, RespHeaders),
DefaultHeaders).
--spec merge_headers(cowboy_http:headers(), cowboy_http:headers())
- -> cowboy_http:headers().
+-spec merge_headers(cowboy:http_headers(), cowboy:http_headers())
+ -> cowboy:http_headers().
%% Merge headers by prepending the tuples in the second list to the
%% first list. It also handles Set-Cookie properly, which supports
@@ -1377,7 +1414,7 @@ connection_to_atom([<<"close">>|_]) ->
connection_to_atom([_|Tail]) ->
connection_to_atom(Tail).
--spec status(cowboy_http:status()) -> binary().
+-spec status(cowboy:http_status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
@@ -1444,38 +1481,28 @@ status(B) when is_binary(B) -> B.
url_test() ->
{undefined, _} =
url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined,
- path= <<>>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<>>, qs= <<>>, pid=self()}),
{<<"http://localhost/path">>, _ } =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:443/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
- pid=self()}),
- {<<"http://localhost:8080/path?dummy=2785#fragment">>, _} =
- url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
- pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
{<<"https://localhost/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
- pid=self()}),
- {<<"https://localhost:8443/path?dummy=2785#fragment">>, _} =
- url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
- pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
ok.
parse_connection_test_() ->
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 0913b26..ecbe7bc 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -45,7 +45,7 @@
language_a :: undefined | binary(),
%% Charset.
- charsets_p = [] :: [{binary(), integer()}],
+ charsets_p = [] :: [binary()],
charset_a :: undefined | binary(),
%% Whether the resource exists.
@@ -412,8 +412,7 @@ charsets_provided(Req, State) ->
cowboy_req:parse_header(<<"accept-charset">>, Req2),
case AcceptCharset of
undefined ->
- set_content_type(Req3, State2#state{
- charset_a=element(1, hd(CP))});
+ set_content_type(Req3, State2#state{charset_a=hd(CP)});
AcceptCharset ->
AcceptCharset2 = prioritize_charsets(AcceptCharset),
choose_charset(Req3, State2, AcceptCharset2)
@@ -433,7 +432,11 @@ prioritize_charsets(AcceptCharsets) ->
end, AcceptCharsets),
case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
true -> AcceptCharsets2;
- false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
+ false ->
+ case lists:keymember(<<"iso-8859-1">>, 1, AcceptCharsets2) of
+ true -> AcceptCharsets2;
+ false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
+ end
end.
choose_charset(Req, State, []) ->
@@ -443,7 +446,7 @@ choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
match_charset(Req, State, Accept, [], _Charset) ->
choose_charset(Req, State, Accept);
-match_charset(Req, State, _Accept, [{Provided, _}|_], {Provided, _}) ->
+match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
set_content_type(Req, State#state{charset_a=Provided});
match_charset(Req, State, Accept, [_|Tail], Charset) ->
match_charset(Req, State, Accept, Tail, Charset).
@@ -848,7 +851,7 @@ process_content_type(Req, State=#state{method=Method,
{false, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
respond(Req2, State2, 422);
- {ResURL, Req2, HandlerState2} when Method =:= <<"POST">> ->
+ {{true, ResURL}, Req2, HandlerState2} when Method =:= <<"POST">> ->
State2 = State#state{handler_state=HandlerState2},
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),
diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl
new file mode 100644
index 0000000..ba02706
--- /dev/null
+++ b/src/cowboy_spdy.erl
@@ -0,0 +1,544 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc SPDY protocol handler.
+%%
+%% The available options are:
+%% <dl>
+%% </dl>
+%%
+%% 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.
+-module(cowboy_spdy).
+
+%% API.
+-export([start_link/4]).
+
+%% Internal.
+-export([init/5]).
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+%% Internal request process.
+-export([request_init/9]).
+-export([resume/5]).
+-export([reply/4]).
+-export([stream_reply/3]).
+-export([stream_data/2]).
+-export([stream_close/1]).
+
+%% Internal transport functions.
+-export([name/0]).
+-export([send/2]).
+
+-record(child, {
+ streamid :: non_neg_integer(),
+ pid :: pid(),
+ input = nofin :: fin | nofin,
+ output = nofin :: fin | nofin
+}).
+
+-record(state, {
+ parent = undefined :: pid(),
+ socket,
+ transport,
+ buffer = <<>> :: binary(),
+ middlewares,
+ env,
+ onrequest,
+ onresponse,
+ peer,
+ zdef,
+ zinf,
+ last_streamid = 0 :: non_neg_integer(),
+ children = [] :: [#child{}]
+}).
+
+-record(special_headers, {
+ method,
+ path,
+ version,
+ host,
+ scheme %% @todo We don't use it.
+}).
+
+-type opts() :: [].
+-export_type([opts/0]).
+
+-include("cowboy_spdy.hrl").
+
+%% API.
+
+%% @doc Start a SPDY protocol process.
+-spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
+start_link(Ref, Socket, Transport, Opts) ->
+ proc_lib:start_link(?MODULE, init,
+ [self(), Ref, Socket, Transport, Opts]).
+
+%% Internal.
+
+%% @doc Faster alternative to proplists:get_value/3.
+%% @private
+get_value(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} -> Value;
+ _ -> Default
+ end.
+
+%% @private
+-spec init(pid(), ranch:ref(), inet:socket(), module(), opts()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts) ->
+ process_flag(trap_exit, true),
+ ok = proc_lib:init_ack(Parent, {ok, self()}),
+ {ok, Peer} = Transport:peername(Socket),
+ Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
+ Env = [{listener, Ref}|get_value(env, Opts, [])],
+ OnRequest = get_value(onrequest, Opts, undefined),
+ OnResponse = get_value(onresponse, Opts, undefined),
+ Zdef = zlib:open(),
+ ok = zlib:deflateInit(Zdef),
+ _ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
+ Zinf = zlib:open(),
+ ok = zlib:inflateInit(Zinf),
+ ok = ranch:accept_ack(Ref),
+ loop(#state{parent=Parent, socket=Socket, transport=Transport,
+ middlewares=Middlewares, env=Env, onrequest=OnRequest,
+ onresponse=OnResponse, peer=Peer, zdef=Zdef, zinf=Zinf}).
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
+ buffer=Buffer, children=Children}) ->
+ {OK, Closed, Error} = Transport:messages(),
+ Transport:setopts(Socket, [{active, once}]),
+ receive
+ {OK, Socket, Data} ->
+ Data2 = << Buffer/binary, Data/binary >>,
+ case Data2 of
+ << _:40, Length:24, _/bits >>
+ when byte_size(Data2) >= Length + 8 ->
+ Length2 = Length + 8,
+ << Frame:Length2/binary, Rest/bits >> = Data2,
+ control_frame(State#state{buffer=Rest}, Frame);
+ Rest ->
+ loop(State#state{buffer=Rest})
+ end;
+ {Closed, Socket} ->
+ terminate(State);
+ {Error, Socket, _Reason} ->
+ terminate(State);
+ {reply, {Pid, StreamID}, Status, Headers}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, fin, StreamID, Status, Headers),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {reply, {Pid, StreamID}, Status, Headers, Body}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, nofin, StreamID, Status, Headers),
+ data(State, fin, StreamID, Body),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {stream_reply, {Pid, StreamID}, Status, Headers}
+ when Pid =:= self() ->
+ #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, nofin, StreamID, Status, Headers),
+ loop(State);
+ {stream_data, {Pid, StreamID}, Data}
+ when Pid =:= self() ->
+ #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ data(State, nofin, StreamID, Data),
+ loop(State);
+ {stream_close, {Pid, StreamID}}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ data(State, fin, StreamID),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {'EXIT', Parent, Reason} ->
+ exit(Reason);
+ {'EXIT', Pid, _} ->
+ Children2 = lists:keydelete(Pid, #child.pid, Children),
+ loop(State#state{children=Children2});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);
+ %% Calls from the supervisor module.
+ {'$gen_call', {To, Tag}, which_children} ->
+ Children = [{?MODULE, Pid, worker, [?MODULE]}
+ || #child{pid=Pid} <- Children],
+ To ! {Tag, Children},
+ loop(State);
+ {'$gen_call', {To, Tag}, count_children} ->
+ NbChildren = length(Children),
+ Counts = [{specs, 1}, {active, NbChildren},
+ {supervisors, 0}, {workers, NbChildren}],
+ To ! {Tag, Counts},
+ loop(State);
+ {'$gen_call', {To, Tag}, _} ->
+ To ! {Tag, {error, ?MODULE}},
+ loop(State)
+ after 60000 ->
+ goaway(State, ok),
+ terminate(State)
+ end.
+
+system_continue(_, _, State) ->
+ loop(State).
+
+-spec system_terminate(any(), _, _, _) -> no_return().
+system_terminate(Reason, _, _, _) ->
+ exit(Reason).
+
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
+
+%% We do not support SYN_STREAM with FLAG_UNIDIRECTIONAL set.
+control_frame(State, << _:38, 1:1, _:26, StreamID:31, _/bits >>) ->
+ rst_stream(State, StreamID, internal_error),
+ loop(State);
+%% We do not support Associated-To-Stream-ID and CREDENTIAL Slot.
+control_frame(State, << _:65, StreamID:31, _:1, AssocToStreamID:31,
+ _:8, Slot:8, _/bits >>) when AssocToStreamID =/= 0; Slot =/= 0 ->
+ rst_stream(State, StreamID, internal_error),
+ loop(State);
+%% SYN_STREAM
+%%
+%% Erlang does not allow us to control the priority of processes
+%% so we ignore that value entirely.
+control_frame(State=#state{middlewares=Middlewares, env=Env,
+ onrequest=OnRequest, onresponse=OnResponse, peer=Peer,
+ zinf=Zinf, children=Children},
+ << 1:1, 3:15, 1:16, Flags:8, _:25, StreamID:31,
+ _:32, _Priority:3, _:13, Rest/bits >>) ->
+ IsFin = case Flags of
+ 1 -> fin;
+ 0 -> nofin
+ end,
+ [<< NbHeaders:32, Rest2/bits >>] = try
+ zlib:inflate(Zinf, Rest)
+ catch _:_ ->
+ ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
+ zlib:inflate(Zinf, <<>>)
+ end,
+ case syn_stream_headers(Rest2, NbHeaders, [], #special_headers{}) of
+ {ok, Headers, Special} ->
+ Pid = spawn_link(?MODULE, request_init,
+ [self(), StreamID, Peer, Headers,
+ OnRequest, OnResponse, Env, Middlewares, Special]),
+ loop(State#state{last_streamid=StreamID,
+ children=[#child{streamid=StreamID, pid=Pid,
+ input=IsFin, output=nofin}|Children]});
+ {error, special} ->
+ rst_stream(State, StreamID, protocol_error),
+ loop(State#state{last_streamid=StreamID})
+ end;
+%% SYN_REPLY
+control_frame(State, << 1:1, 3:15, 2:16, _/bits >>) ->
+ error_logger:error_msg("Ignored SYN_REPLY control frame~n"),
+ loop(State);
+%% RST_STREAM
+control_frame(State, << 1:1, 3:15, 3:16, _Flags:8, _Length:24,
+ _:1, _StreamID:31, StatusCode:32 >>) ->
+ Status = case StatusCode of
+ 1 -> protocol_error;
+ 2 -> invalid_stream;
+ 3 -> refused_stream;
+ 4 -> unsupported_version;
+ 5 -> cancel;
+ 6 -> internal_error;
+ 7 -> flow_control_error;
+ 8 -> stream_in_use;
+ 9 -> stream_already_closed;
+ 10 -> invalid_credentials;
+ 11 -> frame_too_large
+ end,
+ error_logger:error_msg("Received RST_STREAM control frame: ~p~n", [Status]),
+ %% @todo Stop StreamID.
+ loop(State);
+%% SETTINGS
+control_frame(State, << 1:1, 3:15, 4:16, 0:8, _:24,
+ NbEntries:32, Rest/bits >>) ->
+ Settings = [begin
+ Name = case ID of
+ 1 -> upload_bandwidth;
+ 2 -> download_bandwidth;
+ 3 -> round_trip_time;
+ 4 -> max_concurrent_streams;
+ 5 -> current_cwnd;
+ 6 -> download_retrans_rate;
+ 7 -> initial_window_size;
+ 8 -> client_certificate_vector_size
+ end,
+ {Flags, Name, Value}
+ end || << Flags:8, ID:24, Value:32 >> <= Rest],
+ if
+ NbEntries =/= length(Settings) ->
+ goaway(State, protocol_error),
+ terminate(State);
+ true ->
+ error_logger:error_msg("Ignored SETTINGS control frame: ~p~n",
+ [Settings]),
+ loop(State)
+ end;
+%% PING initiated by the server; ignore, we don't send any
+control_frame(State, << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>)
+ when PingID rem 2 =:= 0 ->
+ error_logger:error_msg("Ignored PING control frame: ~p~n", [PingID]),
+ loop(State);
+%% PING initiated by the client; send it back
+control_frame(State=#state{socket=Socket, transport=Transport},
+ Data = << 1:1, 3:15, 6:16, 0:8, 4:24, _:32 >>) ->
+ Transport:send(Socket, Data),
+ loop(State);
+%% GOAWAY
+control_frame(State, << 1:1, 3:15, 7:16, _/bits >>) ->
+ error_logger:error_msg("Ignored GOAWAY control frame~n"),
+ loop(State);
+%% HEADERS
+control_frame(State, << 1:1, 3:15, 8:16, _/bits >>) ->
+ error_logger:error_msg("Ignored HEADERS control frame~n"),
+ loop(State);
+%% WINDOW_UPDATE
+control_frame(State, << 1:1, 3:15, 9:16, 0:8, _/bits >>) ->
+ error_logger:error_msg("Ignored WINDOW_UPDATE control frame~n"),
+ loop(State);
+%% CREDENTIAL
+control_frame(State, << 1:1, 3:15, 10:16, _/bits >>) ->
+ error_logger:error_msg("Ignored CREDENTIAL control frame~n"),
+ loop(State);
+%% ???
+control_frame(State, _) ->
+ goaway(State, protocol_error),
+ terminate(State).
+
+%% @todo We must wait for the children to finish here,
+%% but only up to N milliseconds. Then we shutdown.
+terminate(_State) ->
+ ok.
+
+syn_stream_headers(<<>>, 0, Acc, Special=#special_headers{
+ method=Method, path=Path, version=Version, host=Host, scheme=Scheme}) ->
+ if
+ Method =:= undefined; Path =:= undefined; Version =:= undefined;
+ Host =:= undefined; Scheme =:= undefined ->
+ {error, special};
+ true ->
+ {ok, lists:reverse(Acc), Special}
+ end;
+syn_stream_headers(<< NameLen:32, Rest/bits >>, NbHeaders, Acc, Special) ->
+ << Name:NameLen/binary, ValueLen:32, Rest2/bits >> = Rest,
+ << Value:ValueLen/binary, Rest3/bits >> = Rest2,
+ case Name of
+ <<":host">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1,
+ [{<<"host">>, Value}|Acc],
+ Special#special_headers{host=Value});
+ <<":method">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{method=Value});
+ <<":path">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{path=Value});
+ <<":version">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{version=Value});
+ <<":scheme">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{scheme=Value});
+ _ ->
+ syn_stream_headers(Rest3, NbHeaders - 1,
+ [{Name, Value}|Acc], Special)
+ end.
+
+syn_reply(#state{socket=Socket, transport=Transport, zdef=Zdef},
+ IsFin, StreamID, Status, Headers) ->
+ Headers2 = [{<<":status">>, Status},
+ {<<":version">>, <<"HTTP/1.1">>}|Headers],
+ NbHeaders = length(Headers2),
+ HeaderBlock = [begin
+ NameLen = byte_size(Name),
+ ValueLen = iolist_size(Value),
+ [<< NameLen:32, Name/binary, ValueLen:32 >>, Value]
+ end || {Name, Value} <- Headers2],
+ HeaderBlock2 = [<< NbHeaders:32 >>, HeaderBlock],
+ HeaderBlock3 = zlib:deflate(Zdef, HeaderBlock2, full),
+ Flags = case IsFin of
+ fin -> 1;
+ nofin -> 0
+ end,
+ Len = 4 + iolist_size(HeaderBlock3),
+ Transport:send(Socket, [
+ << 1:1, 3:15, 2:16, Flags:8, Len:24, 0:1, StreamID:31 >>,
+ HeaderBlock3]).
+
+rst_stream(#state{socket=Socket, transport=Transport}, StreamID, Status) ->
+ StatusCode = case Status of
+ protocol_error -> 1;
+%% invalid_stream -> 2;
+%% refused_stream -> 3;
+%% unsupported_version -> 4;
+%% cancel -> 5;
+ internal_error -> 6
+%% flow_control_error -> 7;
+%% stream_in_use -> 8;
+%% stream_already_closed -> 9;
+%% invalid_credentials -> 10;
+%% frame_too_large -> 11
+ end,
+ Transport:send(Socket, << 1:1, 3:15, 3:16, 0:8, 8:24,
+ 0:1, StreamID:31, StatusCode:32 >>).
+
+goaway(#state{socket=Socket, transport=Transport, last_streamid=LastStreamID},
+ Status) ->
+ StatusCode = case Status of
+ ok -> 0;
+ protocol_error -> 1
+%% internal_error -> 2
+ end,
+ Transport:send(Socket, << 1:1, 3:15, 7:16, 0:8, 8:24,
+ 0:1, LastStreamID:31, StatusCode:32 >>).
+
+data(#state{socket=Socket, transport=Transport}, fin, StreamID) ->
+ Transport:send(Socket, << 0:1, StreamID:31, 1:8, 0:24 >>).
+
+data(#state{socket=Socket, transport=Transport}, IsFin, StreamID, Data) ->
+ Flags = case IsFin of
+ fin -> 1;
+ nofin -> 0
+ end,
+ Len = iolist_size(Data),
+ Transport:send(Socket, [
+ << 0:1, StreamID:31, Flags:8, Len:24 >>,
+ Data]).
+
+%% Request process.
+
+request_init(Parent, StreamID, Peer,
+ Headers, OnRequest, OnResponse, Env, Middlewares,
+ #special_headers{method=Method, path=Path, version=Version,
+ host=Host}) ->
+ Version2 = parse_version(Version),
+ {Host2, Port} = cowboy_protocol:parse_host(Host, <<>>),
+ {Path2, Query} = parse_path(Path, <<>>),
+ Req = cowboy_req:new({Parent, StreamID}, ?MODULE, Peer,
+ Method, Path2, Query, Version2, Headers,
+ Host2, Port, <<>>, true, false, OnResponse),
+ case OnRequest of
+ undefined ->
+ execute(Req, Env, Middlewares);
+ _ ->
+ Req2 = OnRequest(Req),
+ case cowboy_req:get(resp_state, Req2) of
+ waiting -> execute(Req2, Env, Middlewares);
+ _ -> ok
+ end
+ end.
+
+parse_version(<<"HTTP/1.1">>) ->
+ 'HTTP/1.1';
+parse_version(<<"HTTP/1.0">>) ->
+ 'HTTP/1.0'.
+
+parse_path(<<>>, Path) ->
+ {Path, <<>>};
+parse_path(<< $?, Rest/binary >>, Path) ->
+ parse_query(Rest, Path, <<>>);
+parse_path(<< C, Rest/binary >>, SoFar) ->
+ parse_path(Rest, << SoFar/binary, C >>).
+
+parse_query(<<>>, Path, Query) ->
+ {Path, Query};
+parse_query(<< C, Rest/binary >>, Path, SoFar) ->
+ parse_query(Rest, Path, << SoFar/binary, C >>).
+
+-spec execute(cowboy_req:req(), cowboy_middleware:env(), [module()])
+ -> ok.
+execute(Req, _, []) ->
+ cowboy_req:ensure_response(Req, 204);
+execute(Req, Env, [Middleware|Tail]) ->
+ case Middleware:execute(Req, Env) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module, Function, Args} ->
+ erlang:hibernate(?MODULE, resume,
+ [Env, Tail, Module, Function, Args]);
+ {halt, Req2} ->
+ cowboy_req:ensure_response(Req2, 204);
+ {error, Code, Req2} ->
+ error_terminate(Code, Req2)
+ end.
+
+%% @private
+-spec resume(cowboy_middleware:env(), [module()],
+ module(), module(), [any()]) -> ok.
+resume(Env, Tail, Module, Function, Args) ->
+ case apply(Module, Function, Args) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module2, Function2, Args2} ->
+ erlang:hibernate(?MODULE, resume,
+ [Env, Tail, Module2, Function2, Args2]);
+ {halt, Req2} ->
+ cowboy_req:ensure_response(Req2, 204);
+ {error, Code, Req2} ->
+ error_terminate(Code, Req2)
+ end.
+
+%% Only send an error reply if there is no resp_sent message.
+-spec error_terminate(cowboy:http_status(), cowboy_req:req()) -> ok.
+error_terminate(Code, Req) ->
+ receive
+ {cowboy_req, resp_sent} -> ok
+ after 0 ->
+ _ = cowboy_req:reply(Code, Req),
+ ok
+ end.
+
+%% Reply functions used by cowboy_req.
+
+reply(Socket = {Pid, _}, Status, Headers, Body) ->
+ _ = case iolist_size(Body) of
+ 0 -> Pid ! {reply, Socket, Status, Headers};
+ _ -> Pid ! {reply, Socket, Status, Headers, Body}
+ end,
+ ok.
+
+stream_reply(Socket = {Pid, _}, Status, Headers) ->
+ _ = Pid ! {stream_reply, Socket, Status, Headers},
+ ok.
+
+stream_data(Socket = {Pid, _}, Data) ->
+ _ = Pid ! {stream_data, Socket, Data},
+ ok.
+
+stream_close(Socket = {Pid, _}) ->
+ _ = Pid ! {stream_close, Socket},
+ ok.
+
+%% Internal transport functions.
+%% @todo recv, sendfile
+
+name() ->
+ spdy.
+
+send(Socket, Data) ->
+ stream_data(Socket, Data).
diff --git a/src/cowboy_spdy.hrl b/src/cowboy_spdy.hrl
new file mode 100644
index 0000000..9637b1c
--- /dev/null
+++ b/src/cowboy_spdy.hrl
@@ -0,0 +1,181 @@
+%% Zlib dictionary.
+
+-define(ZDICT, <<
+ 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69,
+ 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70,
+ 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65,
+ 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05,
+ 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00,
+ 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00,
+ 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63,
+ 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f,
+ 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c,
+ 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73,
+ 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77,
+ 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f,
+ 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63,
+ 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72,
+ 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f,
+ 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e,
+ 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65,
+ 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10,
+ 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d,
+ 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65,
+ 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67,
+ 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f,
+ 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00,
+ 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74,
+ 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00,
+ 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00,
+ 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74,
+ 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69,
+ 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66,
+ 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69,
+ 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00,
+ 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f,
+ 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73,
+ 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d,
+ 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d,
+ 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00,
+ 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67,
+ 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d,
+ 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69,
+ 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65,
+ 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74,
+ 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65,
+ 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63,
+ 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00,
+ 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72,
+ 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00,
+ 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00,
+ 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79,
+ 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74,
+ 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00,
+ 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61,
+ 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05,
+ 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00,
+ 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72,
+ 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72,
+ 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65,
+ 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00,
+ 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c,
+ 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72,
+ 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65,
+ 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00,
+ 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61,
+ 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73,
+ 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74,
+ 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79,
+ 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00,
+ 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69,
+ 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77,
+ 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e,
+ 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64,
+ 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75,
+ 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30,
+ 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76,
+ 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31,
+ 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72,
+ 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62,
+ 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73,
+ 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69,
+ 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65,
+ 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00,
+ 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69,
+ 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32,
+ 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35,
+ 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30,
+ 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33,
+ 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37,
+ 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30,
+ 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34,
+ 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31,
+ 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31,
+ 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34,
+ 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34,
+ 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e,
+ 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f,
+ 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65,
+ 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20,
+ 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65,
+ 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f,
+ 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d,
+ 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34,
+ 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52,
+ 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30,
+ 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30,
+ 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64,
+ 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e,
+ 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64,
+ 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65,
+ 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72,
+ 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f,
+ 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74,
+ 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65,
+ 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20,
+ 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20,
+ 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61,
+ 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46,
+ 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41,
+ 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a,
+ 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41,
+ 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20,
+ 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20,
+ 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30,
+ 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e,
+ 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57,
+ 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c,
+ 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61,
+ 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20,
+ 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b,
+ 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61,
+ 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69,
+ 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67,
+ 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67,
+ 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c,
+ 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c,
+ 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74,
+ 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72,
+ 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c,
+ 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74,
+ 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65,
+ 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65,
+ 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64,
+ 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63,
+ 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69,
+ 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d,
+ 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a,
+ 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>).
diff --git a/src/cowboy_sub_protocol.erl b/src/cowboy_sub_protocol.erl
index 0b231d3..26ccd7e 100644
--- a/src/cowboy_sub_protocol.erl
+++ b/src/cowboy_sub_protocol.erl
@@ -31,7 +31,7 @@
-callback upgrade(Req, Env, module(), any())
-> {ok, Req, Env}
- | {suspend, module(), atom(), any()}
+ | {suspend, module(), atom(), [any()]}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index b5075c0..3667797 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -29,8 +29,8 @@
-export_type([close_code/0]).
-type frame() :: close | ping | pong
- | {text | binary | close | ping | pong, binary()}
- | {close, close_code(), binary()}.
+ | {text | binary | close | ping | pong, iodata()}
+ | {close, close_code(), iodata()}.
-export_type([frame/0]).
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 98d4376..21cdd4b 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -209,11 +209,8 @@ init_per_group(http, Config) ->
{transport, Transport}, {client, Client}|Config1];
init_per_group(https, Config) ->
Transport = ranch_ssl,
- Opts = [
- {certfile, ?config(data_dir, Config) ++ "ssl/cert.pem"},
- {keyfile, ?config(data_dir, Config) ++ "ssl/key.pem"},
- {password, "cowboy"}
- ],
+ {_, Cert, Key} = ct_helper:make_certs(),
+ Opts = [{cert, Cert}, {key, Key}],
Config1 = init_static_dir(Config),
application:start(public_key),
application:start(ssl),
@@ -241,11 +238,8 @@ init_per_group(http_compress, Config) ->
{transport, Transport}, {client, Client}|Config1];
init_per_group(https_compress, Config) ->
Transport = ranch_ssl,
- Opts = [
- {certfile, ?config(data_dir, Config) ++ "ssl/cert.pem"},
- {keyfile, ?config(data_dir, Config) ++ "ssl/key.pem"},
- {password, "cowboy"}
- ],
+ {_, Cert, Key} = ct_helper:make_certs(),
+ Opts = [{cert, Cert}, {key, Key}],
Config1 = init_static_dir(Config),
application:start(public_key),
application:start(ssl),
diff --git a/test/http_SUITE_data/rest_forbidden_resource.erl b/test/http_SUITE_data/rest_forbidden_resource.erl
index 287ff62..920ba31 100644
--- a/test/http_SUITE_data/rest_forbidden_resource.erl
+++ b/test/http_SUITE_data/rest_forbidden_resource.erl
@@ -28,4 +28,4 @@ to_text(Req, State) ->
from_text(Req, State) ->
{Path, Req2} = cowboy_req:path(Req),
- {Path, Req2, State}.
+ {{true, Path}, Req2, State}.
diff --git a/test/http_SUITE_data/ssl/cert.pem b/test/http_SUITE_data/ssl/cert.pem
deleted file mode 100644
index a772007..0000000
--- a/test/http_SUITE_data/ssl/cert.pem
+++ /dev/null
@@ -1,14 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICKTCCAZICCQCl9gdHk5NqUjANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB
-VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
-cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTEwNDA4MTMxNTE3WhcN
-MTEwNTA4MTMxNTE3WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0
-ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAls
-b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjgFPS0dP4d8F1e
-bNJPB+kAjM2FyTZGmkFCLUYONTPrdGOUIHL/UOGtU22BQzlskE+a6/j2Kg72tm8x
-4X7yf+6s7CdRe086idNx9+GymZ64ZTnly33rD3AJffbBeWHwT2e9fuBeFk9WGC8v
-kqECFZyqf7+znS0o48oBNcx3ePB5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEASTkv
-oHuZyO8DgT8bIE6W3yM2fvlNshkhh7Thgpf32qQoVOxRU9EF0KpuJCCAHQHQNQlI
-nf9Zc4UzOrLhxZBGocNhkkn4WLw2ysto/7+/+9xHah0M0l4auHLQagVLCoOsHUn2
-JX+A2NrbvuX5wnUrZGOdgY70tvMBeU/xLtp3af8=
------END CERTIFICATE-----
diff --git a/test/http_SUITE_data/ssl/key.pem b/test/http_SUITE_data/ssl/key.pem
deleted file mode 100644
index 0b699cc..0000000
--- a/test/http_SUITE_data/ssl/key.pem
+++ /dev/null
@@ -1,18 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,F11262DB77BB804C
-
-jOJ+ft/dihIxz7CTuuK47fCTGdX7xMLANmA7mRg8y9OYhNZQiCz5GjcWLqe0NNl5
-qXPW0uvT/9B5O9o21Y2i/CKU1BqRLuXHXDsjHg7RGaSH6wIavWt+lR+I1sjieFbX
-VByK1KHXjEU704DEILKJIA9gVzoYAgMzo+FTw2e/2jusXntxk8HXyF5zKTzjHBtI
-NQGweJqTmfZjX3SgPP4Co/ShrA6fUG0uTp1HwbByJnwtAeT3xWJrAD4QSn7+qrlv
-3qmEIqVXsvLrfZRY1WZ4uIsbLK8wkvxboSIoIK55VV9R2zRbwQULon6QJwKYujAr
-J2WUYkHHQOMpaAzUmalaT+8GUt8/A1oSK4BdiSZywsMMm46/hDadXBzFg+dPL5g2
-Td+7/L0S6tUVWq4+YBp5EalZH6VQ4cqPYDJZUZ9xt6+yY7V5748lSdA7cHCROnbG
-bKbSW9WbF7MPDHCjvCAfq+s1dafHJgyIOlMg2bm7V8eHWAA0xKQ/o7i5EyEyaKYR
-UXGeAf+KfXcclEZ77v2RCXZvd6ceWkifm59qWv/3TCYaHiS2Aa3lVToMKTwYzzXQ
-p5X5os6wv3IAi2nGyAIOoSDisdHmFteZNXNQsw0n3XCAYfsNMk+r5/r5YqDffURH
-c8SMOCP4BIPoZ/abi/gnEntGqsx1YALg0aosHwHGDJ/l+QJC6u6PZk310YzRw4GL
-K9+wscFgEub2OO+R83Vkfesj4tYzgOjab7+92a/soHdW0zhGejlvehODOgNZ6NUG
-MPQlT+qpF9Jh5IThYXupXXFzJzQe3O/qVXy89m69JGa+AWRvbu+M/A==
------END RSA PRIVATE KEY-----
diff --git a/test/spdy_SUITE.erl b/test/spdy_SUITE.erl
new file mode 100644
index 0000000..df29281
--- /dev/null
+++ b/test/spdy_SUITE.erl
@@ -0,0 +1,163 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(spdy_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("../src/cowboy_spdy.hrl").
+
+%% ct.
+-export([all/0]).
+-export([groups/0]).
+-export([init_per_suite/1]).
+-export([end_per_suite/1]).
+-export([init_per_group/2]).
+-export([end_per_group/2]).
+
+%% Tests.
+-export([check_status/1]).
+
+%% ct.
+
+all() ->
+ [{group, spdy}].
+
+groups() ->
+ [{spdy, [], [
+ check_status
+ ]}].
+
+init_per_suite(Config) ->
+ application:start(crypto),
+ application:start(ranch),
+ application:start(cowboy),
+ application:start(public_key),
+ application:start(ssl),
+ Config.
+
+end_per_suite(_Config) ->
+ application:stop(ssl),
+ application:stop(public_key),
+ application:stop(cowboy),
+ application:stop(ranch),
+ application:stop(crypto),
+ ok.
+
+init_per_group(Name, Config) ->
+ {_, Cert, Key} = ct_helper:make_certs(),
+ Opts = [{cert, Cert}, {key, Key}],
+ {ok, _} = cowboy:start_spdy(Name, 100, Opts ++ [{port, 0}], [
+ {env, [{dispatch, init_dispatch(Config)}]}
+ ]),
+ Port = ranch:get_port(Name),
+ [{port, Port}|Config].
+
+end_per_group(Name, _) ->
+ cowboy:stop_listener(Name),
+ ok.
+
+%% Dispatch configuration.
+
+init_dispatch(_) ->
+ cowboy_router:compile([
+ {"localhost", [
+ {"/chunked", http_chunked, []},
+ {"/", http_handler, []}
+ ]}
+ ]).
+
+%% Convenience functions.
+
+quick_get(Host, Path, ExpectedFlags, Config) ->
+ {_, Port} = lists:keyfind(port, 1, Config),
+ {ok, Socket} = ssl:connect("localhost", Port, [
+ binary, {active, false},
+ {client_preferred_next_protocols, client, [<<"spdy/3">>]}
+ ]),
+ {Zdef, Zinf} = zlib_init(),
+ ReqHeaders = headers_encode(Zdef, [
+ {<<":method">>, <<"GET">>},
+ {<<":path">>, list_to_binary(Path)},
+ {<<":version">>, <<"HTTP/1.1">>},
+ {<<":host">>, list_to_binary(Host)},
+ {<<":scheme">>, <<"https">>}
+ ]),
+ ReqLength = 10 + byte_size(ReqHeaders),
+ StreamID = 1,
+ ok = ssl:send(Socket, << 1:1, 3:15, 1:16, 0:8, ReqLength:24,
+ 0:1, StreamID:31, 0:1, 0:31, 0:3, 0:5, 0:8, ReqHeaders/binary >>),
+ {ok, Packet} = ssl:recv(Socket, 0, 1000),
+ << 1:1, 3:15, 2:16, Flags:8, RespLength:24,
+ _:1, StreamID:31, RespHeaders/bits >> = Packet,
+ Flags = ExpectedFlags,
+ RespLength = 4 + byte_size(RespHeaders),
+ [<< NbHeaders:32, Rest/bits >>] = try
+ zlib:inflate(Zinf, RespHeaders)
+ catch _:_ ->
+ ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
+ zlib:inflate(Zinf, <<>>)
+ end,
+ RespHeaders2 = headers_decode(Zinf, Rest, []),
+ NbHeaders = length(RespHeaders2),
+ {_, << Status:3/binary, _/bits >>}
+ = lists:keyfind(<<":status">>, 1, RespHeaders2),
+ StatusCode = list_to_integer(binary_to_list(Status)),
+ ok = ssl:close(Socket),
+ zlib_terminate(Zdef, Zinf),
+ {StatusCode, RespHeaders2}.
+
+zlib_init() ->
+ Zdef = zlib:open(),
+ ok = zlib:deflateInit(Zdef),
+ _ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
+ Zinf = zlib:open(),
+ ok = zlib:inflateInit(Zinf),
+ {Zdef, Zinf}.
+
+zlib_terminate(Zdef, Zinf) ->
+ zlib:close(Zdef),
+ zlib:close(Zinf).
+
+headers_encode(Zdef, Headers) ->
+ NbHeaders = length(Headers),
+ Headers2 = << << (begin
+ SizeN = byte_size(N),
+ SizeV = byte_size(V),
+ << SizeN:32, N/binary, SizeV:32, V/binary >>
+ end)/binary >> || {N, V} <- Headers >>,
+ Headers3 = << NbHeaders:32, Headers2/binary >>,
+ iolist_to_binary(zlib:deflate(Zdef, Headers3, full)).
+
+headers_decode(_, <<>>, Acc) ->
+ lists:reverse(Acc);
+headers_decode(Zinf, << SizeN:32, Rest/bits >>, Acc) ->
+ << Name:SizeN/binary, SizeV:32, Rest2/bits >> = Rest,
+ << Value:SizeV/binary, Rest3/bits >> = Rest2,
+ headers_decode(Zinf, Rest3, [{Name, Value}|Acc]).
+
+%% Tests.
+
+check_status(Config) ->
+ Tests = [
+ {200, nofin, "localhost", "/"},
+ {200, nofin, "localhost", "/chunked"},
+ {400, fin, "bad-host", "/"},
+ {400, fin, "localhost", "bad-path"},
+ {404, fin, "localhost", "/this/path/does/not/exist"}
+ ],
+ _ = [{Status, Fin, Host, Path} = begin
+ RespFlags = case Fin of fin -> 1; nofin -> 0 end,
+ {Ret, _} = quick_get(Host, Path, RespFlags, Config),
+ {Ret, Fin, Host, Path}
+ end || {Status, Fin, Host, Path} <- Tests].