From b58a0549e139b410db50ddd443cc457fb677fa7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 12 Apr 2013 18:20:41 +0200 Subject: Add default operations for OPTIONS method in REST It defaults to setting the Allow header to "HEAD, GET, OPTIONS". --- guide/rest_handlers.md | 2 +- src/cowboy_rest.erl | 40 +++++++++++++++++++++++++++++----------- test/http_SUITE.erl | 10 ++++++++++ test/rest_empty_resource.erl | 5 +++++ 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 test/rest_empty_resource.erl 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}. -- cgit v1.2.3