| /* |
| * 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/rewriter/public/resource_fetch.h" |
| |
| #include "base/logging.h" |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/sync_fetcher_adapter_callback.h" |
| #include "net/instaweb/public/global_constants.h" |
| #include "net/instaweb/rewriter/public/resource_namer.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver_pool.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/rewrite_stats.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/statistics.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/request_headers.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| |
| namespace net_instaweb { |
| |
| void ResourceFetch::ApplyExperimentOptions(const GoogleUrl& url, |
| const RequestContextPtr& request_ctx, |
| ServerContext* server_context, |
| RewriteOptions** custom_options) { |
| const RewriteOptions* active_options; |
| if (*custom_options == NULL) { |
| RewriteDriverPool* driver_pool = server_context->SelectDriverPool( |
| request_ctx->using_spdy()); |
| active_options = driver_pool->TargetOptions(); |
| } else { |
| active_options = *custom_options; |
| } |
| if (active_options->running_experiment()) { |
| // If we're running an experiment and this resource url specifies a |
| // experiment_spec, make sure the custom options have that experiment |
| // selected. |
| ResourceNamer namer; |
| namer.DecodeIgnoreHashAndSignature(url.LeafSansQuery()); |
| if (namer.has_experiment()) { |
| if (*custom_options == NULL) { |
| *custom_options = active_options->Clone(); |
| } |
| (*custom_options)->SetExperimentStateStr(namer.experiment()); |
| server_context->ComputeSignature(*custom_options); |
| } |
| } |
| } |
| |
| RewriteDriver* ResourceFetch::GetDriver( |
| const GoogleUrl& url, RewriteOptions* custom_options, |
| ServerContext* server_context, const RequestContextPtr& request_ctx) { |
| ApplyExperimentOptions(url, request_ctx, server_context, &custom_options); |
| RewriteDriver* driver = (custom_options == NULL) |
| ? server_context->NewRewriteDriver(request_ctx) |
| : server_context->NewCustomRewriteDriver(custom_options, request_ctx); |
| return driver; |
| } |
| |
| void ResourceFetch::StartWithDriver( |
| const GoogleUrl& url, CleanupMode cleanup_mode, |
| ServerContext* server_context, RewriteDriver* driver, |
| AsyncFetch* async_fetch) { |
| |
| ResourceFetch* resource_fetch = new ResourceFetch( |
| url, cleanup_mode, driver, server_context->timer(), |
| server_context->message_handler(), async_fetch); |
| |
| if (!driver->FetchResource(url.Spec(), resource_fetch)) { |
| resource_fetch->Done(false); |
| } |
| } |
| |
| void ResourceFetch::Start(const GoogleUrl& url, |
| RewriteOptions* custom_options, |
| bool using_spdy, |
| ServerContext* server_context, |
| AsyncFetch* async_fetch) { |
| RewriteDriver* driver = GetDriver( |
| url, custom_options, server_context, async_fetch->request_context()); |
| StartWithDriver(url, kAutoCleanupDriver, |
| server_context, driver, async_fetch); |
| } |
| |
| bool ResourceFetch::BlockingFetch(const GoogleUrl& url, |
| ServerContext* server_context, |
| RewriteDriver* driver, |
| SyncFetcherAdapterCallback* callback) { |
| // Here, we do not want the driver to be cleaned up by the ResourceFetch |
| // since we will be calling BoundedWaitFor on it! |
| StartWithDriver(url, kDontAutoCleanupDriver, server_context, driver, |
| callback); |
| |
| // Wait for resource fetch to complete. |
| if (!callback->IsDone()) { |
| int64 max_ms = driver->options()->blocking_fetch_timeout_ms(); |
| for (int64 start_ms = server_context->timer()->NowMs(), now_ms = start_ms; |
| !callback->IsDone() && now_ms - start_ms < max_ms; |
| now_ms = server_context->timer()->NowMs()) { |
| int64 remaining_ms = max_ms - (now_ms - start_ms); |
| |
| driver->BoundedWaitFor(RewriteDriver::kWaitForCompletion, remaining_ms); |
| } |
| } |
| |
| MessageHandler* message_handler = server_context->message_handler(); |
| bool ok = false; |
| if (callback->IsDone()) { |
| if (callback->success()) { |
| ok = true; |
| } else { |
| message_handler->Message(kWarning, "Fetch failed for %s, status=%d", |
| url.spec_c_str(), |
| callback->response_headers()->status_code()); |
| } |
| } else { |
| message_handler->Message(kWarning, "Fetch timed out for %s", |
| url.spec_c_str()); |
| } |
| |
| driver->Cleanup(); |
| |
| return ok; |
| } |
| |
| ResourceFetch::ResourceFetch(const GoogleUrl& url, |
| CleanupMode cleanup_mode, |
| RewriteDriver* driver, |
| Timer* timer, |
| MessageHandler* handler, |
| AsyncFetch* async_fetch) |
| : SharedAsyncFetch(async_fetch), |
| driver_(driver), |
| timer_(timer), |
| message_handler_(handler), |
| start_time_ms_(timer->NowMs()), |
| redirect_count_(0), |
| cleanup_mode_(cleanup_mode) { |
| resource_url_.Reset(url); |
| DCHECK(driver_->request_headers() == NULL); |
| } |
| |
| ResourceFetch::~ResourceFetch() { |
| } |
| |
| void ResourceFetch::HandleHeadersComplete() { |
| // We do not want any cookies (or other person information) in pagespeed |
| // resources. They shouldn't be here anyway, but we assure that. |
| ConstStringStarVector v; |
| DCHECK(!response_headers()->Lookup(HttpAttributes::kSetCookie, &v)); |
| DCHECK(!response_headers()->Lookup(HttpAttributes::kSetCookie2, &v)); |
| response_headers()->RemoveAll(HttpAttributes::kSetCookie); |
| response_headers()->RemoveAll(HttpAttributes::kSetCookie2); |
| |
| // "Vary: Accept-Encoding" for all resources that are transmitted compressed. |
| // Server ought to set these, I suppose. |
| // response_headers()->Add(HttpAttributes::kVary, "Accept-Encoding"); |
| |
| response_headers()->Add(kPageSpeedHeader, |
| driver_->options()->x_header_value()); |
| SharedAsyncFetch::HandleHeadersComplete(); |
| } |
| |
| void ResourceFetch::HandleDone(bool success) { |
| if (success) { |
| LOG(INFO) << "Resource " << resource_url_.Spec() |
| << " : " << response_headers()->status_code(); |
| } else { |
| // This is a fetcher failure, like connection refused, not just an error |
| // status code. |
| LOG(WARNING) << "Fetch failed for resource url " << resource_url_.Spec(); |
| if (!response_headers()->headers_complete()) { |
| response_headers()->SetStatusAndReason(HttpStatus::kNotFound); |
| } |
| } |
| RewriteStats* stats = driver_->server_context()->rewrite_stats(); |
| stats->fetch_latency_histogram()->Add(timer_->NowMs() - start_time_ms_); |
| stats->total_fetch_count()->IncBy(1); |
| if (cleanup_mode_ == kAutoCleanupDriver) { |
| driver_->Cleanup(); |
| } |
| SharedAsyncFetch::HandleDone(success); |
| delete this; |
| } |
| |
| } // namespace net_instaweb |