mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-15 04:30:25 +00:00
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.
This commit is contained in:
parent
dbc1bc6d91
commit
bb1362c744
3 changed files with 108 additions and 8 deletions
|
@ -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
|
||||
|
|
|
@ -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">>,
|
||||
|
|
36
test/rest_param_all.erl
Normal file
36
test/rest_param_all.erl
Normal file
|
@ -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}.
|
Loading…
Add table
Add a link
Reference in a new issue