Skip to content

Commit 0d52043

Browse files
mlibbeyclaude
andauthored
Fix HTTP caching compliance by adding Vary headers for compressible content (#12741)
The compress plugin was only adding Vary: Accept-Encoding headers when content was actually going to be compressed, not when content could be compressed. This can cause downstream caches to never get the compressed version in cache. Now adds Vary: Accept-Encoding headers for all compressible content regardless of whether compression is applied, ensuring proper HTTP cache behavior. Co-authored-by: Claude <[email protected]>
1 parent daa956f commit 0d52043

File tree

5 files changed

+213
-87
lines changed

5 files changed

+213
-87
lines changed

plugins/compress/compress.cc

Lines changed: 147 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ compress_transform_init(TSCont contp, Data *data)
330330
}
331331

332332
if (content_encoding_header(bufp, hdr_loc, data->compression_type, data->compression_algorithms) == TS_SUCCESS &&
333-
vary_header(bufp, hdr_loc) == TS_SUCCESS && etag_header(bufp, hdr_loc) == TS_SUCCESS) {
333+
etag_header(bufp, hdr_loc) == TS_SUCCESS) {
334334
downstream_conn = TSTransformOutputVConnGet(contp);
335335
data->downstream_buffer = TSIOBufferCreate();
336336
data->downstream_reader = TSIOBufferReaderAlloc(data->downstream_buffer);
@@ -518,7 +518,7 @@ compress_transform(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
518518
}
519519

520520
static int
521-
transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration, int *compress_type, int *algorithms)
521+
is_content_compressible(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration)
522522
{
523523
/* Server response header */
524524
TSMBuffer bufp;
@@ -528,7 +528,6 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
528528
/* Client request header */
529529
TSMBuffer cbuf;
530530
TSMLoc chdr;
531-
TSMLoc cfield;
532531

533532
const char *value;
534533
int len;
@@ -570,6 +569,100 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
570569
return 0;
571570
}
572571

572+
// the only compressible method is currently GET or POST.
573+
int method_length;
574+
const char *method = TSHttpHdrMethodGet(cbuf, chdr, &method_length);
575+
576+
if (!((method_length == TS_HTTP_LEN_GET && memcmp(method, TS_HTTP_METHOD_GET, TS_HTTP_LEN_GET) == 0) ||
577+
(method_length == TS_HTTP_LEN_POST && memcmp(method, TS_HTTP_METHOD_POST, TS_HTTP_LEN_POST) == 0))) {
578+
debug("method is not GET or POST, not compressible");
579+
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
580+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
581+
return 0;
582+
}
583+
584+
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
585+
586+
/* If there already exists a content encoding then we don't want
587+
to do anything. */
588+
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, -1);
589+
if (field_loc) {
590+
info("response is already content encoded, not compressible");
591+
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
592+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
593+
return 0;
594+
}
595+
596+
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH);
597+
if (field_loc != TS_NULL_MLOC) {
598+
unsigned int hdr_value = TSMimeHdrFieldValueUintGet(bufp, hdr_loc, field_loc, -1);
599+
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
600+
if (hdr_value == 0) {
601+
info("response is 0-length, not compressible");
602+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
603+
return 0;
604+
}
605+
606+
if (hdr_value < host_configuration->minimum_content_length()) {
607+
info("response is smaller than minimum content length, not compressing");
608+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
609+
return 0;
610+
}
611+
}
612+
613+
// Check if content type is compressible based on configuration.
614+
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, -1);
615+
if (!field_loc) {
616+
info("no content type header found, not compressible");
617+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
618+
return 0;
619+
}
620+
621+
value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &len);
622+
623+
int rv = host_configuration->is_content_type_compressible(value, len);
624+
625+
if (!rv) {
626+
info("content-type [%.*s] not compressible", len, value);
627+
}
628+
629+
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
630+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
631+
632+
return rv;
633+
}
634+
635+
static int
636+
client_accepts_compression(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration, int *compress_type, int *algorithms)
637+
{
638+
/* Server response header */
639+
TSMBuffer bufp;
640+
TSMLoc hdr_loc;
641+
642+
/* Client request header */
643+
TSMBuffer cbuf;
644+
TSMLoc chdr;
645+
TSMLoc cfield;
646+
647+
const char *value;
648+
int len;
649+
650+
if (server) {
651+
if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) {
652+
return 0;
653+
}
654+
} else {
655+
if (TS_SUCCESS != TSHttpTxnCachedRespGet(txnp, &bufp, &hdr_loc)) {
656+
return 0;
657+
}
658+
}
659+
660+
if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &cbuf, &chdr)) {
661+
info("cound not get client request");
662+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
663+
return 0;
664+
}
665+
573666
// check Partial Object is transformable
574667
if (host_configuration->range_request_ctl() == RangeRequestCtrl::NO_COMPRESSION) {
575668
// check Range header in client request
@@ -592,21 +685,6 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
592685
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
593686
return 0;
594687
}
595-
596-
TSHandleMLocRelease(bufp, hdr_loc, content_range_hdr_field);
597-
TSHandleMLocRelease(cbuf, chdr, range_hdr_field);
598-
}
599-
600-
// the only compressible method is currently GET.
601-
int method_length;
602-
const char *method = TSHttpHdrMethodGet(cbuf, chdr, &method_length);
603-
604-
if (!((method_length == TS_HTTP_LEN_GET && memcmp(method, TS_HTTP_METHOD_GET, TS_HTTP_LEN_GET) == 0) ||
605-
(method_length == TS_HTTP_LEN_POST && memcmp(method, TS_HTTP_METHOD_POST, TS_HTTP_LEN_POST) == 0))) {
606-
debug("method is not GET or POST, not compressible");
607-
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
608-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
609-
return 0;
610688
}
611689

612690
*algorithms = host_configuration->compression_algorithms();
@@ -647,10 +725,10 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
647725

648726
TSHandleMLocRelease(cbuf, chdr, cfield);
649727
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
728+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
650729

651730
if (!compression_acceptable) {
652731
info("no acceptable encoding match found in request header, not compressible");
653-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
654732
return 0;
655733
}
656734
} else {
@@ -661,52 +739,48 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
661739
return 0;
662740
}
663741

664-
/* If there already exists a content encoding then we don't want
665-
to do anything. */
666-
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, -1);
667-
if (field_loc) {
668-
info("response is already content encoded, not compressible");
669-
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
670-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
671-
return 0;
672-
}
673-
674-
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH);
675-
if (field_loc != TS_NULL_MLOC) {
676-
unsigned int hdr_value = TSMimeHdrFieldValueUintGet(bufp, hdr_loc, field_loc, -1);
677-
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
678-
if (hdr_value == 0) {
679-
info("response is 0-length, not compressible");
680-
return 0;
681-
}
682-
683-
if (hdr_value < host_configuration->minimum_content_length()) {
684-
info("response is smaller than minimum content length, not compressing");
685-
return 0;
686-
}
687-
}
742+
return 1;
743+
}
688744

689-
/* We only want to do gzip compression on documents that have a
690-
content type of "text/" or "application/x-javascript". */
691-
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, -1);
692-
if (!field_loc) {
693-
info("no content type header found, not compressible");
694-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
745+
static int
746+
transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration, int *compress_type, int *algorithms,
747+
bool *content_is_compressible)
748+
{
749+
// First check if content could be compressible
750+
*content_is_compressible = is_content_compressible(txnp, server, host_configuration);
751+
if (!*content_is_compressible) {
695752
return 0;
696753
}
697754

698-
value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &len);
755+
// Then check if client accepts compression
756+
return client_accepts_compression(txnp, server, host_configuration, compress_type, algorithms);
757+
}
699758

700-
int rv = host_configuration->is_content_type_compressible(value, len);
759+
static void
760+
add_vary_header_for_compressible_content(TSHttpTxn txnp, bool server, HostConfiguration * /* hc ATS_UNUSED */)
761+
{
762+
TSMBuffer resp_buf;
763+
TSMLoc resp_loc;
701764

702-
if (!rv) {
703-
info("content-type [%.*s] not compressible", len, value);
765+
// Get the response headers
766+
if (server) {
767+
if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &resp_buf, &resp_loc)) {
768+
return;
769+
}
770+
} else {
771+
if (TS_SUCCESS != TSHttpTxnCachedRespGet(txnp, &resp_buf, &resp_loc)) {
772+
return;
773+
}
704774
}
705775

706-
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
707-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
776+
// Add Vary: Accept-Encoding header
777+
if (vary_header(resp_buf, resp_loc) != TS_SUCCESS) {
778+
error("failed to add Vary header for compressible content");
779+
TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
780+
return;
781+
}
708782

709-
return rv;
783+
TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
710784
}
711785

712786
static void
@@ -734,6 +808,21 @@ compress_transform_add(TSHttpTxn txnp, HostConfiguration *hc, int compress_type,
734808
TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
735809
}
736810

811+
static void
812+
handle_compression_and_vary(TSHttpTxn txnp, bool server, HostConfiguration *hc, int *compress_type, int *algorithms)
813+
{
814+
// Check if content is compressible and add compression if client accepts it
815+
bool content_is_compressible;
816+
if (transformable(txnp, server, hc, compress_type, algorithms, &content_is_compressible)) {
817+
compress_transform_add(txnp, hc, *compress_type, *algorithms);
818+
}
819+
820+
// Add Vary: Accept-Encoding for all compressible content to ensure proper HTTP caching
821+
if (content_is_compressible) {
822+
add_vary_header_for_compressible_content(txnp, server, hc);
823+
}
824+
}
825+
737826
HostConfiguration *
738827
find_host_configuration(TSHttpTxn /* txnp ATS_UNUSED */, TSMBuffer bufp, TSMLoc locp, Configuration *config)
739828
{
@@ -778,9 +867,7 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
778867
}
779868
}
780869

781-
if (transformable(txnp, true, hc, &compress_type, &algorithms)) {
782-
compress_transform_add(txnp, hc, compress_type, algorithms);
783-
}
870+
handle_compression_and_vary(txnp, true, hc, &compress_type, &algorithms);
784871
}
785872
break;
786873

@@ -806,9 +893,7 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
806893
if (TS_ERROR != TSHttpTxnCacheLookupStatusGet(txnp, &obj_status) && (TS_CACHE_LOOKUP_HIT_FRESH == obj_status)) {
807894
if (hc != nullptr) {
808895
info("handling compression of cached object");
809-
if (transformable(txnp, false, hc, &compress_type, &algorithms)) {
810-
compress_transform_add(txnp, hc, compress_type, algorithms);
811-
}
896+
handle_compression_and_vary(txnp, false, hc, &compress_type, &algorithms);
812897
}
813898
} else {
814899
// Prepare for going to origin

0 commit comments

Comments
 (0)