| /* |
| * 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 "base/logging.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_value.h" |
| #include "net/instaweb/http/public/http_value_writer.h" |
| #include "net/instaweb/http/public/log_record.h" // for AbstractLogRecord |
| #include "net/instaweb/http/public/logging_proto.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/url_async_fetcher.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/timer.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" |
| |
| |
| namespace net_instaweb { |
| |
| // HTTP 501 Not Implemented: The server either does not recognize the |
| // request method, or it lacks the ability to fulfill the request. |
| const int CacheUrlAsyncFetcher::kNotInCacheStatus = HttpStatus::kNotImplemented; |
| |
| namespace { |
| |
| class CachePutFetch : public SharedAsyncFetch { |
| public: |
| CachePutFetch(const GoogleString& url, const GoogleString& fragment, |
| AsyncFetch* base_fetch, |
| ResponseHeaders::VaryOption respect_vary, |
| bool default_cache_html, HTTPCache* cache, |
| Histogram* backend_first_byte_latency, MessageHandler* handler) |
| : SharedAsyncFetch(base_fetch), |
| url_(url), |
| fragment_(fragment), |
| http_options_(request_context()->options()), |
| respect_vary_(respect_vary), |
| default_cache_html_(default_cache_html), |
| cache_(cache), |
| backend_first_byte_latency_(backend_first_byte_latency), |
| handler_(handler), |
| cacheable_(false), |
| cache_value_writer_(&cache_value_, cache_), |
| saved_headers_(http_options_), |
| req_properties_(base_fetch->request_headers()->GetProperties()) { |
| if (backend_first_byte_latency_ != NULL) { |
| start_time_ms_ = cache_->timer()->NowMs(); |
| } |
| } |
| |
| virtual ~CachePutFetch() {} |
| |
| virtual void HandleHeadersComplete() { |
| // We compute the latency here as it's the spot where we're doing an |
| // actual backend fetch and not potentially using the cache. |
| int64 now_ms = cache_->timer()->NowMs(); |
| if (backend_first_byte_latency_ != NULL) { |
| backend_first_byte_latency_->Add(now_ms - start_time_ms_); |
| } |
| ResponseHeaders* headers = response_headers(); |
| headers->FixDateHeaders(now_ms); |
| bool is_html = headers->IsHtmlLike(); |
| const char* cache_control = headers->Lookup1(HttpAttributes::kCacheControl); |
| if (default_cache_html_ && is_html && |
| // TODO(sligocki): Use some sort of computed |
| // headers->HasExplicitCachingTtl() instead |
| // of just checking for the existence of 2 headers. |
| (cache_control == NULL || cache_control == StringPiece("public")) && |
| !headers->Has(HttpAttributes::kExpires)) { |
| headers->Add(HttpAttributes::kCacheControl, |
| "max-age=" + Integer64ToString(headers->implicit_cache_ttl_ms())); |
| } |
| headers->ComputeCaching(); |
| cacheable_ = headers->IsProxyCacheable(req_properties_, respect_vary_, |
| ResponseHeaders::kHasValidator); |
| if (cacheable_) { |
| // Make a copy of the headers which we will send to the |
| // cache_value_writer_ later. |
| saved_headers_.CopyFrom(*headers); |
| } |
| |
| SharedAsyncFetch::HandleHeadersComplete(); |
| } |
| |
| virtual bool HandleWrite(const StringPiece& content, |
| MessageHandler* handler) { |
| bool ret = true; |
| ret &= SharedAsyncFetch::HandleWrite(content, handler); |
| if (cacheable_) { |
| ret &= cache_value_writer_.Write(content, handler); |
| } |
| return ret; |
| } |
| |
| virtual bool HandleFlush(MessageHandler* handler) { |
| // Note cache_value_.Flush doesn't do anything. |
| return SharedAsyncFetch::HandleFlush(handler); |
| } |
| |
| virtual void HandleDone(bool success) { |
| DCHECK_EQ(request_headers()->method(), RequestHeaders::kGet); |
| // We do not cache empty 200 responses. (Empty 404, 500 are fine.) |
| // https://github.com/pagespeed/mod_pagespeed/issues/1050 |
| const bool empty_200 = |
| (response_headers()->status_code() == HttpStatus::kOK && |
| cache_value_.contents_size() == 0); |
| const bool insert_into_cache = (success && |
| cacheable_ && |
| !empty_200 && |
| cache_value_writer_.has_buffered()); |
| |
| if (insert_into_cache) { |
| // The X-Original-Content-Length header will have been added after |
| // HandleHeadersComplete(), so extract its value and add it to the |
| // saved headers. |
| const char* orig_content_length = extra_response_headers()->Lookup1( |
| HttpAttributes::kXOriginalContentLength); |
| int64 ocl; |
| if (orig_content_length != NULL && |
| StringToInt64(orig_content_length, &ocl)) { |
| saved_headers_.SetOriginalContentLength(ocl); |
| } |
| // Finalize the headers. |
| cache_value_writer_.SetHeaders(&saved_headers_); |
| } else { |
| // Set is_original_resource_cacheable. |
| log_record()->SetIsOriginalResourceCacheable(false); |
| } |
| |
| // Finish fetch. |
| SharedAsyncFetch::HandleDone(success); |
| // Note: SharedAsyncFetch::base_fetch_ and other things that refer to that, |
| // like request_context() cannot be accessed or used any more. |
| |
| // Add result to cache. |
| if (insert_into_cache) { |
| cache_->Put(url_, fragment_, req_properties_, http_options_, |
| &cache_value_, handler_); |
| } |
| // Note: We explicitly do not remember fetch failure, uncacheable nor |
| // empty resources here since we still want to proxy those through every |
| // time they are requested. |
| // TODO(sligocki): Maybe we should be remembering failures. |
| delete this; |
| } |
| |
| private: |
| const GoogleString url_; |
| const GoogleString fragment_; |
| const HttpOptions http_options_; |
| // TODO(sligocki): Remove and use http_options_.respect_vary instead. |
| ResponseHeaders::VaryOption respect_vary_; |
| bool default_cache_html_; |
| HTTPCache* cache_; |
| Histogram* backend_first_byte_latency_; |
| MessageHandler* handler_; |
| |
| bool cacheable_; |
| HTTPValue cache_value_; |
| HTTPValueWriter cache_value_writer_; |
| int64 start_time_ms_; // only used if backend_first_byte_latency_ != NULL |
| ResponseHeaders saved_headers_; |
| RequestHeaders::Properties req_properties_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CachePutFetch); |
| }; |
| |
| class CacheFindCallback : public HTTPCache::Callback { |
| public: |
| class BackgroundFreshenFetch : public AsyncFetchWithLock { |
| public: |
| explicit BackgroundFreshenFetch( |
| const Hasher* lock_hasher, |
| const RequestContextPtr& request_context, |
| const GoogleString& url, |
| NamedLockManager* lock_manager, |
| MessageHandler* message_handler, |
| CacheFindCallback* callback, |
| CacheUrlAsyncFetcher::AsyncOpHooks* async_op_hooks) |
| : AsyncFetchWithLock( |
| lock_hasher, request_context, url, url /* cache_key*/, |
| lock_manager, message_handler), |
| callback_(callback), |
| async_op_hooks_(async_op_hooks) { |
| async_op_hooks_->StartAsyncOp(); |
| } |
| |
| virtual ~BackgroundFreshenFetch() { |
| async_op_hooks_->FinishAsyncOp(); |
| } |
| |
| virtual void StartFetch( |
| UrlAsyncFetcher* fetcher, MessageHandler* handler) { |
| AsyncFetch* fetch = callback_->WrapCachePutFetchAndConditionalFetch(this); |
| fetcher->Fetch(url(), handler, fetch); |
| } |
| |
| virtual bool ShouldYieldToRedundantFetchInProgress() { |
| return true; |
| } |
| |
| virtual bool IsBackgroundFetch() const { return true; } |
| |
| private: |
| CacheFindCallback* callback_; |
| CacheUrlAsyncFetcher::AsyncOpHooks* async_op_hooks_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackgroundFreshenFetch); |
| }; |
| |
| CacheFindCallback(const Hasher* lock_hasher, |
| NamedLockManager* lock_manager, |
| const GoogleString& url, |
| const GoogleString& fragment, |
| AsyncFetch* base_fetch, |
| CacheUrlAsyncFetcher* owner, |
| CacheUrlAsyncFetcher::AsyncOpHooks* async_op_hooks, |
| MessageHandler* handler) |
| : HTTPCache::Callback(base_fetch->request_context(), |
| base_fetch->request_headers()->GetProperties()), |
| lock_hasher_(lock_hasher), |
| lock_manager_(lock_manager), |
| url_(url), |
| base_fetch_(base_fetch), |
| cache_(owner->http_cache()), |
| fragment_(fragment), |
| async_op_hooks_(async_op_hooks), |
| fetcher_(owner->fetcher()), |
| backend_first_byte_latency_( |
| owner->backend_first_byte_latency_histogram()), |
| fallback_responses_served_(owner->fallback_responses_served()), |
| fallback_responses_served_while_revalidate_( |
| owner->fallback_responses_served_while_revalidate()), |
| num_conditional_refreshes_(owner->num_conditional_refreshes()), |
| num_proactively_freshen_user_facing_request_( |
| owner->num_proactively_freshen_user_facing_request()), |
| handler_(handler), |
| http_options_(base_fetch->request_context()->options()), |
| respect_vary_(ResponseHeaders::GetVaryOption(owner->respect_vary())), |
| ignore_recent_fetch_failed_(owner->ignore_recent_fetch_failed()), |
| serve_stale_if_fetch_error_(owner->serve_stale_if_fetch_error()), |
| default_cache_html_(owner->default_cache_html()), |
| proactively_freshen_user_facing_request_( |
| owner->proactively_freshen_user_facing_request()), |
| serve_stale_while_revalidate_threshold_sec_( |
| owner->serve_stale_while_revalidate_threshold_sec()) { |
| // Note that this is a cache lookup: there are no request-headers. At |
| // this level, we have already made a policy decision that any Vary |
| // headers present will be ignored (see |
| // http://code.google.com/speed/page-speed/docs/install.html#respectvary ). |
| set_response_headers(base_fetch->response_headers()); |
| } |
| virtual ~CacheFindCallback() {} |
| |
| virtual void Done(HTTPCache::FindResult find_result) { |
| switch (find_result.status) { |
| case HTTPCache::kFound: { |
| VLOG(1) << "Found in cache: " << url_ << " (" << fragment_ << ")"; |
| http_value()->ExtractHeaders(response_headers(), handler_); |
| |
| bool is_imminently_expiring = false; |
| |
| // Respond with a 304 if the If-Modified-Since / If-None-Match values |
| // are equal to those in the request. |
| if (ShouldReturn304()) { |
| response_headers()->Clear(); |
| response_headers()->SetStatusAndReason(HttpStatus::kNotModified); |
| response_headers()->ComputeCaching(); |
| is_imminently_expiring = IsImminentlyExpiring(*response_headers()); |
| base_fetch_->HeadersComplete(); |
| } else if (base_fetch_->request_headers()->method() != |
| RequestHeaders::kHead) { |
| DCHECK_EQ(base_fetch_->request_headers()->method(), |
| RequestHeaders::kGet); |
| |
| // Before calling HeadersComplete, record the content-length so that |
| // http server gaskets have an opportunity to examine |
| // content_length_known() in HandleHeadersComplete and thereby serve |
| // non-chunked responses. |
| StringPiece contents; |
| http_value()->ExtractContents(&contents); |
| base_fetch_->set_content_length(contents.size()); |
| response_headers()->ComputeCaching(); |
| is_imminently_expiring = IsImminentlyExpiring(*response_headers()); |
| base_fetch_->HeadersComplete(); |
| |
| // TODO(sligocki): We are writing all the content in one shot, this |
| // fact might be useful to the HtmlParser if this is HTML. Perhaps |
| // we should add an API for conveying that information, which can |
| // be detected via AsyncFetch::content_length_known(). |
| base_fetch_->Write(contents, handler_); |
| } else { |
| response_headers()->ComputeCaching(); |
| is_imminently_expiring = IsImminentlyExpiring(*response_headers()); |
| } |
| |
| if (fetcher_ != NULL && |
| proactively_freshen_user_facing_request_ && |
| async_op_hooks_ != NULL && |
| is_imminently_expiring) { |
| // Triggers the background fetch to freshen the value in cache if |
| // resource is about to expire. |
| if (num_proactively_freshen_user_facing_request_ != NULL) { |
| num_proactively_freshen_user_facing_request_->Add(1); |
| } |
| TriggerBackgroundFreshenFetch(); |
| } |
| |
| base_fetch_->Done(true); |
| break; |
| } |
| // Note: currently no resources fetched through CacheUrlAsyncFetcher |
| // will be marked RememberFetchFailedOrNotCacheable. |
| // TODO(sligocki): Should we mark resources as such in this class? |
| case HTTPCache::kRecentFailure: |
| VLOG(1) << "RecentFetchFailed, NotCacheable or Empty: " |
| << url_ << " (" << fragment_ << ")"; |
| if (!ignore_recent_fetch_failed_) { |
| base_fetch_->Done(false); |
| break; |
| } else { |
| // If we are ignoring advice of kRecentFetchFailedOrNotCacheable, |
| // we will refetch the resource as we would for kNotFound. |
| // |
| // For example, we should do this for fetches that are being proxied. |
| FALLTHROUGH_INTENDED; |
| } |
| case HTTPCache::kNotFound: { |
| VLOG(1) << "Did not find in cache: " |
| << url_ << " (" << fragment_ << ")"; |
| if (fetcher_ == NULL) { |
| // Set status code to indicate reason we failed Fetch. |
| DCHECK(!base_fetch_->headers_complete()); |
| base_fetch_->response_headers()->set_status_code( |
| CacheUrlAsyncFetcher::kNotInCacheStatus); |
| base_fetch_->Done(false); |
| } else { |
| AsyncFetch* base_fetch = base_fetch_; |
| if (request_headers()->method() == RequestHeaders::kGet) { |
| // Only cache GET results as they can be used for HEAD requests, |
| // but not vice versa. |
| // TODO(gee): It is possible to cache HEAD results as well, but we |
| // must add code to ensure we do not serve GET requests using HEAD |
| // responses. |
| if (ServedStaleContentWhileRevalidate(base_fetch)) { |
| // Serve stale content while revalidate in the background. |
| break; |
| } |
| if (serve_stale_if_fetch_error_) { |
| // If fallback_http_value() is populated, use it in case the |
| // fetch fails. Note that this is only populated if the |
| // response in cache is stale. |
| FallbackSharedAsyncFetch* fallback_fetch = |
| new FallbackSharedAsyncFetch( |
| base_fetch_, fallback_http_value(), handler_); |
| fallback_fetch->set_fallback_responses_served( |
| fallback_responses_served_); |
| base_fetch = fallback_fetch; |
| } |
| |
| base_fetch = WrapCachePutFetchAndConditionalFetch(base_fetch); |
| } |
| |
| fetcher_->Fetch(url_, handler_, base_fetch); |
| } |
| break; |
| } |
| } |
| |
| delete this; |
| } |
| |
| virtual bool IsCacheValid(const GoogleString& key, |
| const ResponseHeaders& headers) { |
| // base_fetch_ already has the key (URL + fragment). |
| return base_fetch_->IsCachedResultValid(headers); |
| } |
| |
| virtual ResponseHeaders::VaryOption RespectVaryOnResources() const { |
| return respect_vary_; |
| } |
| |
| private: |
| bool ServedStaleContentWhileRevalidate(AsyncFetch* base_fetch) { |
| if (serve_stale_while_revalidate_threshold_sec_ == 0 || |
| fallback_http_value() == NULL || |
| fallback_http_value()->Empty()) { |
| return false; |
| } |
| ResponseHeaders* response_headers = base_fetch->response_headers(); |
| if (!fallback_http_value()->ExtractHeaders(response_headers, handler_)) { |
| // Returns false if it fails to extract headers. |
| response_headers->Clear(); |
| return false; |
| } |
| response_headers->ComputeCaching(); |
| const int64 expiry_ms = response_headers->CacheExpirationTimeMs(); |
| const int64 now_ms = cache_->timer()->NowMs(); |
| const int64 serve_stale_threshold_ms = |
| serve_stale_while_revalidate_threshold_sec_ * 1000; |
| if (now_ms > expiry_ms + serve_stale_threshold_ms || |
| response_headers->IsHtmlLike()) { |
| // Serve non-html request with fallback http value if resource |
| // was expired within serve_stale_while_revalidate_threshold_ms_. |
| response_headers->Clear(); |
| return false; |
| } |
| if (fallback_responses_served_while_revalidate_ != NULL) { |
| fallback_responses_served_while_revalidate_->Add(1); |
| } |
| // CacheControl header is changed to private, max-age=0 to avoid caching |
| // of the resource either by browser or intermediate proxy as stale |
| // content should be served only for this request, any future requests |
| // should be served with fresh content. |
| response_headers->Replace(HttpAttributes::kCacheControl, |
| "private, max-age=0"); |
| response_headers->RemoveAll(HttpAttributes::kExpires); |
| response_headers->ComputeCaching(); |
| base_fetch_->HeadersComplete(); |
| StringPiece contents; |
| fallback_http_value()->ExtractContents(&contents); |
| base_fetch_->Write(contents, handler_); |
| |
| // Issue a background fetch to update the cache with a fresh value so |
| // that future request will be responded with fresh content. |
| TriggerBackgroundFreshenFetch(); |
| base_fetch_->Done(true); |
| return true; |
| } |
| |
| void TriggerBackgroundFreshenFetch() { |
| AsyncFetchWithLock* fetch = new BackgroundFreshenFetch( |
| lock_hasher_, |
| base_fetch_->request_context(), |
| url_, |
| lock_manager_, |
| handler_, |
| this, |
| async_op_hooks_); |
| RequestHeaders* request_headers = fetch->request_headers(); |
| request_headers->CopyFrom(*base_fetch_->request_headers()); |
| DCHECK(request_headers->method() == RequestHeaders::kGet || |
| request_headers->method() == RequestHeaders::kHead); |
| // It's possible for us to trigger a background freshen on a HEAD. |
| // If so, actually send the GET request, since we don't want to be |
| // trying to cache a HEAD response. |
| request_headers->set_method(RequestHeaders::kGet); |
| fetch->Start(fetcher_); |
| } |
| |
| bool ShouldReturn304() const { |
| if (ConditionalHeadersMatch(HttpAttributes::kIfNoneMatch, |
| HttpAttributes::kEtag)) { |
| // If the Etag matches, return a 304. |
| return true; |
| } |
| // Otherwise, return a 304 only if there was no If-None-Match header in the |
| // request and the last modified timestamp matches. |
| // (from http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) |
| return request_headers()->Lookup1(HttpAttributes::kIfNoneMatch) == NULL && |
| ConditionalHeadersMatch(HttpAttributes::kIfModifiedSince, |
| HttpAttributes::kLastModified); |
| } |
| |
| bool ConditionalHeadersMatch(const StringPiece& request_header, |
| const StringPiece& response_header) const { |
| const char* request_header_value = |
| request_headers()->Lookup1(request_header); |
| const char* response_header_value = |
| response_headers()->Lookup1(response_header); |
| return request_header_value != NULL && response_header_value != NULL && |
| strcmp(request_header_value, response_header_value) == 0; |
| } |
| |
| const RequestHeaders* request_headers() const { |
| return base_fetch_->request_headers(); |
| } |
| RequestHeaders* request_headers() { return base_fetch_->request_headers(); } |
| |
| bool IsImminentlyExpiring(const ResponseHeaders& headers) const { |
| return ResponseHeaders::IsImminentlyExpiring( |
| headers.date_ms(), |
| headers.CacheExpirationTimeMs(), |
| cache_->timer()->NowMs(), |
| headers.http_options()); |
| } |
| |
| AsyncFetch* WrapCachePutFetchAndConditionalFetch(AsyncFetch* base_fetch) { |
| CachePutFetch* put_fetch = new CachePutFetch( |
| url_, fragment_, base_fetch, respect_vary_, default_cache_html_, cache_, |
| backend_first_byte_latency_, handler_); |
| DCHECK_EQ(response_headers(), base_fetch_->response_headers()); |
| |
| // Remove any Etags added by us before sending the request out. This is the |
| // etags generated by server and upstream original code would not |
| // understand them. |
| const char* etag = request_headers()->Lookup1( |
| HttpAttributes::kIfNoneMatch); |
| if (etag != NULL && |
| StringCaseStartsWith(etag, HTTPCache::kEtagPrefix)) { |
| put_fetch->request_headers()->RemoveAll( |
| HttpAttributes::kIfNoneMatch); |
| } |
| |
| ConditionalSharedAsyncFetch* conditional_fetch = |
| new ConditionalSharedAsyncFetch( |
| put_fetch, fallback_http_value(), handler_); |
| conditional_fetch->set_num_conditional_refreshes( |
| num_conditional_refreshes_); |
| return conditional_fetch; |
| } |
| |
| const Hasher* lock_hasher_; |
| NamedLockManager* lock_manager_; |
| const GoogleString url_; |
| RequestHeaders request_headers_; |
| AsyncFetch* base_fetch_; |
| HTTPCache* cache_; |
| GoogleString fragment_; |
| CacheUrlAsyncFetcher::AsyncOpHooks* async_op_hooks_; |
| UrlAsyncFetcher* fetcher_; |
| Histogram* backend_first_byte_latency_; |
| Variable* fallback_responses_served_; |
| Variable* fallback_responses_served_while_revalidate_; |
| Variable* num_conditional_refreshes_; |
| Variable* num_proactively_freshen_user_facing_request_; |
| MessageHandler* handler_; |
| |
| const HttpOptions http_options_; |
| // TODO(sligocki): Remove and use http_options_.respect_vary instead. |
| ResponseHeaders::VaryOption respect_vary_; |
| bool ignore_recent_fetch_failed_; |
| bool serve_stale_if_fetch_error_; |
| bool default_cache_html_; |
| bool proactively_freshen_user_facing_request_; |
| int64 serve_stale_while_revalidate_threshold_sec_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CacheFindCallback); |
| }; |
| |
| } // namespace |
| |
| CacheUrlAsyncFetcher::CacheUrlAsyncFetcher(const Hasher* lock_hasher, |
| NamedLockManager* lock_manager, |
| HTTPCache* cache, |
| const GoogleString& fragment, |
| AsyncOpHooks* async_op_hooks, |
| UrlAsyncFetcher* fetcher) |
| : lock_hasher_(lock_hasher), |
| lock_manager_(lock_manager), |
| http_cache_(cache), |
| fragment_(fragment), |
| fetcher_(fetcher), |
| async_op_hooks_(async_op_hooks), |
| backend_first_byte_latency_(NULL), |
| fallback_responses_served_(NULL), |
| fallback_responses_served_while_revalidate_(NULL), |
| num_conditional_refreshes_(NULL), |
| num_proactively_freshen_user_facing_request_(NULL), |
| respect_vary_(false), |
| ignore_recent_fetch_failed_(false), |
| serve_stale_if_fetch_error_(false), |
| default_cache_html_(false), |
| proactively_freshen_user_facing_request_(false), |
| own_fetcher_(false), |
| serve_stale_while_revalidate_threshold_sec_(0) { |
| } |
| |
| CacheUrlAsyncFetcher::~CacheUrlAsyncFetcher() { |
| if (own_fetcher_) { |
| delete fetcher_; |
| fetcher_ = NULL; |
| } |
| } |
| |
| void CacheUrlAsyncFetcher::Fetch( |
| const GoogleString& url, MessageHandler* handler, AsyncFetch* base_fetch) { |
| switch (base_fetch->request_headers()->method()) { |
| case RequestHeaders::kHead: |
| // HEAD is identical to GET, with the body trimmed. Even though we are |
| // able to respond to HEAD requests with a cached value from a GET |
| // response, at this point we do not allow caching of HEAD responses from |
| // the origin, so mark the "original" resource as uncacheable. |
| base_fetch->log_record()->SetIsOriginalResourceCacheable(false); |
| FALLTHROUGH_INTENDED; |
| case RequestHeaders::kGet: |
| { |
| CacheFindCallback* find_callback = |
| new CacheFindCallback( |
| lock_hasher_, |
| lock_manager_, |
| url, |
| fragment_, |
| base_fetch, |
| this, |
| async_op_hooks_, |
| handler); |
| http_cache_->Find(url, fragment_, handler, find_callback); |
| } |
| return; |
| |
| default: |
| // POST may not be idempotent and thus we must not serve a cached value |
| // from a prior request. |
| // TODO(gee): What about the other methods? |
| break; |
| } |
| |
| // Original resource not cacheable. |
| base_fetch->log_record()->SetIsOriginalResourceCacheable(false); |
| if (fetcher_ != NULL) { |
| fetcher_->Fetch(url, handler, base_fetch); |
| } else { |
| // Set status code to indicate reason we failed Fetch. |
| DCHECK(!base_fetch->headers_complete()); |
| base_fetch->response_headers()->set_status_code(kNotInCacheStatus); |
| base_fetch->Done(false); |
| } |
| } |
| |
| CacheUrlAsyncFetcher::AsyncOpHooks::~AsyncOpHooks() { |
| } |
| |
| } // namespace net_instaweb |