| /* |
| * 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 |