blob: 3e32af76173b1612e88309b975708b4a04e65e7d [file] [log] [blame]
/*
* Copyright 2010 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: jmarantz@google.com (Joshua Marantz)
#include "net/instaweb/rewriter/public/cacheable_resource_base.h"
#include "base/logging.h" // for operator<<, etc
#include "net/instaweb/config/rewrite_options_manager.h"
#include "net/instaweb/http/public/async_fetch.h"
#include "net/instaweb/http/public/async_fetch_with_lock.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/http_value_writer.h"
#include "net/instaweb/http/public/url_async_fetcher.h"
#include "net/instaweb/rewriter/cached_result.pb.h"
#include "net/instaweb/rewriter/public/request_properties.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_stats.h"
#include "pagespeed/kernel/base/basictypes.h" // for int64
#include "pagespeed/kernel/base/callback.h"
#include "pagespeed/kernel/base/hasher.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
namespace net_instaweb {
namespace {
const char kHitSuffix[] = "_hit";
const char kRecentFetchFailureSuffix[] = "_recent_fetch_failure";
const char kRecentUncacheableMiss[] = "_recent_uncacheable_miss";
const char kRecentUncacheableFailure[] = "_recent_uncacheable_failure";
const char kMissSuffix[] = "_miss";
} // namespace
// Shared base class for fetch callbacks, used by both LoadAndCallback and
// Freshen.
class CacheableResourceBase::FetchCallbackBase : public AsyncFetchWithLock {
public:
FetchCallbackBase(ServerContext* server_context,
const RewriteOptions* rewrite_options,
const GoogleString& url,
const GoogleString& cache_key,
HTTPValue* fallback_value,
const RequestContextPtr& request_context,
MessageHandler* handler,
RewriteDriver* driver,
CacheableResourceBase* resource)
: AsyncFetchWithLock(server_context->lock_hasher(),
request_context,
url,
cache_key,
server_context->lock_manager(),
handler),
resource_(resource),
server_context_(server_context),
driver_(driver),
rewrite_options_(rewrite_options),
message_handler_(handler),
no_cache_ok_(false),
fetcher_(NULL),
fallback_fetch_(NULL) {
if (fallback_value != NULL) {
fallback_value_.Link(fallback_value);
}
}
virtual ~FetchCallbackBase() {}
// Set this to true if implementing a kLoadEvenIfNotCacheable policy.
void set_no_cache_ok(bool x) { no_cache_ok_ = x; }
protected:
// The two derived classes differ in how they provide the
// fields below. LoadAndCallback updates the resource directly, while
// Freshen does not actually change the resource object.
virtual HTTPValue* http_value() = 0;
virtual HTTPCache* http_cache() = 0;
virtual HTTPValueWriter* http_value_writer() = 0;
// Subclasses will also want to override AsyncFetchWithLock's Finalize
// to get all the cases.
// Overridden from AsyncFetch.
virtual void HandleDone(bool success) {
bool cached = false;
// Do not store the response in cache if we are using the fallback.
if (fallback_fetch_ != NULL && fallback_fetch_->serving_fallback()) {
// Normally AddToCache would classify the failure, but we don't
// use that in case of fallback, so make sure to compute it here.
// Unfortunately, FallbackSharedAsyncFetch has already got
// rid of the actual headers of the physical reply, but luckily it only
// ever does things on 5xx anyway.
// (See FallbackSharedAsyncFetch::HandleHeadersComplete())
resource_->set_fetch_response_status(kFetchStatusOtherError);
success = true;
} else {
cached = AddToCache(success && http_value_writer()->has_buffered());
// Unless the client code explicitly opted into dealing with potentially
// uncacheable content (by passing in kLoadEvenIfNotCacheable to
// LoadAsync) we turn it into a fetch failure so we do not
// end up inadvertently rewriting something that's private or highly
// volatile.
if ((!cached && !no_cache_ok_) || !http_value_writer()->has_buffered()) {
success = false;
}
}
if (http_value()->Empty()) {
// If there have been no writes so far, write an empty string to the
// HTTPValue. Note that this is required since empty writes aren't
// propagated while fetching and we need to write something to the
// HTTPValue so that we can successfully extract empty content from it.
http_value()->Write("", message_handler_);
}
// This will also invoke Finalize() on the subclasses.
AsyncFetchWithLock::HandleDone(success);
}
// Overridden from AsyncFetch.
virtual void HandleHeadersComplete() {
if (fallback_fetch_ != NULL && fallback_fetch_->serving_fallback()) {
response_headers()->ComputeCaching();
}
http_value_writer()->CheckCanCacheElseClear(response_headers());
AsyncFetchWithLock::HandleHeadersComplete();
}
// Overridden from AsyncFetch.
virtual bool HandleWrite(const StringPiece& content,
MessageHandler* handler) {
bool success = http_value_writer()->Write(content, handler);
return success && AsyncFetchWithLock::HandleWrite(content, handler);
}
// Overridden from AsyncFetchWithLock.
virtual void StartFetch(UrlAsyncFetcher* fetcher, MessageHandler* handler) {
fetch_url_ = url();
fetcher_ = fetcher;
if (!request_headers()->Has(HttpAttributes::kReferer)) {
if (IsBackgroundFetch()) {
// Set referer for background fetching, if the referer is missing.
request_headers()->Add(HttpAttributes::kReferer,
driver_->base_url().Spec());
} else if (driver_->request_headers() != NULL) {
const char* referer_str = driver_->request_headers()->Lookup1(
HttpAttributes::kReferer);
if (referer_str != NULL) {
request_headers()->Add(HttpAttributes::kReferer, referer_str);
}
}
}
server_context_->rewrite_options_manager()->PrepareRequest(
rewrite_options_,
request_context(),
&fetch_url_,
request_headers(),
NewCallback(this, &FetchCallbackBase::PrepareRequestDone));
}
void PrepareRequestDone(bool success) {
if (!success) {
// TODO(gee): Will this hang the state machine?
return;
}
AsyncFetch* fetch = this;
if (rewrite_options_->serve_stale_if_fetch_error() &&
!fallback_value_.Empty()) {
// Use a stale value if the fetch from the backend fails.
fallback_fetch_ = new FallbackSharedAsyncFetch(
this, &fallback_value_, message_handler_);
fallback_fetch_->set_fallback_responses_served(
server_context_->rewrite_stats()->fallback_responses_served());
fetch = fallback_fetch_;
}
if (!fallback_value_.Empty()) {
// Use the conditional headers in a stale response in cache while
// triggering the outgoing fetch.
ConditionalSharedAsyncFetch* conditional_fetch =
new ConditionalSharedAsyncFetch(
fetch, &fallback_value_, message_handler_);
conditional_fetch->set_num_conditional_refreshes(
server_context_->rewrite_stats()->num_conditional_refreshes());
fetch = conditional_fetch;
}
resource_->PrepareRequest(fetch->request_context(),
fetch->request_headers());
fetcher_->Fetch(fetch_url_, message_handler_, fetch);
}
private:
// Returns true if the result was successfully cached.
bool AddToCache(bool success) {
ResponseHeaders* headers = response_headers();
HTTPValue* value = http_value();
// Merge in any extra response headers.
headers->UpdateFrom(*extra_response_headers());
resource_->PrepareResponseHeaders(headers);
headers->ComputeCaching();
headers->FixDateHeaders(http_cache()->timer()->NowMs());
if (rewrite_options_->IsCacheTtlOverridden(url())) {
headers->ForceCaching(rewrite_options_->override_caching_ttl_ms());
}
bool cacheable = resource_->IsValidAndCacheableImpl(*headers);
StringPiece contents;
if (!value->ExtractContents(&contents)) {
contents.clear();
}
FetchResponseStatus fetch_status = HttpCacheFailure::ClassifyFailure(
*headers, contents, success, cacheable);
resource_->set_fetch_response_status(fetch_status);
if (fetch_status == kFetchStatusOK) {
value->SetHeaders(headers);
// Note that we could potentially store Vary:Cookie responses
// here, as we will have fetched the resource without cookies.
// But we must be careful in the mod_pagespeed ipro flow,
// where we must avoid storing any resource obtained with a
// Cookie. For now we don't implement this.
http_cache()->Put(resource_->cache_key(), driver_->CacheFragment(),
RequestHeaders::Properties(),
request_context()->options(),
value, message_handler_);
return true;
} else {
http_cache()->RememberFailure(resource_->cache_key(),
driver_->CacheFragment(),
fetch_status,
message_handler_);
return false;
}
}
CacheableResourceBase* resource_; // owned by the callback.
ServerContext* server_context_;
RewriteDriver* driver_;
const RewriteOptions* rewrite_options_;
MessageHandler* message_handler_;
// TODO(jmarantz): consider request_headers. E.g. will we ever
// get different resources depending on user-agent?
HTTPValue fallback_value_;
// If this is true, loading of non-cacheable resources will succeed.
// Used to implement kLoadEvenIfNotCacheable.
bool no_cache_ok_;
// These 2 are set only once we get to StartFetch
UrlAsyncFetcher* fetcher_;
GoogleString fetch_url_;
FallbackSharedAsyncFetch* fallback_fetch_;
DISALLOW_COPY_AND_ASSIGN(FetchCallbackBase);
};
// Writes result into cache. Use this when you do not need to wait for the
// response, you just want it to be asynchronously placed in the HttpCache.
//
// For example, this is used for fetches and refreshes of resources
// discovered while rewriting HTML. Note that this uses the Last-Modified and
// If-None-Match headers of the stale value in cache to conditionally refresh
// the resource.
class CacheableResourceBase::FreshenFetchCallback : public FetchCallbackBase {
public:
FreshenFetchCallback(const GoogleString& url,
const GoogleString& cache_key,
HTTPCache* http_cache,
ServerContext* server_context,
RewriteDriver* rewrite_driver,
const RewriteOptions* rewrite_options,
HTTPValue* fallback_value,
CacheableResourceBase* resource,
Resource::FreshenCallback* callback)
: FetchCallbackBase(server_context,
rewrite_options,
url,
cache_key,
fallback_value,
rewrite_driver->request_context(),
server_context->message_handler(),
rewrite_driver,
resource),
url_(url),
http_cache_(http_cache),
rewrite_driver_(rewrite_driver),
callback_(callback),
http_value_writer_(&http_value_, http_cache_),
resource_(resource),
own_resource_(resource) {
// TODO(morlovich): This is duplicated a few times, clean this up.
response_headers()->set_implicit_cache_ttl_ms(
rewrite_options->implicit_cache_ttl_ms());
response_headers()->set_min_cache_ttl_ms(
rewrite_options->min_cache_ttl_ms());
}
virtual void Finalize(bool lock_failure, bool resource_ok) {
if (callback_ != NULL) {
if (!lock_failure) {
resource_ok &= resource_->UpdateInputInfoForFreshen(
*response_headers(), http_value_, callback_);
}
callback_->Done(lock_failure, resource_ok);
}
rewrite_driver_->DecrementAsyncEventsCount();
// AsyncFetchWithLock::HandleDone (which calls this method)
// will take care of deleting 'this'.
}
virtual HTTPValue* http_value() { return &http_value_; }
virtual HTTPCache* http_cache() { return http_cache_; }
virtual HTTPValueWriter* http_value_writer() { return &http_value_writer_; }
virtual bool ShouldYieldToRedundantFetchInProgress() { return true; }
virtual bool IsBackgroundFetch() const { return true; }
private:
GoogleString url_;
HTTPCache* http_cache_;
RewriteDriver* rewrite_driver_;
Resource::FreshenCallback* callback_;
HTTPValue http_value_;
HTTPValueWriter http_value_writer_;
CacheableResourceBase* resource_;
ResourcePtr own_resource_; // keep alive resource since callback may be NULL
DISALLOW_COPY_AND_ASSIGN(FreshenFetchCallback);
};
// Fetch callback that writes result directly into a resource.
class CacheableResourceBase::LoadFetchCallback
: public FetchCallbackBase {
public:
explicit LoadFetchCallback(Resource::AsyncCallback* callback,
CacheableResourceBase* resource,
const RequestContextPtr& request_context)
: FetchCallbackBase(resource->server_context(),
resource->rewrite_options(),
resource->url(),
resource->cache_key(),
&resource->fallback_value_,
request_context,
resource->server_context()->message_handler(),
resource->rewrite_driver(),
resource),
resource_(resource),
callback_(callback),
http_value_writer_(http_value(), http_cache()),
respect_vary_(resource->respect_vary()) {
set_response_headers(&resource_->response_headers_);
response_headers()->set_implicit_cache_ttl_ms(
resource->rewrite_options()->implicit_cache_ttl_ms());
response_headers()->set_min_cache_ttl_ms(
resource->rewrite_options()->min_cache_ttl_ms());
}
virtual void Finalize(bool lock_failure, bool resource_ok) {
if (!lock_failure && resource_ok) {
// Because we've authorized the Fetcher to directly populate the
// ResponseHeaders in resource_->response_headers_, we must explicitly
// propagate the content-type to the resource_->type_.
resource_->DetermineContentType();
} else {
DCHECK_NE(kFetchStatusNotSet, resource_->fetch_response_status());
// It's possible that the fetcher has read some of the headers into
// our response_headers (perhaps even a 200) before it called Done(false)
// or before we decided inside AddToCache() that we don't want to deal
// with this particular resource. In that case, make sure to clear the
// response_headers() so the various validity bits in Resource are
// accurate.
response_headers()->Clear();
}
Statistics* stats = resource_->server_context()->statistics();
if (resource_ok) {
stats->GetVariable(RewriteStats::kNumResourceFetchSuccesses)->Add(1);
} else {
stats->GetVariable(RewriteStats::kNumResourceFetchFailures)->Add(1);
}
callback_->Done(lock_failure, resource_ok);
// AsyncFetchWithLock will delete 'this' eventually.
}
virtual bool IsBackgroundFetch() const {
return resource_->is_background_fetch();
}
virtual HTTPValue* http_value() { return &resource_->value_; }
virtual HTTPCache* http_cache() {
return resource_->server_context()->http_cache();
}
virtual HTTPValueWriter* http_value_writer() { return &http_value_writer_; }
virtual bool ShouldYieldToRedundantFetchInProgress() { return false; }
private:
CacheableResourceBase* resource_;
Resource::AsyncCallback* callback_;
HTTPValueWriter http_value_writer_;
ResponseHeaders::VaryOption respect_vary_;
DISALLOW_COPY_AND_ASSIGN(LoadFetchCallback);
};
class CacheableResourceBase::LoadHttpCacheCallback
: public OptionsAwareHTTPCacheCallback {
public:
LoadHttpCacheCallback(
const RequestContextPtr& request_context,
NotCacheablePolicy not_cacheable_policy,
AsyncCallback* resource_callback,
CacheableResourceBase* resource);
virtual ~LoadHttpCacheCallback();
virtual void Done(HTTPCache::FindResult find_result);
private:
void LoadAndSaveToCache();
// protected via resource_callback_->resource().
CacheableResourceBase* resource_;
Resource::AsyncCallback* resource_callback_;
Resource::NotCacheablePolicy not_cacheable_policy_;
DISALLOW_COPY_AND_ASSIGN(LoadHttpCacheCallback);
};
CacheableResourceBase::LoadHttpCacheCallback::LoadHttpCacheCallback(
const RequestContextPtr& request_context,
NotCacheablePolicy not_cacheable_policy,
AsyncCallback* resource_callback,
CacheableResourceBase* resource)
: OptionsAwareHTTPCacheCallback(
resource->rewrite_options(), request_context),
resource_(resource),
resource_callback_(resource_callback),
not_cacheable_policy_(not_cacheable_policy) {
}
CacheableResourceBase::LoadHttpCacheCallback::~LoadHttpCacheCallback() {
}
void CacheableResourceBase::LoadHttpCacheCallback::Done(
HTTPCache::FindResult find_result) {
MessageHandler* handler = resource_->message_handler();
// Note, we pass lock_failure==false to the resource callbacks
// when we are taking action based on the cache. We haven't locked,
// but we didn't fail-to-lock. Resource callbacks need to know if
// the lock failed, because they will delete expired cache metadata
// if they have the lock, or if the lock was not needed, but they
// should not delete it if they fail to lock.
switch (find_result.status) {
case HTTPCache::kFound:
resource_->hits_->Add(1);
resource_->Link(http_value(), handler);
resource_->response_headers()->CopyFrom(*response_headers());
resource_->DetermineContentType();
resource_->RefreshIfImminentlyExpiring();
resource_->set_fetch_response_status(
response_headers()->status_code() == HttpStatus::kOK ?
kFetchStatusOK : kFetchStatusOtherError);
resource_callback_->Done(false /* lock_failure */,
true /* resource_ok */);
break;
case HTTPCache::kRecentFailure:
// We may need to retry even if we have a cached failure if:
// a) The user wants uncacheable resources, and that's the failure we
// recorded.
// b) The fetch is user-facing and the "failure" is actually us
// load-shedding in the past.
if ((not_cacheable_policy_ == Resource::kLoadEvenIfNotCacheable &&
(find_result.failure_details == kFetchStatusUncacheable200 ||
find_result.failure_details == kFetchStatusUncacheableError ||
find_result.failure_details == kFetchStatusEmpty)) ||
(!resource_->is_background_fetch() &&
find_result.failure_details == kFetchStatusDropped)) {
resource_->recent_uncacheables_miss_->Add(1);
LoadAndSaveToCache();
} else {
if (find_result.failure_details == kFetchStatusUncacheable200 ||
find_result.failure_details == kFetchStatusUncacheableError) {
resource_->recent_uncacheables_failure_->Add(1);
} else {
resource_->recent_fetch_failures_->Add(1);
}
resource_->set_fetch_response_status(find_result.failure_details);
resource_callback_->Done(false /* lock_failure */,
false /* resource_ok */);
}
break;
case HTTPCache::kNotFound:
resource_->misses_->Add(1);
// If not, load it asynchronously.
// Link the fallback value which can be used if the fetch fails.
resource_->LinkFallbackValue(fallback_http_value());
LoadAndSaveToCache();
break;
}
delete this;
}
void CacheableResourceBase::LoadHttpCacheCallback::LoadAndSaveToCache() {
if (resource_->ShouldSkipBackgroundFetch()) {
// Note that this isn't really a lock failure, but we treat them the same
// way.
resource_callback_->Done(true /* lock_failure */,
false /* resource_ok */);
return;
}
CHECK(resource_callback_ != NULL)
<< "A callback must be supplied, or else it will "
"not be possible to determine when it's safe to delete the resource.";
CHECK(resource_ == resource_callback_->resource().get())
<< "The callback must keep a reference to the resource";
DCHECK(!resource_->loaded()) << "Shouldn't get this far if already loaded.";
LoadFetchCallback* cb =
new LoadFetchCallback(resource_callback_, resource_, request_context());
if (not_cacheable_policy_ == Resource::kLoadEvenIfNotCacheable) {
cb->set_no_cache_ok(true);
}
cb->Start(resource_->rewrite_driver()->async_fetcher());
}
// HTTPCache::Callback which checks if we have a fresh response in the cache.
// Note that we don't really care about what the response in cache is. We just
// check whether it is fresh enough to avoid having to trigger an external
// fetch. This keeps the RewriteDriver alive via the async event count.
class CacheableResourceBase::FreshenHttpCacheCallback
: public OptionsAwareHTTPCacheCallback {
public:
FreshenHttpCacheCallback(const GoogleString& url,
const GoogleString& cache_key,
ServerContext* server_context,
RewriteDriver* driver,
const RewriteOptions* options,
CacheableResourceBase* resource,
Resource::FreshenCallback* callback)
: OptionsAwareHTTPCacheCallback(options, driver->request_context()),
url_(url),
cache_key_(cache_key),
server_context_(server_context),
driver_(driver),
options_(options),
resource_(resource),
own_resource_(resource),
callback_(callback) {}
virtual ~FreshenHttpCacheCallback() {}
virtual void Done(HTTPCache::FindResult find_result) {
if (find_result.status == HTTPCache::kNotFound &&
!resource_->ShouldSkipBackgroundFetch()) {
// Not found in cache. Invoke the fetcher.
FreshenFetchCallback* cb = new FreshenFetchCallback(
url_, cache_key_, server_context_->http_cache(), server_context_,
driver_, options_, fallback_http_value(), resource_, callback_);
cb->Start(driver_->async_fetcher());
} else {
if (callback_ != NULL) {
bool success = (find_result.status == HTTPCache::kFound) &&
resource_->UpdateInputInfoForFreshen(
*response_headers(), *http_value(), callback_);
callback_->Done(true, success);
}
driver_->DecrementAsyncEventsCount();
}
delete this;
}
// Checks if the response is fresh enough. We may have an imminently
// expiring resource in the L1 cache, but a fresh response in the L2 cache and
// regular cache lookups will return the response in the L1.
virtual bool IsFresh(const ResponseHeaders& headers) {
int64 date_ms = headers.date_ms();
int64 expiry_ms = headers.CacheExpirationTimeMs();
return !ResponseHeaders::IsImminentlyExpiring(
date_ms, expiry_ms, server_context_->timer()->NowMs(),
options_->ComputeHttpOptions());
}
private:
GoogleString url_;
GoogleString cache_key_;
ServerContext* server_context_;
RewriteDriver* driver_;
const RewriteOptions* options_;
CacheableResourceBase* resource_;
// Note that we need to own the resource since callback_ might be NULL.
ResourcePtr own_resource_;
Resource::FreshenCallback* callback_;
DISALLOW_COPY_AND_ASSIGN(FreshenHttpCacheCallback);
};
CacheableResourceBase::CacheableResourceBase(
StringPiece stat_prefix,
StringPiece url,
StringPiece cache_key,
const ContentType* type,
RewriteDriver* rewrite_driver)
: Resource(rewrite_driver, type),
url_(url.data(), url.size()),
cache_key_(cache_key.data(), cache_key.size()),
rewrite_driver_(rewrite_driver) {
set_enable_cache_purge(rewrite_options()->enable_cache_purge());
set_respect_vary(ResponseHeaders::GetVaryOption(
rewrite_options()->respect_vary()));
set_proactive_resource_freshening(
rewrite_options()->proactive_resource_freshening());
Statistics* stats = server_context()->statistics();
hits_ = stats->GetVariable(StrCat(stat_prefix, kHitSuffix));
recent_fetch_failures_ =
stats->GetVariable(StrCat(stat_prefix, kRecentFetchFailureSuffix));
recent_uncacheables_miss_ =
stats->GetVariable(StrCat(stat_prefix, kRecentUncacheableMiss));
recent_uncacheables_failure_ =
stats->GetVariable(StrCat(stat_prefix, kRecentUncacheableFailure));
misses_ = stats->GetVariable(StrCat(stat_prefix, kMissSuffix));
}
CacheableResourceBase::~CacheableResourceBase() {
}
bool CacheableResourceBase::IsValidAndCacheable() const {
return IsValidAndCacheableImpl(*response_headers());
}
bool CacheableResourceBase::IsValidAndCacheableImpl(
const ResponseHeaders& headers) const {
if (headers.status_code() != HttpStatus::kOK) {
return false;
}
// Conservatively assume that the request has cookies, since the site may
// want to serve different content based on the cookie. If we consider the
// response to be cacheable here, we will serve the optimized version
// without contacting the origin which would be against the webmaster's
// intent. We also don't have cookies available at lookup time, so we
// cannot try to use this response only when the request doesn't have a
// cookie.
RequestHeaders::Properties req_properties;
bool cacheable = headers.IsProxyCacheable(req_properties, respect_vary(),
ResponseHeaders::kNoValidator);
// If we are setting a TTL for HTML, we cannot rewrite any resource
// with a shorter TTL.
cacheable &= (headers.cache_ttl_ms() >=
rewrite_options()->min_resource_cache_time_to_rewrite_ms());
if (!cacheable && !http_cache()->force_caching()) {
return false;
}
return !http_cache()->IsExpired(headers);
}
void CacheableResourceBase::InitStats(StringPiece stat_prefix,
Statistics* stats) {
stats->AddVariable(StrCat(stat_prefix, kHitSuffix));
stats->AddVariable(StrCat(stat_prefix, kRecentFetchFailureSuffix));
stats->AddVariable(StrCat(stat_prefix, kRecentUncacheableMiss));
stats->AddVariable(StrCat(stat_prefix, kRecentUncacheableFailure));
stats->AddVariable(StrCat(stat_prefix, kMissSuffix));
}
void CacheableResourceBase::RefreshIfImminentlyExpiring() {
if (!http_cache()->force_caching()) {
const ResponseHeaders* headers = response_headers();
int64 start_date_ms = headers->date_ms();
int64 expire_ms = headers->CacheExpirationTimeMs();
if (ResponseHeaders::IsImminentlyExpiring(
start_date_ms, expire_ms, timer()->NowMs(),
rewrite_options()->ComputeHttpOptions())) {
Freshen(NULL, server_context()->message_handler());
}
}
}
void CacheableResourceBase::LoadAndCallback(
NotCacheablePolicy not_cacheable_policy,
const RequestContextPtr& request_context,
AsyncCallback* callback) {
LoadHttpCacheCallback* cache_callback =
new LoadHttpCacheCallback(request_context, not_cacheable_policy,
callback, this);
cache_callback->set_is_background(is_background_fetch());
http_cache()->Find(cache_key(), rewrite_driver()->CacheFragment(),
message_handler(), cache_callback);
}
void CacheableResourceBase::Freshen(Resource::FreshenCallback* callback,
MessageHandler* handler) {
// TODO(jmarantz): use if-modified-since
// For now this is much like Load(), except we do not
// touch our value, but just the cache
HTTPCache* http_cache = server_context()->http_cache();
// Ensure that the rewrite driver is alive until the freshen is completed.
rewrite_driver_->IncrementAsyncEventsCount();
FreshenHttpCacheCallback* freshen_callback = new FreshenHttpCacheCallback(
url(), cache_key(), server_context(), rewrite_driver_, rewrite_options(),
this, callback);
// Lookup the cache before doing the fetch since the response may have already
// been fetched elsewhere.
http_cache->Find(cache_key(), rewrite_driver()->CacheFragment(),
handler, freshen_callback);
}
bool CacheableResourceBase::UpdateInputInfoForFreshen(
const ResponseHeaders& headers,
const HTTPValue& value,
Resource::FreshenCallback* callback) {
InputInfo* input_info = callback->input_info();
if (input_info != NULL && input_info->has_input_content_hash() &&
IsValidAndCacheableImpl(headers)) {
StringPiece content;
if (value.ExtractContents(&content)) {
GoogleString new_hash =
server_context()->contents_hasher()->Hash(content);
// TODO(nikhilmadan): Consider using the Etag / Last-Modified header to
// validate if the resource has changed instead of computing the hash.
if (new_hash == input_info->input_content_hash()) {
FillInPartitionInputInfoFromResponseHeaders(headers, input_info);
return true;
}
}
}
return false;
}
const RewriteOptions* CacheableResourceBase::rewrite_options() const {
return rewrite_driver_->options();
}
void CacheableResourceBase::PrepareRequest(
const RequestContextPtr& request_context, RequestHeaders* headers) {
}
void CacheableResourceBase::PrepareResponseHeaders(ResponseHeaders* headers) {
}
bool CacheableResourceBase::ShouldSkipBackgroundFetch() const {
return is_background_fetch() &&
rewrite_options()->disable_background_fetches_for_bots() &&
rewrite_driver()->request_properties()->IsBot();
}
} // namespace net_instaweb