From bb1362c744ec24a72fbb2fa3e1cc8e44ef51ab2d Mon Sep 17 00:00:00 2001 From: Slava Yurin Date: Fri, 15 Feb 2013 22:41:55 +0700 Subject: Add '*' matcher for parameters For get_type_provided: '*' will be match any parameters of media-range in "accept" header. If '*' matched, then '*' is replaced by the matching parameters. If Accept header is missing and '*' using, then in media_type in parameters will be '*' and reply content-type will be without any parameters. For content_types_accepted: '*' will be match any parameters in "content-type" header. --- src/cowboy_rest.erl | 38 ++++++++++++++++++++++++++++++-------- test/http_SUITE.erl | 42 ++++++++++++++++++++++++++++++++++++++++++ test/rest_param_all.erl | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 test/rest_param_all.erl diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index a40e1c6..cb4fffb 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -33,9 +33,11 @@ %% Media type. content_types_p = [] :: - [{binary() | {binary(), binary(), [{binary(), binary()}]}, atom()}], + [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, + atom()}], content_type_a :: undefined - | {binary() | {binary(), binary(), [{binary(), binary()}]}, atom()}, + | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, + atom()}, %% Language. languages_p = [] :: [binary()], @@ -286,6 +288,12 @@ match_media_type(Req, State, Accept, match_media_type(Req, State, Accept, [_Any|Tail], MediaType) -> match_media_type(Req, State, Accept, Tail, MediaType). +match_media_type_params(Req, State, _Accept, + [Provided = {{TP, STP, '*'}, _Fun}|_Tail], + {{_TA, _STA, Params_A}, _QA, _APA}) -> + PMT = {TP, STP, Params_A}, + languages_provided(cowboy_req:set_meta(media_type, PMT, Req), + State#state{content_type_a=Provided}); match_media_type_params(Req, State, Accept, [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail], MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) -> @@ -426,6 +434,8 @@ set_content_type(Req, State=#state{ Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req), encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State). +set_content_type_build_params('*', []) -> + <<>>; set_content_type_build_params([], []) -> <<>>; set_content_type_build_params([], Acc) -> @@ -855,10 +865,24 @@ patch_resource(Req, State) -> %% list of content types, otherwise it'll shadow the ones following. choose_content_type(Req, State, _OnTrue, _ContentType, []) -> respond(Req, State, 415); -choose_content_type(Req, +choose_content_type(Req, State, OnTrue, ContentType, [{Accepted, Fun}|_Tail]) + when Accepted =:= '*'; Accepted =:= ContentType -> + process_content_type(Req, State, OnTrue, Fun); +%% The special parameter '*' will always match any kind of content type +%% parameters. +%% Note that because it will always match, it should be the last of the +%% list for specific content type, otherwise it'll shadow the ones following. +choose_content_type(Req, State, OnTrue, + {Type, SubType, Param}, + [{{Type, SubType, AcceptedParam}, Fun}|_Tail]) + when AcceptedParam =:= '*'; AcceptedParam =:= Param -> + process_content_type(Req, State, OnTrue, Fun); +choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> + choose_content_type(Req, State, OnTrue, ContentType, Tail). + +process_content_type(Req, State=#state{handler=Handler, handler_state=HandlerState}, - OnTrue, ContentType, [{Accepted, Fun}|_Tail]) - when Accepted =:= '*' orelse Accepted =:= ContentType -> + OnTrue, Fun) -> case call(Req, State, Fun) of no_call -> error_logger:error_msg( @@ -875,9 +899,7 @@ choose_content_type(Req, {false, Req2, HandlerState2} -> State2 = State#state{handler_state=HandlerState2}, respond(Req2, State2, 422) - end; -choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> - choose_content_type(Req, State, OnTrue, ContentType, Tail). + end. %% Whether we created a new resource, either through PUT or POST. %% This is easily testable because we would have set the Location diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 1b687c6..bd76f00 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -58,6 +58,7 @@ -export([rest_missing_get_callbacks/1]). -export([rest_missing_put_callbacks/1]). -export([rest_nodelete/1]). +-export([rest_param_all/1]). -export([rest_patch/1]). -export([rest_resource_etags/1]). -export([rest_resource_etags_if_none_match/1]). @@ -124,6 +125,7 @@ groups() -> rest_missing_get_callbacks, rest_missing_put_callbacks, rest_nodelete, + rest_param_all, rest_patch, rest_resource_etags, rest_resource_etags_if_none_match, @@ -346,6 +348,7 @@ init_dispatch(Config) -> {file, <<"test_file.css">>}]}, {"/multipart", http_handler_multipart, []}, {"/echo/body", http_handler_echo_body, []}, + {"/param_all", rest_param_all, []}, {"/bad_accept", rest_simple_resource, []}, {"/simple", rest_simple_resource, []}, {"/forbidden_post", rest_forbidden_resource, [true]}, @@ -790,6 +793,45 @@ pipeline_long_polling(Config) -> {ok, 102, _, Client5} = cowboy_client:response(Client4), {error, closed} = cowboy_client:response(Client5). +rest_param_all(Config) -> + Client = ?config(client, Config), + URL = build_url("/param_all", Config), + % Accept without param + {ok, Client2} = cowboy_client:request(<<"GET">>, URL, + [{<<"accept">>, <<"text/plain">>}], Client), + Client3 = check_response(Client2, <<"[]">>), + % Accept with param + {ok, Client4} = cowboy_client:request(<<"GET">>, URL, + [{<<"accept">>, <<"text/plain;level=1">>}], Client3), + Client5 = check_response(Client4, <<"level=1">>), + % Accept with param and quality + {ok, Client6} = cowboy_client:request(<<"GET">>, URL, + [{<<"accept">>, + <<"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5">>}], + Client5), + Client7 = check_response(Client6, <<"level=1">>), + {ok, Client8} = cowboy_client:request(<<"GET">>, URL, + [{<<"accept">>, + <<"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8">>}], + Client7), + Client9 = check_response(Client8, <<"level=2">>), + % Without Accept + {ok, Client10} = cowboy_client:request(<<"GET">>, URL, [], Client9), + Client11 = check_response(Client10, <<"'*'">>), + % Content-Type without param + {ok, Client12} = cowboy_client:request(<<"PUT">>, URL, + [{<<"content-type">>, <<"text/plain">>}], Client11), + {ok, 204, _, Client13} = cowboy_client:response(Client12), + % Content-Type with param + {ok, Client14} = cowboy_client:request(<<"PUT">>, URL, + [{<<"content-type">>, <<"text/plain; charset=utf-8">>}], Client13), + {ok, 204, _, _} = cowboy_client:response(Client14). + +check_response(Client, Body) -> + {ok, 200, _, Client2} = cowboy_client:response(Client), + {ok, Body, Client3} = cowboy_client:response_body(Client2), + Client3. + rest_bad_accept(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, diff --git a/test/rest_param_all.erl b/test/rest_param_all.erl new file mode 100644 index 0000000..09b8cd3 --- /dev/null +++ b/test/rest_param_all.erl @@ -0,0 +1,36 @@ +-module(rest_param_all). + +-export([init/3]). +-export([allowed_methods/2]). +-export([content_types_provided/2]). +-export([get_text_plain/2]). +-export([content_types_accepted/2]). +-export([put_text_plain/2]). + +init(_Transport, _Req, _Opts) -> + {upgrade, protocol, cowboy_rest}. + +allowed_methods(Req, State) -> + {[<<"GET">>, <<"PUT">>], Req, State}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}. + +get_text_plain(Req, State) -> + {{_, _, Param}, Req2} = + cowboy_req:meta(media_type, Req, {{<<"text">>, <<"plain">>}, []}), + Body = if + Param == '*' -> + <<"'*'">>; + Param == [] -> + <<"[]">>; + Param /= [] -> + iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param]) + end, + {Body, Req2, State}. + +content_types_accepted(Req, State) -> + {[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}. + +put_text_plain(Req, State) -> + {true, Req, State}. -- cgit v1.2.3