aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS8
-rw-r--r--CHANGELOG.md38
-rw-r--r--Makefile2
-rw-r--r--erlang.mk39
-rw-r--r--examples/elixir_hello_world/mix.exs2
-rw-r--r--guide/erlang_beginners.md43
-rw-r--r--guide/erlang_web.md181
-rw-r--r--guide/getting_started.md80
-rw-r--r--guide/introduction.md99
-rw-r--r--guide/modern_web.md224
-rw-r--r--guide/toc.md30
-rw-r--r--manual/cowboy_app.md2
-rw-r--r--rebar.config2
-rw-r--r--src/cowboy.app.src5
-rw-r--r--src/cowboy.erl7
-rw-r--r--src/cowboy_http.erl55
-rw-r--r--src/cowboy_req.erl58
-rw-r--r--src/cowboy_rest.erl2
-rw-r--r--src/cowboy_spdy.erl13
-rw-r--r--src/cowboy_static.erl4
-rw-r--r--src/cowboy_websocket.erl275
-rw-r--r--test/http_SUITE.erl3
-rw-r--r--test/spdy_SUITE.erl2
-rw-r--r--test/ws_SUITE.erl57
24 files changed, 990 insertions, 241 deletions
diff --git a/AUTHORS b/AUTHORS
index 4803afd..a15107c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,17 +4,17 @@ Loïc Hoguin
Magnus Klaar
Anthony Ramine
Adam Cammack
-Tom Burdick
Ali Sabil
+Tom Burdick
James Fish
Paul Oliver
Slava Yurin
+Vladimir Dronnikov
Yurii Rashkovskii
Andrew Majorov
Egobrain
Josh Toft
Steven Gravell
-Vladimir Dronnikov
Andrew Thompson
Hunter Morris
Ivan Lisenkov
@@ -25,6 +25,7 @@ Tristan Sloughter
Adam Cammmack
Andre Graf
Andrzej Sliwa
+Blake Gentry
Bob Ippolito
Boris Faure
Cameron Bytheway
@@ -46,8 +47,11 @@ Max Lapshin
Michiel Hakvoort
Ori Bar
Pablo Vieytes
+Radosław Szymczyszyn
Richard Ramsden
Roberto Ostinelli
+Sergey Rublev
+Sergey Urbanovich
Seven Du
Thomas Nordström
Tim Dysinger
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 478a9a0..d7caabe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,44 @@
CHANGELOG
=========
+0.8.6
+-----
+
+ * Make sure Cowboy compiles on R16B01
+
+ * Update Ranch to 0.8.4
+
+ * Add experimental support for the x-webkit-deflate-frame Websocket extension
+
+ This allows Cowboy to handle compressed Websocket frames,
+ lowering the amount of data that needs to be sent over the
+ socket.
+
+ The extension will only be used if compression was enabled
+ using the `compress` protocol option.
+
+ * Add experimental SPDY support
+
+ SPDY is a new protocol implemented by most browsers. It is
+ the basis for what will become HTTP/2.0.
+
+ To use SPDY, you need to call `start_spdy` where you would
+ have used `start_https` before.
+
+ This protocol is still incomplete. It cannot accept request
+ bodies yet, making most methods other than GET and HEAD
+ not too useful at this point.
+
+ * Allow an empty method list in allowed_methods
+
+ * The charset parameter of content-types is now always lowercase
+
+ * Don't overwrite the stacktrace when a REST handler crashes
+
+ * Don't crash when the Cookie header is empty
+
+ * Don't crash on invalid Accept-Encoding header when replying
+
0.8.5
-----
diff --git a/Makefile b/Makefile
index 9810deb..79c2b6c 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ PLT_APPS = crypto public_key ssl
DEPS = ranch
TEST_DEPS = ct_helper
-dep_ranch = https://github.com/extend/ranch.git 0.8.3
+dep_ranch = https://github.com/extend/ranch.git 0.8.4
dep_ct_helper = https://github.com/extend/ct_helper.git master
# Standard targets.
diff --git a/erlang.mk b/erlang.mk
index ecd4d54..d353c33 100644
--- a/erlang.mk
+++ b/erlang.mk
@@ -19,9 +19,12 @@ V ?= 0
appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
appsrc_verbose = $(appsrc_verbose_$(V))
-erlc_verbose_0 = @echo " ERLC " $(?F);
+erlc_verbose_0 = @echo " ERLC " $(filter-out %.dtl,$(?F));
erlc_verbose = $(erlc_verbose_$(V))
+dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
+dtl_verbose = $(dtl_verbose_$(V))
+
gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V))
@@ -48,30 +51,48 @@ 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
+ $(eval MODULES := $(shell find ebin -name \*.beam \
+ | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//'))
$(appsrc_verbose) cat src/$(PROJECT).app.src \
| sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \
> ebin/$(PROJECT).app
-ebin/$(PROJECT).app: src/*.erl
- @mkdir -p ebin/
+define compile_erl
$(erlc_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \
- $(COMPILE_FIRST_PATHS) $?
+ $(COMPILE_FIRST_PATHS) $(1)
+endef
+
+define compile_dtl
+ $(dtl_verbose) erl -noshell -pa ebin/ deps/erlydtl/ebin/ -eval ' \
+ Compile = fun(F) -> \
+ Module = list_to_atom( \
+ string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \
+ erlydtl_compiler:compile(F, Module, [{out_dir, "ebin/"}]) \
+ end, \
+ _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
+ init:stop()'
+endef
+
+ebin/$(PROJECT).app: src/*.erl $(wildcard src/*.core) $(wildcard templates/*.dtl)
+ @mkdir -p ebin/
+ $(if $(strip $(filter-out %.dtl,$?)), \
+ $(call compile_erl,$(filter-out %.dtl,$?)))
+ $(if $(strip $(filter %.dtl,$?)), \
+ $(call compile_dtl,$(filter %.dtl,$?)))
clean:
$(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump
# Dependencies.
-define get_dep =
+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 =
+define dep_target
$(DEPS_DIR)/$(1):
$(call get_dep,$(1))
endef
@@ -101,7 +122,7 @@ 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/ \
+ $(gen_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o test/ \
$(wildcard test/*.erl test/*/*.erl) -pa ebin/
CT_RUN = ct_run \
diff --git a/examples/elixir_hello_world/mix.exs b/examples/elixir_hello_world/mix.exs
index b2e3dd3..9055175 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.3"},
+ [ {:ranch, github: "extend/ranch", tag: "0.8.4"},
{:cowboy, github: "extend/cowboy"} ]
end
end
diff --git a/guide/erlang_beginners.md b/guide/erlang_beginners.md
new file mode 100644
index 0000000..7778dee
--- /dev/null
+++ b/guide/erlang_beginners.md
@@ -0,0 +1,43 @@
+Erlang for beginners
+====================
+
+Chances are you are interested in using Cowboy, but have
+no idea how to write an Erlang program. Fear not! This
+chapter will help you get started.
+
+We recommend two books for beginners. You should read them
+both at some point, as they cover Erlang from two entirely
+different perspectives.
+
+Learn You Some Erlang for Great Good!
+-------------------------------------
+
+The quickest way to get started with Erlang is by reading
+a book with the funny name of [LYSE](http://learnyousomeerlang.com),
+as we affectionately call it.
+
+It will get right into the syntax and quickly answer the questions
+a beginner would ask themselves, all the while showing funny
+pictures and making insightful jokes.
+
+You can read an early version of the book online for free,
+but you really should buy the much more refined paper and
+ebook versions.
+
+Programming Erlang
+------------------
+
+After writing some code, you will probably want to understand
+the very concepts that make Erlang what it is today. These
+are best explained by Joe Armstrong, the godfather of Erlang,
+in his book [Programming Erlang](http://pragprog.com/book/jaerlang2/programming-erlang).
+
+Instead of going into every single details of the language,
+Joe focuses on the central concepts behind Erlang, and shows
+you how they can be used to write a variety of different
+applications.
+
+At the time of writing, the 2nd edition of the book is in beta,
+and includes a few details about upcoming Erlang features that
+cannot be used today. Choose the edition you want, then get
+reading!
diff --git a/guide/erlang_web.md b/guide/erlang_web.md
new file mode 100644
index 0000000..fa3d922
--- /dev/null
+++ b/guide/erlang_web.md
@@ -0,0 +1,181 @@
+Erlang and the Web
+==================
+
+The Web is concurrent
+---------------------
+
+When you access a website there is little concurrency
+involved. A few connections are opened and requests
+are sent through these connections. Then the web page
+is displayed on your screen. Your browser will only
+open up to 4 or 8 connections to the server, depending
+on your settings. This isn't much.
+
+But think about it. You are not the only one accessing
+the server at the same time. There can be hundreds, if
+not thousands, if not millions of connections to the
+same server at the same time.
+
+Even today a lot of systems used in production haven't
+solved the C10K problem (ten thousand concurrent connections).
+And the ones who did are trying hard to get to the next
+step, C100K, and are pretty far from it.
+
+Erlang meanwhile has no problem handling millions of
+connections. At the time of writing there are application
+servers written in Erlang that can handle more than two
+million connections on a single server in a real production
+application, with spare memory and CPU!
+
+The Web is concurrent, and Erlang is a language designed
+for concurrency, so it is a perfect match.
+
+Of course, various platforms need to scale beyond a few
+million connections. This is where Erlang's built-in
+distribution mechanisms come in. If one server isn't
+enough, add more! Erlang allows you to use the same code
+for talking to local processes or to processes in other
+parts of your cluster, which means you can scale very
+quickly if the need arises.
+
+The Web has large userbases, and the Erlang platform was
+designed to work in a distributed setting, so it is a
+perfect match.
+
+Or is it? Surely you can find solutions to handle that many
+concurrent connections with your favorite language... But all
+these solutions will break down in the next few years. Why?
+Firstly because servers don't get any more powerful, they
+instead get a lot more cores and memory. This is only useful
+if your application can use them properly, and Erlang is
+light-years away from anything else in that area. Secondly,
+today your computer and your phone are online, tomorrow your
+watch, goggles, bike, car, fridge and tons of other devices
+will also connect to various applications on the Internet.
+
+Only Erlang is prepared to deal with what's coming.
+
+The Web is soft real time
+-------------------------
+
+What does soft real time mean, you ask? It means we want the
+operations done as quickly as possible, and in the case of
+web applications, it means we want the data propagated fast.
+
+In comparison, hard real time has a similar meaning, but also
+has a hard time constraint, for example an operation needs to
+be done in under N milliseconds otherwise the system fails
+entirely.
+
+Users aren't that needy yet, they just want to get access
+to their content in a reasonable delay, and they want the
+actions they make to register at most a few seconds after
+they submitted them, otherwise they'll start worrying about
+whether it successfully went through.
+
+The Web is soft real time because taking longer to perform an
+operation would be seen as bad quality of service.
+
+Erlang is a soft real time system. It will always run
+processes fairly, a little at a time, switching to another
+process after a while and preventing a single process to
+steal resources from all others. This means that Erlang
+can guarantee stable low latency of operations.
+
+Erlang provides the guarantees that the soft real time Web
+requires.
+
+The Web is asynchronous
+-----------------------
+
+Long ago, the Web was synchronous because HTTP was synchronous.
+You fired a request, and then waited for a response. Not anymore.
+It all began when XmlHttpRequest started being used. It allowed
+the client to perform asynchronous calls to the server.
+
+Then Websocket appeared and allowed both the server and the client
+to send data to the other endpoint completely asynchronously. The
+data is contained within frames and no response is necessary.
+
+Erlang processes work the same. They send each other data contained
+within messages and then continue running without needing a response.
+They tend to spend most of their time inactive, waiting for a new
+message, and the Erlang VM happily activate them when one is received.
+
+It is therefore quite easy to imagine Erlang being good at receiving
+Websocket frames, which may come in at unpredictable times, pass the
+data to the responsible processes which are always ready waiting for
+new messages, and perform the operations required by only activating
+the required parts of the system.
+
+The more recent Web technologies, like Websocket of course, but also
+SPDY and HTTP/2.0, are all fully asynchronous protocols. The concept
+of requests and responses is retained of course, but anything could
+be sent in between, by both the client or the browser, and the
+responses could also be received in a completely different order.
+
+Erlang is by nature asynchronous and really good at it thanks to the
+great engineering that has been done in the VM over the years. It's
+only natural that it's so good at dealing with the asynchronous Web.
+
+The Web is omnipresent
+----------------------
+
+The Web has taken a very important part of our lives. We're
+connected at all times, when we're on our phone, using our computer,
+passing time using a tablet while in the bathroom... And this
+isn't going to slow down, every single device at home or on us
+will be connected.
+
+All these devices are always connected. And with the number of
+alternatives to give you access to the content you seek, users
+tend to not stick around when problems arise. Users today want
+their applications to be always available and if it's having
+too many issues they just move on.
+
+Despite this, when developers choose a product to use for building
+web applications, their only concern seem to be "Is it fast?",
+and they look around for synthetic benchmarks showing which one
+is the fastest at sending "Hello world" with only a handful
+concurrent connections. Web benchmarks haven't been representative
+of reality in a long time, and are drifting further away as
+time goes on.
+
+What developers should really ask themselves is "Can I service
+all my users with no interruption?" and they'd find that they have
+two choices. They can either hope for the best, or they can use
+Erlang.
+
+Erlang is built for fault tolerance. When writing code in any other
+language, you have to check all the return values and act accordingly
+to avoid any unforeseen issues. If you're lucky, you won't miss
+anything important. When writing Erlang code, you can just check
+the success condition and ignore all errors. If an error happen,
+the Erlang process crashes and is then restarted by a special
+process called a supervisor.
+
+The Erlang developer thus has no need to fear about unhandled
+errors, and can focus on handling only the errors that should
+give some feedback to the user and let the system take care of
+the rest. This also has the advantage of allowing him to write
+a lot less code, and letting him sleep at night.
+
+Erlang's fault tolerance oriented design is the first piece of
+what makes it the best choice for the omnipresent, always available
+Web.
+
+The second piece is Erlang's built-in distribution. Distribution
+is a key part of building a fault tolerant system, because it
+allows you to handle bigger failures, like a whole server going
+down, or even a data center entirely.
+
+Fault tolerance and distribution are important today, and will be
+vital in the future of the Web. Erlang is ready.
+
+Erlang is the ideal platform for the Web
+----------------------------------------
+
+Erlang provides all the important features that the Web requires
+or will require in the near future. Erlang is a perfect match
+for the Web, and it only makes sense to use it to build web
+applications.
diff --git a/guide/getting_started.md b/guide/getting_started.md
new file mode 100644
index 0000000..abd807a
--- /dev/null
+++ b/guide/getting_started.md
@@ -0,0 +1,80 @@
+Getting started
+===============
+
+Cowboy does nothing by default.
+
+Cowboy requires the `crypto` and `ranch` applications to be started.
+
+``` erlang
+ok = application:start(crypto).
+ok = application:start(ranch).
+ok = application:start(cowboy).
+```
+
+Cowboy uses Ranch for handling the connections and provides convenience
+functions to start Ranch listeners.
+
+The `cowboy:start_http/4` function starts a listener for HTTP connections
+using the TCP transport. The `cowboy:start_https/4` function starts a
+listener for HTTPS connections using the SSL transport.
+
+Listeners are a group of processes that are used to accept and manage
+connections. The processes used specifically for accepting connections
+are called acceptors. The number of acceptor processes is unrelated to
+the maximum number of connections Cowboy can handle. Please refer to
+the [Ranch guide](http://ninenines.eu/docs/en/ranch/HEAD/guide/toc)
+for in-depth information.
+
+Listeners are named. They spawn a given number of acceptors, listen for
+connections using the given transport options and pass along the protocol
+options to the connection processes. The protocol options must include
+the dispatch list for routing requests to handlers.
+
+The dispatch list is explained in greater details in the
+[Routing](routing.md) chapter.
+
+``` erlang
+Dispatch = cowboy_router:compile([
+ %% {URIHost, list({URIPath, Handler, Opts})}
+ {'_', [{'_', my_handler, []}]}
+]),
+%% Name, NbAcceptors, TransOpts, ProtoOpts
+cowboy:start_http(my_http_listener, 100,
+ [{port, 8080}],
+ [{env, [{dispatch, Dispatch}]}]
+).
+```
+
+Cowboy features many kinds of handlers. For this simple example,
+we will just use the plain HTTP handler, which has three callback
+functions: init/3, handle/2 and terminate/3. You can find more information
+about the arguments and possible return values of these callbacks in the
+[cowboy_http_handler function reference](http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_http_handler).
+Here is an example of a simple HTTP handler module.
+
+``` erlang
+-module(my_handler).
+-behaviour(cowboy_http_handler).
+
+-export([init/3]).
+-export([handle/2]).
+-export([terminate/3]).
+
+init({tcp, http}, Req, Opts) ->
+ {ok, Req, undefined_state}.
+
+handle(Req, State) ->
+ {ok, Req2} = cowboy_req:reply(200, [], <<"Hello World!">>, Req),
+ {ok, Req2, State}.
+
+terminate(Reason, Req, State) ->
+ ok.
+```
+
+The `Req` variable above is the Req object, which allows the developer
+to obtain information about the request and to perform a reply. Its usage
+is explained in the [cowboy_req function reference](http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_req).
+
+You can find many examples in the `examples/` directory of the
+Cowboy repository. A more complete "Hello world" example can be
+found in the `examples/hello_world/` directory.
diff --git a/guide/introduction.md b/guide/introduction.md
index a7f35a2..8c936a5 100644
--- a/guide/introduction.md
+++ b/guide/introduction.md
@@ -14,23 +14,19 @@ Cowboy is a high quality project. It has a small code base, is very
efficient (both in latency and memory use) and can easily be embedded
in another application.
-Cowboy is clean Erlang code. It bans the use of parameterized modules
-and the process dictionary. It includes documentation and typespecs
-for all public interfaces.
+Cowboy is clean Erlang code. It includes hundreds of tests and its code
+is fully compliant with the Dialyzer. It is also well documented and
+features both a Function Reference and a User Guide.
Prerequisites
-------------
-It is assumed the developer already knows Erlang and has basic knowledge
-about the HTTP protocol.
+No Erlang knowledge is required for reading this guide. The reader will
+be introduced to Erlang concepts and redirected to reference material
+whenever necessary.
-In order to run the examples available in this user guide, you will need
-Erlang and rebar installed and in your $PATH.
-
-Please see the [rebar repository](https://github.com/basho/rebar) for
-downloading and building instructions. Please look up the environment
-variables documentation of your system for details on how to update the
-$PATH information.
+Knowledge of the HTTP protocol is recommended but not required, as it
+will be detailed throughout the guide.
Supported platforms
-------------------
@@ -57,81 +53,4 @@ Header names are case insensitive. Cowboy converts all the request
header names to lowercase, and expects your application to provide
lowercase header names in the response.
-Getting started
----------------
-
-Cowboy does nothing by default.
-
-Cowboy requires the `crypto` and `ranch` applications to be started.
-
-``` erlang
-ok = application:start(crypto).
-ok = application:start(ranch).
-ok = application:start(cowboy).
-```
-
-Cowboy uses Ranch for handling the connections and provides convenience
-functions to start Ranch listeners.
-
-The `cowboy:start_http/4` function starts a listener for HTTP connections
-using the TCP transport. The `cowboy:start_https/4` function starts a
-listener for HTTPS connections using the SSL transport.
-
-Listeners are a group of processes that are used to accept and manage
-connections. The processes used specifically for accepting connections
-are called acceptors. The number of acceptor processes is unrelated to
-the maximum number of connections Cowboy can handle. Please refer to
-the Ranch guide for in-depth information.
-
-Listeners are named. They spawn a given number of acceptors, listen for
-connections using the given transport options and pass along the protocol
-options to the connection processes. The protocol options must include
-the dispatch list for routing requests to handlers.
-
-The dispatch list is explained in greater details in the Routing section
-of the guide.
-
-``` erlang
-Dispatch = cowboy_router:compile([
- %% {URIHost, list({URIPath, Handler, Opts})}
- {'_', [{'_', my_handler, []}]}
-]),
-%% Name, NbAcceptors, TransOpts, ProtoOpts
-cowboy:start_http(my_http_listener, 100,
- [{port, 8080}],
- [{env, [{dispatch, Dispatch}]}]
-).
-```
-
-Cowboy features many kinds of handlers. It has plain HTTP handlers, loop
-handlers, Websocket handlers, REST handlers and static handlers. Their
-usage is documented in the respective sections of the guide.
-
-Most applications use the plain HTTP handler, which has three callback
-functions: init/3, handle/2 and terminate/3. You can find more information
-about the arguments and possible return values of these callbacks in the
-HTTP handlers section of this guide. Following is an example of a simple
-HTTP handler module.
-
-``` erlang
--module(my_handler).
--behaviour(cowboy_http_handler).
-
--export([init/3]).
--export([handle/2]).
--export([terminate/3]).
-
-init({tcp, http}, Req, Opts) ->
- {ok, Req, undefined_state}.
-
-handle(Req, State) ->
- {ok, Req2} = cowboy_req:reply(200, [], <<"Hello World!">>, Req),
- {ok, Req2, State}.
-
-terminate(Reason, Req, State) ->
- ok.
-```
-
-The `Req` variable above is the Req object, which allows the developer
-to obtain information about the request and to perform a reply. Its usage
-is explained in its respective section of the guide.
+The same applies to any other case insensitive value.
diff --git a/guide/modern_web.md b/guide/modern_web.md
new file mode 100644
index 0000000..65e1c1c
--- /dev/null
+++ b/guide/modern_web.md
@@ -0,0 +1,224 @@
+The modern Web
+==============
+
+Let's take a look at various technologies from the beginnings
+of the Web up to this day, and get a preview of what's
+coming next.
+
+Cowboy is compatible with all the technology cited in this
+chapter except of course HTTP/2.0 which has no implementation
+in the wild at the time of writing.
+
+The prehistoric Web
+-------------------
+
+HTTP was initially created to serve HTML pages and only
+had the GET method for retrieving them. This initial
+version is documented and is sometimes called HTTP/0.9.
+HTTP/1.0 defined the GET, HEAD and POST methods, and
+was able to send data with POST requests.
+
+HTTP/1.0 works in a very simple way. A TCP connection
+is first established to the server. Then a request is
+sent. Then the server sends a response back and closes
+the connection.
+
+Suffice to say, HTTP/1.0 is not very efficient. Opening
+a TCP connection takes some time, and pages containing
+many assets load much slower than they could because of
+this.
+
+Most improvements done in recent years focused on reducing
+this load time and reducing the latency of the requests.
+
+HTTP/1.1
+--------
+
+HTTP/1.1 quickly followed and added a keep-alive mechanism
+to allow using the same connection for many requests, as
+well as streaming capabilities, allowing an endpoint to send
+a body in well defined chunks.
+
+HTTP/1.1 defines the OPTIONS, GET, HEAD, POST, PUT, DELETE,
+TRACE and CONNECT methods. The PATCH method was added in more
+recent years. It also improves the caching capabilities with
+the introduction of many headers.
+
+HTTP/1.1 still works like HTTP/1.0 does, except the connection
+can be kept alive for subsequent requests. This however allows
+clients to perform what is called as pipelining: sending many
+requests in a row, and then processing the responses which will
+be received in the same order as the requests.
+
+REST
+----
+
+The design of HTTP/1.1 was influenced by the REST architectural
+style. REST, or REpresentational State Transfer, is a style of
+architecture for loosely connected distributed systems.
+
+REST defines constraints that systems must obey to in order to
+be RESTful. A system which doesn't follow all the constraints
+cannot be considered RESTful.
+
+REST is a client-server architecture with a clean separation
+of concerns between the client and the server. They communicate
+by referencing resources. Resources can be identified, but
+also manipulated. A resource representation has a media type
+and information about whether it can be cached and how. Hypermedia
+determines how resources are related and how they can be used.
+REST is also stateless. All requests contain the complete
+information necessary to perform the action.
+
+HTTP/1.1 defines all the methods, headers and semantics required
+to implement RESTful systems.
+
+REST is most often used when designing web application APIs
+which are generally meant to be used by executable code directly.
+
+XmlHttpRequest
+--------------
+
+Also know as AJAX, this technology allows Javascript code running
+on a web page to perform asynchronous requests to the server.
+This is what started the move from static websites to dynamic
+web applications.
+
+XmlHttpRequest still performs HTTP requests under the hood,
+and then waits for a response, but the Javascript code can
+continue to run until the response arrives. It will then receive
+the response through a callback previously defined.
+
+This is of course still requests initiated by the client,
+the server still had no way of pushing data to the client
+on its own, so new technology appeared to allow that.
+
+Long-polling
+------------
+
+Polling was a technique used to overcome the fact that the server
+cannot push data directly to the client. Therefore the client had
+to repeatedly create a connection, make a request, get a response,
+then try again a few seconds later. This is overly expensive and
+adds an additional delay before the client receives the data.
+
+Polling was necessary to implement message queues and other
+similar mechanisms, where a user must be informed of something
+when it happens, rather than when he refreshes the page next.
+A typical example would be a chat application.
+
+Long-polling was created to reduce the server load by creating
+less connections, but also to improve latency by getting the
+response back to the client as soon as it becomes available
+on the server.
+
+Long-polling works in a similar manner to polling, except the
+request will not get a response immediately. Instead the server
+leaves it open until it has a response to send. After getting
+the response, the client creates a new request and gets back
+to waiting.
+
+You probably guessed by now that long-polling is a hack, and
+like most hacks it can suffer from unforeseen issues, in this
+case it doesn't always play well with proxies.
+
+HTML5
+-----
+
+HTML5 is, of course, the HTML version after HTML4. But HTML5
+emerged to solve a specific problem: dynamic web applications.
+
+HTML was initially created to write web pages which compose
+a website. But soon people and companies wanted to use HTML
+to write more and more complex websites, eventually known as
+web applications. They are for example your news reader, your
+email client in the browser, or your video streaming website.
+
+Because HTML wasn't enough, they started using proprietary
+solutions, often implemented using plug-ins. This wasn't
+perfect of course, but worked well enough for most people.
+
+However, the needs for a standard solution eventually became
+apparent. The browser needed to be able to play media natively.
+It needed to be able to draw anything. It needed an efficient
+way of streaming events to the server, but also receiving
+events from the server.
+
+The solution went on to become HTML5. At the time of writing
+it is being standardized.
+
+EventSource
+-----------
+
+EventSource, sometimes also called Server-Sent Events, is a
+technology allowing servers to push data to HTML5 applications.
+
+EventSource is one-way communication channel from the server
+to the client. The client has no means to talk to the server
+other than by using HTTP requests.
+
+It consists of a Javascript object allowing setting up an
+EventSource connection to the server, and a very small protocol
+for sending events to the client on top of the HTTP/1.1
+connection.
+
+EventSource is a lightweight solution that only works for
+UTF-8 encoded text data. Binary data and text data encoded
+differently are not allowed by the protocol. A heavier but
+more generic approach can be found in Websocket.
+
+Websocket
+---------
+
+Websocket is a protocol built on top of HTTP/1.1 that provides
+a two-ways communication channel between the client and the
+server. Communication is asynchronous and can occur concurrently.
+
+It consists of a Javascript object allowing setting up a
+Websocket connection to the server, and a binary based
+protocol for sending data to the server or the client.
+
+Websocket connections can transfer either UTF-8 encoded text
+data or binary data. The protocol also includes support for
+implementing a ping/pong mechanism, allowing the server and
+the client to have more confidence that the connection is still
+alive.
+
+A Websocket connection can be used to transfer any kind of data,
+small or big, text or binary. Because of this Websocket is
+sometimes used for communication between systems.
+
+SPDY
+----
+
+SPDY is an attempt to reduce page loading time by opening a
+single connection per server, keeping it open for subsequent
+requests, and also by compressing the HTTP headers to reduce
+the size of requests.
+
+SPDY is compatible with HTTP/1.1 semantics, and is actually
+just a different way of performing HTTP requests and responses,
+by using binary frames instead of a text-based protocol.
+SPDY also allows the server to send responses without needing
+a request to exist, essentially enabling server push.
+
+SPDY is an experiment that has proven successful and is used
+as the basis for the HTTP/2.0 standard.
+
+Browsers make use of TLS Next Protocol Negotiation to upgrade
+to a SPDY connection seamlessly if the protocol supports it.
+
+The protocol itself has a few shortcomings which are being
+fixed in HTTP/2.0.
+
+HTTP/2.0
+--------
+
+HTTP/2.0 is the long-awaited update to the HTTP/1.1 protocol.
+It is based on SPDY although a lot has been improved at the
+time of writing.
+
+HTTP/2.0 is an asynchronous two-ways communication channel
+between two endpoints.
+
+It is planned to be ready late 2014.
diff --git a/guide/toc.md b/guide/toc.md
index f8eeb18..f30a5bd 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -1,11 +1,39 @@
Cowboy User Guide
=================
+The Cowboy User Guide explores the modern Web and how to make
+best use of Cowboy for writing powerful web applications.
+
+Introducing Cowboy
+------------------
+
* [Introduction](introduction.md)
* Purpose
* Prerequisites
+ * Supported platforms
* Conventions
- * Getting started
+ * [The modern Web](modern_web.md)
+ * The prehistoric Web
+ * HTTP/1.1
+ * REST
+ * Long-polling
+ * HTML5
+ * EventSource
+ * Websocket
+ * SPDY
+ * HTTP/2.0
+ * [Erlang and the Web](erlang_web.md)
+ * The Web is concurrent
+ * The Web is soft real time
+ * The Web is asynchronous
+ * The Web is omnipresent
+ * Erlang is the ideal platform for the Web
+ * [Erlang for beginners](erlang_beginners.md)
+ * [Getting started](getting_started.md)
+
+Using Cowboy
+------------
+
* [Routing](routing.md)
* Purpose
* Structure
diff --git a/manual/cowboy_app.md b/manual/cowboy_app.md
index 5311109..9fe316d 100644
--- a/manual/cowboy_app.md
+++ b/manual/cowboy_app.md
@@ -15,7 +15,7 @@ environment this means that they need to be started with the
application is started.
The `cowboy` application also uses the Erlang applications
-`public_key` and `ssl` when listening for HTTPS connections.
+`asn1`, `public_key` and `ssl` when listening for HTTPS connections.
These are started automatically if they weren't before.
Environment
diff --git a/rebar.config b/rebar.config
index bab6fa3..443949a 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,3 +1,3 @@
{deps, [
- {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.3"}}
+ {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.4"}}
]}.
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index e9cfcb8..0c4a5b1 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -13,11 +13,8 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{application, cowboy, [
- {id, "Cowboy"},
{description, "Small, fast, modular HTTP server."},
- {sub_description, "Cowboy is also a socket acceptor pool, "
- "able to accept connections for any kind of TCP protocol."},
- {vsn, "0.8.5"},
+ {vsn, "0.8.6"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
{applications, [
diff --git a/src/cowboy.erl b/src/cowboy.erl
index 16445e1..abc7911 100644
--- a/src/cowboy.erl
+++ b/src/cowboy.erl
@@ -39,7 +39,7 @@
%% @doc Start an HTTP listener.
-spec start_http(ranch:ref(), non_neg_integer(), ranch_tcp:opts(),
- cowboy_protocol:opts()) -> {ok, pid()}.
+ cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}.
start_http(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
@@ -47,14 +47,15 @@ start_http(Ref, NbAcceptors, TransOpts, ProtoOpts)
%% @doc Start an HTTPS listener.
-spec start_https(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
- cowboy_protocol:opts()) -> {ok, pid()}.
+ cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}.
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()}.
+-spec start_spdy(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
+ cowboy_spdy:opts()) -> {ok, pid()} | {error, any()}.
start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
TransOpts2 = [
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index d2bdf3b..7e20615 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -38,6 +38,7 @@
-export([quoted_string/2]).
-export([authorization/2]).
-export([range/1]).
+-export([parameterized_tokens/1]).
%% Decoding.
-export([te_chunked/2]).
@@ -905,6 +906,49 @@ range_digits(Data, Default, Fun) ->
Fun(Data, Default)
end).
+%% @doc Parse a non empty list of tokens followed with optional parameters.
+-spec parameterized_tokens(binary()) -> any().
+parameterized_tokens(Data) ->
+ nonempty_list(Data,
+ fun (D, Fun) ->
+ token(D,
+ fun (_Rest, <<>>) -> {error, badarg};
+ (Rest, Token) ->
+ parameterized_tokens_params(Rest,
+ fun (Rest2, Params) ->
+ Fun(Rest2, {Token, Params})
+ end, [])
+ end)
+ end).
+
+-spec parameterized_tokens_params(binary(), fun(), [binary() | {binary(), binary()}]) -> any().
+parameterized_tokens_params(Data, Fun, Acc) ->
+ whitespace(Data,
+ fun (<< $;, Rest/binary >>) ->
+ parameterized_tokens_param(Rest,
+ fun (Rest2, Param) ->
+ parameterized_tokens_params(Rest2, Fun, [Param|Acc])
+ end);
+ (Rest) ->
+ Fun(Rest, lists:reverse(Acc))
+ end).
+
+-spec parameterized_tokens_param(binary(), fun()) -> any().
+parameterized_tokens_param(Data, Fun) ->
+ whitespace(Data,
+ fun (Rest) ->
+ token(Rest,
+ fun (_Rest2, <<>>) -> {error, badarg};
+ (<< $=, Rest2/binary >>, Attr) ->
+ word(Rest2,
+ fun (Rest3, Value) ->
+ Fun(Rest3, {Attr, Value})
+ end);
+ (Rest2, Attr) ->
+ Fun(Rest2, Attr)
+ end)
+ end).
+
%% Decoding.
%% @doc Decode a stream of chunks.
@@ -1290,6 +1334,17 @@ content_type_test_() ->
],
[{V, fun () -> R = content_type(V) end} || {V, R} <- Tests].
+parameterized_tokens_test_() ->
+ %% {ParameterizedTokens, Result}
+ Tests = [
+ {<<"foo">>, [{<<"foo">>, []}]},
+ {<<"bar; baz=2">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
+ {<<"bar; baz=2;bat">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, <<"bat">>]}]},
+ {<<"bar; baz=2;bat=\"z=1,2;3\"">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, {<<"bat">>, <<"z=1,2;3">>}]}]},
+ {<<"foo, bar; baz=2">>, [{<<"foo">>, []}, {<<"bar">>, [{<<"baz">>, <<"2">>}]}]}
+ ],
+ [{V, fun () -> R = parameterized_tokens(V) end} || {V, R} <- Tests].
+
digits_test_() ->
%% {Digits, Result}
Tests = [
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 0e1c8a7..5ebcf99 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -460,6 +460,8 @@ parse_header(Name, Req, Default)
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
end);
+parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) ->
+ parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1);
parse_header(Name, Req, Default) ->
{Value, Req2} = header(Name, Req, Default),
{undefined, Value, Req2}.
@@ -1021,31 +1023,36 @@ reply(Status, Headers, Body, Req=#http_req{
reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method) ->
BodySize = iolist_size(Body),
- {ok, Encodings, Req2} = parse_header(<<"accept-encoding">>, Req),
- CanGzip = (BodySize > 300)
- andalso (false =:= lists:keyfind(<<"content-encoding">>,
- 1, Headers))
- andalso (false =:= lists:keyfind(<<"content-encoding">>,
- 1, RespHeaders))
- andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
- 1, Headers))
- andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
- 1, RespHeaders))
- andalso (Encodings =/= undefined)
- andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)),
- case CanGzip of
- true ->
- GzBody = zlib:gzip(Body),
- {_, Req3} = response(Status, Headers, RespHeaders, [
- {<<"content-length">>, integer_to_list(byte_size(GzBody))},
- {<<"content-encoding">>, <<"gzip">>},
- {<<"date">>, cowboy_clock:rfc1123()},
- {<<"server">>, <<"Cowboy">>}
- |HTTP11Headers],
- case Method of <<"HEAD">> -> <<>>; _ -> GzBody end,
- Req2),
- Req3;
- false ->
+ case parse_header(<<"accept-encoding">>, Req) of
+ {ok, Encodings, Req2} ->
+ CanGzip = (BodySize > 300)
+ andalso (false =:= lists:keyfind(<<"content-encoding">>,
+ 1, Headers))
+ andalso (false =:= lists:keyfind(<<"content-encoding">>,
+ 1, RespHeaders))
+ andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
+ 1, Headers))
+ andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
+ 1, RespHeaders))
+ andalso (Encodings =/= undefined)
+ andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)),
+ case CanGzip of
+ true ->
+ GzBody = zlib:gzip(Body),
+ {_, Req3} = response(Status, Headers, RespHeaders, [
+ {<<"content-length">>, integer_to_list(byte_size(GzBody))},
+ {<<"content-encoding">>, <<"gzip">>},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers],
+ case Method of <<"HEAD">> -> <<>>; _ -> GzBody end,
+ Req2),
+ Req3;
+ false ->
+ reply_no_compress(Status, Headers, Body, Req,
+ RespHeaders, HTTP11Headers, Method, BodySize)
+ end;
+ {error, badarg} ->
reply_no_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method, BodySize)
end.
@@ -1168,6 +1175,7 @@ g(port, #http_req{port=Ret}) -> Ret;
g(qs, #http_req{qs=Ret}) -> Ret;
g(qs_vals, #http_req{qs_vals=Ret}) -> Ret;
g(resp_body, #http_req{resp_body=Ret}) -> Ret;
+g(resp_compress, #http_req{resp_compress=Ret}) -> Ret;
g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
g(resp_state, #http_req{resp_state=Ret}) -> Ret;
g(socket, #http_req{socket=Ret}) -> Ret;
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index ecbe7bc..34bfce1 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -1092,7 +1092,7 @@ terminate(Req, State=#state{env=Env}) ->
-spec error_terminate(cowboy_req:req(), #state{}) -> no_return().
error_terminate(Req, State) ->
rest_terminate(Req, State),
- erlang:throw({?MODULE, error}).
+ erlang:raise(throw, {?MODULE, error}, erlang:get_stacktrace()).
rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
case erlang:function_exported(Handler, rest_terminate, 2) of
diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl
index d605331..182e6da 100644
--- a/src/cowboy_spdy.erl
+++ b/src/cowboy_spdy.erl
@@ -221,12 +221,14 @@ 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 >>) ->
+control_frame(State, << 1:1, 3:15, 1:16, _:6, 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 ->
+control_frame(State, << 1:1, 3:15, 1:16, _:33, StreamID:31, _:1,
+ AssocToStreamID:31, _:8, Slot:8, _/bits >>)
+ when AssocToStreamID =/= 0; Slot =/= 0 ->
rst_stream(State, StreamID, internal_error),
loop(State);
%% SYN_STREAM
@@ -256,6 +258,9 @@ control_frame(State=#state{middlewares=Middlewares, env=Env,
loop(State#state{last_streamid=StreamID,
children=[#child{streamid=StreamID, pid=Pid,
input=IsFin, output=nofin}|Children]});
+ {error, badname} ->
+ rst_stream(State, StreamID, protocol_error),
+ loop(State#state{last_streamid=StreamID});
{error, special} ->
rst_stream(State, StreamID, protocol_error),
loop(State#state{last_streamid=StreamID})
@@ -353,6 +358,8 @@ syn_stream_headers(<<>>, 0, Acc, Special=#special_headers{
true ->
{ok, lists:reverse(Acc), Special}
end;
+syn_stream_headers(<< 0:32, _Rest/bits >>, _NbHeaders, _Acc, _Special) ->
+ {error, badname};
syn_stream_headers(<< NameLen:32, Rest/bits >>, NbHeaders, Acc, Special) ->
<< Name:NameLen/binary, ValueLen:32, Rest2/bits >> = Rest,
<< Value:ValueLen/binary, Rest3/bits >> = Rest2,
diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl
index fd5654e..d144dd3 100644
--- a/src/cowboy_static.erl
+++ b/src/cowboy_static.erl
@@ -233,7 +233,7 @@ rest_init(Req, Opts) ->
end.
rest_init(Req, Opts, Filepath) ->
- Fileinfo = file:read_file_info(Filepath),
+ Fileinfo = file:read_file_info(Filepath, [{time, universal}]),
Mimetypes = case lists:keyfind(mimetypes, 1, Opts) of
false -> {fun path_to_mimetypes/2, []};
{_, {{M, F}, E}} -> {fun M:F/2, E};
@@ -290,7 +290,7 @@ forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) ->
-spec last_modified(Req, #state{})
-> {calendar:datetime(), Req, #state{}} when Req::cowboy_req:req().
last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) ->
- {erlang:localtime_to_universaltime(Modified), Req, State}.
+ {Modified, Req, State}.
%% @private Generate the ETag header value for this file.
%% The ETag header value is only generated if the resource is a file that
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 3667797..df50162 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -19,6 +19,10 @@
-module(cowboy_websocket).
-behaviour(cowboy_sub_protocol).
+%% Ignore the deprecation warning for crypto:sha/1.
+%% @todo Remove when we support only R16B+.
+-compile(nowarn_deprecated_function).
+
%% API.
-export([upgrade/4]).
@@ -37,6 +41,7 @@
-type mask_key() :: 0..16#ffffffff.
-type frag_state() :: undefined
| {nofin, opcode(), binary()} | {fin, opcode(), binary()}.
+-type rsv() :: << _:3 >>.
-record(state, {
env :: cowboy_middleware:env(),
@@ -50,7 +55,11 @@
messages = undefined :: undefined | {atom(), atom(), atom()},
hibernate = false :: boolean(),
frag_state = undefined :: frag_state(),
- utf8_state = <<>> :: binary()
+ utf8_state = <<>> :: binary(),
+ deflate_frame = false :: boolean(),
+ inflate_state :: any(),
+ inflate_buffer = <<>> :: binary(),
+ deflate_state :: any()
}).
%% @doc Upgrade an HTTP request to the Websocket protocol.
@@ -88,8 +97,39 @@ websocket_upgrade(State, Req) ->
orelse (IntVersion =:= 13),
{Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4),
false = Key =:= undefined,
- {ok, State#state{key=Key},
- cowboy_req:set_meta(websocket_version, IntVersion, Req5)}.
+ websocket_extensions(State#state{key=Key},
+ cowboy_req:set_meta(websocket_version, IntVersion, Req5)).
+
+-spec websocket_extensions(#state{}, Req)
+ -> {ok, #state{}, Req} when Req::cowboy_req:req().
+websocket_extensions(State, Req) ->
+ case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of
+ {ok, Extensions, Req2} when Extensions =/= undefined ->
+ [Compress] = cowboy_req:get([resp_compress], Req),
+ case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of
+ {<<"x-webkit-deflate-frame">>, []} when Compress =:= true ->
+ Inflate = zlib:open(),
+ Deflate = zlib:open(),
+ % Since we are negotiating an unconstrained deflate-frame
+ % then we must be willing to accept frames using the
+ % maximum window size which is 2^15. The negative value
+ % indicates that zlib headers are not used.
+ ok = zlib:inflateInit(Inflate, -15),
+ % Initialize the deflater with a window size of 2^15 bits and disable
+ % the zlib headers.
+ ok = zlib:deflateInit(Deflate, best_compression, deflated, -15, 8, default),
+ {ok, State#state{
+ deflate_frame = true,
+ inflate_state = Inflate,
+ inflate_buffer = <<>>,
+ deflate_state = Deflate
+ }, Req2};
+ _ ->
+ {ok, State, Req2}
+ end;
+ _ ->
+ {ok, State, Req}
+ end.
-spec handler_init(#state{}, Req)
-> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
@@ -137,14 +177,21 @@ upgrade_error(Req, Env) ->
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-websocket_handshake(State=#state{transport=Transport, key=Key},
+websocket_handshake(State=#state{
+ transport=Transport, key=Key, deflate_frame=DeflateFrame},
Req, HandlerState) ->
+ %% @todo Change into crypto:hash/2 for R17B+ or when supporting only R16B+.
Challenge = base64:encode(crypto:sha(
<< Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
+ Extensions = case DeflateFrame of
+ false -> [];
+ true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}]
+ end,
{ok, Req2} = cowboy_req:upgrade_reply(
101,
[{<<"upgrade">>, <<"websocket">>},
- {<<"sec-websocket-accept">>, Challenge}],
+ {<<"sec-websocket-accept">>, Challenge}|
+ Extensions],
Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
@@ -211,7 +258,7 @@ handler_loop(State=#state{socket=Socket, messages={OK, Closed, Error},
%% RSV bits MUST be 0 unless an extension is negotiated
%% that defines meanings for non-zero values.
websocket_data(State, Req, HandlerState, << _:1, Rsv:3, _/bits >>)
- when Rsv =/= 0 ->
+ when Rsv =/= 0, State#state.deflate_frame =:= false ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Invalid opcode. Note that these opcodes may be used by extensions.
websocket_data(State, Req, HandlerState, << _:4, Opcode:4, _/bits >>)
@@ -239,23 +286,23 @@ websocket_data(State, Req, HandlerState,
when Len > 1, byte_size(Data) < 8 ->
handler_before_loop(State, Req, HandlerState, Data);
%% 7 bits payload length.
-websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
Len:7, MaskKey:32, Rest/bits >>)
when Len < 126 ->
websocket_data(State, Req, HandlerState,
- Opcode, Len, MaskKey, Rest, Fin);
+ Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% 16 bits payload length.
-websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
126:7, Len:16, MaskKey:32, Rest/bits >>)
when Len > 125, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
- Opcode, Len, MaskKey, Rest, Fin);
+ Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% 63 bits payload length.
-websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>)
when Len > 16#ffff, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
- Opcode, Len, MaskKey, Rest, Fin);
+ Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% When payload length is over 63 bits, the most significant bit MUST be 0.
websocket_data(State, Req, HandlerState, << _:8, 1:1, 127:7, 1:1, _:7, _/binary >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
@@ -276,120 +323,141 @@ websocket_data(State, Req, HandlerState, Data) ->
%% Initialize or update fragmentation state.
-spec websocket_data(#state{}, Req, any(),
- opcode(), non_neg_integer(), mask_key(), binary(), 0 | 1)
+ opcode(), non_neg_integer(), mask_key(), binary(), rsv(), 0 | 1)
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% The opcode is only included in the first frame fragment.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
- Opcode, Len, MaskKey, Data, 0) ->
+ Opcode, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}},
- Req, HandlerState, 0, Len, MaskKey, <<>>, Data);
+ Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv);
%% Subsequent frame fragments.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
- 0, Len, MaskKey, Data, 0) ->
+ 0, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State, Req, HandlerState,
- 0, Len, MaskKey, <<>>, Data);
+ 0, Len, MaskKey, <<>>, Data, Rsv);
%% Final frame fragment.
websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}},
- Req, HandlerState, 0, Len, MaskKey, Data, 1) ->
+ Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State#state{frag_state={fin, Opcode, SoFar}},
- Req, HandlerState, 0, Len, MaskKey, <<>>, Data);
+ Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv);
%% Unfragmented frame.
-websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, 1) ->
+websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State, Req, HandlerState,
- Opcode, Len, MaskKey, <<>>, Data).
+ Opcode, Len, MaskKey, <<>>, Data, Rsv).
-spec websocket_payload(#state{}, Req, any(),
- opcode(), non_neg_integer(), mask_key(), binary(), binary())
+ opcode(), non_neg_integer(), mask_key(), binary(), binary(), rsv())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% Close control frames with a payload MUST contain a valid close code.
websocket_payload(State, Req, HandlerState,
- Opcode=8, Len, MaskKey, <<>>, << MaskedCode:2/binary, Rest/bits >>) ->
+ Opcode=8, Len, MaskKey, <<>>, << MaskedCode:2/binary, Rest/bits >>, Rsv) ->
Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>),
if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006;
(Code > 1011) and (Code < 3000); Code > 4999 ->
websocket_close(State, Req, HandlerState, {error, badframe});
true ->
websocket_payload(State, Req, HandlerState,
- Opcode, Len - 2, MaskKey, Unmasked, Rest)
+ Opcode, Len - 2, MaskKey, Unmasked, Rest, Rsv)
end;
%% Text frames and close control frames MUST have a payload that is valid UTF-8.
websocket_payload(State=#state{utf8_state=Incomplete},
- Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data)
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv)
when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse
((Opcode =:= 8) andalso (Unmasked =/= <<>>))) ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
- case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
+ case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false ->
- websocket_close(State, Req, HandlerState, {error, badencoding});
+ websocket_close(State2, Req, HandlerState, {error, badencoding});
Utf8State ->
- websocket_payload_loop(State#state{utf8_state=Utf8State},
+ websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
- << Unmasked/binary, Unmasked2/binary >>)
+ << Unmasked/binary, Unmasked3/binary >>, Rsv)
end;
websocket_payload(State=#state{utf8_state=Incomplete},
- Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data)
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv)
when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
- case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
+ case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> ->
- websocket_dispatch(State#state{utf8_state= <<>>},
+ websocket_dispatch(State2#state{utf8_state= <<>>},
Req, HandlerState, Rest, Opcode,
- << Unmasked/binary, Unmasked2/binary >>);
+ << Unmasked/binary, Unmasked3/binary >>);
_ ->
- websocket_close(State, Req, HandlerState, {error, badencoding})
+ websocket_close(State2, Req, HandlerState, {error, badencoding})
end;
%% Fragmented text frames may cut payload in the middle of UTF-8 codepoints.
websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete},
- Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data)
+ Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv)
when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
- case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
+ case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false ->
- websocket_close(State, Req, HandlerState, {error, badencoding});
+ websocket_close(State2, Req, HandlerState, {error, badencoding});
Utf8State ->
- websocket_payload_loop(State#state{utf8_state=Utf8State},
+ websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
- << Unmasked/binary, Unmasked2/binary >>)
+ << Unmasked/binary, Unmasked3/binary >>, Rsv)
end;
websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete},
- Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data) ->
+ Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
- case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
+ case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> ->
- websocket_dispatch(State#state{utf8_state= <<>>},
+ websocket_dispatch(State2#state{utf8_state= <<>>},
Req, HandlerState, Rest, Opcode,
- << Unmasked/binary, Unmasked2/binary >>);
+ << Unmasked/binary, Unmasked3/binary >>);
Utf8State when is_binary(Utf8State), Fin =:= nofin ->
- websocket_dispatch(State#state{utf8_state=Utf8State},
+ websocket_dispatch(State2#state{utf8_state=Utf8State},
Req, HandlerState, Rest, Opcode,
- << Unmasked/binary, Unmasked2/binary >>);
+ << Unmasked/binary, Unmasked3/binary >>);
_ ->
websocket_close(State, Req, HandlerState, {error, badencoding})
end;
%% Other frames have a binary payload.
websocket_payload(State, Req, HandlerState,
- Opcode, Len, MaskKey, Unmasked, Data)
+ Opcode, Len, MaskKey, Unmasked, Data, Rsv)
when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked),
- websocket_payload_loop(State, Req, HandlerState,
- Opcode, Len - byte_size(Data), MaskKey, Unmasked2);
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
+ websocket_payload_loop(State2, Req, HandlerState,
+ Opcode, Len - byte_size(Data), MaskKey, Unmasked3, Rsv);
websocket_payload(State, Req, HandlerState,
- Opcode, Len, MaskKey, Unmasked, Data) ->
+ Opcode, Len, MaskKey, Unmasked, Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked),
- websocket_dispatch(State, Req, HandlerState, Rest, Opcode, Unmasked2).
+ {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
+ websocket_dispatch(State2, Req, HandlerState, Rest, Opcode, Unmasked3).
+
+-spec websocket_inflate_frame(binary(), rsv(), boolean(), #state{}) ->
+ {binary(), #state{}}.
+websocket_inflate_frame(Data, << Rsv1:1, _:2 >>, _,
+ #state{deflate_frame = DeflateFrame} = State)
+ when DeflateFrame =:= false orelse Rsv1 =:= 0 ->
+ {Data, State};
+websocket_inflate_frame(Data, << 1:1, _:2 >>, false,
+ #state{inflate_buffer = Buffer} = State) ->
+ {<<>>, State#state{inflate_buffer = << Buffer/binary, Data/binary >>}};
+websocket_inflate_frame(Data, << 1:1, _:2 >>, true,
+ #state{inflate_state = Inflate, inflate_buffer = Buffer} = State) ->
+ Deflated = << Buffer/binary, Data/binary, 0:8, 0:8, 255:8, 255:8 >>,
+ Result = zlib:inflate(Inflate, Deflated),
+ {iolist_to_binary(Result), State#state{inflate_buffer = <<>>}}.
-spec websocket_unmask(B, mask_key(), B) -> B when B::binary().
websocket_unmask(<<>>, _, Unmasked) ->
@@ -448,19 +516,19 @@ is_utf8(_) ->
false.
-spec websocket_payload_loop(#state{}, Req, any(),
- opcode(), non_neg_integer(), mask_key(), binary())
+ opcode(), non_neg_integer(), mask_key(), binary(), rsv())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
messages={OK, Closed, Error}, timeout_ref=TRef},
- Req, HandlerState, Opcode, Len, MaskKey, Unmasked) ->
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Rsv) ->
Transport:setopts(Socket, [{active, once}]),
receive
{OK, Socket, Data} ->
State2 = handler_loop_timeout(State),
websocket_payload(State2, Req, HandlerState,
- Opcode, Len, MaskKey, Unmasked, Data);
+ Opcode, Len, MaskKey, Unmasked, Data, Rsv);
{Closed, Socket} ->
handler_terminate(State, Req, HandlerState, {error, closed});
{Error, Socket, Reason} ->
@@ -469,13 +537,13 @@ websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
websocket_close(State, Req, HandlerState, {normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
websocket_payload_loop(State, Req, HandlerState,
- Opcode, Len, MaskKey, Unmasked);
+ Opcode, Len, MaskKey, Unmasked, Rsv);
Message ->
handler_call(State, Req, HandlerState,
<<>>, websocket_info, Message,
fun (State2, Req2, HandlerState2, _) ->
websocket_payload_loop(State2, Req2, HandlerState2,
- Opcode, Len, MaskKey, Unmasked)
+ Opcode, Len, MaskKey, Unmasked, Rsv)
end)
end.
@@ -534,48 +602,48 @@ handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req,
{reply, Payload, Req2, HandlerState2}
when is_tuple(Payload) ->
case websocket_send(Payload, State) of
- ok ->
- NextState(State, Req2, HandlerState2, RemainingData);
- shutdown ->
- handler_terminate(State, Req2, HandlerState2,
+ {ok, State2} ->
+ NextState(State2, Req2, HandlerState2, RemainingData);
+ {shutdown, State2} ->
+ handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
- {error, _} = Error ->
- handler_terminate(State, Req2, HandlerState2, Error)
+ {{error, _} = Error, State2} ->
+ handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_tuple(Payload) ->
case websocket_send(Payload, State) of
- ok ->
- NextState(State#state{hibernate=true},
+ {ok, State2} ->
+ NextState(State2#state{hibernate=true},
Req2, HandlerState2, RemainingData);
- shutdown ->
- handler_terminate(State, Req2, HandlerState2,
+ {shutdown, State2} ->
+ handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
- {error, _} = Error ->
- handler_terminate(State, Req2, HandlerState2, Error)
+ {{error, _} = Error, State2} ->
+ handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2}
when is_list(Payload) ->
case websocket_send_many(Payload, State) of
- ok ->
- NextState(State, Req2, HandlerState2, RemainingData);
- shutdown ->
- handler_terminate(State, Req2, HandlerState2,
+ {ok, State2} ->
+ NextState(State2, Req2, HandlerState2, RemainingData);
+ {shutdown, State2} ->
+ handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
- {error, _} = Error ->
- handler_terminate(State, Req2, HandlerState2, Error)
+ {{error, _} = Error, State2} ->
+ handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_list(Payload) ->
case websocket_send_many(Payload, State) of
- ok ->
- NextState(State#state{hibernate=true},
+ {ok, State2} ->
+ NextState(State2#state{hibernate=true},
Req2, HandlerState2, RemainingData);
- shutdown ->
- handler_terminate(State, Req2, HandlerState2,
+ {shutdown, State2} ->
+ handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
- {error, _} = Error ->
- handler_terminate(State, Req2, HandlerState2, Error)
+ {{error, _} = Error, State2} ->
+ handler_terminate(State2, Req2, HandlerState2, Error)
end;
{shutdown, Req2, HandlerState2} ->
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
@@ -597,22 +665,36 @@ websocket_opcode(close) -> 8;
websocket_opcode(ping) -> 9;
websocket_opcode(pong) -> 10.
+-spec websocket_deflate_frame(opcode(), binary(), #state{}) -> {binary(), <<_:3>>, #state{}}.
+websocket_deflate_frame(Opcode, Payload,
+ State=#state{deflate_frame = DeflateFrame})
+ when DeflateFrame =:= false orelse Opcode >= 8 ->
+ {Payload, <<0:3>>, State};
+websocket_deflate_frame(_, Payload, State=#state{deflate_state = Deflate}) ->
+ Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)),
+ DeflatedBodyLength = erlang:size(Deflated) - 4,
+ Deflated1 = case Deflated of
+ <<Body:DeflatedBodyLength/binary, 0:8, 0:8, 255:8, 255:8>> -> Body;
+ _ -> Deflated
+ end,
+ {Deflated1, <<1:1, 0:2>>, State}.
+
-spec websocket_send(frame(), #state{})
- -> ok | shutdown | {error, atom()}.
-websocket_send(Type, #state{socket=Socket, transport=Transport})
+-> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}.
+websocket_send(Type, State=#state{socket=Socket, transport=Transport})
when Type =:= close ->
Opcode = websocket_opcode(Type),
case Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>) of
- ok -> shutdown;
- Error -> Error
+ ok -> {shutdown, State};
+ Error -> {Error, State}
end;
-websocket_send(Type, #state{socket=Socket, transport=Transport})
+websocket_send(Type, State=#state{socket=Socket, transport=Transport})
when Type =:= ping; Type =:= pong ->
Opcode = websocket_opcode(Type),
- Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>);
+ {Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>), State};
websocket_send({close, Payload}, State) ->
websocket_send({close, 1000, Payload}, State);
-websocket_send({Type = close, StatusCode, Payload}, #state{
+websocket_send({Type = close, StatusCode, Payload}, State=#state{
socket=Socket, transport=Transport}) ->
Opcode = websocket_opcode(Type),
Len = 2 + iolist_size(Payload),
@@ -621,9 +703,10 @@ websocket_send({Type = close, StatusCode, Payload}, #state{
BinLen = payload_length_to_binary(Len),
Transport:send(Socket,
[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
- shutdown;
-websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
+ {shutdown, State};
+websocket_send({Type, Payload0}, State=#state{socket=Socket, transport=Transport}) ->
Opcode = websocket_opcode(Type),
+ {Payload, Rsv, State2} = websocket_deflate_frame(Opcode, iolist_to_binary(Payload0), State),
Len = iolist_size(Payload),
%% Control packets must not be > 125 in length.
true = if Type =:= ping; Type =:= pong ->
@@ -632,18 +715,18 @@ websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
true
end,
BinLen = payload_length_to_binary(Len),
- Transport:send(Socket,
- [<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits >>, Payload]).
+ {Transport:send(Socket,
+ [<< 1:1, Rsv/bits, Opcode:4, 0:1, BinLen/bits >>, Payload]), State2}.
-spec websocket_send_many([frame()], #state{})
- -> ok | shutdown | {error, atom()}.
-websocket_send_many([], _) ->
- ok;
+ -> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}.
+websocket_send_many([], State) ->
+ {ok, State};
websocket_send_many([Frame|Tail], State) ->
case websocket_send(Frame, State) of
- ok -> websocket_send_many(Tail, State);
- shutdown -> shutdown;
- Error -> Error
+ {ok, State2} -> websocket_send_many(Tail, State2);
+ {shutdown, State2} -> {shutdown, State2};
+ {Error, State2} -> {Error, State2}
end.
-spec websocket_close(#state{}, Req, any(),
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 39b30db..7483599 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -216,6 +216,7 @@ init_per_group(https, Config) ->
Transport = ranch_ssl,
{_, Cert, Key} = ct_helper:make_certs(),
Opts = [{cert, Cert}, {key, Key}],
+ application:start(asn1),
application:start(public_key),
application:start(ssl),
{ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, 0}], [
@@ -243,6 +244,7 @@ init_per_group(https_compress, Config) ->
Transport = ranch_ssl,
{_, Cert, Key} = ct_helper:make_certs(),
Opts = [{cert, Cert}, {key, Key}],
+ application:start(asn1),
application:start(public_key),
application:start(ssl),
{ok, _} = cowboy:start_https(https_compress, 100, Opts ++ [{port, 0}], [
@@ -307,6 +309,7 @@ end_per_group(Name, _) when Name =:= https; Name =:= https_compress ->
cowboy:stop_listener(Name),
application:stop(ssl),
application:stop(public_key),
+ application:stop(asn1),
ok;
end_per_group(Name, _) ->
cowboy:stop_listener(Name),
diff --git a/test/spdy_SUITE.erl b/test/spdy_SUITE.erl
index 1089991..469e5d6 100644
--- a/test/spdy_SUITE.erl
+++ b/test/spdy_SUITE.erl
@@ -42,6 +42,7 @@ init_per_suite(Config) ->
application:start(crypto),
application:start(ranch),
application:start(cowboy),
+ application:start(asn1),
application:start(public_key),
application:start(ssl),
Dir = ?config(priv_dir, Config) ++ "/static",
@@ -53,6 +54,7 @@ end_per_suite(Config) ->
ct_helper:delete_static_dir(Dir),
application:stop(ssl),
application:stop(public_key),
+ application:stop(asn1),
application:stop(cowboy),
application:stop(ranch),
application:stop(crypto),
diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl
index fbef41a..bdb3565 100644
--- a/test/ws_SUITE.erl
+++ b/test/ws_SUITE.erl
@@ -30,6 +30,7 @@
-export([ws8_init_shutdown/1]).
-export([ws8_single_bytes/1]).
-export([ws13/1]).
+-export([ws_deflate/1]).
-export([ws_send_close/1]).
-export([ws_send_close_payload/1]).
-export([ws_send_many/1]).
@@ -51,6 +52,7 @@ groups() ->
ws8_init_shutdown,
ws8_single_bytes,
ws13,
+ ws_deflate,
ws_send_close,
ws_send_close_payload,
ws_send_many,
@@ -76,7 +78,8 @@ end_per_suite(_Config) ->
init_per_group(ws, Config) ->
cowboy:start_http(ws, 100, [{port, 0}], [
- {env, [{dispatch, init_dispatch()}]}
+ {env, [{dispatch, init_dispatch()}]},
+ {compress, true}
]),
Port = ranch:get_port(ws),
[{port, Port}|Config].
@@ -309,6 +312,58 @@ ws13(Config) ->
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
+ws_deflate(Config) ->
+ {port, Port} = lists:keyfind(port, 1, Config),
+ {ok, Socket} = gen_tcp:connect("localhost", Port,
+ [binary, {active, false}, {packet, raw}]),
+ ok = gen_tcp:send(Socket, [
+ "GET /ws_echo HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: Upgrade\r\n"
+ "Upgrade: websocket\r\n"
+ "Sec-WebSocket-Origin: http://localhost\r\n"
+ "Sec-WebSocket-Version: 8\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
+ "\r\n"]),
+ {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
+ {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
+ = erlang:decode_packet(http, Handshake, []),
+ [Headers, <<>>] = websocket_headers(
+ erlang:decode_packet(httph, Rest, []), []),
+ {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
+ {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
+ {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
+ = lists:keyfind("sec-websocket-accept", 1, Headers),
+ {"sec-websocket-extensions", "x-webkit-deflate-frame"}
+ = lists:keyfind("sec-websocket-extensions", 1, Headers),
+
+ % send uncompressed text frame containing the Hello string
+ ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
+ 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
+ % receive compressed text frame containing the Hello string
+ {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, 242, 72, 205, 201, 201, 7, 0 >>}
+ = gen_tcp:recv(Socket, 0, 6000),
+
+ % send uncompressed text frame containing the HelloHello string
+ % as 2 separate fragments
+ ok = gen_tcp:send(Socket, [
+ << 0:1, 0:3, 1:4, 1:1, 5:7 >>,
+ << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
+ << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
+ ok = gen_tcp:send(Socket, [
+ << 1:1, 0:3, 0:4, 1:1, 5:7 >>,
+ << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
+ << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
+ % receive compressed text frame containing the HelloHello string
+ {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 5:7, 242, 128, 19, 0, 0 >>}
+ = gen_tcp:recv(Socket, 0, 6000),
+
+ ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
+ {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
+ {error, closed} = gen_tcp:recv(Socket, 0, 6000),
+ ok.
+
ws_send_close(Config) ->
{port, Port} = lists:keyfind(port, 1, Config),
{ok, Socket} = gen_tcp:connect("localhost", Port,