blob: 3494aecccfb1f1bc894d4de59845150e75456dac [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: jmarantz@google.com (Joshua Marantz)
// sligocki@google.com (Shawn Ligocki)
#include "net/instaweb/http/public/async_fetch.h"
#include "base/logging.h"
#include "net/instaweb/http/public/http_cache.h"
#include "net/instaweb/http/public/request_timing_info.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/statistics.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 {
AsyncFetch::AsyncFetch()
: request_headers_(NULL),
response_headers_(NULL),
extra_response_headers_(NULL),
request_ctx_(NULL),
owns_request_headers_(false),
owns_response_headers_(false),
owns_extra_response_headers_(false),
headers_complete_(false),
content_length_(kContentLengthUnknown) {
}
AsyncFetch::AsyncFetch(const RequestContextPtr& request_ctx)
: request_headers_(NULL),
response_headers_(NULL),
extra_response_headers_(NULL),
request_ctx_(request_ctx),
owns_request_headers_(false),
owns_response_headers_(false),
owns_extra_response_headers_(false),
headers_complete_(false),
content_length_(kContentLengthUnknown) {
DCHECK(request_ctx_.get() != NULL);
}
AsyncFetch::~AsyncFetch() {
if (owns_response_headers_) {
delete response_headers_;
}
if (owns_extra_response_headers_) {
delete extra_response_headers_;
}
if (owns_request_headers_) {
delete request_headers_;
}
}
AbstractLogRecord* AsyncFetch::log_record() {
CHECK(request_context().get() != NULL);
return request_context()->log_record();
}
bool AsyncFetch::Write(const StringPiece& sp, MessageHandler* handler) {
bool ret = true;
if (!sp.empty()) { // empty-writes should be no-ops.
if (!headers_complete_) {
HeadersComplete();
}
if (request_headers()->method() == RequestHeaders::kHead) {
// If the request is a head request, then don't write the contents of
// body.
return ret;
}
ret = HandleWrite(sp, handler);
}
return ret;
}
bool AsyncFetch::Flush(MessageHandler* handler) {
if (!headers_complete_) {
HeadersComplete();
}
return HandleFlush(handler);
}
void AsyncFetch::HeadersComplete() {
DCHECK_NE(0, response_headers()->status_code());
if (headers_complete_) {
LOG(DFATAL) << "AsyncFetch::HeadersComplete() called twice.";
} else {
headers_complete_ = true;
HandleHeadersComplete();
}
}
void AsyncFetch::Done(bool success) {
if (!headers_complete_) {
if (!success &&
(response_headers()->status_code() == 0)) {
// Failing fetches might not set status codes but we expect
// successful ones to.
response_headers()->set_status_code(HttpStatus::kNotFound);
}
response_headers()->ComputeCaching();
HeadersComplete();
}
HandleDone(success);
}
// Sets the request-headers to the specified pointer. The caller must
// guarantee that the pointed-to headers remain valid as long as the
// AsyncFetch is running.
void AsyncFetch::set_request_headers(RequestHeaders* headers) {
DCHECK(!owns_request_headers_);
if (owns_request_headers_) {
delete request_headers_;
}
request_headers_ = headers;
owns_request_headers_ = false;
}
void AsyncFetch::SetRequestHeadersTakingOwnership(RequestHeaders* headers) {
set_request_headers(headers);
owns_request_headers_ = true;
}
RequestHeaders* AsyncFetch::request_headers() {
// TODO(jmarantz): Consider DCHECKing that only const reads occur
// after headers_complete_.
if (request_headers_ == NULL) {
request_headers_ = new RequestHeaders;
owns_request_headers_ = true;
}
return request_headers_;
}
const RequestHeaders* AsyncFetch::request_headers() const {
CHECK(request_headers_ != NULL);
return request_headers_;
}
ResponseHeaders* AsyncFetch::response_headers() {
if (response_headers_ == NULL) {
response_headers_ = new ResponseHeaders(request_ctx_->options());
owns_response_headers_ = true;
}
return response_headers_;
}
void AsyncFetch::set_response_headers(ResponseHeaders* headers) {
DCHECK(!owns_response_headers_);
if (owns_response_headers_) {
delete response_headers_;
}
response_headers_ = headers;
owns_response_headers_ = false;
}
ResponseHeaders* AsyncFetch::extra_response_headers() {
if (extra_response_headers_ == NULL) {
extra_response_headers_ = new ResponseHeaders(request_ctx_->options());
owns_extra_response_headers_ = true;
}
return extra_response_headers_;
}
void AsyncFetch::set_extra_response_headers(ResponseHeaders* headers) {
DCHECK(!owns_extra_response_headers_);
if (owns_extra_response_headers_) {
delete extra_response_headers_;
}
extra_response_headers_ = headers;
owns_extra_response_headers_ = false;
}
GoogleString AsyncFetch::LoggingString() {
GoogleString logging_info_str;
if (NULL == request_ctx_.get()) {
return logging_info_str;
}
int64 latency;
const RequestTimingInfo& timing_info = request_ctx_->timing_info();
if (timing_info.GetHTTPCacheLatencyMs(&latency)) {
StrAppend(&logging_info_str, "c1:", Integer64ToString(latency), ";");
}
if (timing_info.GetL2HTTPCacheLatencyMs(&latency)) {
StrAppend(&logging_info_str, "c2:", Integer64ToString(latency), ";");
}
if (timing_info.GetFetchHeaderLatencyMs(&latency)) {
StrAppend(&logging_info_str, "hf:", Integer64ToString(latency), ";");
}
if (timing_info.GetFetchLatencyMs(&latency)) {
StrAppend(&logging_info_str, "f:", Integer64ToString(latency), ";");
}
return logging_info_str;
}
StringAsyncFetch::~StringAsyncFetch() {
}
bool AsyncFetchUsingWriter::HandleWrite(const StringPiece& sp,
MessageHandler* handler) {
return writer_->Write(sp, handler);
}
bool AsyncFetchUsingWriter::HandleFlush(MessageHandler* handler) {
return writer_->Flush(handler);
}
AsyncFetchUsingWriter::~AsyncFetchUsingWriter() {
}
SharedAsyncFetch::SharedAsyncFetch(AsyncFetch* base_fetch)
: AsyncFetch(base_fetch->request_context()),
base_fetch_(base_fetch) {
set_response_headers(base_fetch->response_headers());
set_extra_response_headers(base_fetch->extra_response_headers());
set_request_headers(base_fetch->request_headers());
}
SharedAsyncFetch::~SharedAsyncFetch() {
}
void SharedAsyncFetch::PropagateContentLength() {
if (content_length_known()) {
base_fetch_->set_content_length(content_length());
}
}
void SharedAsyncFetch::HandleHeadersComplete() {
PropagateContentLength();
base_fetch_->HeadersComplete();
}
void AsyncFetch::FixCacheControlForGoogleCache() {
ConstStringStarVector values;
if (request_headers()->Lookup(HttpAttributes::kVia, &values)) {
for (int i = 0, n = values.size(); i < n; ++i) {
// See https://github.com/pagespeed/ngx_pagespeed/issues/1149
// In general, it is not necessary to include a specific 'public'
// in a cache-control entry to allow a proxy-cache to cache it;
// it's sufficient to specify a max-age, as long as 'private'
// is not present.
//
// However, the Google Cloud CDN cache requires it, with some
// controversy. See this discussion with Mark Nottingham:
// https://groups.google.com/forum/#!searchin/pagespeed-insights-discuss/nottingham/pagespeed-insights-discuss/NWwrz1By36c/9RN9sHdj9EIJ
//
// Mark's comment about Firefox treating public differently is
// no longer current.
//
// However the Google Cloud CDN help page on caching unambiguously
// requires that 'public' be included in Cache-Control to enable
// the Google Cache.
// https://cloud.google.com/cdn/docs/caching#cacheability
//
// We only need to put in the 'public" header if there's
// a "Via: 1.1 google" header. As there's only a small cost
// in bytes, we'll just scan the via value for 'google', so
// that a future HTTP rev won't break caching.
if (IsGoogleCacheVia(*values[i])) {
response_headers()->SetCacheControlPublic();
break;
}
}
}
}
// static
bool AsyncFetch::IsGoogleCacheVia(StringPiece via_value) {
// The value of the Google Via header as of this writing
// is "1.1 google". In an attempt to be future proof, we'll
// match "X.Y google", and be case-insensitive matching "google".
StringPieceVector tokens;
SplitStringPieceToVector(via_value, " ", &tokens, true);
double version;
return
(tokens.size() == 2) &&
StringCaseEqual(tokens[1], "google") &&
StringToDouble(tokens[0], &version);
}
const char FallbackSharedAsyncFetch::kStaleWarningHeaderValue[] =
"110 Response is stale";
FallbackSharedAsyncFetch::FallbackSharedAsyncFetch(AsyncFetch* base_fetch,
HTTPValue* fallback,
MessageHandler* handler)
: SharedAsyncFetch(base_fetch),
handler_(handler),
serving_fallback_(false),
fallback_responses_served_(NULL) {
if (fallback != NULL && !fallback->Empty()) {
fallback_.Link(fallback);
}
}
FallbackSharedAsyncFetch::~FallbackSharedAsyncFetch() {}
void FallbackSharedAsyncFetch::HandleHeadersComplete() {
if (response_headers()->IsServerErrorStatus() && !fallback_.Empty()) {
// If the fetch resulted in a server side error from the origin, stop
// passing any events through to the base fetch until HandleDone().
serving_fallback_ = true;
response_headers()->Clear();
fallback_.ExtractHeaders(response_headers(), handler_);
// Add a warning header indicating that the response is stale.
response_headers()->Add(HttpAttributes::kWarning, kStaleWarningHeaderValue);
response_headers()->ComputeCaching();
StringPiece contents;
fallback_.ExtractContents(&contents);
set_content_length(contents.size());
SharedAsyncFetch::HandleHeadersComplete();
SharedAsyncFetch::HandleWrite(contents, handler_);
SharedAsyncFetch::HandleFlush(handler_);
if (fallback_responses_served_ != NULL) {
fallback_responses_served_->Add(1);
}
// Do not call Done() on the base fetch yet since it could delete shared
// pointers.
} else {
SharedAsyncFetch::HandleHeadersComplete();
}
}
bool FallbackSharedAsyncFetch::HandleWrite(const StringPiece& content,
MessageHandler* handler) {
if (serving_fallback_) {
return true;
}
return SharedAsyncFetch::HandleWrite(content, handler);
}
bool FallbackSharedAsyncFetch::HandleFlush(MessageHandler* handler) {
if (serving_fallback_) {
return true;
}
return SharedAsyncFetch::HandleFlush(handler);
}
void FallbackSharedAsyncFetch::HandleDone(bool success) {
SharedAsyncFetch::HandleDone(serving_fallback_ || success);
delete this;
}
ConditionalSharedAsyncFetch::ConditionalSharedAsyncFetch(
AsyncFetch* base_fetch,
HTTPValue* cached_value,
MessageHandler* handler)
: SharedAsyncFetch(base_fetch),
handler_(handler),
serving_cached_value_(false),
added_conditional_headers_to_request_(false),
num_conditional_refreshes_(NULL) {
if (cached_value != NULL && !cached_value->Empty()) {
// Only do our own conditional fetch if the original request wasn't
// conditional.
if (!request_headers()->Has(HttpAttributes::kIfModifiedSince) &&
!request_headers()->Has(HttpAttributes::kIfNoneMatch)) {
ResponseHeaders cached_response_headers(request_context()->options());
cached_value->ExtractHeaders(&cached_response_headers, handler_);
// Check that the cached response is a 200.
if (cached_response_headers.status_code() == HttpStatus::kOK) {
// Copy the Etag and Last-Modified if any into the If-None-Match and
// If-Modified-Since request headers. Also, ensure that the Etag wasn't
// added by us.
const char* etag = cached_response_headers.Lookup1(
HttpAttributes::kEtag);
if (etag != NULL && !StringCaseStartsWith(etag,
HTTPCache::kEtagPrefix)) {
request_headers()->Add(HttpAttributes::kIfNoneMatch, etag);
added_conditional_headers_to_request_ = true;
}
const char* last_modified = cached_response_headers.Lookup1(
HttpAttributes::kLastModified);
if (last_modified != NULL) {
request_headers()->Add(HttpAttributes::kIfModifiedSince,
last_modified);
added_conditional_headers_to_request_ = true;
}
}
if (added_conditional_headers_to_request_) {
cached_value_.Link(cached_value);
}
}
}
}
ConditionalSharedAsyncFetch::~ConditionalSharedAsyncFetch() {}
void ConditionalSharedAsyncFetch::HandleHeadersComplete() {
if (added_conditional_headers_to_request_ &&
response_headers()->status_code() == HttpStatus::kNotModified) {
// If the fetch resulted in a 304 from the server, serve the cached response
// and stop passing any events through to the base fetch.
serving_cached_value_ = true;
int64 implicit_cache_ttl_ms = response_headers()->implicit_cache_ttl_ms();
int64 min_cache_ttl_ms = response_headers()->min_cache_ttl_ms();
response_headers()->Clear();
cached_value_.ExtractHeaders(response_headers(), handler_);
if (response_headers()->is_implicitly_cacheable()) {
response_headers()->SetCacheControlMaxAge(implicit_cache_ttl_ms);
response_headers()->ComputeCaching();
} else if (response_headers()->cache_ttl_ms() < min_cache_ttl_ms) {
response_headers()->SetCacheControlMaxAge(min_cache_ttl_ms);
response_headers()->ComputeCaching();
}
SharedAsyncFetch::HandleHeadersComplete();
StringPiece contents;
cached_value_.ExtractContents(&contents);
SharedAsyncFetch::HandleWrite(contents, handler_);
SharedAsyncFetch::HandleFlush(handler_);
// Do not call Done() on the base fetch yet since it could delete shared
// pointers.
if (num_conditional_refreshes_ != NULL) {
num_conditional_refreshes_->Add(1);
}
} else {
SharedAsyncFetch::HandleHeadersComplete();
}
}
bool ConditionalSharedAsyncFetch::HandleWrite(const StringPiece& content,
MessageHandler* handler) {
if (serving_cached_value_) {
return true;
}
return SharedAsyncFetch::HandleWrite(content, handler);
}
bool ConditionalSharedAsyncFetch::HandleFlush(MessageHandler* handler) {
if (serving_cached_value_) {
return true;
}
return SharedAsyncFetch::HandleFlush(handler);
}
void ConditionalSharedAsyncFetch::HandleDone(bool success) {
SharedAsyncFetch::HandleDone(serving_cached_value_ || success);
delete this;
}
} // namespace net_instaweb