diff options
-rw-r--r-- | chrome/browser/net/http_pipelining_compatibility_client.cc | 188 | ||||
-rw-r--r-- | chrome/browser/net/http_pipelining_compatibility_client.h | 125 | ||||
-rw-r--r-- | chrome/browser/net/http_pipelining_compatibility_client_unittest.cc | 440 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/test/data/http_pipelining/alphabet.txt | 1 | ||||
-rw-r--r-- | chrome/test/data/http_pipelining/alphabet.txt.mock-http-headers | 1 | ||||
-rw-r--r-- | net/base/net_error_list.h | 8 | ||||
-rwxr-xr-x | net/tools/testserver/testserver.py | 16 |
9 files changed, 777 insertions, 5 deletions
diff --git a/chrome/browser/net/http_pipelining_compatibility_client.cc b/chrome/browser/net/http_pipelining_compatibility_client.cc new file mode 100644 index 0000000..2a44b2c --- /dev/null +++ b/chrome/browser/net/http_pipelining_compatibility_client.cc @@ -0,0 +1,188 @@ +// 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/histogram.h" +#include "base/stringprintf.h" +#include "net/base/load_flags.h" +#include "net/disk_cache/histogram_macros.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_version.h" + +namespace chrome_browser_net { + +HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient() + : num_finished_(0) { +} + +HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() { +} + +void HttpPipeliningCompatibilityClient::Start( + const std::string& base_url, + std::vector<RequestInfo>& requests, + const net::CompletionCallback& callback, + net::URLRequestContext* url_request_context) { + finished_callback_ = callback; + for (size_t i = 0; i < requests.size(); ++i) { + requests_.push_back(new Request(i, base_url, requests[i], this, + url_request_context)); + } +} + +void HttpPipeliningCompatibilityClient::OnRequestFinished(int request_id, + Status status) { + // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric + // names. + CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"), + status, STATUS_MAX); + ++num_finished_; + if (num_finished_ == 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); +} + +HttpPipeliningCompatibilityClient::Request::Request( + int request_id, + const std::string& base_url, + const RequestInfo& info, + HttpPipeliningCompatibilityClient* client, + net::URLRequestContext* url_request_context) + : request_id_(request_id), + request_(GURL(base_url + info.filename), this), + info_(info), + client_(client), + finished_(false) { + request_.set_context(url_request_context); + // TODO(simonjam): Force pipelining. + request_.set_load_flags(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); + request_.Start(); +} + +HttpPipeliningCompatibilityClient::Request::~Request() { +} + +void HttpPipeliningCompatibilityClient::Request::OnReceivedRedirect( + net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + *defer_redirect = true; + request->Cancel(); + Finished(REDIRECTED); +} + +void HttpPipeliningCompatibilityClient::Request::OnSSLCertificateError( + net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) { + Finished(CERT_ERROR); +} + +void HttpPipeliningCompatibilityClient::Request::OnResponseStarted( + net::URLRequest* request) { + if (finished_) { + return; + } + int response_code = request->GetResponseCode(); + if (response_code > 0) { + client_->ReportResponseCode(request_id_, response_code); + } + if (response_code == 200) { + const net::HttpVersion required_version(1, 1); + if (request->response_info().headers->GetParsedHttpVersion() < + required_version) { + Finished(BAD_HTTP_VERSION); + } else { + read_buffer_ = new net::IOBuffer(info_.expected_response.length()); + DoRead(); + } + } else { + Finished(BAD_RESPONSE_CODE); + } +} + +void HttpPipeliningCompatibilityClient::Request::OnReadCompleted( + net::URLRequest* request, + int bytes_read) { + if (bytes_read == 0) { + DoReadFinished(); + } else if (bytes_read < 0) { + Finished(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(TOO_LARGE); + } else { + Finished(CONTENT_MISMATCH); + } + } +} + +void HttpPipeliningCompatibilityClient::Request::DoRead() { + int bytes_read = 0; + if (request_.Read(read_buffer_.get(), info_.expected_response.length(), + &bytes_read)) { + OnReadCompleted(&request_, bytes_read); + } +} + +void HttpPipeliningCompatibilityClient::Request::DoReadFinished() { + if (response_.length() != info_.expected_response.length()) { + if (info_.expected_response.find(response_) == 0) { + Finished(TOO_SMALL); + } else { + Finished(CONTENT_MISMATCH); + } + } else if (response_ == info_.expected_response) { + Finished(SUCCESS); + } else { + Finished(CONTENT_MISMATCH); + } +} + +void HttpPipeliningCompatibilityClient::Request::Finished(Status result) { + if (finished_) { + return; + } + finished_ = true; + const net::URLRequestStatus& status = request_.status(); + 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. + client_->ReportNetworkError(request_id_, status.error()); + client_->OnRequestFinished(request_id_, NETWORK_ERROR); + return; + } + client_->OnRequestFinished(request_id_, result); +} + +} // namespace chrome_browser_net diff --git a/chrome/browser/net/http_pipelining_compatibility_client.h b/chrome/browser/net/http_pipelining_compatibility_client.h new file mode 100644 index 0000000..1931b8c --- /dev/null +++ b/chrome/browser/net/http_pipelining_compatibility_client.h @@ -0,0 +1,125 @@ +// 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. + +#ifndef CHROME_BROWSER_NET_HTTP_PIPELINING_COMPATIBILITY_CLIENT_H_ +#define CHROME_BROWSER_NET_HTTP_PIPELINING_COMPATIBILITY_CLIENT_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/memory/scoped_vector.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" + +namespace chrome_browser_net { + +// Class for performing a background test of users' Internet connections. +// Fetches a collection of resources on a test server and verifies all were +// received correctly. This will be used to determine whether or not proxies are +// interfering with a user's ability to use HTTP pipelining. Results are +// recorded with UMA. +// +// TODO(simonjam): Connect this to something. We should start with a field trial +// that affects a subset of canary channel users. But first, we need a test +// server. +class HttpPipeliningCompatibilityClient { + public: + struct RequestInfo { + std::string filename; // The path relative to the test server's base_url. + std::string expected_response; // The expected body of the response. + }; + + enum Status { + SUCCESS, + REDIRECTED, // Response was redirected. We won't follow. + CERT_ERROR, // Any certificate problem. + BAD_RESPONSE_CODE, // Any non-200 response. + NETWORK_ERROR, // Any socket error reported by the network layer. + TOO_LARGE, // The response matched, but had extra data on the end. + TOO_SMALL, // The response was shorter than expected, but what we + // got matched. + CONTENT_MISMATCH, // The response didn't match the expected value. + BAD_HTTP_VERSION, // Any version older than HTTP/1.1. + STATUS_MAX, + }; + + HttpPipeliningCompatibilityClient(); + ~HttpPipeliningCompatibilityClient(); + + // Launches the asynchronous URLRequests to fetch the URLs specified by + // |requests| combined with |base_url|. |base_url| should match the pattern + // "http://host/". |callback| is invoked once all the requests have completed. + // URLRequests are initiated in |url_request_context|. Results are recorded to + // UMA as they are received. + void Start(const std::string& base_url, + std::vector<RequestInfo>& requests, + const net::CompletionCallback& callback, + net::URLRequestContext* url_request_context); + + private: + // There is one Request per RequestInfo passed in to Start() above. + class Request : public net::URLRequest::Delegate { + public: + Request(int request_id, + const std::string& base_url, + const RequestInfo& info, + HttpPipeliningCompatibilityClient* client, + net::URLRequestContext* url_request_context); + virtual ~Request(); + + // 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; + + 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. + void DoReadFinished(); + + // Called when this request has determined its result. Returns the result to + // the |client_|. + void Finished(Status result); + + const int request_id_; + net::URLRequest request_; + const RequestInfo info_; + HttpPipeliningCompatibilityClient* client_; + bool finished_; + scoped_refptr<net::IOBuffer> read_buffer_; + std::string response_; + }; + + // Called when a Request determines its result. Reports to UMA. + void OnRequestFinished(int request_id, Status status); + + // Called when a Request encounters a network error. Reports to UMA. + void ReportNetworkError(int request_id, int error_code); + + // Called when a Request determines its HTTP response code. Reports to UMA. + void ReportResponseCode(int request_id, int response_code); + + // Returns the full UMA metric name based on |request_id| and |description|. + std::string GetMetricName(int request_id, const char* description); + + ScopedVector<Request> requests_; + net::CompletionCallback finished_callback_; + size_t num_finished_; +}; + +} // namespace chrome_browser_net + +#endif // CHROME_BROWSER_NET_HTTP_PIPELINING_COMPATIBILITY_CLIENT_H_ diff --git a/chrome/browser/net/http_pipelining_compatibility_client_unittest.cc b/chrome/browser/net/http_pipelining_compatibility_client_unittest.cc new file mode 100644 index 0000000..240d6ef --- /dev/null +++ b/chrome/browser/net/http_pipelining_compatibility_client_unittest.cc @@ -0,0 +1,440 @@ +// 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 <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "chrome/test/base/test_url_request_context_getter.h" +#include "content/test/test_browser_thread.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/test/test_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_browser_net { + +namespace { + +static const char* const kHistogramNames[] = { + "NetConnectivity.Pipeline.0.NetworkError", + "NetConnectivity.Pipeline.0.ResponseCode", + "NetConnectivity.Pipeline.0.Status", + "NetConnectivity.Pipeline.1.NetworkError", + "NetConnectivity.Pipeline.1.ResponseCode", + "NetConnectivity.Pipeline.1.Status", + "NetConnectivity.Pipeline.2.NetworkError", + "NetConnectivity.Pipeline.2.ResponseCode", + "NetConnectivity.Pipeline.2.Status", +}; + +enum HistogramField { + FIELD_NETWORK_ERROR, + FIELD_RESPONSE_CODE, + FIELD_STATUS, +}; + +class HttpPipeliningCompatibilityClientTest : public testing::Test { + public: + HttpPipeliningCompatibilityClientTest() + : test_server_( + net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data/http_pipelining"))) { + } + + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(test_server_.Start()); + context_ = new TestURLRequestContextGetter; + context_->AddRef(); + + for (size_t i = 0; i < arraysize(kHistogramNames); ++i) { + const char* name = kHistogramNames[i]; + base::Histogram::SampleSet sample = GetHistogram(name); + if (sample.TotalCount() > 0) { + original_samples_[name] = sample; + } + } + } + + virtual void TearDown() OVERRIDE { + content::BrowserThread::ReleaseSoon(content::BrowserThread::IO, + FROM_HERE, context_); + message_loop_.RunAllPending(); + } + + void RunTest( + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests) { + HttpPipeliningCompatibilityClient client; + net::TestCompletionCallback callback; + client.Start(test_server_.GetURL("").spec(), + requests, callback.callback(), + context_->GetURLRequestContext()); + callback.WaitForResult(); + + for (size_t i = 0; i < arraysize(kHistogramNames); ++i) { + const char* name = kHistogramNames[i]; + base::Histogram::SampleSet sample = GetHistogram(name); + if (ContainsKey(original_samples_, name)) { + sample.Subtract(original_samples_[name]); + } + samples_[name] = sample; + } + } + + base::Histogram::SampleSet GetHistogramValue(int request_id, + HistogramField field) { + const char* field_str = ""; + switch (field) { + case FIELD_STATUS: + field_str = "Status"; + break; + + case FIELD_NETWORK_ERROR: + field_str = "NetworkError"; + break; + + case FIELD_RESPONSE_CODE: + field_str = "ResponseCode"; + break; + + default: + NOTREACHED(); + break; + } + + std::string name = base::StringPrintf("NetConnectivity.Pipeline.%d.%s", + request_id, field_str); + return samples_[name]; + } + + MessageLoopForIO message_loop_; + net::TestServer test_server_; + TestURLRequestContextGetter* context_; + + private: + base::Histogram::SampleSet GetHistogram(const char* name) { + base::Histogram::SampleSet sample; + base::Histogram* histogram; + if (ContainsKey(histograms_, name)) { + histogram = histograms_[name]; + histogram->SnapshotSample(&sample); + } else if (base::StatisticsRecorder::FindHistogram(name, &histogram)) { + histograms_[name] = histogram; + histogram->SnapshotSample(&sample); + } + return sample; + } + + static std::map<std::string, base::Histogram*> histograms_; + std::map<std::string, base::Histogram::SampleSet> samples_; + std::map<std::string, base::Histogram::SampleSet> original_samples_; + base::StatisticsRecorder recorder_; +}; + +// static +std::map<std::string, base::Histogram*> + HttpPipeliningCompatibilityClientTest::histograms_; + +TEST_F(HttpPipeliningCompatibilityClientTest, Success) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "files/alphabet.txt"; + info.expected_response = "abcdefghijklmnopqrstuvwxyz"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::SUCCESS)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(200)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, TooSmall) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "files/alphabet.txt"; + info.expected_response = "abcdefghijklmnopqrstuvwxyz26"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::TOO_SMALL)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(200)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, TooLarge) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "files/alphabet.txt"; + info.expected_response = "abc"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::TOO_LARGE)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(200)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, Mismatch) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "files/alphabet.txt"; + info.expected_response = "zyxwvutsrqponmlkjihgfedcba"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::CONTENT_MISMATCH)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(200)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, Redirect) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "server-redirect?http://foo.bar/asdf"; + info.expected_response = "shouldn't matter"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::REDIRECTED)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(0, response_sample.TotalCount()); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, AuthRequired) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "auth-basic"; + info.expected_response = "shouldn't matter"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::BAD_RESPONSE_CODE)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(401)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, NoContent) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "nocontent"; + info.expected_response = "shouldn't matter"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::BAD_RESPONSE_CODE)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(204)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, CloseSocket) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "close-socket"; + info.expected_response = "shouldn't matter"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::NETWORK_ERROR)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(1, network_sample.TotalCount()); + EXPECT_EQ(1, network_sample.counts(-net::ERR_EMPTY_RESPONSE)); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(0, response_sample.TotalCount()); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, OldHttpVersion) { + HttpPipeliningCompatibilityClient::RequestInfo info; + info.filename = "http-1.0"; + info.expected_response = "abcdefghijklmnopqrstuvwxyz"; + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + requests.push_back(info); + + RunTest(requests); + + base::Histogram::SampleSet status_sample = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample.TotalCount()); + EXPECT_EQ(1, status_sample.counts( + HttpPipeliningCompatibilityClient::BAD_HTTP_VERSION)); + + base::Histogram::SampleSet network_sample = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample.TotalCount()); + + base::Histogram::SampleSet response_sample = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample.TotalCount()); + EXPECT_EQ(1, response_sample.counts(200)); +} + +TEST_F(HttpPipeliningCompatibilityClientTest, MultipleRequests) { + std::vector<HttpPipeliningCompatibilityClient::RequestInfo> requests; + + HttpPipeliningCompatibilityClient::RequestInfo info1; + info1.filename = "files/alphabet.txt"; + info1.expected_response = "abcdefghijklmnopqrstuvwxyz"; + requests.push_back(info1); + + HttpPipeliningCompatibilityClient::RequestInfo info2; + info2.filename = "close-socket"; + info2.expected_response = "shouldn't matter"; + requests.push_back(info2); + + HttpPipeliningCompatibilityClient::RequestInfo info3; + info3.filename = "auth-basic"; + info3.expected_response = "shouldn't matter"; + requests.push_back(info3); + + RunTest(requests); + + base::Histogram::SampleSet status_sample1 = + GetHistogramValue(0, FIELD_STATUS); + EXPECT_EQ(1, status_sample1.TotalCount()); + EXPECT_EQ(1, status_sample1.counts( + HttpPipeliningCompatibilityClient::SUCCESS)); + + base::Histogram::SampleSet network_sample1 = + GetHistogramValue(0, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample1.TotalCount()); + + base::Histogram::SampleSet response_sample1 = + GetHistogramValue(0, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample1.TotalCount()); + EXPECT_EQ(1, response_sample1.counts(200)); + + base::Histogram::SampleSet status_sample2 = + GetHistogramValue(1, FIELD_STATUS); + EXPECT_EQ(1, status_sample2.TotalCount()); + EXPECT_EQ(1, status_sample2.counts( + HttpPipeliningCompatibilityClient::NETWORK_ERROR)); + + base::Histogram::SampleSet network_sample2 = + GetHistogramValue(1, FIELD_NETWORK_ERROR); + EXPECT_EQ(1, network_sample2.TotalCount()); + EXPECT_EQ(1, network_sample2.counts(-net::ERR_EMPTY_RESPONSE)); + + base::Histogram::SampleSet response_sample2 = + GetHistogramValue(1, FIELD_RESPONSE_CODE); + EXPECT_EQ(0, response_sample2.TotalCount()); + + base::Histogram::SampleSet status_sample3 = + GetHistogramValue(2, FIELD_STATUS); + EXPECT_EQ(1, status_sample3.TotalCount()); + EXPECT_EQ(1, status_sample3.counts( + HttpPipeliningCompatibilityClient::BAD_RESPONSE_CODE)); + + base::Histogram::SampleSet network_sample3 = + GetHistogramValue(2, FIELD_NETWORK_ERROR); + EXPECT_EQ(0, network_sample3.TotalCount()); + + base::Histogram::SampleSet response_sample3 = + GetHistogramValue(2, FIELD_RESPONSE_CODE); + EXPECT_EQ(1, response_sample3.TotalCount()); + EXPECT_EQ(1, response_sample3.counts(401)); +} + +} // anonymous namespace + +} // namespace chrome_browser_net diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 13897c9..81c969c 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1674,6 +1674,8 @@ 'browser/net/gaia/gaia_oauth_fetcher.h', 'browser/net/load_timing_observer.cc', 'browser/net/load_timing_observer.h', + 'browser/net/http_pipelining_compatibility_client.cc', + 'browser/net/http_pipelining_compatibility_client.h', 'browser/net/net_log_logger.cc', 'browser/net/net_log_logger.h', 'browser/net/net_pref_observer.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 0745992..a7910c4 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1531,6 +1531,7 @@ 'browser/net/chrome_net_log_unittest.cc', 'browser/net/connection_tester_unittest.cc', 'browser/net/gaia/gaia_oauth_fetcher_unittest.cc', + 'browser/net/http_pipelining_compatibility_client_unittest.cc', 'browser/net/http_server_properties_manager_unittest.cc', 'browser/net/load_timing_observer_unittest.cc', 'browser/net/network_stats_unittest.cc', diff --git a/chrome/test/data/http_pipelining/alphabet.txt b/chrome/test/data/http_pipelining/alphabet.txt new file mode 100644 index 0000000..e85d5b4 --- /dev/null +++ b/chrome/test/data/http_pipelining/alphabet.txt @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyz
\ No newline at end of file diff --git a/chrome/test/data/http_pipelining/alphabet.txt.mock-http-headers b/chrome/test/data/http_pipelining/alphabet.txt.mock-http-headers new file mode 100644 index 0000000..a919d52 --- /dev/null +++ b/chrome/test/data/http_pipelining/alphabet.txt.mock-http-headers @@ -0,0 +1 @@ +HTTP/1.1 200 OK diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 37f5f31..8f27cd7 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -522,6 +522,9 @@ NET_ERROR(SPDY_SERVER_REFUSED_STREAM, -351) // SPDY server didn't respond to the PING message. NET_ERROR(SPDY_PING_FAILED, -352) +// The request couldn't be completed on an HTTP pipeline. Client should retry. +NET_ERROR(PIPELINE_EVICTION, -353) + // The cache does not have the requested entry. NET_ERROR(CACHE_MISS, -400) @@ -651,6 +654,3 @@ NET_ERROR(DNS_TIMED_OUT, -803) // The entry was not found in cache, for cache-only lookups. NET_ERROR(DNS_CACHE_MISS, -804) - -// FIXME: Take the next number. -NET_ERROR(PIPELINE_EVICTION, -900) diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index f917789..12882b5 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -378,6 +378,7 @@ class TestPageHandler(BasePageHandler): self.MultipartHandler, self.MultipartSlowHandler, self.GetSSLSessionCacheHandler, + self.CloseSocketHandler, self.DefaultResponseHandler] post_handlers = [ self.EchoTitleHandler, @@ -922,6 +923,7 @@ class TestPageHandler(BasePageHandler): return self._FileHandlerHelper(prefix) def _FileHandlerHelper(self, prefix): + old_protocol_version = self.protocol_version _, _, url_path, _, query, _ = urlparse.urlparse(self.path) sub_path = url_path[len(prefix):] entries = sub_path.split('/') @@ -948,7 +950,9 @@ class TestPageHandler(BasePageHandler): # "HTTP/1.1 200 OK" response = f.readline() - status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0] + http_major, http_minor, status_code = re.findall( + 'HTTP/(\d+).(\d+) (\d+)', response)[0] + self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor) self.send_response(int(status_code)) for line in f: @@ -990,6 +994,7 @@ class TestPageHandler(BasePageHandler): if (self.command != 'HEAD'): self.wfile.write(data) + self.protocol_version = old_protocol_version return True def SetCookieHandler(self): @@ -1427,6 +1432,15 @@ class TestPageHandler(BasePageHandler): ' this request') return True + def CloseSocketHandler(self): + """Closes the socket without sending anything.""" + + if not self._ShouldHandleRequest('/close-socket'): + return False + + self.wfile.close() + return True + def DefaultResponseHandler(self): """This is the catch-all response handler for requests that aren't handled by one of the special handlers above. |