You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ki...@apache.org on 2017/03/20 12:01:55 UTC

[trafficserver] branch master updated: Implement Cache-Control: immutable for combohandler

This is an automated email from the ASF dual-hosted git repository.

kichan 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  9d33ca2   Implement Cache-Control: immutable for combohandler
9d33ca2 is described below

commit 9d33ca209bcd04d118ed1fea634423a6bafb8884
Author: Daniel Xu <dx...@dxuuu.xyz>
AuthorDate: Thu Mar 16 12:28:40 2017 -0500

    Implement Cache-Control: immutable for combohandler
    
    Before, combohandler would not insert the immutable cache control even
    if all the requested documents had the header. Now, combohandler will
    respect the presence of the header.
---
 doc/admin-guide/plugins/combo_handler.en.rst |  13 +++
 plugins/esi/README.combo                     |  14 +++
 plugins/esi/combo_handler.cc                 | 160 ++++++++++++++++++---------
 3 files changed, 135 insertions(+), 52 deletions(-)

diff --git a/doc/admin-guide/plugins/combo_handler.en.rst b/doc/admin-guide/plugins/combo_handler.en.rst
index 65c805c..bfa0214 100644
--- a/doc/admin-guide/plugins/combo_handler.en.rst
+++ b/doc/admin-guide/plugins/combo_handler.en.rst
@@ -83,3 +83,16 @@ results in these file paths being "reconstructed"::
     /dir:path2/file5
     /dir:path2/file6
 
+Caching
+=======
+Combohandler follows a few rules for the "Cache-Control" header:
+
+1) All requested documents must have "immutable" for the combo'd
+   response to also have "immutable".
+
+2) [Feature gated for 8.0 release] If one or more requested documents has "private" set,
+   then the combo'd response will also have "private". If no requested documents have a
+   publicity setting, then the default is "public".
+
+3) The "max-age" value will be set to the smallest of all the requested "max-age"
+   values. If no documents has "max-age" set, then the default is 10 years.
diff --git a/plugins/esi/README.combo b/plugins/esi/README.combo
index 9e7fee2..97b7b45 100644
--- a/plugins/esi/README.combo
+++ b/plugins/esi/README.combo
@@ -52,6 +52,20 @@ results in these file paths being "reconstructed":
 /dir:path2/file5
 /dir:path2/file6
 
+Caching
+-------
+Combohandler follows a few rules for the "Cache-Control" header:
+
+1) All requested documents must have "immutable" for the combo'd
+   response to also have "immutable".
+
+2) [Feature gated for 8.0 release] If one or more requested documents has "private" set,
+   then the combo'd response will also have "private". If no requested documents have a
+   publicity setting, then the default is "public".
+
+3) The "max-age" value will be set to the smallest of all the requested "max-age"
+   values. If no documents has "max-age" set, then the default is 10 years.
+
 Config sample
 ------
  [plugin.config]
diff --git a/plugins/esi/combo_handler.cc b/plugins/esi/combo_handler.cc
index 788498c..0b61ad7 100644
--- a/plugins/esi/combo_handler.cc
+++ b/plugins/esi/combo_handler.cc
@@ -29,6 +29,7 @@
 #include <time.h>
 #include <pthread.h>
 #include <arpa/inet.h>
+#include <limits>
 
 #include "ts/ts.h"
 #include "ts/experimental.h"
@@ -43,9 +44,12 @@ using namespace std;
 using namespace EsiLib;
 
 #define DEBUG_TAG "combo_handler"
+#define FEAT_GATE_8_0
 
 #define MAX_FILE_COUNT 30
 #define MAX_QUERY_LENGTH 3000
+// We hardcode "immutable" here because it's not yet defined in the ATS API
+#define HTTP_IMMUTABLE "immutable"
 
 int arg_idx;
 static string SIG_KEY_NAME;
@@ -136,6 +140,25 @@ struct InterceptData {
   ~InterceptData();
 };
 
+/*
+ * This class is responsible for keeping track of and processing the various
+ * Cache-Control values between all the requested documents
+ */
+struct CacheControlHeader {
+  enum Publicity { PRIVATE, PUBLIC, DEFAULT };
+
+  // Update the object with a document's Cache-Control header
+  void update(TSMBuffer bufp, TSMLoc hdr_loc);
+
+  // Return the Cache-Control for the combined document
+  string generate() const;
+
+  // Cache-Control values we're keeping track of
+  unsigned int _max_age = numeric_limits<unsigned int>::max();
+  Publicity _publicity  = Publicity::DEFAULT;
+  bool _immutable       = true;
+};
+
 bool
 InterceptData::init(TSVConn vconn)
 {
@@ -187,6 +210,86 @@ InterceptData::~InterceptData()
   }
 }
 
+void
+CacheControlHeader::update(TSMBuffer bufp, TSMLoc hdr_loc)
+{
+  bool found_immutable = false;
+  bool found_private   = false;
+
+  // Load each value from the Cache-Control header into the vector values
+  TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL);
+  if (field_loc != TS_NULL_MLOC) {
+    int n_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
+    if ((n_values != TS_ERROR) && (n_values > 0)) {
+      for (int i = 0; i < n_values; i++) {
+        // Grab this current header value
+        int _val_len    = 0;
+        const char *val = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, i, &_val_len);
+
+        // Update max-age if necessary
+        if (strncasecmp(val, TS_HTTP_VALUE_MAX_AGE, TS_HTTP_LEN_MAX_AGE) == 0) {
+          unsigned int max_age = 0;
+          char *ptr            = const_cast<char *>(val);
+          ptr += TS_HTTP_LEN_MAX_AGE;
+          while ((*ptr == ' ') || (*ptr == '\t'))
+            ptr++;
+          if (*ptr == '=') {
+            ptr++;
+            max_age = atoi(ptr);
+          }
+          if (max_age > 0 && max_age < _max_age) {
+            _max_age = max_age;
+          }
+          // If we find even a single occurrence of private, the whole response must be private
+        } else if (strncasecmp(val, TS_HTTP_VALUE_PRIVATE, TS_HTTP_LEN_PRIVATE) == 0) {
+          found_private = true;
+          // Every requested document must have immutable for the final response to be immutable
+        } else if (strncasecmp(val, HTTP_IMMUTABLE, strlen(HTTP_IMMUTABLE)) == 0) {
+          found_immutable = true;
+        }
+      }
+    }
+    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+  }
+
+  if (!found_immutable) {
+    LOG_DEBUG("Did not see an immutable cache control. The response will be not be immutable");
+    _immutable = false;
+  }
+
+  if (found_private) {
+    LOG_DEBUG("Saw a private cache control. The response will be private");
+    _publicity = Publicity::PRIVATE;
+  }
+}
+
+string
+CacheControlHeader::generate() const
+{
+  unsigned int max_age;
+  char line_buf[256];
+  const char *publicity;
+  const char *immutable;
+
+// TODO This feature gate should be removed for the 8.0 release. Previously, all combo_cache
+// documents were public. However, that's a bug. If any requested document is private the combo_cache
+// document should private as well.
+#ifndef FEAT_GATE_8_0
+  if (_publicity == Publicity::PUBLIC || _publicity == Publicity::DEFAULT) {
+    publicity = TS_HTTP_VALUE_PUBLIC;
+  } else {
+    publicity = TS_HTTP_VALUE_PRIVATE;
+  }
+#else
+  publicity = TS_HTTP_VALUE_PUBLIC;
+#endif
+  immutable = (_immutable ? ", " HTTP_IMMUTABLE : "");
+  max_age   = (_max_age == numeric_limits<unsigned int>::max() ? 315360000 : _max_age); // default is 10 years
+
+  sprintf(line_buf, "Cache-Control: max-age=%u, %s%s\r\n", max_age, publicity, immutable);
+  return string(line_buf);
+}
+
 // forward declarations
 static int handleReadRequestHeader(TSCont contp, TSEvent event, void *edata);
 static bool isComboHandlerRequest(TSMBuffer bufp, TSMLoc hdr_loc, TSMLoc url_loc);
@@ -201,7 +304,6 @@ static bool writeErrorResponse(InterceptData &int_data, int &n_bytes_written);
 static bool writeStandardHeaderFields(InterceptData &int_data, int &n_bytes_written);
 static void prepareResponse(InterceptData &int_data, ByteBlockList &body_blocks, string &resp_header_fields);
 static bool getContentType(TSMBuffer bufp, TSMLoc hdr_loc, string &resp_header_fields);
-static int getMaxAge(TSMBuffer bufp, TSMLoc hdr_loc);
 static bool getDefaultBucket(TSHttpTxn txnp, TSMBuffer bufp, TSMLoc hdr_obj, ClientRequest &creq);
 
 // libesi TLS key.
@@ -792,12 +894,11 @@ prepareResponse(InterceptData &int_data, ByteBlockList &body_blocks, string &res
   if (int_data.creq.status == TS_HTTP_STATUS_OK) {
     HttpDataFetcherImpl::ResponseData resp_data;
     TSMLoc field_loc;
-    int max_age      = 0;
-    bool got_max_age = false;
     time_t expires_time;
     bool got_expires_time = false;
     int num_headers       = HEADER_WHITELIST.size();
     int flags_list[num_headers];
+    CacheControlHeader cch;
 
     for (int i = 0; i < num_headers; i++) {
       flags_list[i] = 0;
@@ -812,15 +913,8 @@ prepareResponse(InterceptData &int_data, ByteBlockList &body_blocks, string &res
           }
         }
 
-        int curr_field_max_age = getMaxAge(resp_data.bufp, resp_data.hdr_loc);
-        if (curr_field_max_age > 0) {
-          if (!got_max_age) {
-            max_age     = curr_field_max_age;
-            got_max_age = true;
-          } else if (curr_field_max_age < max_age) {
-            max_age = curr_field_max_age;
-          }
-        }
+        // Load this document's Cache-Control header into our managing object
+        cch.update(resp_data.bufp, resp_data.hdr_loc);
 
         field_loc = TSMimeHdrFieldFind(resp_data.bufp, resp_data.hdr_loc, TS_MIME_FIELD_EXPIRES, TS_MIME_LEN_EXPIRES);
         if (field_loc != TS_NULL_MLOC) {
@@ -878,14 +972,9 @@ prepareResponse(InterceptData &int_data, ByteBlockList &body_blocks, string &res
       }
     }
     if (int_data.creq.status == TS_HTTP_STATUS_OK) {
+      // Add in Cache-Control header
       if (find(HEADER_WHITELIST.begin(), HEADER_WHITELIST.end(), TS_MIME_FIELD_CACHE_CONTROL) == HEADER_WHITELIST.end()) {
-        if (got_max_age && max_age > 0) {
-          char line_buf[128];
-          int line_size = sprintf(line_buf, "Cache-Control: max-age=%d, public\r\n", max_age);
-          resp_header_fields.append(line_buf, line_size);
-        } else {
-          resp_header_fields.append("Cache-Control: max-age=315360000, public\r\n"); // set 10-years max-age
-        }
+        resp_header_fields.append(cch.generate());
       }
       if (find(HEADER_WHITELIST.begin(), HEADER_WHITELIST.end(), TS_MIME_FIELD_EXPIRES) == HEADER_WHITELIST.end()) {
         if (got_expires_time) {
@@ -943,39 +1032,6 @@ getContentType(TSMBuffer bufp, TSMLoc hdr_loc, string &resp_header_fields)
   return retval;
 }
 
-static int
-getMaxAge(TSMBuffer bufp, TSMLoc hdr_loc)
-{
-  int max_age = 0;
-  const char *value, *ptr;
-  int value_len = 0;
-
-  TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL);
-  if (field_loc != TS_NULL_MLOC) {
-    int n_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
-    if ((n_values != TS_ERROR) && (n_values > 0)) {
-      for (int i = 0; i < n_values; i++) {
-        value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, i, &value_len);
-        ptr   = value;
-        if (strncmp(value, TS_HTTP_VALUE_MAX_AGE, TS_HTTP_LEN_MAX_AGE) == 0) {
-          ptr += TS_HTTP_LEN_MAX_AGE;
-          while ((*ptr == ' ') || (*ptr == '\t')) {
-            ptr++;
-          }
-          if (*ptr == '=') {
-            ptr++;
-            max_age = atoi(ptr);
-          }
-          break;
-        }
-      }
-    }
-    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
-  }
-
-  return max_age;
-}
-
 static const char INVARIANT_FIELD_LINES[]    = {"Vary: Accept-Encoding\r\n"};
 static const char INVARIANT_FIELD_LINES_SIZE = sizeof(INVARIANT_FIELD_LINES) - 1;
 

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>'].