diff options
author | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-30 11:33:56 +0000 |
---|---|---|
committer | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-30 11:33:56 +0000 |
commit | f311c233e1c2cb66205eb777d6b40b16ea36e58b (patch) | |
tree | b02cf7d50f993df44e876fa47ecdbe2fcc5b126c /net | |
parent | 58883bde2f0ffc24fddd23da6406b822f62091fd (diff) | |
download | chromium_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.cc | 28 | ||||
-rw-r--r-- | net/http/http_cache_unittest.cc | 70 | ||||
-rw-r--r-- | net/http/http_response_headers.cc | 55 | ||||
-rw-r--r-- | net/http/http_response_headers.h | 7 | ||||
-rw-r--r-- | net/http/http_response_headers_unittest.cc | 150 |
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()); +} |