aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--examples/README.md6
-rw-r--r--examples/eventsource/README.md22
-rw-r--r--examples/eventsource/priv/index.html44
-rw-r--r--examples/eventsource/rebar.config6
-rw-r--r--examples/eventsource/src/eventsource.app.src15
-rw-r--r--examples/eventsource/src/eventsource.erl14
-rw-r--r--examples/eventsource/src/eventsource_app.erl30
-rw-r--r--examples/eventsource/src/eventsource_handler.erl27
-rw-r--r--examples/eventsource/src/eventsource_sup.erl23
-rwxr-xr-xexamples/eventsource/start.sh3
-rw-r--r--examples/rest_pastebin/src/toppage_handler.erl1
-rw-r--r--examples/web_server/README.md27
-rw-r--r--examples/web_server/priv/small.mp4bin0 -> 383631 bytes
-rw-r--r--examples/web_server/priv/small.ogvbin0 -> 872453 bytes
-rw-r--r--examples/web_server/priv/test.txt1
-rw-r--r--examples/web_server/priv/video.html11
-rw-r--r--examples/web_server/rebar.config8
-rw-r--r--examples/web_server/src/directory_handler.erl51
-rw-r--r--examples/web_server/src/directory_lister.erl37
-rw-r--r--examples/web_server/src/web_server.app.src15
-rw-r--r--examples/web_server/src/web_server.erl14
-rw-r--r--examples/web_server/src/web_server_app.erl30
-rw-r--r--examples/web_server/src/web_server_sup.erl23
-rwxr-xr-xexamples/web_server/start.sh3
-rw-r--r--guide/handlers.md3
-rw-r--r--guide/middlewares.md3
-rw-r--r--guide/routing.md14
-rw-r--r--guide/toc.md7
-rw-r--r--src/cowboy.erl14
-rw-r--r--src/cowboy_handler.erl31
-rw-r--r--src/cowboy_rest.erl1
-rw-r--r--src/cowboy_sub_protocol.erl37
-rw-r--r--src/cowboy_websocket.erl1
-rw-r--r--test/http_SUITE.erl29
35 files changed, 543 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index 03cbcc3..d04758c 100644
--- a/Makefile
+++ b/Makefile
@@ -36,10 +36,12 @@ app: ebin/$(PROJECT).app
| 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/ \
- src/$(PROJECT)_middleware.erl $?
+ $(COMPILE_FIRST) $?
clean:
$(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump
diff --git a/examples/README.md b/examples/README.md
index e166876..ef5bfaf 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -22,6 +22,9 @@ Cowboy Examples
* [elixir_hello_world](./elixir_hello_world):
simplest example application with Elixir
+ * [eventsource](./eventsource):
+ eventsource emitter and consumer
+
* [hello_world](./hello_world):
simplest example application
@@ -40,5 +43,8 @@ Cowboy Examples
* [static_world](./static_world):
static file handler
+ * [web_sever](./web_server):
+ serves files with lists directory entries
+
* [websocket](./websocket):
websocket example
diff --git a/examples/eventsource/README.md b/examples/eventsource/README.md
new file mode 100644
index 0000000..c9662a1
--- /dev/null
+++ b/examples/eventsource/README.md
@@ -0,0 +1,22 @@
+Cowboy EventSource
+==================
+
+To compile this example you need rebar in your PATH.
+
+Type the following command:
+```
+$ rebar get-deps compile
+```
+
+You can then start the Erlang node with the following command:
+```
+./start.sh
+```
+
+Uses Cowboy's loop functionality to continuously send events to the browser.
+
+Example
+-------
+
+Point your browser to http://localhost:8080 to see EventSource in action with
+any modern browser (not IE).
diff --git a/examples/eventsource/priv/index.html b/examples/eventsource/priv/index.html
new file mode 100644
index 0000000..f057195
--- /dev/null
+++ b/examples/eventsource/priv/index.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script type="text/javascript">
+ function ready() {
+ if (!!window.EventSource) {
+ setupEventSource();
+ } else {
+ document.getElementById('status').innerHTML =
+ "Sorry but your browser doesn't support the EventSource API";
+ }
+ }
+
+ function setupEventSource() {
+ var source = new EventSource('/eventsource');
+
+ source.addEventListener('message', function(event) {
+ addStatus("server sent the following: '" + event.data + "'");
+ }, false);
+
+ source.addEventListener('open', function(event) {
+ addStatus('eventsource connected.')
+ }, false);
+
+ source.addEventListener('error', function(event) {
+ if (event.eventPhase == EventSource.CLOSED) {
+ addStatus('eventsource was closed.')
+ }
+ }, false);
+ }
+
+ function addStatus(text) {
+ var date = new Date();
+ document.getElementById('status').innerHTML
+ = document.getElementById('status').innerHTML
+ + date + ": " + text + "<br/>";
+ }
+ </script>
+ </head>
+ <body onload="ready();">
+ Hi!
+ <div id="status"></div>
+ </body>
+</html>
diff --git a/examples/eventsource/rebar.config b/examples/eventsource/rebar.config
new file mode 100644
index 0000000..eb6f194
--- /dev/null
+++ b/examples/eventsource/rebar.config
@@ -0,0 +1,6 @@
+{deps, [
+ {cowboy, ".*",
+ {git, "git://github.com/extend/cowboy.git", "master"}},
+ {mimetypes, ".*",
+ {git, "git://github.com/spawngrid/mimetypes.git", "master"}}
+]}.
diff --git a/examples/eventsource/src/eventsource.app.src b/examples/eventsource/src/eventsource.app.src
new file mode 100644
index 0000000..002ea85
--- /dev/null
+++ b/examples/eventsource/src/eventsource.app.src
@@ -0,0 +1,15 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+{application, eventsource, [
+ {description, "Cowboy EventSource example."},
+ {vsn, "1"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ cowboy
+ ]},
+ {mod, {eventsource_app, []}},
+ {env, []}
+]}.
diff --git a/examples/eventsource/src/eventsource.erl b/examples/eventsource/src/eventsource.erl
new file mode 100644
index 0000000..6505e62
--- /dev/null
+++ b/examples/eventsource/src/eventsource.erl
@@ -0,0 +1,14 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(eventsource).
+
+%% API.
+-export([start/0]).
+
+%% API.
+
+start() ->
+ ok = application:start(crypto),
+ ok = application:start(ranch),
+ ok = application:start(cowboy),
+ ok = application:start(eventsource).
diff --git a/examples/eventsource/src/eventsource_app.erl b/examples/eventsource/src/eventsource_app.erl
new file mode 100644
index 0000000..d002676
--- /dev/null
+++ b/examples/eventsource/src/eventsource_app.erl
@@ -0,0 +1,30 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(eventsource_app).
+-behaviour(application).
+
+%% API.
+-export([start/2]).
+-export([stop/1]).
+
+%% API.
+
+start(_Type, _Args) ->
+ Dispatch = cowboy_router:compile([
+ {'_', [
+ {"/eventsource", eventsource_handler, []},
+ {"/", cowboy_static, [
+ {directory, {priv_dir, eventsource, []}},
+ {file, <<"index.html">>},
+ {mimetypes, {fun mimetypes:path_to_mimes/2, default}}
+ ]}
+ ]}
+ ]),
+ {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
+ {env, [{dispatch, Dispatch}]}
+ ]),
+ eventsource_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/examples/eventsource/src/eventsource_handler.erl b/examples/eventsource/src/eventsource_handler.erl
new file mode 100644
index 0000000..661057c
--- /dev/null
+++ b/examples/eventsource/src/eventsource_handler.erl
@@ -0,0 +1,27 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @doc EventSource emitter.
+-module(eventsource_handler).
+
+-export([init/3]).
+-export([info/3]).
+-export([terminate/3]).
+
+init(_Transport, Req, []) ->
+ Headers = [{<<"content-type">>, <<"text/event-stream">>}],
+ {ok, Req2} = cowboy_req:chunked_reply(200, Headers, Req),
+ erlang:send_after(1000, self(), {message, "Tick"}),
+ {loop, Req2, undefined, 5000}.
+
+info({message, Msg}, Req, State) ->
+ ok = cowboy_req:chunk(["id: ", id(), "\ndata: ", Msg, "\n\n"], Req),
+ erlang:send_after(1000, self(), {message, "Tick"}),
+ {loop, Req, State}.
+
+terminate(_Reason, _Req, _State) ->
+ ok.
+
+id() ->
+ {Mega, Sec, Micro} = erlang:now(),
+ Id = (Mega * 1000000 + Sec) * 1000000 + Micro,
+ integer_to_list(Id, 16).
diff --git a/examples/eventsource/src/eventsource_sup.erl b/examples/eventsource/src/eventsource_sup.erl
new file mode 100644
index 0000000..611b015
--- /dev/null
+++ b/examples/eventsource/src/eventsource_sup.erl
@@ -0,0 +1,23 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(eventsource_sup).
+-behaviour(supervisor).
+
+%% API.
+-export([start_link/0]).
+
+%% supervisor.
+-export([init/1]).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% supervisor.
+
+init([]) ->
+ Procs = [],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/examples/eventsource/start.sh b/examples/eventsource/start.sh
new file mode 100755
index 0000000..e97398f
--- /dev/null
+++ b/examples/eventsource/start.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+erl -pa ebin deps/*/ebin -s eventsource \
+ -eval "io:format(\"Point your browser at http://localhost:8080/~n\")."
diff --git a/examples/rest_pastebin/src/toppage_handler.erl b/examples/rest_pastebin/src/toppage_handler.erl
index 758e11d..5e904d9 100644
--- a/examples/rest_pastebin/src/toppage_handler.erl
+++ b/examples/rest_pastebin/src/toppage_handler.erl
@@ -89,6 +89,7 @@ file_exists(Name) ->
valid_path(<<>>) -> true;
valid_path(<<$., _T/binary>>) -> false;
+valid_path(<<$/, _T/binary>>) -> false;
valid_path(<<_Char, T/binary>>) -> valid_path(T).
new_paste_id() ->
diff --git a/examples/web_server/README.md b/examples/web_server/README.md
new file mode 100644
index 0000000..c123c98
--- /dev/null
+++ b/examples/web_server/README.md
@@ -0,0 +1,27 @@
+Cowboy Static File Handler with Index Support
+=============================================
+
+To compile this example you need rebar in your PATH.
+
+Type the following command:
+```
+$ rebar get-deps compile
+```
+
+You can then start the Erlang node with the following command:
+```
+./start.sh
+```
+
+Cowboy will serve all the files you put in the priv/ directory. You can replace
+the filename given in the example URL with the one of a file you added to this
+directory to receive that file. A middleware has been added that will re-route
+the request to a different handler if the requested path is a directory.
+
+Example
+-------
+
+Point your browser to http://localhost:8080 to see the contents of `priv/`. You
+can click on a link to see that file. If HTML is not preferred, the contents of
+a directory will be listed as a JSON array (e.g. with `curl
+http://localhost:8080`).
diff --git a/examples/web_server/priv/small.mp4 b/examples/web_server/priv/small.mp4
new file mode 100644
index 0000000..1fc4788
--- /dev/null
+++ b/examples/web_server/priv/small.mp4
Binary files differ
diff --git a/examples/web_server/priv/small.ogv b/examples/web_server/priv/small.ogv
new file mode 100644
index 0000000..6409d6e
--- /dev/null
+++ b/examples/web_server/priv/small.ogv
Binary files differ
diff --git a/examples/web_server/priv/test.txt b/examples/web_server/priv/test.txt
new file mode 100644
index 0000000..760cddb
--- /dev/null
+++ b/examples/web_server/priv/test.txt
@@ -0,0 +1 @@
+If you read this then the static file server works!
diff --git a/examples/web_server/priv/video.html b/examples/web_server/priv/video.html
new file mode 100644
index 0000000..eca63ee
--- /dev/null
+++ b/examples/web_server/priv/video.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <h1>HTML5 Video Example</h1>
+ <video controls>
+ <source src="small.ogv" type="video/ogg"/>
+ <source src="small.mp4" type="video/mp4"/>
+ </video>
+ <p>Videos taken from <a href="http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5/">TechSlides</a></p>
+</body>
+</html>
diff --git a/examples/web_server/rebar.config b/examples/web_server/rebar.config
new file mode 100644
index 0000000..f9cbdef
--- /dev/null
+++ b/examples/web_server/rebar.config
@@ -0,0 +1,8 @@
+{deps, [
+ {cowboy, ".*",
+ {git, "git://github.com/extend/cowboy.git", "master"}},
+ {mimetypes, ".*",
+ {git, "git://github.com/spawngrid/mimetypes.git", "master"}},
+ {jsx, ".*",
+ {git, "git://github.com/talentdeficit/jsx.git", "master"}}
+]}.
diff --git a/examples/web_server/src/directory_handler.erl b/examples/web_server/src/directory_handler.erl
new file mode 100644
index 0000000..ed342b5
--- /dev/null
+++ b/examples/web_server/src/directory_handler.erl
@@ -0,0 +1,51 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @doc Directory handler.
+-module(directory_handler).
+
+%% REST Callbacks
+-export([init/3]).
+-export([rest_init/2]).
+-export([allowed_methods/2]).
+-export([resource_exists/2]).
+-export([content_types_provided/2]).
+
+%% Callback Callbacks
+-export([list_json/2]).
+-export([list_html/2]).
+
+init(_Transport, _Req, _Paths) ->
+ {upgrade, protocol, cowboy_rest}.
+
+rest_init(Req, Paths) ->
+ {ok, Req, Paths}.
+
+allowed_methods(Req, State) ->
+ {[<<"GET">>], Req, State}.
+
+resource_exists(Req, {ReqPath, FilePath}) ->
+ case file:list_dir(FilePath) of
+ {ok, Fs} -> {true, Req, {ReqPath, lists:sort(Fs)}};
+ _Err -> {false, Req, {ReqPath, FilePath}}
+ end.
+
+content_types_provided(Req, State) ->
+ {[
+ {{<<"application">>, <<"json">>, []}, list_json},
+ {{<<"text">>, <<"html">>, []}, list_html}
+ ], Req, State}.
+
+list_json(Req, {Path, Fs}) ->
+ Files = [[ <<(list_to_binary(F))/binary>> || F <- Fs ]],
+ {jsx:encode(Files), Req, Path}.
+
+list_html(Req, {Path, Fs}) ->
+ Body = [[ links(Path, F) || F <- [".."|Fs] ]],
+ HTML = [<<"<!DOCTYPE html><html><head><title>Index</title></head>",
+ "<body>">>, Body, <<"</body></html>\n">>],
+ {HTML, Req, Path}.
+
+links(<<>>, File) ->
+ ["<a href='/", File, "'>", File, "</a><br>\n"];
+links(Prefix, File) ->
+ ["<a href='/", Prefix, $/, File, "'>", File, "</a><br>\n"].
diff --git a/examples/web_server/src/directory_lister.erl b/examples/web_server/src/directory_lister.erl
new file mode 100644
index 0000000..a59f957
--- /dev/null
+++ b/examples/web_server/src/directory_lister.erl
@@ -0,0 +1,37 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(directory_lister).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+
+execute(Req, Env) ->
+ case lists:keyfind(handler, 1, Env) of
+ {handler, cowboy_static} -> redirect_directory(Req, Env);
+ _H -> {ok, Req, Env}
+ end.
+
+redirect_directory(Req, Env) ->
+ {Path, Req1} = cowboy_req:path_info(Req),
+ Path1 = << <<S/binary, $/>> || S <- Path >>,
+ {handler_opts, StaticOpts} = lists:keyfind(handler_opts, 1, Env),
+ {dir_handler, DirHandler} = lists:keyfind(dir_handler, 1, StaticOpts),
+ FullPath = resource_path(Path1),
+ case valid_path(Path) and filelib:is_dir(FullPath) of
+ true -> handle_directory(Req1, Env, Path1, FullPath, DirHandler);
+ false -> {ok, Req1, Env}
+ end.
+
+handle_directory(Req, Env, Prefix, Path, DirHandler) ->
+ Env1 = lists:keydelete(handler, 1,
+ lists:keydelete(handler_opts, 1, Env)),
+ {ok, Req, [{handler, DirHandler}, {handler_opts, {Prefix, Path}} | Env1]}.
+
+valid_path([]) -> true;
+valid_path([<<"..">> | _T]) -> false;
+valid_path([<<"/", _/binary>> | _T]) -> false;
+valid_path([_H | Rest]) -> valid_path(Rest).
+
+resource_path(Path) ->
+ {ok, Cwd} = file:get_cwd(),
+ filename:join([Cwd, "priv", Path]).
diff --git a/examples/web_server/src/web_server.app.src b/examples/web_server/src/web_server.app.src
new file mode 100644
index 0000000..b4326d7
--- /dev/null
+++ b/examples/web_server/src/web_server.app.src
@@ -0,0 +1,15 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+{application, web_server, [
+ {description, "Cowboy static file handler with directory indexes."},
+ {vsn, "1"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ cowboy
+ ]},
+ {mod, {web_server_app, []}},
+ {env, []}
+]}.
diff --git a/examples/web_server/src/web_server.erl b/examples/web_server/src/web_server.erl
new file mode 100644
index 0000000..ae75c3a
--- /dev/null
+++ b/examples/web_server/src/web_server.erl
@@ -0,0 +1,14 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(web_server).
+
+%% API.
+-export([start/0]).
+
+%% API.
+
+start() ->
+ ok = application:start(crypto),
+ ok = application:start(ranch),
+ ok = application:start(cowboy),
+ ok = application:start(web_server).
diff --git a/examples/web_server/src/web_server_app.erl b/examples/web_server/src/web_server_app.erl
new file mode 100644
index 0000000..43dc078
--- /dev/null
+++ b/examples/web_server/src/web_server_app.erl
@@ -0,0 +1,30 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(web_server_app).
+-behaviour(application).
+
+%% API.
+-export([start/2]).
+-export([stop/1]).
+
+%% API.
+
+start(_Type, _Args) ->
+ Dispatch = cowboy_router:compile([
+ {'_', [
+ {"/[...]", cowboy_static, [
+ {directory, {priv_dir, web_server, []}},
+ {dir_handler, directory_handler},
+ {mimetypes, {fun mimetypes:path_to_mimes/2, default}}
+ ]}
+ ]}
+ ]),
+ {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
+ {env, [{dispatch, Dispatch}]},
+ {middlewares, [cowboy_router, directory_lister, cowboy_handler]}
+ ]),
+ web_server_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/examples/web_server/src/web_server_sup.erl b/examples/web_server/src/web_server_sup.erl
new file mode 100644
index 0000000..03f9f67
--- /dev/null
+++ b/examples/web_server/src/web_server_sup.erl
@@ -0,0 +1,23 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(web_server_sup).
+-behaviour(supervisor).
+
+%% API.
+-export([start_link/0]).
+
+%% supervisor.
+-export([init/1]).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% supervisor.
+
+init([]) ->
+ Procs = [],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/examples/web_server/start.sh b/examples/web_server/start.sh
new file mode 100755
index 0000000..ede9bf9
--- /dev/null
+++ b/examples/web_server/start.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+erl -pa ebin deps/*/ebin -s web_server \
+ -eval "io:format(\"Point your browser at http://localhost:8080/~n\")."
diff --git a/guide/handlers.md b/guide/handlers.md
index e2c1264..5ffe401 100644
--- a/guide/handlers.md
+++ b/guide/handlers.md
@@ -41,7 +41,8 @@ Custom protocol upgrades
------------------------
The `my_protocol` module above will be used for further processing
-of the request. It requires only one callback, `upgrade/4`.
+of the request. It should use the `cowboy_sub_protocol` behaviour,
+which requires only one callback, `upgrade/4`.
It receives the request object, the middleware environment, and
the handler this request has been routed to along with its options.
diff --git a/guide/middlewares.md b/guide/middlewares.md
index 0ab6dc2..b5f055c 100644
--- a/guide/middlewares.md
+++ b/guide/middlewares.md
@@ -58,6 +58,9 @@ will not process any subsequent requests on this connection.
The middlewares that come with Cowboy may define or require other
environment values to perform.
+You can update the environment by calling the `cowboy:set_env/3`
+convenience function, adding or replacing a value in the environment.
+
Routing middleware
------------------
diff --git a/guide/routing.md b/guide/routing.md
index ebf34de..3f71654 100644
--- a/guide/routing.md
+++ b/guide/routing.md
@@ -242,3 +242,17 @@ cowboy:start_http(my_http_listener, 100,
Note that this function will return `{error, badarg}` if the structure
given is incorrect.
+
+Live update
+-----------
+
+You can use the `cowboy:set_env/3` function for updating the dispatch
+list used by routing. This will apply to all new connections accepted
+by the listener.
+
+``` erlang
+cowboy:set_env(my_http_listener, dispatch,
+ cowboy_router:compile(Dispatch)).
+```
+
+Note that you need to compile the routes before updating.
diff --git a/guide/toc.md b/guide/toc.md
index 2f8fa36..2f0d914 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -8,10 +8,11 @@ Cowboy User Guide
* Getting started
* [Routing](routing.md)
* Purpose
- * Dispatch list
- * Match rules
- * Bindings
+ * Structure
+ * Match syntax
* Constraints
+ * Compilation
+ * Live update
* [Handlers](handlers.md)
* Purpose
* Protocol upgrades
diff --git a/src/cowboy.erl b/src/cowboy.erl
index 79dbb71..257172d 100644
--- a/src/cowboy.erl
+++ b/src/cowboy.erl
@@ -18,6 +18,7 @@
-export([start_http/4]).
-export([start_https/4]).
-export([stop_listener/1]).
+-export([set_env/3]).
%% @doc Start an HTTP listener.
-spec start_http(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
@@ -37,3 +38,16 @@ start_https(Ref, NbAcceptors, TransOpts, ProtoOpts)
-spec stop_listener(any()) -> ok.
stop_listener(Ref) ->
ranch:stop_listener(Ref).
+
+%% @doc Convenience function for setting an environment value.
+%%
+%% 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.
+set_env(Ref, Name, Value) ->
+ Opts = ranch:get_protocol_options(Ref),
+ {_, Env} = lists:keyfind(env, 1, Opts),
+ Env2 = [{Name, Value}|lists:keydelete(Name, 1, Env)],
+ Opts2 = lists:keyreplace(env, 1, Opts, {env, Env2}),
+ ok = ranch:set_protocol_options(Ref, Opts2).
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl
index 65e22b7..fd2e085 100644
--- a/src/cowboy_handler.erl
+++ b/src/cowboy_handler.erl
@@ -62,7 +62,7 @@ execute(Req, Env) ->
-spec handler_init(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()}
- | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ | {error, 500, Req} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_init(Req, State, Handler, HandlerOpts) ->
Transport = cowboy_req:get(transport, Req),
@@ -137,7 +137,7 @@ handler_handle(Req, State, Handler, HandlerState) ->
%% to receive data and buffer it indefinitely.
-spec handler_before_loop(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()}
- | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ | {error, 500, Req} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
@@ -165,7 +165,7 @@ handler_loop_timeout(State=#state{loop_timeout=Timeout,
%% @private
-spec handler_loop(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()}
- | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ | {error, 500, Req} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
loop_max_buffer=Threshold, loop_timeout_ref=TRef},
@@ -195,7 +195,7 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
handler_before_loop(Req, State#state{resp_sent=true},
Handler, HandlerState);
{timeout, TRef, ?MODULE} ->
- terminate_request(Req, State, Handler, HandlerState,
+ handler_after_loop(Req, State, Handler, HandlerState,
{normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
handler_before_loop(Req, State, Handler, HandlerState);
@@ -205,12 +205,12 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
-spec handler_call(Req, #state{}, module(), any(), any())
-> {ok, Req, cowboy_middleware:env()}
- | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ | {error, 500, Req} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_call(Req, State, Handler, HandlerState, Message) ->
try Handler:info(Message, Req, HandlerState) of
{ok, Req2, HandlerState2} ->
- terminate_request(Req2, State, Handler, HandlerState2,
+ handler_after_loop(Req2, State, Handler, HandlerState2,
{normal, shutdown});
{loop, Req2, HandlerState2} ->
handler_before_loop(Req2, State, Handler, HandlerState2);
@@ -230,6 +230,25 @@ handler_call(Req, State, Handler, HandlerState, Message) ->
error_terminate(Req, State)
end.
+%% It is sometimes important to make a socket passive as it was initially
+%% and as it is expected to be by cowboy_protocol, right after we're done
+%% with loop handling. The browser may freely pipeline a bunch of requests
+%% if previous one was, say, a JSONP long-polling request.
+-spec handler_after_loop(Req, #state{}, module(), any(),
+ {normal, timeout | shutdown} | {error, atom()}) ->
+ {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
+handler_after_loop(Req, State, Handler, HandlerState, Reason) ->
+ [Socket, Transport] = cowboy_req:get([socket, transport], Req),
+ Transport:setopts(Socket, [{active, false}]),
+ {OK, _Closed, _Error} = Transport:messages(),
+ Req2 = receive
+ {OK, Socket, Data} ->
+ cowboy_req:append_buffer(Data, Req)
+ after 0 ->
+ Req
+ end,
+ terminate_request(Req2, State, Handler, HandlerState, Reason).
+
-spec terminate_request(Req, #state{}, module(), any(),
{normal, timeout | shutdown} | {error, atom()}) ->
{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index d3a91d3..a40e1c6 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -19,6 +19,7 @@
%% documentation available at http://wiki.basho.com/Webmachine.html
%% at the time of writing.
-module(cowboy_rest).
+-behaviour(cowboy_sub_protocol).
-export([upgrade/4]).
diff --git a/src/cowboy_sub_protocol.erl b/src/cowboy_sub_protocol.erl
new file mode 100644
index 0000000..0b231d3
--- /dev/null
+++ b/src/cowboy_sub_protocol.erl
@@ -0,0 +1,37 @@
+%% Copyright (c) 2013, James Fish <[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 Behaviour for sub protocols.
+%%
+%% Only one function needs to be implemented, <em>upgrade/4</em>.
+%% It receives the Req, the environment, the handler that the request has been
+%% routed to and the handler's options. It acts exactly the same as a
+%% middleware, so returns the same values a middleware's execute/2.
+%%
+%% Once the sub protocol has processed the request it should add the result
+%% to the environment. This is done by adding the tuple {result, Value} to the
+%% environment list. To continue handling requests on the current connection the
+%% Value should be the atom ok. Any other value will prevent the processing of
+%% subsequent requests.
+%%
+%% <em>upgrade/4</em> will be called when a handler's init/3 returns
+%% {upgrade, protocol, Module}, where Module is the module of the sub protocol.
+-module(cowboy_sub_protocol).
+
+-callback upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env}
+ | {suspend, module(), atom(), any()}
+ | {halt, 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 fa74223..7bb5e74 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -17,6 +17,7 @@
%% Cowboy supports versions 7 through 17 of the Websocket drafts.
%% It also supports RFC6455, the proposed standard for Websocket.
-module(cowboy_websocket).
+-behaviour(cowboy_sub_protocol).
%% API.
-export([upgrade/4]).
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 209be7e..8e53dc7 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -61,6 +61,7 @@
-export([rest_patch/1]).
-export([rest_resource_etags/1]).
-export([rest_resource_etags_if_none_match/1]).
+-export([set_env_dispatch/1]).
-export([set_resp_body/1]).
-export([set_resp_header/1]).
-export([set_resp_overwrite/1]).
@@ -90,7 +91,8 @@ all() ->
{group, https_compress},
{group, onrequest},
{group, onresponse},
- {group, onresponse_capitalize}
+ {group, onresponse_capitalize},
+ {group, set_env}
].
groups() ->
@@ -159,6 +161,9 @@ groups() ->
]},
{onresponse_capitalize, [], [
onresponse_capitalize
+ ]},
+ {set_env, [], [
+ set_env_dispatch
]}
].
@@ -273,6 +278,17 @@ init_per_group(onresponse_capitalize, Config) ->
]),
{ok, Client} = cowboy_client:init([]),
[{scheme, <<"http">>}, {port, Port}, {opts, []},
+ {transport, Transport}, {client, Client}|Config];
+init_per_group(set_env, Config) ->
+ Port = 33087,
+ Transport = ranch_tcp,
+ {ok, _} = cowboy:start_http(set_env, 100, [{port, Port}], [
+ {env, [{dispatch, []}]},
+ {max_keepalive, 50},
+ {timeout, 500}
+ ]),
+ {ok, Client} = cowboy_client:init([]),
+ [{scheme, <<"http">>}, {port, Port}, {opts, []},
{transport, Transport}, {client, Client}|Config].
end_per_group(Group, Config) when Group =:= https; Group =:= https_compress ->
@@ -923,6 +939,17 @@ rest_resource_etags_if_none_match(Config) ->
{Ret, Type}
end || {Status, ETag, Type} <- Tests].
+set_env_dispatch(Config) ->
+ Client = ?config(client, Config),
+ {ok, Client2} = cowboy_client:request(<<"GET">>,
+ build_url("/", Config), Client),
+ {ok, 400, _, _} = cowboy_client:response(Client2),
+ ok = cowboy:set_env(set_env, dispatch,
+ cowboy_router:compile([{'_', [{"/", http_handler, []}]}])),
+ {ok, Client3} = cowboy_client:request(<<"GET">>,
+ build_url("/", Config), Client),
+ {ok, 200, _, _} = cowboy_client:response(Client3).
+
set_resp_body(Config) ->
Client = ?config(client, Config),
{ok, Client2} = cowboy_client:request(<<"GET">>,