aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--guide/rest_handlers.md49
-rw-r--r--guide/toc.md5
-rw-r--r--src/cowboy_bstr.erl4
-rw-r--r--src/cowboy_clock.erl4
-rw-r--r--src/cowboy_http.erl79
-rw-r--r--src/cowboy_multipart.erl11
-rw-r--r--src/cowboy_req.erl40
-rw-r--r--src/cowboy_rest.erl20
-rw-r--r--src/cowboy_router.erl4
-rw-r--r--src/cowboy_static.erl2
-rw-r--r--test/http_SUITE.erl12
-rw-r--r--test/rest_postonly_resource.erl14
13 files changed, 144 insertions, 102 deletions
diff --git a/Makefile b/Makefile
index 1463cb4..08adaa8 100644
--- a/Makefile
+++ b/Makefile
@@ -84,7 +84,7 @@ CT_RUN = ct_run \
-logdir logs
# -cover test/cover.spec
-tests: ERLC_OPTS += -DTEST=1
+tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
tests: clean clean-deps deps app build-tests
@mkdir -p logs/
@$(CT_RUN) -suite eunit_SUITE http_SUITE ws_SUITE
diff --git a/guide/rest_handlers.md b/guide/rest_handlers.md
index ac11d98..7e8427e 100644
--- a/guide/rest_handlers.md
+++ b/guide/rest_handlers.md
@@ -15,6 +15,21 @@ describing the resource and modifying the machine's behavior.
As the REST handler is still subject to change, the documentation is
still thin. This state of affair will be improved in the coming weeks.
+Usage
+-----
+
+Like Websocket, REST is a sub-protocol of HTTP. It therefore
+requires a protocol upgrade.
+
+``` erlang
+init({tcp, http}, Req, Opts) ->
+ {upgrade, protocol, cowboy_rest}.
+```
+
+Cowboy will then switch to the REST protocol and start executing
+the flow diagram, starting from `rest_init/2` if it's defined,
+and ending with `rest_terminate/2` also if defined.
+
Flow diagram
------------
@@ -70,7 +85,7 @@ empty column means there is no default value for this callback.
| allow_missing_post | `true` |
| charsets_provided | skip |
| content_types_accepted | |
-| content_types_provided | |
+| content_types_provided | `[{{<<"text">>, <<"html">>, '*'}, to_html}] ` |
| delete_completed | `true` |
| delete_resource | `false` |
| expires | `undefined` |
@@ -106,6 +121,9 @@ each function. For example, `from_html` and `to_html` indicate
in the first case that we're accepting a resource given as HTML,
and in the second case that we send one as HTML.
+Meta data
+---------
+
Cowboy will set informative meta values at various points of the
execution. You can retrieve them using `cowboy_req:meta/{2,3}`.
The values are defined in the following table.
@@ -119,17 +137,18 @@ The values are defined in the following table.
They can be used to reply a response entity to a request with
an idempotent method (`POST`, `PUT`, `PATCH`, `DELETE`).
-Usage
------
-
-Like Websocket, REST is a sub-protocol of HTTP. It therefore
-requires a protocol upgrade.
-
-``` erlang
-init({tcp, http}, Req, Opts) ->
- {upgrade, protocol, cowboy_rest}.
-```
-
-Cowboy will then switch to the REST protocol and start executing
-the flow diagram, starting from `rest_init/2` if it's defined,
-and ending with `rest_terminate/2` also if defined.
+Response headers
+----------------
+
+Cowboy will set response headers automatically over the execution
+of the REST code. They are listed in the following table.
+
+| Header name | Details |
+| ---------------- | -------------------------------------------------- |
+| content-language | Language used in the response body |
+| content-type | Media type and charset of the response body |
+| etag | Etag of the resource |
+| expires | Expiration date of the resource |
+| last-modified | Last modification date for the resource |
+| location | Relative or absolute URI to the requested resource |
+| vary | List of headers that may change the representation of the resource |
diff --git a/guide/toc.md b/guide/toc.md
index 44c8e22..f8eeb18 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -28,9 +28,12 @@ Cowboy User Guide
* Usage
* [REST handlers](rest_handlers.md)
* Purpose
+ * Usage
* Flow diagram
+ * Methods
* Callbacks
- * Usage
+ * Meta data
+ * Response headers
* [Static handlers](static_handlers.md)
* Purpose
* Usage
diff --git a/src/cowboy_bstr.erl b/src/cowboy_bstr.erl
index 01ed9ae..0c1f66a 100644
--- a/src/cowboy_bstr.erl
+++ b/src/cowboy_bstr.erl
@@ -24,10 +24,6 @@
-export([char_to_lower/1]).
-export([char_to_upper/1]).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
%% @doc Capitalize a token.
%%
%% The first letter and all letters after a dash are capitalized.
diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl
index 71bcb21..f21616c 100644
--- a/src/cowboy_clock.erl
+++ b/src/cowboy_clock.erl
@@ -45,10 +45,6 @@
-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
%% API.
%% @private
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index b958020..c4d3d3d 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -60,10 +60,6 @@
-export_type([headers/0]).
-export_type([status/0]).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
%% Parsing.
%% @doc Parse a non-empty list of the given type.
@@ -1319,40 +1315,53 @@ x_www_form_urlencoded_test_() ->
[{Qs, fun() -> R = x_www_form_urlencoded(Qs) end} || {Qs, R} <- Tests].
urldecode_test_() ->
- U = fun urldecode/2,
- [?_assertEqual(<<" ">>, U(<<"%20">>, crash)),
- ?_assertEqual(<<" ">>, U(<<"+">>, crash)),
- ?_assertEqual(<<0>>, U(<<"%00">>, crash)),
- ?_assertEqual(<<255>>, U(<<"%fF">>, crash)),
- ?_assertEqual(<<"123">>, U(<<"123">>, crash)),
- ?_assertEqual(<<"%i5">>, U(<<"%i5">>, skip)),
- ?_assertEqual(<<"%5">>, U(<<"%5">>, skip)),
- ?_assertError(badarg, U(<<"%i5">>, crash)),
- ?_assertError(badarg, U(<<"%5">>, crash))
- ].
+ F = fun(Qs, O) ->
+ try urldecode(Qs, O) of
+ R ->
+ {ok, R}
+ catch _:E ->
+ {error, E}
+ end
+ end,
+ Tests = [
+ {<<"%20">>, crash, {ok, <<" ">>}},
+ {<<"+">>, crash, {ok, <<" ">>}},
+ {<<"%00">>, crash, {ok, <<0>>}},
+ {<<"%fF">>, crash, {ok, <<255>>}},
+ {<<"123">>, crash, {ok, <<"123">>}},
+ {<<"%i5">>, skip, {ok, <<"%i5">>}},
+ {<<"%5">>, skip, {ok, <<"%5">>}},
+ {<<"%i5">>, crash, {error, badarg}},
+ {<<"%5">>, crash, {error, badarg}}
+ ],
+ [{Qs, fun() -> R = F(Qs,O) end} || {Qs, O, R} <- Tests].
urlencode_test_() ->
- U = fun urlencode/2,
- [?_assertEqual(<<"%ff%00">>, U(<<255,0>>, [])),
- ?_assertEqual(<<"%FF%00">>, U(<<255,0>>, [upper])),
- ?_assertEqual(<<"+">>, U(<<" ">>, [])),
- ?_assertEqual(<<"%20">>, U(<<" ">>, [noplus])),
- ?_assertEqual(<<"aBc">>, U(<<"aBc">>, [])),
- ?_assertEqual(<<".-~_">>, U(<<".-~_">>, [])),
- ?_assertEqual(<<"%ff+">>, urlencode(<<255, " ">>))
- ].
+ Tests = [
+ {<<255,0>>, [], <<"%ff%00">>},
+ {<<255,0>>, [upper], <<"%FF%00">>},
+ {<<" ">>, [], <<"+">>},
+ {<<" ">>, [noplus], <<"%20">>},
+ {<<"aBc">>, [], <<"aBc">>},
+ {<<".-~_">>, [], <<".-~_">>}
+ ],
+ Tests2 = [{<<255, " ">>,<<"%ff+">>}],
+ [{V, fun() -> R = urlencode(V, O) end} || {V, O, R} <- Tests] ++
+ [{V, fun() -> R = urlencode(V) end} || {V, R} <- Tests2].
http_authorization_test_() ->
- [?_assertEqual({<<"basic">>, {<<"Alladin">>, <<"open sesame">>}},
- authorization(<<"QWxsYWRpbjpvcGVuIHNlc2FtZQ==">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"dXNlcm5hbWUK">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"_[]@#$%^&*()-AA==">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"dXNlcjpwYXNzCA==">>, <<"basic">>)), %% user:pass\010
- ?_assertEqual({<<"bearer">>,<<"some_secret_key">>},
- authorization(<<" some_secret_key">>, <<"bearer">>))
- ].
+ Tests = [
+ {<<"basic">>, <<"QWxsYWRpbjpvcGVuIHNlc2FtZQ==">>,
+ {<<"basic">>, {<<"Alladin">>, <<"open sesame">>}}},
+ {<<"basic">>, <<"dXNlcm5hbWUK">>,
+ {error, badarg}},
+ {<<"basic">>, <<"_[]@#$%^&*()-AA==">>,
+ {error, badarg}},
+ {<<"basic">>, <<"dXNlcjpwYXNzCA==">>,
+ {error, badarg}},
+ {<<"bearer">>, <<" some_secret_key">>,
+ {<<"bearer">>,<<"some_secret_key">>}}
+ ],
+ [{V, fun() -> R = authorization(V,T) end} || {T, V, R} <- Tests].
-endif.
diff --git a/src/cowboy_multipart.erl b/src/cowboy_multipart.erl
index 4e8fff0..4df5a27 100644
--- a/src/cowboy_multipart.erl
+++ b/src/cowboy_multipart.erl
@@ -30,10 +30,6 @@
-type end_of_part() :: {end_of_part, cont(more(part_result()))}.
-type disposition() :: {binary(), [{binary(), binary()}]}.
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
%% API.
%% @doc Return a multipart parser for the given boundary.
@@ -298,8 +294,7 @@ title(Bin) ->
iolist_to_binary(Title).
suffix_test_() ->
- [?_assertEqual(Part, suffix_match(Packet, pattern(Boundary))) ||
- {Part, Packet, Boundary} <- [
+ Tests = [
{nomatch, <<>>, <<"ABC">>},
{{0, 1}, <<"\r">>, <<"ABC">>},
{{0, 2}, <<"\r\n">>, <<"ABC">>},
@@ -311,6 +306,8 @@ suffix_test_() ->
{{1, 1}, <<"1\r">>, <<"ABC">>},
{{2, 2}, <<"12\r\n">>, <<"ABC">>},
{{3, 4}, <<"123\r\n--">>, <<"ABC">>}
- ]].
+ ],
+ [fun() -> Part = suffix_match(Packet, pattern(Boundary)) end ||
+ {Part, Packet, Boundary} <- Tests].
-endif.
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 11b0e26..552048a 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -115,10 +115,6 @@
-export([lock/1]).
-export([to_list/1]).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
-type cookie_option() :: {max_age, non_neg_integer()}
| {domain, binary()} | {path, binary()}
| {secure, boolean()} | {http_only, boolean()}.
@@ -1471,26 +1467,20 @@ connection_to_atom_test_() ->
[{lists:flatten(io_lib:format("~p", [T])),
fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
-merge_headers_test() ->
- Left0 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
- Right0 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}],
-
- ?assertMatch(
- [{<<"set-cookie">>,<<"foo=bar">>},
- {<<"content-length">>,<<"13">>},
- {<<"server">>,<<"Cowboy">>}],
- merge_headers(Left0, Right0)),
-
- Left1 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
- Right1 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}],
-
- ?assertMatch(
- [{<<"set-cookie">>,<<"bar=baz">>},
- {<<"set-cookie">>,<<"foo=bar">>},
- {<<"content-length">>,<<"13">>},
- {<<"server">>,<<"Cowboy">>}],
- merge_headers(Left1, Right1)),
-
- ok.
+merge_headers_test_() ->
+ Tests = [
+ {[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
+ [{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}],
+ [{<<"set-cookie">>,<<"foo=bar">>},
+ {<<"content-length">>,<<"13">>},
+ {<<"server">>,<<"Cowboy">>}]},
+ {[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
+ [{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}],
+ [{<<"set-cookie">>,<<"bar=baz">>},
+ {<<"set-cookie">>,<<"foo=bar">>},
+ {<<"content-length">>,<<"13">>},
+ {<<"server">>,<<"Cowboy">>}]}
+ ],
+ [fun() -> Res = merge_headers(L,R) end || {L, R, Res} <- Tests].
-endif.
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index c28b627..4ba2b47 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -219,13 +219,25 @@ options(Req, State) ->
content_types_provided(Req, State) ->
case call(Req, State, content_types_provided) of
no_call ->
- not_acceptable(Req, State);
+ State2 = State#state{
+ content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
+ case cowboy_req:parse_header(<<"accept">>, Req) of
+ {error, badarg} ->
+ respond(Req, State2, 400);
+ {ok, undefined, Req2} ->
+ languages_provided(
+ cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req2),
+ State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
+ {ok, Accept, Req2} ->
+ Accept2 = prioritize_accept(Accept),
+ choose_media_type(Req2, State2, Accept2)
+ end;
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState});
{CTP, Req2, HandlerState} ->
- CTP2 = [normalize_content_types(P) || P <- CTP],
+ CTP2 = [normalize_content_types(P) || P <- CTP],
State2 = State#state{
handler_state=HandlerState, content_types_p=CTP2},
case cowboy_req:parse_header(<<"accept">>, Req2) of
@@ -244,7 +256,7 @@ content_types_provided(Req, State) ->
normalize_content_types({ContentType, Callback})
when is_binary(ContentType) ->
- {cowboy_http:content_type(ContentType), Callback};
+ {cowboy_http:content_type(ContentType), Callback};
normalize_content_types(Normalized) ->
Normalized.
@@ -779,7 +791,7 @@ accept_resource(Req, State) ->
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{CTA, Req2, HandlerState} ->
- CTA2 = [normalize_content_types(P) || P <- CTA],
+ CTA2 = [normalize_content_types(P) || P <- CTA],
State2 = State#state{handler_state=HandlerState},
case cowboy_req:parse_header(<<"content-type">>, Req2) of
{ok, ContentType, Req3} ->
diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl
index 91912d8..e52b70b 100644
--- a/src/cowboy_router.erl
+++ b/src/cowboy_router.erl
@@ -51,10 +51,6 @@
-opaque dispatch_rules() :: [dispatch_rule()].
-export_type([dispatch_rules/0]).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
%% @doc Compile a list of routes into the dispatch format used
%% by Cowboy's routing.
-spec compile(routes()) -> dispatch_rules().
diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl
index ae0f9e6..fd5654e 100644
--- a/src/cowboy_static.erl
+++ b/src/cowboy_static.erl
@@ -422,8 +422,6 @@ attr_etag_function(Args, Attrs) ->
{strong, list_to_binary([H|T])}.
-ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--define(_eq(E, I), ?_assertEqual(E, I)).
directory_path_test_() ->
PL = fun(D) -> length(filename:split(directory_path(D))) end,
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index f436dd9..73ac127 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -64,6 +64,7 @@
-export([rest_options_default/1]).
-export([rest_param_all/1]).
-export([rest_patch/1]).
+-export([rest_postonly/1]).
-export([rest_resource_etags/1]).
-export([rest_resource_etags_if_none_match/1]).
-export([set_env_dispatch/1]).
@@ -135,6 +136,7 @@ groups() ->
rest_options_default,
rest_param_all,
rest_patch,
+ rest_postonly,
rest_resource_etags,
rest_resource_etags_if_none_match,
set_resp_body,
@@ -366,6 +368,7 @@ init_dispatch(Config) ->
{"/missing_get_callbacks", rest_missing_callbacks, []},
{"/missing_put_callbacks", rest_missing_callbacks, []},
{"/nodelete", rest_nodelete_resource, []},
+ {"/postonly", rest_postonly_resource, []},
{"/patch", rest_patch_resource, []},
{"/resetags", rest_resource_etags, []},
{"/rest_expires", rest_expires, []},
@@ -994,6 +997,15 @@ rest_patch(Config) ->
ok
end || {Status, Headers, Body} <- Tests].
+rest_postonly(Config) ->
+ Client = ?config(client, Config),
+ Headers = [
+ {<<"content-type">>, <<"text/plain">>}
+ ],
+ {ok, Client2} = cowboy_client:request(<<"POST">>,
+ build_url("/postonly", Config), Headers, "12345", Client),
+ {ok, 204, _, _} = cowboy_client:response(Client2).
+
rest_resource_get_etag(Config, Type) ->
rest_resource_get_etag(Config, Type, []).
diff --git a/test/rest_postonly_resource.erl b/test/rest_postonly_resource.erl
new file mode 100644
index 0000000..4f725c9
--- /dev/null
+++ b/test/rest_postonly_resource.erl
@@ -0,0 +1,14 @@
+-module(rest_postonly_resource).
+-export([init/3, allowed_methods/2, content_types_accepted/2, from_text/2]).
+
+init(_Transport, _Req, _Opts) ->
+ {upgrade, protocol, cowboy_rest}.
+
+allowed_methods(Req, State) ->
+ {[<<"POST">>], Req, State}.
+
+content_types_accepted(Req, State) ->
+ {[{{<<"text">>, <<"plain">>, '*'}, from_text}], Req, State}.
+
+from_text(Req, State) ->
+ {true, Req, State}.