aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2024-01-16 16:28:52 +0100
committerLoïc Hoguin <[email protected]>2024-01-16 16:28:52 +0100
commitdefce46fdf85d16bbe3c0a8de6058334e8a53775 (patch)
treed26ddce251a9f633b7cd20684e9eb3b8e97d40d4
parentec12c2f051f94df6f4c86175caddc0d1108254cd (diff)
downloadcowboy-defce46fdf85d16bbe3c0a8de6058334e8a53775.tar.gz
cowboy-defce46fdf85d16bbe3c0a8de6058334e8a53775.tar.bz2
cowboy-defce46fdf85d16bbe3c0a8de6058334e8a53775.zip
REST: Allow generate_etag to return undefined
This allows conditionally generating an etag.
-rw-r--r--doc/src/manual/cowboy_rest.asciidoc9
-rw-r--r--src/cowboy_rest.erl8
-rw-r--r--test/handlers/generate_etag_h.erl3
-rw-r--r--test/rest_handler_SUITE.erl11
4 files changed, 29 insertions, 2 deletions
diff --git a/doc/src/manual/cowboy_rest.asciidoc b/doc/src/manual/cowboy_rest.asciidoc
index 0bb6d47..ea6dba9 100644
--- a/doc/src/manual/cowboy_rest.asciidoc
+++ b/doc/src/manual/cowboy_rest.asciidoc
@@ -379,7 +379,7 @@ and that the request shouldn't be repeated.
----
generate_etag(Req, State) -> {Result, Req, State}
-Result :: binary() | {weak | strong, binary()}
+Result :: binary() | {weak | strong, binary()} | undefined
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
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
[source,erlang]
@@ -730,6 +734,9 @@ listed here, like the authorization header.
== 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
`{see_other, URI}`. The return value `{true, URI}`
is deprecated.
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 7d0fe80..003e5f9 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -97,7 +97,7 @@
-optional_callbacks([forbidden/2]).
-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().
-optional_callbacks([generate_etag/2]).
@@ -1527,6 +1527,12 @@ generate_etag(Req, State=#state{etag=undefined}) ->
case unsafe_call(Req, State, generate_etag) of
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) ->
Etag2 = cow_http_hd:parse_etag(Etag),
{Etag2, Req2, State2#state{etag=Etag2}};
diff --git a/test/handlers/generate_etag_h.erl b/test/handlers/generate_etag_h.erl
index 97ee82b..b9e1302 100644
--- a/test/handlers/generate_etag_h.erl
+++ b/test/handlers/generate_etag_h.erl
@@ -34,6 +34,9 @@ generate_etag(Req=#{qs := <<"binary-weak-unquoted">>}, State) ->
generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"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.
generate_etag(#{qs := <<"missing">>}, _) ->
no_call.
diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl
index b082810..510098f 100644
--- a/test/rest_handler_SUITE.erl
+++ b/test/rest_handler_SUITE.erl
@@ -571,6 +571,17 @@ generate_etag_missing(Config) ->
false = lists:keyfind(<<"etag">>, 1, Headers),
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) ->
doc("The etag header must be sent when the generate_etag "
"callback returns a strong binary. (RFC7232 2.3)"),