summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-30 11:33:56 +0000
committerricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-30 11:33:56 +0000
commitf311c233e1c2cb66205eb777d6b40b16ea36e58b (patch)
treeb02cf7d50f993df44e876fa47ecdbe2fcc5b126c /net
parent58883bde2f0ffc24fddd23da6406b822f62091fd (diff)
downloadchromium_src-f311c233e1c2cb66205eb777d6b40b16ea36e58b.zip
chromium_src-f311c233e1c2cb66205eb777d6b40b16ea36e58b.tar.gz
chromium_src-f311c233e1c2cb66205eb777d6b40b16ea36e58b.tar.bz2
Initial implementation of Chrome-Freshness header.
Add a header like Chrome-Freshness: max-age=30,stale-while-revalidate=60,age=10 when sending a revalidation request to a server which supplied the Cache-Control stale-while-revalidate directive on the previous response. Design doc: https://docs.google.com/document/d/1DMCIMAKjyKeYiu69jlI5OsO2pGyAMb81XflYK4hxsNM/edit BUG=348877 TEST=net_unittests Review URL: https://codereview.chromium.org/391763002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286482 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_cache_transaction.cc28
-rw-r--r--net/http/http_cache_unittest.cc70
-rw-r--r--net/http/http_response_headers.cc55
-rw-r--r--net/http/http_response_headers.h7
-rw-r--r--net/http/http_response_headers_unittest.cc150
5 files changed, 287 insertions, 23 deletions
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index aba7923..58cb3b4 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -15,13 +15,16 @@
#include "base/bind.h"
#include "base/compiler_specific.h"
+#include "base/format_macros.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
@@ -48,6 +51,9 @@ using base::TimeTicks;
namespace {
+// TODO(ricea): Move this to HttpResponseHeaders once it is standardised.
+static const char kFreshnessHeader[] = "Chromium-Resource-Freshness";
+
// Stores data relevant to the statistics of writing and reading entire
// certificate chains using DiskBasedCertCache. |num_pending_ops| is the number
// of certificates in the chain that have pending operations in the
@@ -2257,6 +2263,28 @@ bool HttpCache::Transaction::ConditionalizeRequest() {
bool use_if_range = partial_.get() && !partial_->IsCurrentRangeCached() &&
!invalid_range_;
+ if (!use_if_range) {
+ // stale-while-revalidate is not useful when we only have a partial response
+ // cached, so don't set the header in that case.
+ TimeDelta stale_while_revalidate;
+ if (response_.headers->GetStaleWhileRevalidateValue(
+ &stale_while_revalidate) &&
+ stale_while_revalidate > TimeDelta()) {
+ TimeDelta max_age =
+ response_.headers->GetFreshnessLifetime(response_.response_time);
+ TimeDelta current_age = response_.headers->GetCurrentAge(
+ response_.request_time, response_.response_time, Time::Now());
+
+ custom_request_->extra_headers.SetHeader(
+ kFreshnessHeader,
+ base::StringPrintf("max-age=%" PRId64
+ ",stale-while-revalidate=%" PRId64 ",age=%" PRId64,
+ max_age.InSeconds(),
+ stale_while_revalidate.InSeconds(),
+ current_age.InSeconds()));
+ }
+ }
+
if (!etag_value.empty()) {
if (use_if_range) {
// We don't want to switch to WRITE mode if we don't have this block of a
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 83241ab..fc917fd 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -6443,3 +6443,73 @@ TEST(HttpCache, ReceivedBytesRange) {
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
+
+static void CheckResourceFreshnessHeader(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ std::string value;
+ EXPECT_TRUE(
+ request->extra_headers.GetHeader("Chromium-Resource-Freshness", &value));
+ EXPECT_EQ("max-age=3600,stale-while-revalidate=7200,age=10801", value);
+}
+
+// Verify that the Chromium-Resource-Freshness header is sent on a revalidation
+// if the stale-while-revalidate directive was on the response.
+// TODO(ricea): Rename this test when a final name for the header is decided.
+TEST(HttpCache, ResourceFreshnessHeaderSent) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction stale_while_revalidate_transaction(
+ kSimpleGET_Transaction);
+ stale_while_revalidate_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Age: 10801\n"
+ "Cache-Control: max-age=3600,stale-while-revalidate=7200\n";
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ // Send the request again and check that Chromium-Resource-Freshness header is
+ // added.
+ stale_while_revalidate_transaction.handler = CheckResourceFreshnessHeader;
+
+ RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+}
+
+static void CheckResourceFreshnessAbsent(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_FALSE(request->extra_headers.HasHeader("Chromium-Resource-Freshness"));
+}
+
+// Verify that the Chromium-Resource-Freshness header is not sent when
+// stale-while-revalidate is 0.
+TEST(HttpCache, ResourceFreshnessHeaderNotSent) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction stale_while_revalidate_transaction(
+ kSimpleGET_Transaction);
+ stale_while_revalidate_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Age: 10801\n"
+ "Cache-Control: max-age=3600,stale-while-revalidate=0\n";
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ // Send the request again and check that Chromium-Resource-Freshness header is
+ // absent.
+ stale_while_revalidate_transaction.handler = CheckResourceFreshnessAbsent;
+
+ RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+}
diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc
index b7ef98a..e767ebb 100644
--- a/net/http/http_response_headers.cc
+++ b/net/http/http_response_headers.cc
@@ -753,6 +753,32 @@ size_t HttpResponseHeaders::FindHeader(size_t from,
return std::string::npos;
}
+bool HttpResponseHeaders::GetCacheControlDirective(const StringPiece& directive,
+ TimeDelta* result) const {
+ StringPiece name("cache-control");
+ std::string value;
+
+ size_t directive_size = directive.size();
+
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, name, &value)) {
+ if (value.size() > directive_size + 1 &&
+ LowerCaseEqualsASCII(value.begin(),
+ value.begin() + directive_size,
+ directive.begin()) &&
+ value[directive_size] == '=') {
+ int64 seconds;
+ base::StringToInt64(
+ StringPiece(value.begin() + directive_size + 1, value.end()),
+ &seconds);
+ *result = TimeDelta::FromSeconds(seconds);
+ return true;
+ }
+ }
+
+ return false;
+}
+
void HttpResponseHeaders::AddHeader(std::string::const_iterator name_begin,
std::string::const_iterator name_end,
std::string::const_iterator values_begin,
@@ -1092,29 +1118,7 @@ TimeDelta HttpResponseHeaders::GetCurrentAge(const Time& request_time,
}
bool HttpResponseHeaders::GetMaxAgeValue(TimeDelta* result) const {
- std::string name = "cache-control";
- std::string value;
-
- const char kMaxAgePrefix[] = "max-age=";
- const size_t kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1;
-
- void* iter = NULL;
- while (EnumerateHeader(&iter, name, &value)) {
- if (value.size() > kMaxAgePrefixLen) {
- if (LowerCaseEqualsASCII(value.begin(),
- value.begin() + kMaxAgePrefixLen,
- kMaxAgePrefix)) {
- int64 seconds;
- base::StringToInt64(StringPiece(value.begin() + kMaxAgePrefixLen,
- value.end()),
- &seconds);
- *result = TimeDelta::FromSeconds(seconds);
- return true;
- }
- }
- }
-
- return false;
+ return GetCacheControlDirective("max-age", result);
}
bool HttpResponseHeaders::GetAgeValue(TimeDelta* result) const {
@@ -1140,6 +1144,11 @@ bool HttpResponseHeaders::GetExpiresValue(Time* result) const {
return GetTimeValuedHeader("Expires", result);
}
+bool HttpResponseHeaders::GetStaleWhileRevalidateValue(
+ TimeDelta* result) const {
+ return GetCacheControlDirective("stale-while-revalidate", result);
+}
+
bool HttpResponseHeaders::GetTimeValuedHeader(const std::string& name,
Time* result) const {
std::string value;
diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h
index 2d306c9..ddab798 100644
--- a/net/http/http_response_headers.h
+++ b/net/http/http_response_headers.h
@@ -229,6 +229,7 @@ class NET_EXPORT HttpResponseHeaders
bool GetDateValue(base::Time* value) const;
bool GetLastModifiedValue(base::Time* value) const;
bool GetExpiresValue(base::Time* value) const;
+ bool GetStaleWhileRevalidateValue(base::TimeDelta* value) const;
// Extracts the time value of a particular header. This method looks for the
// first matching header value and parses its value as a HTTP-date.
@@ -320,6 +321,12 @@ class NET_EXPORT HttpResponseHeaders
// index |from|. Returns string::npos if not found.
size_t FindHeader(size_t from, const base::StringPiece& name) const;
+ // Search the Cache-Control header for a directive matching |directive|. If
+ // present, treat its value as a time offset in seconds, write it to |result|,
+ // and return true.
+ bool GetCacheControlDirective(const base::StringPiece& directive,
+ base::TimeDelta* result) const;
+
// Add a header->value pair to our list. If we already have header in our
// list, append the value to it.
void AddHeader(std::string::const_iterator name_begin,
diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc
index cc236d1..76790e3 100644
--- a/net/http/http_response_headers_unittest.cc
+++ b/net/http/http_response_headers_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <algorithm>
+#include <limits>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
@@ -43,6 +44,53 @@ void HeadersToRaw(std::string* headers) {
*headers += '\0';
}
+class HttpResponseHeadersCacheControlTest : public HttpResponseHeadersTest {
+ protected:
+ // Make tests less verbose.
+ typedef base::TimeDelta TimeDelta;
+
+ // Initilise the headers() value with a Cache-Control header set to
+ // |cache_control|. |cache_control| is copied and so can safely be a
+ // temporary.
+ void InitializeHeadersWithCacheControl(const char* cache_control) {
+ std::string raw_headers("HTTP/1.1 200 OK\n");
+ raw_headers += "Cache-Control: ";
+ raw_headers += cache_control;
+ raw_headers += "\n";
+ HeadersToRaw(&raw_headers);
+ headers_ = new net::HttpResponseHeaders(raw_headers);
+ }
+
+ const scoped_refptr<net::HttpResponseHeaders>& headers() { return headers_; }
+
+ // Return a pointer to a TimeDelta object. For use when the value doesn't
+ // matter.
+ TimeDelta* TimeDeltaPointer() { return &delta_; }
+
+ // Get the max-age value. This should only be used in tests where a valid
+ // max-age parameter is expected to be present.
+ TimeDelta GetMaxAgeValue() {
+ DCHECK(headers_) << "Call InitializeHeadersWithCacheControl() first";
+ TimeDelta max_age_value;
+ EXPECT_TRUE(headers()->GetMaxAgeValue(&max_age_value));
+ return max_age_value;
+ }
+
+ // Get the stale-while-revalidate value. This should only be used in tests
+ // where a valid max-age parameter is expected to be present.
+ TimeDelta GetStaleWhileRevalidateValue() {
+ DCHECK(headers_) << "Call InitializeHeadersWithCacheControl() first";
+ TimeDelta stale_while_revalidate_value;
+ EXPECT_TRUE(
+ headers()->GetStaleWhileRevalidateValue(&stale_while_revalidate_value));
+ return stale_while_revalidate_value;
+ }
+
+ private:
+ scoped_refptr<net::HttpResponseHeaders> headers_;
+ TimeDelta delta_;
+};
+
void TestCommon(const TestData& test) {
std::string raw_headers(test.raw_headers);
HeadersToRaw(&raw_headers);
@@ -1935,3 +1983,105 @@ TEST(HttpResponseHeadersTest, ToNetLogParamAndBackAgain) {
parsed->GetNormalizedHeaders(&normalized_recreated);
EXPECT_EQ(normalized_parsed, normalized_recreated);
}
+
+TEST_F(HttpResponseHeadersCacheControlTest, AbsentMaxAgeReturnsFalse) {
+ InitializeHeadersWithCacheControl("nocache");
+ EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithNoParameterRejected) {
+ InitializeHeadersWithCacheControl("max-age=,private");
+ EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithSpaceParameterRejected) {
+ InitializeHeadersWithCacheControl("max-age= ,private");
+ EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest,
+ MaxAgeWithSpaceBeforeEqualsIsRejected) {
+ InitializeHeadersWithCacheControl("max-age = 7");
+ EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeFirstMatchUsed) {
+ InitializeHeadersWithCacheControl("max-age=10, max-age=20");
+ EXPECT_EQ(TimeDelta::FromSeconds(10), GetMaxAgeValue());
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeBogusFirstMatchUsed) {
+ // max-age10 isn't parsed as max-age; max-age=now is parsed as max-age=0 and
+ // so max-age=20 is not used.
+ InitializeHeadersWithCacheControl("max-age10, max-age=now, max-age=20");
+ EXPECT_EQ(TimeDelta::FromSeconds(0), GetMaxAgeValue());
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeCaseInsensitive) {
+ InitializeHeadersWithCacheControl("Max-aGe=15");
+ EXPECT_EQ(TimeDelta::FromSeconds(15), GetMaxAgeValue());
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeEdgeCases) {
+ // This test doesn't use TEST_P() for consistency with the rest of the tests
+ // in this file.
+ // TODO(ricea): Port the tests in this file to use TEST_P().
+ const struct {
+ const char* max_age_string;
+ int64 expected_seconds;
+ } tests[] = {
+ {" 1 ", 1}, // Spaces are ignored
+ {"-1", -1}, // Negative numbers are passed through
+ {"--1", 0}, // Leading junk gives 0
+ {"2s", 2}, // trailing junk is ignored
+ {"3 days", 3},
+ {"'4'", 0}, // single quotes don't work
+ {"\"5\"", 0}, // double quotes don't work
+ {"0x6", 0}, // hex not parsed as hex
+ {"7F", 7}, // hex without 0x still not parsed as hex
+ {"010", 10}, // octal not parsed as octal
+ {"9223372036854", 9223372036854},
+ // {"9223372036855", -9223372036854}, // undefined behaviour
+ // {"9223372036854775806", -2}, // undefined behaviour
+ {"9223372036854775807", 9223372036854775807},
+ {"20000000000000000000",
+ std::numeric_limits<int64>::max()}, // overflow int64
+ };
+ std::string max_age = "max-age=";
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ InitializeHeadersWithCacheControl(
+ (max_age + tests[i].max_age_string).c_str());
+ EXPECT_EQ(tests[i].expected_seconds, GetMaxAgeValue().InSeconds())
+ << " for max-age=" << tests[i].max_age_string;
+ }
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest,
+ AbsentStaleWhileRevalidateReturnsFalse) {
+ InitializeHeadersWithCacheControl("max-age=3600");
+ EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest,
+ StaleWhileRevalidateWithoutValueRejected) {
+ InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=");
+ EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer()));
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest,
+ StaleWhileRevalidateWithInvalidValueTreatedAsZero) {
+ InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=true");
+ EXPECT_EQ(TimeDelta(), GetStaleWhileRevalidateValue());
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest, StaleWhileRevalidateValueReturned) {
+ InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=7200");
+ EXPECT_EQ(TimeDelta::FromSeconds(7200), GetStaleWhileRevalidateValue());
+}
+
+TEST_F(HttpResponseHeadersCacheControlTest,
+ FirstStaleWhileRevalidateValueUsed) {
+ InitializeHeadersWithCacheControl(
+ "stale-while-revalidate=1,stale-while-revalidate=7200");
+ EXPECT_EQ(TimeDelta::FromSeconds(1), GetStaleWhileRevalidateValue());
+}