aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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--src/cowboy_handler.erl23
25 files changed, 432 insertions, 2 deletions
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/src/cowboy_handler.erl b/src/cowboy_handler.erl
index 65e22b7..3b0af6f 100644
--- a/src/cowboy_handler.erl
+++ b/src/cowboy_handler.erl
@@ -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);
@@ -210,7 +210,7 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
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().