blob: 4ccafbbdb2875f8d05b3c57c04fa8ce065fcc7f8 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/net/http_pipelining_compatibility_client.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/io_thread.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/base/network_change_notifier.h"
#include "net/base/request_priority.h"
#include "net/disk_cache/histogram_macros.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_network_session.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
namespace chrome_browser_net {
static const int kCanaryRequestId = 999;
namespace {
// There is one Request per RequestInfo passed in to Start() above.
class Request : public internal::PipelineTestRequest,
public net::URLRequest::Delegate {
public:
Request(int request_id,
const std::string& base_url,
const RequestInfo& info,
internal::PipelineTestRequest::Delegate* delegate,
net::URLRequestContext* url_request_context);
virtual ~Request() {}
virtual void Start() OVERRIDE;
protected:
// Called when this request has determined its result. Returns the result to
// the |client_|.
virtual void Finished(internal::PipelineTestRequest::Status result);
const std::string& response() const { return response_; }
internal::PipelineTestRequest::Delegate* delegate() { return delegate_; }
private:
// Called when a response can be read. Reads bytes into |response_| until it
// consumes the entire response or it encounters an error.
void DoRead();
// Called when all bytes have been received. Compares the |response_| to
// |info_|'s expected response.
virtual void DoReadFinished();
// net::URLRequest::Delegate interface
virtual void OnReceivedRedirect(net::URLRequest* request,
const GURL& new_url,
bool* defer_redirect) OVERRIDE;
virtual void OnSSLCertificateError(net::URLRequest* request,
const net::SSLInfo& ssl_info,
bool fatal) OVERRIDE;
virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
virtual void OnReadCompleted(net::URLRequest* request,
int bytes_read) OVERRIDE;
internal::PipelineTestRequest::Delegate* delegate_;
const int request_id_;
scoped_ptr<net::URLRequest> url_request_;
const RequestInfo info_;
scoped_refptr<net::IOBuffer> read_buffer_;
std::string response_;
int response_code_;
};
Request::Request(int request_id,
const std::string& base_url,
const RequestInfo& info,
internal::PipelineTestRequest::Delegate* delegate,
net::URLRequestContext* url_request_context)
: delegate_(delegate),
request_id_(request_id),
url_request_(url_request_context->CreateRequest(GURL(base_url +
info.filename),
net::DEFAULT_PRIORITY,
this)),
info_(info),
response_code_(0) {
url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
net::LOAD_DO_NOT_SEND_AUTH_DATA);
}
void Request::Start() {
url_request_->Start();
}
void Request::OnReceivedRedirect(
net::URLRequest* request,
const GURL& new_url,
bool* defer_redirect) {
*defer_redirect = true;
request->Cancel();
Finished(STATUS_REDIRECTED);
}
void Request::OnSSLCertificateError(
net::URLRequest* request,
const net::SSLInfo& ssl_info,
bool fatal) {
Finished(STATUS_CERT_ERROR);
}
void Request::OnResponseStarted(net::URLRequest* request) {
response_code_ = request->GetResponseCode();
if (response_code_ != 200) {
Finished(STATUS_BAD_RESPONSE_CODE);
return;
}
const net::HttpVersion required_version(1, 1);
if (request->response_info().headers->GetParsedHttpVersion() <
required_version) {
Finished(STATUS_BAD_HTTP_VERSION);
return;
}
read_buffer_ = new net::IOBuffer(info_.expected_response.length());
DoRead();
}
void Request::OnReadCompleted(net::URLRequest* request, int bytes_read) {
if (bytes_read == 0) {
DoReadFinished();
} else if (bytes_read < 0) {
Finished(STATUS_NETWORK_ERROR);
} else {
response_.append(read_buffer_->data(), bytes_read);
if (response_.length() <= info_.expected_response.length()) {
DoRead();
} else if (response_.find(info_.expected_response) == 0) {
Finished(STATUS_TOO_LARGE);
} else {
Finished(STATUS_CONTENT_MISMATCH);
}
}
}
void Request::DoRead() {
int bytes_read = 0;
if (url_request_->Read(read_buffer_.get(), info_.expected_response.length(),
&bytes_read)) {
OnReadCompleted(url_request_.get(), bytes_read);
}
}
void Request::DoReadFinished() {
if (response_.length() != info_.expected_response.length()) {
if (info_.expected_response.find(response_) == 0) {
Finished(STATUS_TOO_SMALL);
} else {
Finished(STATUS_CONTENT_MISMATCH);
}
} else if (response_ == info_.expected_response) {
Finished(STATUS_SUCCESS);
} else {
Finished(STATUS_CONTENT_MISMATCH);
}
}
void Request::Finished(internal::PipelineTestRequest::Status result) {
const net::URLRequestStatus status = url_request_->status();
url_request_.reset();
if (response_code_ > 0) {
delegate()->ReportResponseCode(request_id_, response_code_);
}
if (status.status() == net::URLRequestStatus::FAILED) {
// Network errors trump all other status codes, because network errors can
// be detected by the network stack even with real content. If we determine
// that all pipelining errors can be detected by the network stack, then we
// don't need to worry about broken proxies.
delegate()->ReportNetworkError(request_id_, status.error());
delegate()->OnRequestFinished(request_id_, STATUS_NETWORK_ERROR);
} else {
delegate()->OnRequestFinished(request_id_, result);
}
// WARNING: We may be deleted at this point.
}
// A special non-pipelined request sent before pipelining begins to test basic
// HTTP connectivity.
class CanaryRequest : public Request {
public:
CanaryRequest(int request_id,
const std::string& base_url,
const RequestInfo& info,
internal::PipelineTestRequest::Delegate* delegate,
net::URLRequestContext* url_request_context)
: Request(request_id, base_url, info, delegate, url_request_context) {
}
virtual ~CanaryRequest() {}
private:
virtual void Finished(
internal::PipelineTestRequest::Status result) OVERRIDE {
delegate()->OnCanaryFinished(result);
}
};
// A special request that parses a /stats.txt response from the test server.
class StatsRequest : public Request {
public:
// Note that |info.expected_response| is only used to determine the correct
// length of the response. The exact string content isn't used.
StatsRequest(int request_id,
const std::string& base_url,
const RequestInfo& info,
internal::PipelineTestRequest::Delegate* delegate,
net::URLRequestContext* url_request_context)
: Request(request_id, base_url, info, delegate, url_request_context) {
}
virtual ~StatsRequest() {}
private:
virtual void DoReadFinished() OVERRIDE {
internal::PipelineTestRequest::Status status =
internal::ProcessStatsResponse(response());
Finished(status);
}
};
class RequestFactory : public internal::PipelineTestRequest::Factory {
public:
virtual internal::PipelineTestRequest* NewRequest(
int request_id,
const std::string& base_url,
const RequestInfo& info,
internal::PipelineTestRequest::Delegate* delegate,
net::URLRequestContext* url_request_context,
internal::PipelineTestRequest::Type request_type) OVERRIDE {
switch (request_type) {
case internal::PipelineTestRequest::TYPE_PIPELINED:
return new Request(request_id, base_url, info, delegate,
url_request_context);
case internal::PipelineTestRequest::TYPE_CANARY:
return new CanaryRequest(request_id, base_url, info, delegate,
url_request_context);
case internal::PipelineTestRequest::TYPE_STATS:
return new StatsRequest(request_id, base_url, info, delegate,
url_request_context);
default:
NOTREACHED();
return NULL;
}
}
};
} // anonymous namespace
HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient(
internal::PipelineTestRequest::Factory* factory)
: factory_(factory),
num_finished_(0),
num_succeeded_(0) {
if (!factory_.get()) {
factory_.reset(new RequestFactory);
}
}
HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() {
}
void HttpPipeliningCompatibilityClient::Start(
const std::string& base_url,
std::vector<RequestInfo>& requests,
Options options,
const net::CompletionCallback& callback,
net::URLRequestContext* url_request_context) {
net::HttpNetworkSession* old_session =
url_request_context->http_transaction_factory()->GetSession();
net::HttpNetworkSession::Params params = old_session->params();
params.force_http_pipelining = true;
scoped_refptr<net::HttpNetworkSession> session =
new net::HttpNetworkSession(params);
http_transaction_factory_.reset(
net::HttpNetworkLayer::CreateFactory(session.get()));
url_request_context_.reset(new net::URLRequestContext);
url_request_context_->CopyFrom(url_request_context);
url_request_context_->set_http_transaction_factory(
http_transaction_factory_.get());
finished_callback_ = callback;
for (size_t i = 0; i < requests.size(); ++i) {
requests_.push_back(factory_->NewRequest(
i, base_url, requests[i], this, url_request_context_.get(),
internal::PipelineTestRequest::TYPE_PIPELINED));
}
if (options == PIPE_TEST_COLLECT_SERVER_STATS ||
options == PIPE_TEST_CANARY_AND_STATS) {
RequestInfo info;
info.filename = "stats.txt";
// This is just to determine the expected length of the response.
// StatsRequest doesn't expect this exact value, but it does expect this
// exact length.
info.expected_response =
"were_all_requests_http_1_1:1,max_pipeline_depth:5";
requests_.push_back(factory_->NewRequest(
requests.size(), base_url, info, this, url_request_context_.get(),
internal::PipelineTestRequest::TYPE_STATS));
}
if (options == PIPE_TEST_RUN_CANARY_REQUEST ||
options == PIPE_TEST_CANARY_AND_STATS) {
RequestInfo info;
info.filename = "index.html";
info.expected_response =
"\nThis is a test server operated by Google. It's used by Google "
"Chrome to test\nproxies for compatibility with HTTP pipelining. More "
"information can be found\nhere:\n\nhttp://dev.chromium.org/developers/"
"design-documents/network-stack/http-pipelining\n\nSource code can be "
"found here:\n\nhttp://code.google.com/p/http-pipelining-test/\n";
canary_request_.reset(factory_->NewRequest(
kCanaryRequestId, base_url, info, this, url_request_context,
internal::PipelineTestRequest::TYPE_CANARY));
canary_request_->Start();
} else {
StartTestRequests();
}
}
void HttpPipeliningCompatibilityClient::StartTestRequests() {
for (size_t i = 0; i < requests_.size(); ++i) {
requests_[i]->Start();
}
}
void HttpPipeliningCompatibilityClient::OnCanaryFinished(
internal::PipelineTestRequest::Status status) {
canary_request_.reset();
bool success = (status == internal::PipelineTestRequest::STATUS_SUCCESS);
UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.CanarySuccess", success);
if (success) {
StartTestRequests();
} else {
finished_callback_.Run(0);
}
}
void HttpPipeliningCompatibilityClient::OnRequestFinished(
int request_id, internal::PipelineTestRequest::Status status) {
// The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric
// names.
CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"),
status,
internal::PipelineTestRequest::STATUS_MAX);
++num_finished_;
if (status == internal::PipelineTestRequest::STATUS_SUCCESS) {
++num_succeeded_;
}
if (num_finished_ == requests_.size()) {
UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success",
num_succeeded_ == requests_.size());
finished_callback_.Run(0);
}
}
void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id,
int error_code) {
CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "NetworkError"),
-error_code, 900);
}
void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id,
int response_code) {
CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "ResponseCode"),
response_code, 600);
}
std::string HttpPipeliningCompatibilityClient::GetMetricName(
int request_id, const char* description) {
return base::StringPrintf("NetConnectivity.Pipeline.%d.%s",
request_id, description);
}
namespace internal {
internal::PipelineTestRequest::Status ProcessStatsResponse(
const std::string& response) {
bool were_all_requests_http_1_1 = false;
int max_pipeline_depth = 0;
std::vector<std::pair<std::string, std::string> > kv_pairs;
base::SplitStringIntoKeyValuePairs(response, ':', ',', &kv_pairs);
if (kv_pairs.size() != 2) {
return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
}
for (size_t i = 0; i < kv_pairs.size(); ++i) {
const std::string& key = kv_pairs[i].first;
int value;
if (!base::StringToInt(kv_pairs[i].second, &value)) {
return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
}
if (key == "were_all_requests_http_1_1") {
were_all_requests_http_1_1 = (value == 1);
} else if (key == "max_pipeline_depth") {
max_pipeline_depth = value;
} else {
return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
}
}
UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.AllHTTP11",
were_all_requests_http_1_1);
UMA_HISTOGRAM_ENUMERATION("NetConnectivity.Pipeline.Depth",
max_pipeline_depth, 6);
return internal::PipelineTestRequest::STATUS_SUCCESS;
}
} // namespace internal
namespace {
void DeleteClient(IOThread* io_thread, int /* rv */) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
io_thread->globals()->http_pipelining_compatibility_client.reset();
}
void CollectPipeliningCapabilityStatsOnIOThread(
const std::string& pipeline_test_server,
IOThread* io_thread) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
net::URLRequestContext* url_request_context =
io_thread->globals()->system_request_context.get();
if (!url_request_context->proxy_service()->config().proxy_rules().empty()) {
// Pipelining with explicitly configured proxies is disabled for now.
return;
}
const base::FieldTrial::Probability kDivisor = 100;
base::FieldTrial::Probability probability_to_run_test = 0;
const char* kTrialName = "HttpPipeliningCompatibility";
base::FieldTrial* trial = base::FieldTrialList::Find(kTrialName);
if (trial) {
return;
}
// After May 4, 2012, the trial will disable itself.
trial = base::FieldTrialList::FactoryGetFieldTrial(
kTrialName, kDivisor, "disable_test", 2012, 5, 4,
base::FieldTrial::SESSION_RANDOMIZED, NULL);
chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
probability_to_run_test = 100;
} else if (channel == chrome::VersionInfo::CHANNEL_DEV) {
probability_to_run_test = 100;
}
int collect_stats_group = trial->AppendGroup("enable_test",
probability_to_run_test);
if (trial->group() != collect_stats_group) {
return;
}
std::vector<RequestInfo> requests;
RequestInfo info0;
info0.filename = "alphabet.txt";
info0.expected_response = "abcdefghijklmnopqrstuvwxyz";
requests.push_back(info0);
RequestInfo info1;
info1.filename = "cached.txt";
info1.expected_response = "azbycxdwevfugthsirjqkplomn";
requests.push_back(info1);
RequestInfo info2;
info2.filename = "reverse.txt";
info2.expected_response = "zyxwvutsrqponmlkjihgfedcba";
requests.push_back(info2);
RequestInfo info3;
info3.filename = "chunked.txt";
info3.expected_response = "chunkedencodingisfun";
requests.push_back(info3);
RequestInfo info4;
info4.filename = "cached.txt";
info4.expected_response = "azbycxdwevfugthsirjqkplomn";
requests.push_back(info4);
HttpPipeliningCompatibilityClient* client =
new HttpPipeliningCompatibilityClient(NULL);
client->Start(pipeline_test_server, requests,
HttpPipeliningCompatibilityClient::PIPE_TEST_CANARY_AND_STATS,
base::Bind(&DeleteClient, io_thread),
url_request_context);
io_thread->globals()->http_pipelining_compatibility_client.reset(client);
}
} // anonymous namespace
void CollectPipeliningCapabilityStatsOnUIThread(
const std::string& pipeline_test_server, IOThread* io_thread) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (pipeline_test_server.empty())
return;
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&CollectPipeliningCapabilityStatsOnIOThread,
pipeline_test_server,
io_thread));
}
} // namespace chrome_browser_net