You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bn...@apache.org on 2026/08/25 18:07:35 UTC
(trafficserver) branch master updated: compress: add Vary to cached client responses (#13580)
This is an automated email from the ASF dual-hosted git repository.
traeak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 46763082af compress: add Vary to cached client responses (#13580)
46763082af is described below
commit 46763082af3cdce72654a99e295ca5759a2e951b
Author: Brian Olsen <br...@comcast.com>
AuthorDate: Tue Aug 25 12:07:29 2026 -0600
compress: add Vary to cached client responses (#13580)
* compress: add Vary to cached client responses
* rename function to add_vary_header_to_server_response
---
plugins/compress/compress.cc | 50 +++++++---
.../compress/compress-vary-cached-response.test.py | 101 +++++++++++++++++++++
2 files changed, 136 insertions(+), 15 deletions(-)
diff --git a/plugins/compress/compress.cc b/plugins/compress/compress.cc
index 58fd52802f..3994be9b72 100644
--- a/plugins/compress/compress.cc
+++ b/plugins/compress/compress.cc
@@ -772,23 +772,16 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
}
static void
-add_vary_header_for_compressible_content(TSHttpTxn txnp, bool server, HostConfiguration * /* hc ATS_UNUSED */)
+add_vary_header_to_server_response(TSHttpTxn txnp)
{
TSMBuffer resp_buf;
TSMLoc resp_loc;
- // Get the response headers
- if (server) {
- if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &resp_buf, &resp_loc)) {
- return;
- }
- } else {
- if (TS_SUCCESS != TSHttpTxnCachedRespGet(txnp, &resp_buf, &resp_loc)) {
- return;
- }
+ if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &resp_buf, &resp_loc)) {
+ return;
}
- // Add Vary: Accept-Encoding header
+ // Add Vary: Accept-Encoding header to the origin response before caching.
if (vary_header(resp_buf, resp_loc) != TS_SUCCESS) {
error("failed to add Vary header for compressible content");
TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
@@ -798,6 +791,25 @@ add_vary_header_for_compressible_content(TSHttpTxn txnp, bool server, HostConfig
TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
}
+static void
+add_vary_header_to_client_response(TSHttpTxn txnp)
+{
+ TSMBuffer resp_buf;
+ TSMLoc resp_loc;
+
+ if (TS_SUCCESS != TSHttpTxnClientRespGet(txnp, &resp_buf, &resp_loc)) {
+ return;
+ }
+
+ if (vary_header(resp_buf, resp_loc) != TS_SUCCESS) {
+ error("failed to add Vary header to client response");
+ TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
+ return;
+ }
+
+ TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
+}
+
static void
compress_transform_add(TSHttpTxn txnp, HostConfiguration *hc, int compress_type, int algorithms)
{
@@ -824,7 +836,7 @@ compress_transform_add(TSHttpTxn txnp, HostConfiguration *hc, int compress_type,
}
static void
-handle_compression_and_vary(TSHttpTxn txnp, bool server, HostConfiguration *hc, int *compress_type, int *algorithms)
+handle_compression_and_vary(TSCont contp, TSHttpTxn txnp, bool server, HostConfiguration *hc, int *compress_type, int *algorithms)
{
// Check if content is compressible and add compression if client accepts it
bool content_is_compressible;
@@ -834,7 +846,11 @@ handle_compression_and_vary(TSHttpTxn txnp, bool server, HostConfiguration *hc,
// Add Vary: Accept-Encoding for all compressible content to ensure proper HTTP caching
if (content_is_compressible) {
- add_vary_header_for_compressible_content(txnp, server, hc);
+ if (server) {
+ add_vary_header_to_server_response(txnp);
+ } else {
+ TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
+ }
}
}
@@ -882,7 +898,7 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
}
}
- handle_compression_and_vary(txnp, true, hc, &compress_type, &algorithms);
+ handle_compression_and_vary(contp, txnp, true, hc, &compress_type, &algorithms);
}
break;
@@ -908,7 +924,7 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
if (TS_ERROR != TSHttpTxnCacheLookupStatusGet(txnp, &obj_status) && (TS_CACHE_LOOKUP_HIT_FRESH == obj_status)) {
if (hc != nullptr) {
info("handling compression of cached object");
- handle_compression_and_vary(txnp, false, hc, &compress_type, &algorithms);
+ handle_compression_and_vary(contp, txnp, false, hc, &compress_type, &algorithms);
}
} else {
// Prepare for going to origin
@@ -917,6 +933,10 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
}
} break;
+ case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
+ add_vary_header_to_client_response(txnp);
+ break;
+
case TS_EVENT_HTTP_TXN_CLOSE:
// Release the ocnif lease, and destroy this continuation
TSContDestroy(contp);
diff --git a/tests/gold_tests/pluginTest/compress/compress-vary-cached-response.test.py b/tests/gold_tests/pluginTest/compress/compress-vary-cached-response.test.py
new file mode 100644
index 0000000000..033555fc16
--- /dev/null
+++ b/tests/gold_tests/pluginTest/compress/compress-vary-cached-response.test.py
@@ -0,0 +1,101 @@
+'''
+Regression test for cached Vary header updates when the client omits
+Accept-Encoding.
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Test.Summary = '''
+Regression test for compress Vary header updates on cached responses when the
+client does not send Accept-Encoding.
+'''
+
+Test.SkipUnless(Condition.PluginExists('compress.so'))
+
+server = Test.MakeOriginServer("server")
+request_header = {"headers": "GET /object HTTP/1.1\r\nHost: seed.example\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+response_header = {
+ "headers":
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Cache-Control: public, max-age=3600\r\n"
+ "Content-Type: text/javascript\r\n"
+ "Content-Length: 22\r\n\r\n",
+ "timestamp": "1469733493.993",
+ "body": "var cached_value = 1;\n"
+}
+server.addResponse("sessionlog.json", request_header, response_header)
+
+ts = Test.MakeATSProcess("ts", enable_cache=True)
+ts.Disk.records_config.update(
+ {
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'compress',
+ 'proxy.config.diags.output.diag': 'L',
+ })
+ts.Setup.Copy("etc/compress-cache-false.config")
+ts.Disk.remap_config.AddLine(f'map http://seed.example/ http://127.0.0.1:{server.Variables.Port}/')
+ts.Disk.remap_config.AddLine(
+ f'map http://compress.example/ http://127.0.0.1:{server.Variables.Port}/'
+ f' @plugin=compress.so @pparam={Test.RunDirectory}/compress-cache-false.config')
+
+ts.Disk.diags_log.Content += Testers.ContainsExpression(
+ 'handling compression of cached object', 'compress plugin must process a cached response')
+ts.Disk.diags_log.Content += Testers.ExcludesExpression('cannot add/update the Vary header', 'cached response must not be mutated')
+ts.Disk.diags_log.Content += Testers.ExcludesExpression(
+ 'failed to add Vary header for compressible content', 'origin Vary update must succeed')
+ts.Disk.diags_log.Content += Testers.ExcludesExpression(
+ 'failed to add Vary header to client response', 'client Vary update must succeed')
+
+seed = Test.AddTestRun('seed cache without compress plugin')
+seed.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
+seed.Processes.Default.StartBefore(ts)
+seed.Processes.Default.Command = (
+ f'curl --http1.1 -sS -o /dev/null --proxy http://127.0.0.1:{ts.Variables.port}'
+ ' http://seed.example/object')
+seed.Processes.Default.ReturnCode = 0
+seed.StillRunningAfter = server
+seed.StillRunningAfter = ts
+
+first_cached = Test.AddTestRun('add Vary to cached client response')
+first_cached.Processes.Default.Command = (
+ f'curl --http1.1 -sS -o /dev/null --proxy http://127.0.0.1:{ts.Variables.port}'
+ ' http://compress.example/object')
+first_cached.Processes.Default.ReturnCode = 0
+first_cached.StillRunningAfter = server
+first_cached.StillRunningAfter = ts
+
+headers_path = f'{Test.RunDirectory}/cached_headers.txt'
+body_path = f'{Test.RunDirectory}/cached_body.txt'
+second_cached = Test.AddTestRun('verify cached client response Vary header')
+second_cached.Processes.Default.Command = (
+ f'curl --http1.1 -sS -D {headers_path} -o {body_path}'
+ f' --proxy http://127.0.0.1:{ts.Variables.port}'
+ ' http://compress.example/object')
+second_cached.Processes.Default.ReturnCode = 0
+second_cached.StillRunningAfter = server
+second_cached.StillRunningAfter = ts
+
+verify = Test.AddTestRun('verify cached response headers and body')
+verify.Processes.Default.Command = (
+ f"grep -i '^Vary:.*Accept-Encoding' {headers_path}"
+ f" && ! grep -i '^Content-Encoding:' {headers_path}"
+ f" && diff {body_path} - <<'EOF'\n"
+ 'var cached_value = 1;\n'
+ 'EOF')
+verify.Processes.Default.ReturnCode = 0
+verify.StillRunningAfter = server
+verify.StillRunningAfter = ts