blob: 356c4f4678b8e8b9742a88f3e912e8d8ce453dc1 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Licensed 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.
*/
// Author: sligocki@google.com (Shawn Ligocki)
#include "net/instaweb/http/public/cache_url_async_fetcher.h"
#include <cstddef>
#include "net/instaweb/http/public/async_fetch.h"
#include "net/instaweb/http/public/counting_url_async_fetcher.h"
#include "net/instaweb/http/public/http_cache.h"
#include "net/instaweb/http/public/http_cache_failure.h"
#include "net/instaweb/http/public/http_value.h"
#include "net/instaweb/http/public/log_record.h"
#include "net/instaweb/http/public/logging_proto_impl.h"
#include "net/instaweb/http/public/mock_url_fetcher.h"
#include "net/instaweb/http/public/request_context.h"
#include "pagespeed/kernel/base/abstract_mutex.h" // for ScopedMutex
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mem_file_system.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/mock_hasher.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/null_message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/statistics_template.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/http_options.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/thread/mock_scheduler.h"
#include "pagespeed/kernel/thread/queued_worker_pool.h"
#include "pagespeed/kernel/thread/thread_synchronizer.h"
#include "pagespeed/kernel/util/file_system_lock_manager.h"
#include "pagespeed/kernel/util/platform.h"
#include "pagespeed/kernel/util/simple_stats.h"
namespace net_instaweb {
namespace {
const char kStartFetchPrefix[] = "start_fetch";
const char kFetchTriggeredPrefix[] = "fetch_triggered";
const char kDelayedFetchFinishPrefix[] = "delayed_fetch_finish";
class MockFetch : public AsyncFetch {
public:
MockFetch(const RequestContextPtr& ctx,
GoogleString* content,
bool* done,
bool* success,
bool* is_origin_cacheable)
: AsyncFetch(ctx),
content_(content),
done_(done),
success_(success),
is_origin_cacheable_(is_origin_cacheable),
cache_result_valid_(true) {
}
virtual ~MockFetch() {}
virtual void HandleHeadersComplete() {
// Make sure that we've called
// response_headers()->ComputeCaching() before this and that this
// call succeeds. We don't care about the return value in this
// context, we are just trying to ensure that the caching is not
// dirty when this is called.
response_headers()->IsProxyCacheable();
}
virtual bool HandleWrite(const StringPiece& content,
MessageHandler* handler) {
content.AppendToString(content_);
return true;
}
virtual bool HandleFlush(MessageHandler* handler) {
return true;
}
// Fetch complete.
virtual void HandleDone(bool success) {
{
ScopedMutex lock(log_record()->mutex());
*is_origin_cacheable_ = log_record()->logging_info()->
is_original_resource_cacheable();
*success_ = success;
*done_ = true;
}
delete this;
}
virtual bool IsCachedResultValid(const ResponseHeaders& headers) {
// We simply override AsyncFetch and stub this.
return cache_result_valid_;
}
void set_cache_result_valid(bool cache_result_valid) {
cache_result_valid_ = cache_result_valid;
}
private:
GoogleString* content_;
bool* done_;
bool* success_;
bool* is_origin_cacheable_;
bool cache_result_valid_;
DISALLOW_COPY_AND_ASSIGN(MockFetch);
};
class MockCacheUrlAsyncFetcherAsyncOpHooks
: public CacheUrlAsyncFetcher::AsyncOpHooks {
public:
MockCacheUrlAsyncFetcherAsyncOpHooks()
: count_(0) {}
virtual ~MockCacheUrlAsyncFetcherAsyncOpHooks() {
// Check both StartAsyncOp() and FinishAsyncOp() should be called same
// number of times.
EXPECT_EQ(0, count_);
}
virtual void StartAsyncOp() {
++count_;
}
virtual void FinishAsyncOp() {
--count_;
ASSERT_GE(count_, 0);
}
private:
int count_;
DISALLOW_COPY_AND_ASSIGN(MockCacheUrlAsyncFetcherAsyncOpHooks);
};
class DelayedMockUrlFetcher : public MockUrlFetcher {
public:
explicit DelayedMockUrlFetcher(ThreadSynchronizer* sync)
: MockUrlFetcher(),
sync_(sync) {}
virtual void Fetch(const GoogleString& url,
MessageHandler* message_handler,
AsyncFetch* fetch) {
sync_->Signal(kFetchTriggeredPrefix);
sync_->Wait(kStartFetchPrefix);
MockUrlFetcher::Fetch(url, message_handler, fetch);
}
private:
ThreadSynchronizer* sync_;
DISALLOW_COPY_AND_ASSIGN(DelayedMockUrlFetcher);
};
class CacheUrlAsyncFetcherTest : public ::testing::Test {
public:
void TriggerDelayedFetchAndValidate() {
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
thread_synchronizer_->Signal(kDelayedFetchFinishPrefix);
}
protected:
// Helper class for calling Get and Query methods on cache implementations
// that are blocking in nature (e.g. in-memory LRU or blocking file-system).
class Callback : public HTTPCache::Callback {
public:
explicit Callback(const RequestContextPtr& ctx)
: HTTPCache::Callback(ctx) {
Reset();
}
Callback* Reset() {
called_ = false;
result_ = HTTPCache::FindResult();
cache_valid_ = true;
fresh_ = true;
return this;
}
virtual void Done(HTTPCache::FindResult result) {
called_ = true;
result_ = result;
}
virtual bool IsCacheValid(const GoogleString& key,
const ResponseHeaders& headers) {
// For unit testing, we are simply stubbing IsCacheValid.
return cache_valid_;
}
virtual bool IsFresh(const ResponseHeaders& headers) {
// For unit testing, we are simply stubbing IsFresh.
return fresh_;
}
// The detailed Vary testing is handled in response_headers_test.cc and
// is not needed here.
virtual ResponseHeaders::VaryOption RespectVaryOnResources() const {
return ResponseHeaders::kRespectVaryOnResources;
}
bool called_;
HTTPCache::FindResult result_;
bool cache_valid_;
bool fresh_;
};
CacheUrlAsyncFetcherTest()
: lru_cache_(1000),
thread_system_(Platform::CreateThreadSystem()),
statistics_(thread_system_.get()),
timer_(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms),
http_options_(kDefaultHttpOptionsForTests),
cache_url_("http://www.example.com/cacheable.html"),
cache_css_url_("http://www.example.com/cacheable.css"),
cache_https_html_url_("https://www.example.com/cacheable.html"),
cache_https_css_url_("https://www.example.com/cacheable.css"),
nocache_url_("http://www.example.com/non-cacheable.jpg"),
bad_url_("http://www.example.com/bad.url"),
etag_url_("http://www.example.com/etag.jpg"),
last_modified_url_("http://www.example.com/not_modified.jpg"),
etag_and_last_modified_url_("http://www.example.com/double.jpg"),
conditional_last_modified_url_(
"http://www.example.com/cond_not_modified.jpg"),
conditional_etag_url_("http://www.example.com/cond_etag.jpg"),
implicit_cache_url_("http://www.example.com/implicit_cache.jpg"),
vary_url_("http://www.example.com/vary"),
fragment_("www.example.com"),
cache_body_("good"), nocache_body_("bad"), bad_body_("ugly"),
vary_body_("vary"),
etag_("123456790ABCDEF"),
ttl_ms_(Timer::kHourMs),
implicit_cache_ttl_ms_(500 * Timer::kSecondMs),
min_cache_ttl_ms_(-1),
cache_result_valid_(true),
thread_synchronizer_(new ThreadSynchronizer(thread_system_.get())),
mock_fetcher_(thread_synchronizer_.get()),
counting_fetcher_(&mock_fetcher_),
scheduler_(thread_system_.get(), &timer_),
file_system_(thread_system_.get(), &timer_),
lock_manager_(&file_system_, GTestTempDir(), &scheduler_, &handler_) {
HTTPCache::InitStats(&statistics_);
http_cache_.reset(new HTTPCache(&lru_cache_, &timer_, &mock_hasher_,
&statistics_));
cache_fetcher_.reset(
new CacheUrlAsyncFetcher(
&mock_hasher_,
&lock_manager_,
http_cache_.get(),
fragment_,
&mock_async_op_hooks_,
&counting_fetcher_));
// Enable serving of stale content if the fetch fails.
cache_fetcher_->set_serve_stale_if_fetch_error(true);
Variable* fallback_responses_served = statistics_.AddVariable(
"fallback_responses_served");
cache_fetcher_->set_fallback_responses_served(fallback_responses_served);
Variable* num_conditional_refreshes = statistics_.AddVariable(
"num_conditional_refreshes");
cache_fetcher_->set_num_conditional_refreshes(num_conditional_refreshes);
Variable* fallback_responses_served_while_revalidate_ =
statistics_.AddVariable("fallback_responses_served_while_revalidate");
cache_fetcher_->set_fallback_responses_served_while_revalidate(
fallback_responses_served_while_revalidate_);
int64 now_ms = timer_.NowMs();
// Set fetcher result and headers.
ResponseHeaders cache_headers;
SetDefaultHeaders(kContentTypeHtml, &cache_headers);
cache_headers.SetDateAndCaching(now_ms, ttl_ms_);
mock_fetcher_.SetResponse(cache_url_, cache_headers, cache_body_);
mock_fetcher_.SetResponse(cache_https_html_url_, cache_headers,
cache_body_);
cache_headers.Replace(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
mock_fetcher_.SetResponse(cache_css_url_, cache_headers, cache_body_);
mock_fetcher_.SetResponse(cache_https_css_url_, cache_headers,
cache_body_);
ResponseHeaders nocache_headers;
SetDefaultHeaders(kContentTypeJpeg, &nocache_headers);
nocache_headers.SetDate(now_ms);
nocache_headers.Replace(HttpAttributes::kCacheControl, "no-cache");
mock_fetcher_.SetResponse(nocache_url_, nocache_headers, nocache_body_);
ResponseHeaders bad_headers;
bad_headers.set_first_line(1, 1, 404, "Not Found");
bad_headers.SetDate(now_ms);
mock_fetcher_.SetResponse(bad_url_, bad_headers, bad_body_);
ResponseHeaders etag_headers;
etag_headers.CopyFrom(cache_headers);
etag_headers.Add(HttpAttributes::kEtag, etag_);
mock_fetcher_.SetResponse(etag_url_, etag_headers, cache_body_);
ResponseHeaders last_modified_headers;
last_modified_headers.CopyFrom(cache_headers);
last_modified_headers.SetLastModified(
MockTimer::kApr_5_2010_ms - 2 * Timer::kDayMs);
mock_fetcher_.SetResponse(last_modified_url_, last_modified_headers,
cache_body_);
ResponseHeaders etag_and_last_modified_headers;
etag_and_last_modified_headers.CopyFrom(last_modified_headers);
etag_and_last_modified_headers.Add(HttpAttributes::kEtag, etag_);
mock_fetcher_.SetResponse(etag_and_last_modified_url_,
etag_and_last_modified_headers, cache_body_);
mock_fetcher_.SetConditionalResponse(conditional_last_modified_url_,
MockTimer::kApr_5_2010_ms - 2 * Timer::kDayMs, "",
last_modified_headers, cache_body_);
mock_fetcher_.SetConditionalResponse(conditional_etag_url_,
-1 , etag_, etag_headers, cache_body_);
ResponseHeaders implicit_cache_headers;
SetDefaultHeaders(kContentTypeJpeg, &implicit_cache_headers);
implicit_cache_headers.SetDate(now_ms);
implicit_cache_headers.Add(HttpAttributes::kEtag, etag_);
mock_fetcher_.SetConditionalResponse(implicit_cache_url_, -1, etag_,
implicit_cache_headers, cache_body_);
mutable_vary_headers_.SetDateAndCaching(now_ms, ttl_ms_);
mutable_vary_headers_.SetStatusAndReason(HttpStatus::kOK);
mutable_vary_headers_.Add(HttpAttributes::kContentType,
kContentTypePng.mime_type());
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
}
HTTPCache::FindResult FindWithCallback(
const GoogleString& key, HTTPValue* value, ResponseHeaders* headers,
MessageHandler* handler, Callback* callback) {
http_cache_->Find(key, fragment_, handler, callback);
EXPECT_TRUE(callback->called_);
if (callback->result_.status == HTTPCache::kFound) {
value->Link(callback->http_value());
}
headers->CopyFrom(*callback->response_headers());
return callback->result_;
}
HTTPCache::FindResult Find(const GoogleString& key, HTTPValue* value,
ResponseHeaders* headers,
MessageHandler* handler) {
Callback callback(
RequestContext::NewTestRequestContext(thread_system_.get()));
return FindWithCallback(key, value, headers, handler, &callback);
}
HTTPCache::FindResult Find(const GoogleString& key, HTTPValue* value,
ResponseHeaders* headers,
MessageHandler* handler, bool cache_valid) {
Callback callback(
RequestContext::NewTestRequestContext(thread_system_.get()));
callback.cache_valid_ = cache_valid;
return FindWithCallback(key, value, headers, handler, &callback);
}
enum FetchType {
kBackendFetch = 0,
kFallbackFetch = 1,
kServeStaleContentWhileRevalidate = 2,
};
void SetDefaultHeaders(const ContentType& content_type,
ResponseHeaders* header) const {
header->set_major_version(1);
header->set_minor_version(1);
header->SetStatusAndReason(HttpStatus::kOK);
header->Replace(HttpAttributes::kContentType, content_type.mime_type());
}
// cache_result_valid_ == false implies that MockFetch will return false for
// IsCachedResultValid and thus the cache is invalidated.
void FetchAndValidate(const StringPiece& url,
const RequestHeaders& request_headers,
bool success,
HttpStatus::Code code,
const StringPiece& expected_response,
FetchType fetch_type,
bool is_original_resource_cacheable) {
GoogleString fetch_content;
bool fetch_done = false;
bool fetch_success = false;
bool is_cacheable = false;
ResponseHeaders fetch_response_headers;
fetch_response_headers.set_implicit_cache_ttl_ms(implicit_cache_ttl_ms_);
fetch_response_headers.set_min_cache_ttl_ms(min_cache_ttl_ms_);
MockFetch* fetch = new MockFetch(
RequestContextPtr(new RequestContext(
http_options_, thread_system_->NewMutex(), NULL)),
&fetch_content, &fetch_done, &fetch_success, &is_cacheable);
fetch->set_cache_result_valid(cache_result_valid_);
fetch->request_headers()->CopyFrom(request_headers);
fetch->set_response_headers(&fetch_response_headers);
// TODO(sligocki): Make Fetch take a StringPiece.
cache_fetcher_->Fetch(url.as_string(), &handler_, fetch);
// Implementation with LRUCache and MockFetcher should
// call callbacks synchronously.
EXPECT_TRUE(fetch_done);
EXPECT_EQ(success, fetch_success);
if (success) {
EXPECT_TRUE(fetch_response_headers.has_status_code());
EXPECT_EQ(code, fetch_response_headers.status_code());
EXPECT_EQ(min_cache_ttl_ms_,
fetch_response_headers.min_cache_ttl_ms());
EXPECT_EQ(implicit_cache_ttl_ms_,
fetch_response_headers.implicit_cache_ttl_ms());
}
EXPECT_EQ(is_original_resource_cacheable, is_cacheable) << url;
if (fetch_type == kFallbackFetch) {
EXPECT_STREQ(
FallbackSharedAsyncFetch::kStaleWarningHeaderValue,
fetch_response_headers.Lookup1(HttpAttributes::kWarning));
}
if (fetch_type == kServeStaleContentWhileRevalidate) {
EXPECT_TRUE(
fetch_response_headers.HasValue(HttpAttributes::kCacheControl,
"private"));
EXPECT_TRUE(
fetch_response_headers.HasValue(HttpAttributes::kCacheControl,
"max-age=0"));
EXPECT_FALSE(fetch_response_headers.Has(HttpAttributes::kExpires));
}
// TODO(sligocki): Move this out of this function and just check at caller.
EXPECT_STREQ(expected_response, fetch_content);
}
void ClearStats() {
statistics_.Clear();
counting_fetcher_.Clear();
}
void ExpectNoCacheWithOriginalCacheable(
const StringPiece& url, const StringPiece& expected_body) {
RequestHeaders request_headers;
ExpectNoCacheWithRequestHeadersAndIsOriginalCacheable(
url, expected_body, request_headers, true, true);
}
void ExpectNoCache(const StringPiece& url, const StringPiece& expected_body) {
RequestHeaders request_headers;
ExpectNoCacheWithRequestHeadersAndIsOriginalCacheable(
url, expected_body, request_headers, false, true);
}
void ExpectNoCacheWithRequestHeaders(const StringPiece& url,
const StringPiece& expected_body,
const RequestHeaders& request_headers) {
ExpectNoCacheWithRequestHeadersAndIsOriginalCacheable(
url, expected_body, request_headers, false, true);
}
void ExpectNoCacheWithRequestHeadersAndIsOriginalCacheable(
const StringPiece& url,
const StringPiece& expected_body,
const RequestHeaders& request_headers,
bool is_original_cacheable,
bool is_servable_from_cache) {
ClearStats();
FetchAndValidate(url, request_headers, true, HttpStatus::kOK,
expected_body, kBackendFetch, is_original_cacheable);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(is_servable_from_cache ? 1 : 0,
http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... but isn't inserted into the cache, because it's not cacheable.
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(url, request_headers, true, HttpStatus::kOK,
expected_body, kBackendFetch, is_original_cacheable);
// We don't cache it because it has no Cache-Control header set, so the
// same thing should happen as first fetch.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(is_servable_from_cache ? 1 : 0,
http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
void ExpectNoCacheWithMethodAndIsServable(const StringPiece& url,
const StringPiece& expected_body,
RequestHeaders::Method method,
bool servable_from_cache) {
RequestHeaders request_header;
request_header.set_method(method);
ExpectNoCacheWithRequestHeadersAndIsOriginalCacheable(
url, expected_body, request_header, false, servable_from_cache);
}
void ExpectNoCacheWithMethod(const StringPiece& url,
const StringPiece& expected_body,
RequestHeaders::Method method) {
ExpectNoCacheWithMethodAndIsServable(url, expected_body, method, false);
}
void ExpectCache(const StringPiece& url, const StringPiece& expected_body) {
RequestHeaders request_headers;
ExpectCacheWithRequestHeaders(url, expected_body, request_headers);
}
void ExpectCacheWithRequestHeaders(const StringPiece& url,
const StringPiece& expected_body,
const RequestHeaders& request_headers) {
ClearStats();
FetchAndValidate(url, request_headers, true, HttpStatus::kOK,
expected_body, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(url, request_headers, true, HttpStatus::kOK,
expected_body, kBackendFetch, true);
// So second fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
void ExpectServedFromCache(const StringPiece& url,
const StringPiece& expected_body) {
RequestHeaders request_headers;
ExpectServedFromCacheWithRequestHeaders(url, expected_body,
request_headers);
}
void ExpectServedFromCacheWithRequestHeaders(
const StringPiece& url,
const StringPiece& expected_body,
const RequestHeaders& request_headers) {
ClearStats();
FetchAndValidate(url, request_headers, true, HttpStatus::kOK,
expected_body, kBackendFetch, true);
// First fetch hits the cache.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
void DefaultResponseHeaders(const ContentType& content_type, int64 ttl_sec,
ResponseHeaders* headers) {
headers->set_major_version(1);
headers->set_minor_version(1);
headers->SetStatusAndReason(HttpStatus::kOK);
headers->Add(HttpAttributes::kContentType, content_type.mime_type());
const int64 now_ms = timer_.NowMs();
headers->SetDateAndCaching(now_ms, ttl_sec * Timer::kSecondMs);
headers->SetLastModified(now_ms);
headers->ComputeCaching();
}
LRUCache lru_cache_;
scoped_ptr<ThreadSystem> thread_system_;
SimpleStats statistics_;
MockTimer timer_;
MockHasher mock_hasher_;
scoped_ptr<HTTPCache> http_cache_;
HttpOptions http_options_;
scoped_ptr<CacheUrlAsyncFetcher> cache_fetcher_;
NullMessageHandler handler_;
RequestHeaders empty_request_headers_;
const GoogleString cache_url_;
const GoogleString cache_css_url_;
const GoogleString cache_https_html_url_;
const GoogleString cache_https_css_url_;
const GoogleString nocache_url_;
const GoogleString bad_url_;
const GoogleString etag_url_;
const GoogleString last_modified_url_;
const GoogleString etag_and_last_modified_url_;
const GoogleString conditional_last_modified_url_;
const GoogleString conditional_etag_url_;
const GoogleString implicit_cache_url_;
const GoogleString vary_url_;
const GoogleString fragment_;
ResponseHeaders mutable_vary_headers_;
const GoogleString cache_body_;
const GoogleString nocache_body_;
const GoogleString bad_body_;
const GoogleString vary_body_;
const GoogleString etag_;
const int ttl_ms_;
int64 implicit_cache_ttl_ms_;
int64 min_cache_ttl_ms_;
bool cache_result_valid_;
scoped_ptr<ThreadSynchronizer> thread_synchronizer_;
DelayedMockUrlFetcher mock_fetcher_;
CountingUrlAsyncFetcher counting_fetcher_;
MockScheduler scheduler_;
MemFileSystem file_system_;
FileSystemLockManager lock_manager_;
MockCacheUrlAsyncFetcherAsyncOpHooks mock_async_op_hooks_;
};
TEST_F(CacheUrlAsyncFetcherTest, CacheableUrl) {
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Second fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts are needed.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
cache_fetcher_->set_proactively_freshen_user_facing_request(true);
// Advance the time so that cache is about to expire.
timer_.AdvanceMs(ttl_ms_ - 3 * Timer::kMinuteMs);
ClearStats();
// Make sure that if the request that triggers proactive fetching is a HEAD
// we don't mess stuff up.
RequestHeaders head_headers;
head_headers.CopyFrom(empty_request_headers_);
head_headers.set_method(RequestHeaders::kHead);
FetchAndValidate(cache_url_, head_headers, true, HttpStatus::kOK,
"", kBackendFetch, false);
// Fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// Background fetch is triggered to populate the cache with newer value.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// This one is a GET, so it should actually get the bits.
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// No fetch as cache is update with earlier background fetch.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
cache_fetcher_->set_proactively_freshen_user_facing_request(false);
// Induce a fetch failure so we are forced to serve stale data from the cache.
mock_fetcher_.Disable();
timer_.AdvanceMs(2 * ttl_ms_);
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, false,
HttpStatus::kNotFound, "", kBackendFetch, false);
// Cache entry is stale, so we must fetch again. However, the fetch fails.
// Since the fetcher returns a 404 which is not a server error, we don't
// return stale content.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, counting_fetcher_.failure_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
// Re-enable the fetcher.
mock_fetcher_.Enable();
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Cache entry is stale, so we must fetch again. This time the fetch succeeds.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
// Advance the time so that the response put into cache is now stale.
timer_.AdvanceMs(2 * ttl_ms_);
ClearStats();
ResponseHeaders bad_headers;
bad_headers.set_first_line(1, 1, 500, "Internal Server Error");
bad_headers.SetDate(timer_.NowMs());
mock_fetcher_.SetResponse(cache_url_, bad_headers, bad_body_);
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kFallbackFetch, false);
// Since a fallback is present in cache, we don't serve out the 500 and
// instead serve out the stale response.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(1, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// Disable serving of stale content if the fetch fails.
cache_fetcher_->set_serve_stale_if_fetch_error(false);
FetchAndValidate(cache_url_, empty_request_headers_, true,
HttpStatus::kInternalServerError, bad_body_,
kBackendFetch, false);
// Since serving of stale content is disabled, we serve out the 500 instead
// of the fallback value.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
// Enable serving of stale content if the fetch fails.
cache_fetcher_->set_serve_stale_if_fetch_error(true);
// Clear the LRU cache so that no fallback is available any more.
lru_cache_.Clear();
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true,
HttpStatus::kInternalServerError, bad_body_,
kBackendFetch, false);
// The fallback response is no longer available, hence we serve out the 500.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, ServeStaleContentWhileRevalidate) {
// First css request to warm the cache.
ExpectCache(cache_css_url_, cache_body_);
ClearStats();
cache_fetcher_->set_serve_stale_while_revalidate_threshold_sec(
Timer::kDayMs * Timer::kSecondMs);
// Advance the time so that values in cache is expired.
timer_.AdvanceMs(ttl_ms_ + Timer::kHourMs);
ClearStats();
FetchAndValidate(cache_css_url_, empty_request_headers_, true,
HttpStatus::kOK, cache_body_,
kServeStaleContentWhileRevalidate, true);
// Fetch hits initial cache lookup ...
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// Background fetch is triggered to populate the cache with newer value.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
// Stale content is served.
EXPECT_EQ(1,
cache_fetcher_
->fallback_responses_served_while_revalidate()->Get());
ClearStats();
FetchAndValidate(cache_css_url_, empty_request_headers_, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// No fetch as cache is update with earlier background fetch.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
EXPECT_EQ(0,
cache_fetcher_
->fallback_responses_served_while_revalidate()->Get());
// First html request to warm the cache.
ExpectCache(cache_url_, cache_body_);
ClearStats();
// Advance the time so that values in cache is expired.
timer_.AdvanceMs(ttl_ms_ + Timer::kHourMs);
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Fetch hits initial cache lookup ...
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// Fetch is triggered to populate the cache with newer value.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
// Stale content is not served for html requests.
EXPECT_EQ(0,
cache_fetcher_
->fallback_responses_served_while_revalidate()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CachingWithHttpsHtmlCachingEnabled) {
// With caching of html on https enabled, both html and css hosted on https
// get cached.
ClearStats();
ExpectCache(cache_https_html_url_, cache_body_);
ClearStats();
ExpectServedFromCache(cache_https_html_url_, cache_body_);
ClearStats();
ExpectCache(cache_https_css_url_, cache_body_);
ClearStats();
ExpectServedFromCache(cache_https_css_url_, cache_body_);
}
TEST_F(CacheUrlAsyncFetcherTest, CachingWithHttpsHtmlCachingDisabled) {
// With caching of html on https enabled, html hosted on https does not get
// cached, while css does get cached.
http_cache_->set_disable_html_caching_on_https(true);
ClearStats();
ExpectNoCacheWithOriginalCacheable(cache_https_html_url_, cache_body_);
ClearStats();
ExpectCache(cache_https_css_url_, cache_body_);
ClearStats();
ExpectServedFromCache(cache_https_css_url_, cache_body_);
}
TEST_F(CacheUrlAsyncFetcherTest, CacheableUrlCacheInvalidation) {
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Second fetch hits initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts are needed.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
// Invalidate cache and fetch.
cache_result_valid_ = false;
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Cache entry is invalid, so we must fetch again.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, NonCacheableUrl) {
ClearStats();
FetchAndValidate(nocache_url_, empty_request_headers_, true, HttpStatus::kOK,
nocache_body_, kBackendFetch, false);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and we don't insert into cache because it's not cacheable.
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
// Same thing happens second time.
ClearStats();
FetchAndValidate(nocache_url_, empty_request_headers_, true, HttpStatus::kOK,
nocache_body_, kBackendFetch, false);
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CheckEtags) {
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(etag_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(etag_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Second fetch without the If-None-Match in the request hits initial cache
// lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts are needed.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Add(HttpAttributes::kIfNoneMatch, etag_);
FetchAndValidate(etag_url_, request_headers, true, HttpStatus::kNotModified,
"", kBackendFetch, true);
// The Etag matches and a 304 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Replace(HttpAttributes::kIfNoneMatch, "mismatch");
FetchAndValidate(etag_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// The Etag does not match and a 200 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CheckLastModified) {
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(last_modified_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(last_modified_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Second fetch without the If-Modified-Since in the request hits initial
// cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts are needed.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Add(HttpAttributes::kIfModifiedSince,
"Sat, 03 Apr 2010 18:51:26 GMT");
FetchAndValidate(last_modified_url_, request_headers, true,
HttpStatus::kNotModified, "", kBackendFetch, true);
// The last modified timestamp matches and a 304 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Replace(HttpAttributes::kIfModifiedSince,
"Sat, 02 Apr 2010 18:51:26 GMT");
FetchAndValidate(last_modified_url_, request_headers, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// The last modified timestamp does not match and a 200 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CheckEtagAndLastModified) {
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
ClearStats();
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Second fetch without the If-Modified-Since in the request hits initial
// cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// ... and no extra fetches or inserts are needed.
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Add(HttpAttributes::kIfModifiedSince,
"Sat, 03 Apr 2010 18:51:26 GMT");
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kNotModified, "", kBackendFetch, true);
// The last modified timestamp matches and a 304 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Add(HttpAttributes::kIfNoneMatch, "mismatch");
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Even though the last modified timestamp matches, the Etag doesn't match
// and a 200 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
ClearStats();
request_headers.RemoveAll(HttpAttributes::kIfNoneMatch);
request_headers.Replace(HttpAttributes::kIfModifiedSince,
"Sat, 02 Apr 2010 18:51:26 GMT");
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// The last modified timestamp does not match and a 200 is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
ClearStats();
request_headers.Add(HttpAttributes::kIfNoneMatch, etag_);
FetchAndValidate(etag_and_last_modified_url_, request_headers, true,
HttpStatus::kNotModified, "", kBackendFetch, true);
// The etag matches even though the last modified timestamp doesn't and a 304
// is served out.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CheckLastModifiedBased304Freshening) {
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
ClearStats();
// Advance past expiration.
timer_.AdvanceMs(2 * ttl_ms_);
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Second fetch without the If-Modified-Since in the request finds the expired
// entry in cache, uses the Last-Modified header stored there in the outgoing
// fetch ...
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... receives a 304 from the backend and updates the cache.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(1, cache_fetcher_->num_conditional_refreshes()->Get());
ClearStats();
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
ClearStats();
// Advance past expiration.
timer_.AdvanceMs(2 * ttl_ms_);
// Send a request which already has an If-Modified-Since which is different
// from the one in the cache and the fetcher.
request_headers.Replace(HttpAttributes::kIfModifiedSince,
"Sat, 02 Apr 2010 18:51:26 GMT");
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// The last modified timestamp does not match and a 200 is served out.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, CheckEtagBased304Freshening) {
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(conditional_etag_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
ClearStats();
// Advance past expiration.
timer_.AdvanceMs(2 * ttl_ms_);
FetchAndValidate(conditional_etag_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// Second fetch without the If-None-Match in the request finds the expired
// entry in cache, uses the Etag header stored there in the outgoing fetch ...
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... receives a 304 from the backend and updates the cache.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(1, cache_fetcher_->num_conditional_refreshes()->Get());
ClearStats();
// Advance past expiration.
timer_.AdvanceMs(2 * ttl_ms_);
// Send a request which already has an If-None-Match which is different
// from the one in the cache and the fetcher.
request_headers.Replace(HttpAttributes::kIfNoneMatch, "blah");
FetchAndValidate(conditional_etag_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
// The last modified timestamp does not match and a 200 is served out.
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, Check304FresheningModifiedImplicitCacheTtl) {
// First fetch misses initial cache lookup. The fetch succeeds and the result
// is inserted in cache.
RequestHeaders request_headers;
ClearStats();
FetchAndValidate(implicit_cache_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
ResponseHeaders response_headers;
NullMessageHandler message_handler;
HTTPValue value;
StringPiece contents;
ConstStringStarVector values;
// Lookup the url in the cache and confirm the implicit cache ttl and the
// max-age values are as expected.
HTTPCache::FindResult found = Find(implicit_cache_url_, &value,
&response_headers, &message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=500"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
// Second fetch misses the cache. A conditional fetch is done and the result
// is inserted in the cache again. Implicit cache ttl is still the same, and
// hence the max-age is the same as well.
ClearStats();
// Advancing by implicit_cache_ttl_ms_.
timer_.AdvanceMs(implicit_cache_ttl_ms_);
FetchAndValidate(implicit_cache_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(1, cache_fetcher_->num_conditional_refreshes()->Get());
// Lookup the url in the cache and confirm the implicit cache ttl and max-age
// values have not changed.
found = Find(implicit_cache_url_, &value, &response_headers,
&message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=500"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
// Change the value of the implicit cache ttl to some other value.
// Confirm that the new response after the conditional fetch is now
// inserted in the cache with the new implicit cache ttl and max-age.
implicit_cache_ttl_ms_ = 700 * Timer::kSecondMs;
ClearStats();
// Advancing by implicit_cache_ttl_ms_.
timer_.AdvanceMs(implicit_cache_ttl_ms_);
FetchAndValidate(implicit_cache_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(1, cache_fetcher_->num_conditional_refreshes()->Get());
found = Find(implicit_cache_url_, &value, &response_headers,
&message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=700"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
}
TEST_F(CacheUrlAsyncFetcherTest, Check304FresheningMinCacheTtl) {
// First fetch misses initial cache lookup. The fetch succeeds and the result
// is inserted in cache.
RequestHeaders request_headers;
ClearStats();
min_cache_ttl_ms_ = 2 * ttl_ms_;
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(4, counting_fetcher_.byte_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->num_conditional_refreshes()->Get());
ResponseHeaders response_headers;
NullMessageHandler message_handler;
HTTPValue value;
StringPiece contents;
ConstStringStarVector values;
// Lookup the url in the cache and confirm the min cache ttl and the
// max-age values are as expected.
HTTPCache::FindResult found = Find(conditional_last_modified_url_, &value,
&response_headers, &message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=7200"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
ClearStats();
// Advancing by min_cache_ttl_ms_.
timer_.AdvanceMs(min_cache_ttl_ms_);
// Second fetch misses the cache. A conditional fetch is done and the result
// is inserted in the cache again. Min cache ttl is still the same, and
// hence the max-age is updated this time on conditional fetch.
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
// Lookup the url in the cache and confirm the min cache ttl and max-age
// values have not changed.
found = Find(conditional_last_modified_url_, &value, &response_headers,
&message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=7200"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
// Change the value of the min cache ttl to some other value.
// Confirm that the new response after the conditional fetch is now
// inserted in the cache with the new min cache ttl and max-age.
min_cache_ttl_ms_ = 3 * ttl_ms_;
ClearStats();
// Advancing by min_cache_ttl_ms_.
timer_.AdvanceMs(min_cache_ttl_ms_);
FetchAndValidate(conditional_last_modified_url_, request_headers, true,
HttpStatus::kOK, cache_body_, kBackendFetch, true);
EXPECT_EQ(1, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
found = Find(conditional_last_modified_url_, &value, &response_headers,
&message_handler);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kFound, kFetchStatusOK), found);
EXPECT_TRUE(response_headers.headers_complete());
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("max-age=10800"), *(values[0]));
EXPECT_EQ(cache_body_, contents);
}
TEST_F(CacheUrlAsyncFetcherTest, FetchFailedNoIgnore) {
cache_fetcher_->set_ignore_recent_fetch_failed(false);
ClearStats();
FetchAndValidate(bad_url_, empty_request_headers_, true,
HttpStatus::kNotFound, bad_body_, kBackendFetch, false);
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
http_cache_->RememberFailure(bad_url_, fragment_, kFetchStatusOtherError,
&handler_);
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
// bad_url_, is not fetched the second time.
ClearStats();
FetchAndValidate(bad_url_, empty_request_headers_, false,
HttpStatus::kNotFound, "", kBackendFetch, true);
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, FetchFailedIgnore) {
cache_fetcher_->set_ignore_recent_fetch_failed(true);
ClearStats();
FetchAndValidate(bad_url_, empty_request_headers_, true,
HttpStatus::kNotFound, bad_body_, kBackendFetch, false);
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
http_cache_->RememberFailure(bad_url_, fragment_, kFetchStatusOtherError,
&handler_);
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
// bad_url_, is fetched again the second time.
ClearStats();
FetchAndValidate(bad_url_, empty_request_headers_, true,
HttpStatus::kNotFound, bad_body_, kBackendFetch, false);
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, NoCacheEmpty) {
const char url[] = "http://www.example.com/empty.html";
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
int ttl_ms = 5 * Timer::kMinuteMs;
response_headers.SetDateAndCaching(timer_.NowMs(), ttl_ms);
GoogleString empty_contents = "";
mock_fetcher_.SetResponse(url, response_headers, empty_contents);
FetchAndValidate(url, empty_request_headers_, true, HttpStatus::kOK,
empty_contents, kBackendFetch, false);
GoogleString non_empty_contents = "foobar";
mock_fetcher_.SetResponse(url, response_headers, non_empty_contents);
// cache_url_fetcher did not remember the empty contents.
FetchAndValidate(url, empty_request_headers_, true, HttpStatus::kOK,
non_empty_contents, kBackendFetch, true);
}
TEST_F(CacheUrlAsyncFetcherTest, CacheNonEmpty) {
// Companion test to NoCacheEmpty to make sure we are caching non-empty
// through the same flow.
const char url[] = "http://www.example.com/non_empty.html";
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
int ttl_ms = 5 * Timer::kMinuteMs;
response_headers.SetDateAndCaching(timer_.NowMs(), ttl_ms);
GoogleString original_contents = "foo";
mock_fetcher_.SetResponse(url, response_headers, original_contents);
FetchAndValidate(url, empty_request_headers_, true, HttpStatus::kOK,
original_contents, kBackendFetch, true);
GoogleString new_contents = "foobar";
mock_fetcher_.SetResponse(url, response_headers, new_contents);
// cache_url_fetcher did remember the original content.
FetchAndValidate(url, empty_request_headers_, true, HttpStatus::kOK,
original_contents, kBackendFetch, true);
}
TEST_F(CacheUrlAsyncFetcherTest, NoCacheHtmlOnEmptyHeader) {
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
response_headers.SetDate(timer_.NowMs());
response_headers.RemoveAll(HttpAttributes::kCacheControl);
const char url[] = "http://www.example.com/foo.html";
mock_fetcher_.SetResponse(url, response_headers, "foo");
ExpectNoCache(url, "foo");
}
TEST_F(CacheUrlAsyncFetcherTest, DoCacheHtmlOnEmptyHeader) {
// When an option is set. We do cache things with no caching headers.
cache_fetcher_->set_default_cache_html(true);
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
response_headers.SetDate(timer_.NowMs());
response_headers.RemoveAll(HttpAttributes::kCacheControl);
const char url[] = "http://www.example.com/foo.html";
mock_fetcher_.SetResponse(url, response_headers, "foo");
ExpectCache(url, "foo");
}
// Even when set_default_cache_html(true), we still don't cache responses
// with Set-Cookie headers.
TEST_F(CacheUrlAsyncFetcherTest, NoCacheSetCookie) {
cache_fetcher_->set_default_cache_html(true);
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
response_headers.SetDate(timer_.NowMs());
response_headers.RemoveAll(HttpAttributes::kCacheControl);
response_headers.Add(HttpAttributes::kSetCookie, "foo=bar");
const char url[] = "http://www.example.com/foo.html";
mock_fetcher_.SetResponse(url, response_headers, "foo");
ExpectNoCache(url, "foo");
}
TEST_F(CacheUrlAsyncFetcherTest, CachePublicSansTtl) {
// TODO(sligocki): Should this work without default_cache_html == true?
cache_fetcher_->set_default_cache_html(true);
ResponseHeaders response_headers;
SetDefaultHeaders(kContentTypeHtml, &response_headers);
response_headers.SetDate(timer_.NowMs());
response_headers.Replace(HttpAttributes::kCacheControl, "public");
const char url[] = "http://www.example.com/foo.html";
mock_fetcher_.SetResponse(url, response_headers, "foo");
ExpectCache(url, "foo");
}
TEST_F(CacheUrlAsyncFetcherTest, CacheVaryForNonHtml) {
// The content type here is image/png.
RequestHeaders request_headers_with_cookies;
request_headers_with_cookies.Add(HttpAttributes::kCookie, "c");
// Respect Vary is disabled. Vary: Accept-Encoding is cached.
mutable_vary_headers_.Add(HttpAttributes::kVary, "Accept-Encoding");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Accept-Encoding is cached.
cache_fetcher_->set_respect_vary(true);
http_options_.respect_vary = true;
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Accept-Encoding,User-Agent is not cached.
mutable_vary_headers_.Add(HttpAttributes::kVary, "User-Agent");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectNoCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is disabled. Vary: Accept-Encoding,User-Agent is cached.
cache_fetcher_->set_respect_vary(false);
http_options_.respect_vary = false;
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is disabled, but we still respect Vary: Cookie for
// non-html, whether or not the request has cookies.
mutable_vary_headers_.Replace(HttpAttributes::kVary, "Cookie");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectNoCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Cookie is not cached.
cache_fetcher_->set_respect_vary(true);
http_options_.respect_vary = true;
ExpectNoCache(vary_url_, vary_body_);
// Without clearing the cache send a request with cookies in the request. This
// is not served from cache.
ExpectNoCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Cookie is not cached.
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
lru_cache_.Clear();
// Respect Vary is disabled, but Vary: Cookie is still not cached.
cache_fetcher_->set_respect_vary(false);
http_options_.respect_vary = false;
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
// Without clearing the cache, change respect vary to true. We should not
// fetch the response that was inserted into the cache above.
cache_fetcher_->set_respect_vary(true);
http_options_.respect_vary = true;
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
lru_cache_.Clear();
}
TEST_F(CacheUrlAsyncFetcherTest, CacheVaryForHtml) {
// The content type here is text/html. Unlike PNG and other resources, we
// always respect Vary:Cookie on HTML even if RespectVary is off.
mutable_vary_headers_.Replace(HttpAttributes::kContentType,
kContentTypeHtml.mime_type());
RequestHeaders request_headers_with_cookies;
request_headers_with_cookies.Add(HttpAttributes::kCookie, "c");
// Respect Vary is disabled. Vary: Accept-Encoding is cached.
mutable_vary_headers_.Add(HttpAttributes::kVary, "Accept-Encoding");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Accept-Encoding is cached.
cache_fetcher_->set_respect_vary(true);
http_options_.respect_vary = true;
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Accept-Encoding,User-Agent is not cached.
mutable_vary_headers_.Add(HttpAttributes::kVary, "User-Agent");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectNoCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is disabled. Vary: Accept-Encoding,User-Agent is not cached.
cache_fetcher_->set_respect_vary(false);
http_options_.respect_vary = false;
ExpectNoCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is disabled. Vary: Cookie is cached. Note that the request has
// no cookies.
mutable_vary_headers_.Replace(HttpAttributes::kVary, "Cookie");
mock_fetcher_.SetResponse(vary_url_, mutable_vary_headers_, vary_body_);
ExpectCache(vary_url_, vary_body_);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Cookie is cached. Note that the request has
// no cookies.
cache_fetcher_->set_respect_vary(true);
http_options_.respect_vary = true;
ExpectCache(vary_url_, vary_body_);
// Without clearing the cache send a request with cookies in the request. This
// is not served from cache.
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
lru_cache_.Clear();
// Respect Vary is enabled. Vary: Cookie is not cached since the request
// has cookies set.
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
lru_cache_.Clear();
// Respect Vary is disabled. Vary: Cookie is not cached since the request has
// cookies set.
cache_fetcher_->set_respect_vary(false);
http_options_.respect_vary = false;
ExpectNoCacheWithRequestHeaders(vary_url_, vary_body_,
request_headers_with_cookies);
lru_cache_.Clear();
}
TEST_F(CacheUrlAsyncFetcherTest, HeadServedFromCache) {
ClearStats();
// Issue a HEAD request.
RequestHeaders head_request;
head_request.set_method(RequestHeaders::kHead);
FetchAndValidate(cache_url_, head_request, true, HttpStatus::kOK,
"", kBackendFetch, false);
// First fetch misses initial cache lookup ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... but should not be inserted into the cache ...
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// Issue another HEAD request.
FetchAndValidate(cache_url_, head_request, true, HttpStatus::kOK,
"", kBackendFetch, false);
// Second fetch should miss again ...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// Now issue a GET.
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
// Should miss the cache.
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(0, http_cache_->cache_hits()->Get());
EXPECT_EQ(1, http_cache_->cache_misses()->Get());
// ... succeeds at fetch ...
EXPECT_EQ(1, counting_fetcher_.fetch_count());
// ... and inserts result into cache.
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// Issue the HEAD request again, this time it should be served from cache.
FetchAndValidate(cache_url_, head_request, true, HttpStatus::kOK,
"", kBackendFetch, false);
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
ClearStats();
// Replay the GET and ensure the HEAD request has not affected its cache
// value.
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(1, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
EXPECT_EQ(0, counting_fetcher_.fetch_count());
EXPECT_EQ(0, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
}
TEST_F(CacheUrlAsyncFetcherTest, OPTIONS) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kOptions);
}
TEST_F(CacheUrlAsyncFetcherTest, GET) {
// This is already tested above, but leave in for completeness.
RequestHeaders request_header;
request_header.set_method(RequestHeaders::kGet);
ExpectCacheWithRequestHeaders(cache_url_, cache_body_, request_header);
}
TEST_F(CacheUrlAsyncFetcherTest, HEAD) {
ExpectNoCacheWithMethodAndIsServable(
cache_url_, "", RequestHeaders::kHead, true);
}
TEST_F(CacheUrlAsyncFetcherTest, POST) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kPost);
}
TEST_F(CacheUrlAsyncFetcherTest, PUT) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kPut);
}
TEST_F(CacheUrlAsyncFetcherTest, DELETE) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kDelete);
}
TEST_F(CacheUrlAsyncFetcherTest, TRACE) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kTrace);
}
TEST_F(CacheUrlAsyncFetcherTest, CONNECT) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kConnect);
}
TEST_F(CacheUrlAsyncFetcherTest, PATCH) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kPatch);
}
TEST_F(CacheUrlAsyncFetcherTest, ERROR) {
ExpectNoCacheWithMethod(cache_url_, cache_body_, RequestHeaders::kError);
}
// Ensure that non-cacheable requests get kNotInCacheStatus set when there
// is no backup fetcher.
TEST_F(CacheUrlAsyncFetcherTest, NotInCache) {
CacheUrlAsyncFetcher fetcher(
&mock_hasher_,
&lock_manager_,
http_cache_.get(),
fragment_,
&mock_async_op_hooks_,
NULL);
StringAsyncFetch fetch(
RequestContext::NewTestRequestContext(thread_system_.get()));
// Note: nocache_url_ is not even in the cache.
fetcher.Fetch(nocache_url_, &handler_, &fetch);
EXPECT_TRUE(fetch.done());
EXPECT_FALSE(fetch.success());
EXPECT_EQ(CacheUrlAsyncFetcher::kNotInCacheStatus,
fetch.response_headers()->status_code());
}
// Ensure that non-GET requests get kNotInCacheStatus set when there is no
// backup fetcher.
TEST_F(CacheUrlAsyncFetcherTest, NotInCachePost) {
CacheUrlAsyncFetcher fetcher(
&mock_hasher_,
&lock_manager_,
http_cache_.get(),
fragment_,
&mock_async_op_hooks_,
NULL);
StringAsyncFetch fetch(
RequestContext::NewTestRequestContext(thread_system_.get()));
fetch.request_headers()->set_method(RequestHeaders::kPost);
// Put result in cache.
ResponseHeaders headers;
DefaultResponseHeaders(kContentTypeCss, 100, &headers);
http_cache_->Put(cache_url_, fragment_,
fetch.request_headers()->GetProperties(),
ResponseHeaders::kRespectVaryOnResources,
&headers, ".a { color: red; }", &handler_);
fetcher.Fetch(cache_url_, &handler_, &fetch);
EXPECT_TRUE(fetch.done());
EXPECT_FALSE(fetch.success());
EXPECT_EQ(CacheUrlAsyncFetcher::kNotInCacheStatus,
fetch.response_headers()->status_code());
}
TEST_F(CacheUrlAsyncFetcherTest, TestParallelBackgroundFreshenCalls) {
ClearStats();
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
cache_fetcher_->set_proactively_freshen_user_facing_request(true);
// Advance the time so that cache is about to expire.
timer_.AdvanceMs(ttl_ms_ - 3 * Timer::kMinuteMs);
QueuedWorkerPool pool(1, "test", thread_system_.get());
QueuedWorkerPool::Sequence* sequence = pool.NewSequence();
ClearStats();
thread_synchronizer_->EnableForPrefix(kFetchTriggeredPrefix);
thread_synchronizer_->EnableForPrefix(kStartFetchPrefix);
thread_synchronizer_->EnableForPrefix(kDelayedFetchFinishPrefix);
// Start a fetch on Thread 1. This fetch will be delayed until another fetch
// finishes on different thread and second fetch fails to acquire lock.
// Order of the sequence:
// 1) Fetch is triggered which issue a background fetch and able to acquire
// lock but this fetch is delayed until another fetch is also triggered.
// 2) Second fetch is triggered and tries to acquire a lock but fails to do
// so.
// 3) After second fetch is finished it signals first fetch.
sequence->Add(
MakeFunction(
static_cast<CacheUrlAsyncFetcherTest*>(this),
&CacheUrlAsyncFetcherTest::TriggerDelayedFetchAndValidate));
thread_synchronizer_->Wait(kFetchTriggeredPrefix);
FetchAndValidate(cache_url_, empty_request_headers_, true, HttpStatus::kOK,
cache_body_, kBackendFetch, true);
thread_synchronizer_->Signal(kStartFetchPrefix);
thread_synchronizer_->Wait(kDelayedFetchFinishPrefix);
// Fetch hits initial cache lookup for the fetches...
EXPECT_EQ(0, http_cache_->cache_expirations()->Get());
EXPECT_EQ(2, http_cache_->cache_hits()->Get());
EXPECT_EQ(0, http_cache_->cache_misses()->Get());
// Background fetch is triggered to populate the cache with newer value. But
// only fetch is able to update the value.
EXPECT_EQ(1, counting_fetcher_.fetch_count());
EXPECT_EQ(1, http_cache_->cache_inserts()->Get());
EXPECT_EQ(0, cache_fetcher_->fallback_responses_served()->Get());
}
} // namespace
} // namespace net_instaweb