aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2013-04-12 18:20:41 +0200
committerLoïc Hoguin <[email protected]>2013-04-12 19:02:00 +0200
commitb58a0549e139b410db50ddd443cc457fb677fa7e (patch)
tree819ab4484bfb6b75262e2d9eb871190453fecadb
parentd063511a0e4988d62357f9c17744bb9aa0470a06 (diff)
downloadcowboy-b58a0549e139b410db50ddd443cc457fb677fa7e.tar.gz
cowboy-b58a0549e139b410db50ddd443cc457fb677fa7e.tar.bz2
cowboy-b58a0549e139b410db50ddd443cc457fb677fa7e.zip
Add default operations for OPTIONS method in REST
It defaults to setting the Allow header to "HEAD, GET, OPTIONS".
-rw-r--r--guide/rest_handlers.md2
-rw-r--r--src/cowboy_rest.erl40
-rw-r--r--test/http_SUITE.erl10
-rw-r--r--test/rest_empty_resource.erl5
4 files changed, 45 insertions, 12 deletions
diff --git a/guide/rest_handlers.md b/guide/rest_handlers.md
index 1eccc65..5ae6b88 100644
--- a/guide/rest_handlers.md
+++ b/guide/rest_handlers.md
@@ -77,7 +77,7 @@ empty column means there is no default value for this callback.
| moved_permanently | `false` |
| moved_temporarily | `false` |
| multiple_choices | `false` |
-| options | |
+| options | `ok` |
| previously_existed | `false` |
| resource_exists | `true` |
| service_available | `true` |
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 40baabb..d4c4de1 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -31,6 +31,9 @@
handler :: atom(),
handler_state :: any(),
+ %% Allowed methods. Only used for OPTIONS requests.
+ allowed_methods :: [binary()],
+
%% Media type.
content_types_p = [] ::
[{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
@@ -119,32 +122,43 @@ allowed_methods(Req, State=#state{method=Method}) ->
case call(Req, State, allowed_methods) of
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
next(Req, State, fun malformed_request/2);
+ no_call when Method =:= <<"OPTIONS">> ->
+ next(Req, State#state{allowed_methods=
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
+ fun malformed_request/2);
no_call ->
- method_not_allowed(Req, State, [<<"GET">>, <<"HEAD">>]);
+ method_not_allowed(Req, State,
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{List, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState},
case lists:member(Method, List) of
- true -> next(Req2, State2, fun malformed_request/2);
- false -> method_not_allowed(Req2, State2, List)
+ true when Method =:= <<"OPTIONS">> ->
+ next(Req2, State2#state{allowed_methods=
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
+ fun malformed_request/2);
+ true ->
+ next(Req2, State2, fun malformed_request/2);
+ false ->
+ method_not_allowed(Req2, State2, List)
end
end.
method_not_allowed(Req, State, Methods) ->
Req2 = cowboy_req:set_resp_header(
- <<"allow">>, method_not_allowed_build(Methods, []), Req),
+ <<"allow">>, build_allow_header(Methods, []), Req),
respond(Req2, State, 405).
-method_not_allowed_build([], []) ->
+build_allow_header([], []) ->
<<>>;
-method_not_allowed_build([], [_Ignore|Acc]) ->
+build_allow_header([], [_Ignore|Acc]) ->
lists:reverse(Acc);
-method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) ->
+build_allow_header([Method|Tail], Acc) when is_atom(Method) ->
Method2 = list_to_binary(atom_to_list(Method)),
- method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]);
-method_not_allowed_build([Method|Tail], Acc) ->
- method_not_allowed_build(Tail, [<<", ">>, Method|Acc]).
+ build_allow_header(Tail, [<<", ">>, Method2|Acc]);
+build_allow_header([Method|Tail], Acc) ->
+ build_allow_header(Tail, [<<", ">>, Method|Acc]).
malformed_request(Req, State) ->
expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
@@ -180,8 +194,12 @@ valid_entity_length(Req, State) ->
%% If you need to add additional headers to the response at this point,
%% you should do it directly in the options/2 call using set_resp_headers.
-options(Req, State=#state{method= <<"OPTIONS">>}) ->
+options(Req, State=#state{allowed_methods=Allow, method= <<"OPTIONS">>}) ->
case call(Req, State, options) of
+ no_call ->
+ Req2 = cowboy_req:set_resp_header(<<"allow">>,
+ build_allow_header(Allow, []), Req),
+ respond(Req2, State, 200);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{ok, Req2, HandlerState} ->
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 34a7be1..4a01856 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -61,6 +61,7 @@
-export([rest_missing_get_callbacks/1]).
-export([rest_missing_put_callbacks/1]).
-export([rest_nodelete/1]).
+-export([rest_options_default/1]).
-export([rest_param_all/1]).
-export([rest_patch/1]).
-export([rest_resource_etags/1]).
@@ -131,6 +132,7 @@ groups() ->
rest_missing_get_callbacks,
rest_missing_put_callbacks,
rest_nodelete,
+ rest_options_default,
rest_param_all,
rest_patch,
rest_resource_etags,
@@ -367,6 +369,7 @@ init_dispatch(Config) ->
{"/patch", rest_patch_resource, []},
{"/resetags", rest_resource_etags, []},
{"/rest_expires", rest_expires, []},
+ {"/rest_empty_resource", rest_empty_resource, []},
{"/loop_recv", http_handler_loop_recv, []},
{"/loop_timeout", http_handler_loop_timeout, []},
{"/", http_handler, []}
@@ -967,6 +970,13 @@ rest_nodelete(Config) ->
build_url("/nodelete", Config), Client),
{ok, 500, _, _} = cowboy_client:response(Client2).
+rest_options_default(Config) ->
+ Client = ?config(client, Config),
+ {ok, Client2} = cowboy_client:request(<<"OPTIONS">>,
+ build_url("/rest_empty_resource", Config), Client),
+ {ok, 200, Headers, _} = cowboy_client:response(Client2),
+ {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers).
+
rest_patch(Config) ->
Tests = [
{204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>},
diff --git a/test/rest_empty_resource.erl b/test/rest_empty_resource.erl
new file mode 100644
index 0000000..7e7c00a
--- /dev/null
+++ b/test/rest_empty_resource.erl
@@ -0,0 +1,5 @@
+-module(rest_empty_resource).
+-export([init/3]).
+
+init(_Transport, _Req, _Opts) ->
+ {upgrade, protocol, cowboy_rest}.