blob: 05552d065e6a0eeeccd7d310336119c673d56769 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/url_request/sdch_dictionary_fetcher.h"
#include <stdint.h>
#include <queue>
#include <set>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/profiler/scoped_tracker.h"
#include "base/thread_task_runner_handle.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_log.h"
#include "net/base/sdch_net_log_params.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_throttler_manager.h"
namespace net {
namespace {
const int kBufferSize = 4096;
// Map the bytes_read result from a read attempt and a URLRequest's
// status into a single net return value.
int GetReadResult(int bytes_read, const URLRequest* request) {
int rv = request->status().error();
if (request->status().is_success() && bytes_read < 0) {
rv = ERR_FAILED;
request->net_log().AddEventWithNetErrorCode(
NetLog::TYPE_SDCH_DICTIONARY_FETCH_IMPLIED_ERROR, rv);
}
if (rv == OK)
rv = bytes_read;
return rv;
}
struct FetchInfo {
FetchInfo(const GURL& url,
bool cache_only,
const SdchDictionaryFetcher::OnDictionaryFetchedCallback& callback)
: url(url), cache_only(cache_only), callback(callback) {}
FetchInfo() {}
GURL url;
bool cache_only;
SdchDictionaryFetcher::OnDictionaryFetchedCallback callback;
};
} // namespace
// A UniqueFetchQueue is used to queue outgoing requests, which are either cache
// requests or network requests (which *may* still be served from cache).
// The UniqueFetchQueue enforces that a URL can only be queued for network fetch
// at most once. Calling Clear() resets UniqueFetchQueue's memory of which URLs
// have been queued.
class SdchDictionaryFetcher::UniqueFetchQueue {
public:
UniqueFetchQueue();
~UniqueFetchQueue();
bool Push(const FetchInfo& info);
bool Pop(FetchInfo* info);
bool IsEmpty() const;
void Clear();
private:
std::queue<FetchInfo> queue_;
std::set<GURL> ever_network_queued_;
DISALLOW_COPY_AND_ASSIGN(UniqueFetchQueue);
};
SdchDictionaryFetcher::UniqueFetchQueue::UniqueFetchQueue() {}
SdchDictionaryFetcher::UniqueFetchQueue::~UniqueFetchQueue() {}
bool SdchDictionaryFetcher::UniqueFetchQueue::Push(const FetchInfo& info) {
if (ever_network_queued_.count(info.url) != 0)
return false;
if (!info.cache_only)
ever_network_queued_.insert(info.url);
queue_.push(info);
return true;
}
bool SdchDictionaryFetcher::UniqueFetchQueue::Pop(FetchInfo* info) {
if (IsEmpty())
return false;
*info = queue_.front();
queue_.pop();
return true;
}
bool SdchDictionaryFetcher::UniqueFetchQueue::IsEmpty() const {
return queue_.empty();
}
void SdchDictionaryFetcher::UniqueFetchQueue::Clear() {
ever_network_queued_.clear();
while (!queue_.empty())
queue_.pop();
}
SdchDictionaryFetcher::SdchDictionaryFetcher(URLRequestContext* context)
: next_state_(STATE_NONE),
in_loop_(false),
fetch_queue_(new UniqueFetchQueue()),
context_(context) {
DCHECK(CalledOnValidThread());
DCHECK(context);
}
SdchDictionaryFetcher::~SdchDictionaryFetcher() {
}
bool SdchDictionaryFetcher::Schedule(
const GURL& dictionary_url,
const OnDictionaryFetchedCallback& callback) {
return ScheduleInternal(dictionary_url, false, callback);
}
bool SdchDictionaryFetcher::ScheduleReload(
const GURL& dictionary_url,
const OnDictionaryFetchedCallback& callback) {
return ScheduleInternal(dictionary_url, true, callback);
}
void SdchDictionaryFetcher::Cancel() {
DCHECK(CalledOnValidThread());
ResetRequest();
next_state_ = STATE_NONE;
fetch_queue_->Clear();
}
void SdchDictionaryFetcher::OnResponseStarted(URLRequest* request) {
// TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
"423948 SdchDictionaryFetcher::OnResponseStarted"));
DCHECK(CalledOnValidThread());
DCHECK_EQ(request, current_request_.get());
DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE);
DCHECK(!in_loop_);
// Confirm that the response isn't a stale read from the cache (as
// may happen in the reload case). If the response was not retrieved over
// HTTP, it is presumed to be fresh.
HttpResponseHeaders* response_headers = request->response_headers();
int result = request->status().error();
if (result == OK && response_headers) {
ValidationType validation_type = response_headers->RequiresValidation(
request->response_info().request_time,
request->response_info().response_time, base::Time::Now());
// TODO(rdsmith): Maybe handle VALIDATION_ASYNCHRONOUS by queueing
// a non-reload request for the dictionary.
if (validation_type != VALIDATION_NONE)
result = ERR_FAILED;
}
DoLoop(result);
}
void SdchDictionaryFetcher::OnReadCompleted(URLRequest* request,
int bytes_read) {
// TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
"423948 SdchDictionaryFetcher::OnReadCompleted"));
DCHECK(CalledOnValidThread());
DCHECK_EQ(request, current_request_.get());
DCHECK_EQ(next_state_, STATE_READ_BODY_COMPLETE);
DCHECK(!in_loop_);
DoLoop(GetReadResult(bytes_read, current_request_.get()));
}
bool SdchDictionaryFetcher::ScheduleInternal(
const GURL& dictionary_url,
bool reload,
const OnDictionaryFetchedCallback& callback) {
DCHECK(CalledOnValidThread());
// If Push() fails, |dictionary_url| has already been fetched or scheduled to
// be fetched.
if (!fetch_queue_->Push(FetchInfo(dictionary_url, reload, callback))) {
// TODO(rdsmith): Log this error to the net log. In the case of a
// normal fetch, this can be through the URLRequest
// initiating this fetch (once the URLRequest is passed to the fetcher);
// in the case of a reload, it's more complicated.
SdchManager::SdchErrorRecovery(
SDCH_DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD);
return false;
}
// If the loop is already processing, it'll pick up the above in the
// normal course of events.
if (next_state_ != STATE_NONE)
return true;
next_state_ = STATE_SEND_REQUEST;
// There are no callbacks to user code from the dictionary fetcher,
// and Schedule() is only called from user code, so this call to DoLoop()
// does not require an |if (in_loop_) return;| guard.
DoLoop(OK);
return true;
}
void SdchDictionaryFetcher::ResetRequest() {
current_request_.reset();
buffer_ = nullptr;
current_callback_.Reset();
dictionary_.clear();
return;
}
int SdchDictionaryFetcher::DoLoop(int rv) {
DCHECK(!in_loop_);
base::AutoReset<bool> auto_reset_in_loop(&in_loop_, true);
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_SEND_REQUEST:
rv = DoSendRequest(rv);
break;
case STATE_SEND_REQUEST_COMPLETE:
rv = DoSendRequestComplete(rv);
break;
case STATE_READ_BODY:
rv = DoReadBody(rv);
break;
case STATE_READ_BODY_COMPLETE:
rv = DoReadBodyComplete(rv);
break;
case STATE_REQUEST_COMPLETE:
rv = DoCompleteRequest(rv);
break;
case STATE_NONE:
NOTREACHED();
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
return rv;
}
int SdchDictionaryFetcher::DoSendRequest(int rv) {
DCHECK(CalledOnValidThread());
// |rv| is ignored, as the result from the previous request doesn't
// affect the next request.
if (fetch_queue_->IsEmpty() || current_request_.get()) {
next_state_ = STATE_NONE;
return OK;
}
next_state_ = STATE_SEND_REQUEST_COMPLETE;
FetchInfo info;
bool success = fetch_queue_->Pop(&info);
DCHECK(success);
current_request_ = context_->CreateRequest(info.url, IDLE, this);
int load_flags = LOAD_DO_NOT_SEND_COOKIES | LOAD_DO_NOT_SAVE_COOKIES;
if (info.cache_only)
load_flags |= LOAD_ONLY_FROM_CACHE;
current_request_->SetLoadFlags(load_flags);
buffer_ = new IOBuffer(kBufferSize);
current_callback_ = info.callback;
current_request_->Start();
current_request_->net_log().AddEvent(NetLog::TYPE_SDCH_DICTIONARY_FETCH);
return ERR_IO_PENDING;
}
int SdchDictionaryFetcher::DoSendRequestComplete(int rv) {
DCHECK(CalledOnValidThread());
// If there's been an error, abort the current request.
if (rv != OK) {
current_request_.reset();
buffer_ = NULL;
next_state_ = STATE_SEND_REQUEST;
return OK;
}
next_state_ = STATE_READ_BODY;
return OK;
}
int SdchDictionaryFetcher::DoReadBody(int rv) {
DCHECK(CalledOnValidThread());
// If there's been an error, abort the current request.
if (rv != OK) {
ResetRequest();
next_state_ = STATE_SEND_REQUEST;
return OK;
}
next_state_ = STATE_READ_BODY_COMPLETE;
int bytes_read = 0;
current_request_->Read(buffer_.get(), kBufferSize, &bytes_read);
if (current_request_->status().is_io_pending())
return ERR_IO_PENDING;
return GetReadResult(bytes_read, current_request_.get());
}
int SdchDictionaryFetcher::DoReadBodyComplete(int rv) {
DCHECK(CalledOnValidThread());
// An error; abort the current request.
if (rv < 0) {
current_request_.reset();
buffer_ = NULL;
next_state_ = STATE_SEND_REQUEST;
return OK;
}
DCHECK(current_request_->status().is_success());
// Data; append to the dictionary and look for more data.
if (rv > 0) {
dictionary_.append(buffer_->data(), rv);
next_state_ = STATE_READ_BODY;
return OK;
}
// End of file; complete the request.
next_state_ = STATE_REQUEST_COMPLETE;
return OK;
}
int SdchDictionaryFetcher::DoCompleteRequest(int rv) {
DCHECK(CalledOnValidThread());
// If the dictionary was successfully fetched, add it to the manager.
if (rv == OK) {
current_callback_.Run(dictionary_, current_request_->url(),
current_request_->net_log());
}
ResetRequest();
next_state_ = STATE_SEND_REQUEST;
return OK;
}
} // namespace net