From defce46fdf85d16bbe3c0a8de6058334e8a53775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 16 Jan 2024 16:28:52 +0100 Subject: REST: Allow generate_etag to return undefined This allows conditionally generating an etag. --- doc/src/manual/cowboy_rest.asciidoc | 9 ++++++++- src/cowboy_rest.erl | 8 +++++++- test/handlers/generate_etag_h.erl | 3 +++ test/rest_handler_SUITE.erl | 11 +++++++++++ 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)"), -- cgit v1.2.3