0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

REST: Allow generate_etag to return undefined

This allows conditionally generating an etag.
This commit is contained in:
Loïc Hoguin 2024-01-16 16:28:52 +01:00
parent ec12c2f051
commit defce46fdf
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
4 changed files with 29 additions and 2 deletions

View file

@ -379,7 +379,7 @@ and that the request shouldn't be repeated.
---- ----
generate_etag(Req, State) -> {Result, Req, State} generate_etag(Req, State) -> {Result, Req, State}
Result :: binary() | {weak | strong, binary()} Result :: binary() | {weak | strong, binary()} | undefined
Default - no etag value Default - no etag value
---- ----
@ -389,6 +389,10 @@ When a binary is returned, the value is automatically
parsed to a tuple. The binary must be in the same parsed to a tuple. The binary must be in the same
format as the etag header, including quotes. format as the etag header, including quotes.
It is possible to conditionally generate an etag.
When no etag can be generated, `undefined` should
be returned.
=== is_authorized === is_authorized
[source,erlang] [source,erlang]
@ -730,6 +734,9 @@ listed here, like the authorization header.
== Changelog == Changelog
* *2.11*: The `generate_etag` callback can now return
`undefined` to conditionally avoid generating
an etag.
* *2.9*: An `AcceptCallback` can now return `{created, URI}` or * *2.9*: An `AcceptCallback` can now return `{created, URI}` or
`{see_other, URI}`. The return value `{true, URI}` `{see_other, URI}`. The return value `{true, URI}`
is deprecated. is deprecated.

View file

@ -97,7 +97,7 @@
-optional_callbacks([forbidden/2]). -optional_callbacks([forbidden/2]).
-callback generate_etag(Req, State) -callback generate_etag(Req, State)
-> {binary() | {weak | strong, binary()}, Req, State} -> {binary() | {weak | strong, binary()} | undefined, Req, State}
when Req::cowboy_req:req(), State::any(). when Req::cowboy_req:req(), State::any().
-optional_callbacks([generate_etag/2]). -optional_callbacks([generate_etag/2]).
@ -1527,6 +1527,12 @@ generate_etag(Req, State=#state{etag=undefined}) ->
case unsafe_call(Req, State, generate_etag) of case unsafe_call(Req, State, generate_etag) of
no_call -> no_call ->
{undefined, Req, State#state{etag=no_call}}; {undefined, Req, State#state{etag=no_call}};
%% We allow the callback to return 'undefined'
%% to allow conditionally generating etags. We
%% handle 'undefined' the same as if the function
%% was not exported.
{undefined, Req2, State2} ->
{undefined, Req2, State2#state{etag=no_call}};
{Etag, Req2, State2} when is_binary(Etag) -> {Etag, Req2, State2} when is_binary(Etag) ->
Etag2 = cow_http_hd:parse_etag(Etag), Etag2 = cow_http_hd:parse_etag(Etag),
{Etag2, Req2, State2#state{etag=Etag2}}; {Etag2, Req2, State2#state{etag=Etag2}};

View file

@ -34,6 +34,9 @@ generate_etag(Req=#{qs := <<"binary-weak-unquoted">>}, State) ->
generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) -> generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1), ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"etag-header-value">>, Req, State}; {<<"etag-header-value">>, Req, State};
%% Returning 'undefined' to indicate no etag.
generate_etag(Req=#{qs := <<"undefined">>}, State) ->
{undefined, Req, State};
%% Simulate the callback being missing in other cases. %% Simulate the callback being missing in other cases.
generate_etag(#{qs := <<"missing">>}, _) -> generate_etag(#{qs := <<"missing">>}, _) ->
no_call. no_call.

View file

@ -571,6 +571,17 @@ generate_etag_missing(Config) ->
false = lists:keyfind(<<"etag">>, 1, Headers), false = lists:keyfind(<<"etag">>, 1, Headers),
ok. ok.
generate_etag_undefined(Config) ->
doc("The etag header must not be sent when "
"the generate_etag callback returns undefined."),
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/generate_etag?undefined", [
{<<"accept-encoding">>, <<"gzip">>}
]),
{response, _, 200, Headers} = gun:await(ConnPid, Ref),
false = lists:keyfind(<<"etag">>, 1, Headers),
ok.
generate_etag_binary_strong(Config) -> generate_etag_binary_strong(Config) ->
doc("The etag header must be sent when the generate_etag " doc("The etag header must be sent when the generate_etag "
"callback returns a strong binary. (RFC7232 2.3)"), "callback returns a strong binary. (RFC7232 2.3)"),