1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/net/http_pipelining_compatibility_client.h"
7 #include "base/metrics/field_trial.h"
8 #include "base/metrics/histogram.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/stringprintf.h"
12 #include "chrome/browser/io_thread.h"
13 #include "chrome/common/chrome_version_info.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "net/base/load_flags.h"
16 #include "net/base/network_change_notifier.h"
17 #include "net/base/request_priority.h"
18 #include "net/disk_cache/histogram_macros.h"
19 #include "net/http/http_network_layer.h"
20 #include "net/http/http_network_session.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/http/http_version.h"
23 #include "net/proxy/proxy_config.h"
24 #include "net/proxy/proxy_service.h"
25 #include "net/url_request/url_request_context.h"
26 #include "net/url_request/url_request_context_getter.h"
28 namespace chrome_browser_net
{
30 static const int kCanaryRequestId
= 999;
34 // There is one Request per RequestInfo passed in to Start() above.
35 class Request
: public internal::PipelineTestRequest
,
36 public net::URLRequest::Delegate
{
38 Request(int request_id
,
39 const std::string
& base_url
,
40 const RequestInfo
& info
,
41 internal::PipelineTestRequest::Delegate
* delegate
,
42 net::URLRequestContext
* url_request_context
);
46 virtual void Start() OVERRIDE
;
49 // Called when this request has determined its result. Returns the result to
51 virtual void Finished(internal::PipelineTestRequest::Status result
);
53 const std::string
& response() const { return response_
; }
55 internal::PipelineTestRequest::Delegate
* delegate() { return delegate_
; }
58 // Called when a response can be read. Reads bytes into |response_| until it
59 // consumes the entire response or it encounters an error.
62 // Called when all bytes have been received. Compares the |response_| to
63 // |info_|'s expected response.
64 virtual void DoReadFinished();
66 // net::URLRequest::Delegate interface
67 virtual void OnReceivedRedirect(net::URLRequest
* request
,
69 bool* defer_redirect
) OVERRIDE
;
70 virtual void OnSSLCertificateError(net::URLRequest
* request
,
71 const net::SSLInfo
& ssl_info
,
73 virtual void OnResponseStarted(net::URLRequest
* request
) OVERRIDE
;
74 virtual void OnReadCompleted(net::URLRequest
* request
,
75 int bytes_read
) OVERRIDE
;
77 internal::PipelineTestRequest::Delegate
* delegate_
;
78 const int request_id_
;
79 scoped_ptr
<net::URLRequest
> url_request_
;
80 const RequestInfo info_
;
81 scoped_refptr
<net::IOBuffer
> read_buffer_
;
82 std::string response_
;
86 Request::Request(int request_id
,
87 const std::string
& base_url
,
88 const RequestInfo
& info
,
89 internal::PipelineTestRequest::Delegate
* delegate
,
90 net::URLRequestContext
* url_request_context
)
91 : delegate_(delegate
),
92 request_id_(request_id
),
93 url_request_(url_request_context
->CreateRequest(GURL(base_url
+
95 net::DEFAULT_PRIORITY
,
99 url_request_
->SetLoadFlags(net::LOAD_BYPASS_CACHE
|
100 net::LOAD_DISABLE_CACHE
|
101 net::LOAD_DO_NOT_SAVE_COOKIES
|
102 net::LOAD_DO_NOT_SEND_COOKIES
|
103 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN
|
104 net::LOAD_DO_NOT_SEND_AUTH_DATA
);
107 void Request::Start() {
108 url_request_
->Start();
111 void Request::OnReceivedRedirect(
112 net::URLRequest
* request
,
114 bool* defer_redirect
) {
115 *defer_redirect
= true;
117 Finished(STATUS_REDIRECTED
);
120 void Request::OnSSLCertificateError(
121 net::URLRequest
* request
,
122 const net::SSLInfo
& ssl_info
,
124 Finished(STATUS_CERT_ERROR
);
127 void Request::OnResponseStarted(net::URLRequest
* request
) {
128 response_code_
= request
->GetResponseCode();
129 if (response_code_
!= 200) {
130 Finished(STATUS_BAD_RESPONSE_CODE
);
133 const net::HttpVersion
required_version(1, 1);
134 if (request
->response_info().headers
->GetParsedHttpVersion() <
136 Finished(STATUS_BAD_HTTP_VERSION
);
139 read_buffer_
= new net::IOBuffer(info_
.expected_response
.length());
143 void Request::OnReadCompleted(net::URLRequest
* request
, int bytes_read
) {
144 if (bytes_read
== 0) {
146 } else if (bytes_read
< 0) {
147 Finished(STATUS_NETWORK_ERROR
);
149 response_
.append(read_buffer_
->data(), bytes_read
);
150 if (response_
.length() <= info_
.expected_response
.length()) {
152 } else if (response_
.find(info_
.expected_response
) == 0) {
153 Finished(STATUS_TOO_LARGE
);
155 Finished(STATUS_CONTENT_MISMATCH
);
160 void Request::DoRead() {
162 if (url_request_
->Read(read_buffer_
.get(), info_
.expected_response
.length(),
164 OnReadCompleted(url_request_
.get(), bytes_read
);
168 void Request::DoReadFinished() {
169 if (response_
.length() != info_
.expected_response
.length()) {
170 if (info_
.expected_response
.find(response_
) == 0) {
171 Finished(STATUS_TOO_SMALL
);
173 Finished(STATUS_CONTENT_MISMATCH
);
175 } else if (response_
== info_
.expected_response
) {
176 Finished(STATUS_SUCCESS
);
178 Finished(STATUS_CONTENT_MISMATCH
);
182 void Request::Finished(internal::PipelineTestRequest::Status result
) {
183 const net::URLRequestStatus status
= url_request_
->status();
184 url_request_
.reset();
185 if (response_code_
> 0) {
186 delegate()->ReportResponseCode(request_id_
, response_code_
);
188 if (status
.status() == net::URLRequestStatus::FAILED
) {
189 // Network errors trump all other status codes, because network errors can
190 // be detected by the network stack even with real content. If we determine
191 // that all pipelining errors can be detected by the network stack, then we
192 // don't need to worry about broken proxies.
193 delegate()->ReportNetworkError(request_id_
, status
.error());
194 delegate()->OnRequestFinished(request_id_
, STATUS_NETWORK_ERROR
);
196 delegate()->OnRequestFinished(request_id_
, result
);
198 // WARNING: We may be deleted at this point.
201 // A special non-pipelined request sent before pipelining begins to test basic
202 // HTTP connectivity.
203 class CanaryRequest
: public Request
{
205 CanaryRequest(int request_id
,
206 const std::string
& base_url
,
207 const RequestInfo
& info
,
208 internal::PipelineTestRequest::Delegate
* delegate
,
209 net::URLRequestContext
* url_request_context
)
210 : Request(request_id
, base_url
, info
, delegate
, url_request_context
) {
213 virtual ~CanaryRequest() {}
216 virtual void Finished(
217 internal::PipelineTestRequest::Status result
) OVERRIDE
{
218 delegate()->OnCanaryFinished(result
);
222 // A special request that parses a /stats.txt response from the test server.
223 class StatsRequest
: public Request
{
225 // Note that |info.expected_response| is only used to determine the correct
226 // length of the response. The exact string content isn't used.
227 StatsRequest(int request_id
,
228 const std::string
& base_url
,
229 const RequestInfo
& info
,
230 internal::PipelineTestRequest::Delegate
* delegate
,
231 net::URLRequestContext
* url_request_context
)
232 : Request(request_id
, base_url
, info
, delegate
, url_request_context
) {
235 virtual ~StatsRequest() {}
238 virtual void DoReadFinished() OVERRIDE
{
239 internal::PipelineTestRequest::Status status
=
240 internal::ProcessStatsResponse(response());
245 class RequestFactory
: public internal::PipelineTestRequest::Factory
{
247 virtual internal::PipelineTestRequest
* NewRequest(
249 const std::string
& base_url
,
250 const RequestInfo
& info
,
251 internal::PipelineTestRequest::Delegate
* delegate
,
252 net::URLRequestContext
* url_request_context
,
253 internal::PipelineTestRequest::Type request_type
) OVERRIDE
{
254 switch (request_type
) {
255 case internal::PipelineTestRequest::TYPE_PIPELINED
:
256 return new Request(request_id
, base_url
, info
, delegate
,
257 url_request_context
);
259 case internal::PipelineTestRequest::TYPE_CANARY
:
260 return new CanaryRequest(request_id
, base_url
, info
, delegate
,
261 url_request_context
);
263 case internal::PipelineTestRequest::TYPE_STATS
:
264 return new StatsRequest(request_id
, base_url
, info
, delegate
,
265 url_request_context
);
274 } // anonymous namespace
276 HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient(
277 internal::PipelineTestRequest::Factory
* factory
)
281 if (!factory_
.get()) {
282 factory_
.reset(new RequestFactory
);
286 HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() {
289 void HttpPipeliningCompatibilityClient::Start(
290 const std::string
& base_url
,
291 std::vector
<RequestInfo
>& requests
,
293 const net::CompletionCallback
& callback
,
294 net::URLRequestContext
* url_request_context
) {
295 net::HttpNetworkSession
* old_session
=
296 url_request_context
->http_transaction_factory()->GetSession();
297 net::HttpNetworkSession::Params params
= old_session
->params();
298 params
.force_http_pipelining
= true;
299 scoped_refptr
<net::HttpNetworkSession
> session
=
300 new net::HttpNetworkSession(params
);
301 http_transaction_factory_
.reset(
302 net::HttpNetworkLayer::CreateFactory(session
.get()));
304 url_request_context_
.reset(new net::URLRequestContext
);
305 url_request_context_
->CopyFrom(url_request_context
);
306 url_request_context_
->set_http_transaction_factory(
307 http_transaction_factory_
.get());
309 finished_callback_
= callback
;
310 for (size_t i
= 0; i
< requests
.size(); ++i
) {
311 requests_
.push_back(factory_
->NewRequest(
312 i
, base_url
, requests
[i
], this, url_request_context_
.get(),
313 internal::PipelineTestRequest::TYPE_PIPELINED
));
315 if (options
== PIPE_TEST_COLLECT_SERVER_STATS
||
316 options
== PIPE_TEST_CANARY_AND_STATS
) {
318 info
.filename
= "stats.txt";
319 // This is just to determine the expected length of the response.
320 // StatsRequest doesn't expect this exact value, but it does expect this
322 info
.expected_response
=
323 "were_all_requests_http_1_1:1,max_pipeline_depth:5";
324 requests_
.push_back(factory_
->NewRequest(
325 requests
.size(), base_url
, info
, this, url_request_context_
.get(),
326 internal::PipelineTestRequest::TYPE_STATS
));
328 if (options
== PIPE_TEST_RUN_CANARY_REQUEST
||
329 options
== PIPE_TEST_CANARY_AND_STATS
) {
331 info
.filename
= "index.html";
332 info
.expected_response
=
333 "\nThis is a test server operated by Google. It's used by Google "
334 "Chrome to test\nproxies for compatibility with HTTP pipelining. More "
335 "information can be found\nhere:\n\nhttp://dev.chromium.org/developers/"
336 "design-documents/network-stack/http-pipelining\n\nSource code can be "
337 "found here:\n\nhttp://code.google.com/p/http-pipelining-test/\n";
338 canary_request_
.reset(factory_
->NewRequest(
339 kCanaryRequestId
, base_url
, info
, this, url_request_context
,
340 internal::PipelineTestRequest::TYPE_CANARY
));
341 canary_request_
->Start();
347 void HttpPipeliningCompatibilityClient::StartTestRequests() {
348 for (size_t i
= 0; i
< requests_
.size(); ++i
) {
349 requests_
[i
]->Start();
353 void HttpPipeliningCompatibilityClient::OnCanaryFinished(
354 internal::PipelineTestRequest::Status status
) {
355 canary_request_
.reset();
356 bool success
= (status
== internal::PipelineTestRequest::STATUS_SUCCESS
);
357 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.CanarySuccess", success
);
361 finished_callback_
.Run(0);
365 void HttpPipeliningCompatibilityClient::OnRequestFinished(
366 int request_id
, internal::PipelineTestRequest::Status status
) {
367 // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric
369 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id
, "Status"),
371 internal::PipelineTestRequest::STATUS_MAX
);
374 if (status
== internal::PipelineTestRequest::STATUS_SUCCESS
) {
377 if (num_finished_
== requests_
.size()) {
378 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success",
379 num_succeeded_
== requests_
.size());
380 finished_callback_
.Run(0);
384 void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id
,
386 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id
, "NetworkError"),
390 void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id
,
392 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id
, "ResponseCode"),
396 std::string
HttpPipeliningCompatibilityClient::GetMetricName(
397 int request_id
, const char* description
) {
398 return base::StringPrintf("NetConnectivity.Pipeline.%d.%s",
399 request_id
, description
);
404 internal::PipelineTestRequest::Status
ProcessStatsResponse(
405 const std::string
& response
) {
406 bool were_all_requests_http_1_1
= false;
407 int max_pipeline_depth
= 0;
409 std::vector
<std::pair
<std::string
, std::string
> > kv_pairs
;
410 base::SplitStringIntoKeyValuePairs(response
, ':', ',', &kv_pairs
);
412 if (kv_pairs
.size() != 2) {
413 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS
;
416 for (size_t i
= 0; i
< kv_pairs
.size(); ++i
) {
417 const std::string
& key
= kv_pairs
[i
].first
;
419 if (!base::StringToInt(kv_pairs
[i
].second
, &value
)) {
420 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS
;
423 if (key
== "were_all_requests_http_1_1") {
424 were_all_requests_http_1_1
= (value
== 1);
425 } else if (key
== "max_pipeline_depth") {
426 max_pipeline_depth
= value
;
428 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS
;
432 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.AllHTTP11",
433 were_all_requests_http_1_1
);
434 UMA_HISTOGRAM_ENUMERATION("NetConnectivity.Pipeline.Depth",
435 max_pipeline_depth
, 6);
437 return internal::PipelineTestRequest::STATUS_SUCCESS
;
440 } // namespace internal
444 void DeleteClient(IOThread
* io_thread
, int /* rv */) {
445 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
446 io_thread
->globals()->http_pipelining_compatibility_client
.reset();
449 void CollectPipeliningCapabilityStatsOnIOThread(
450 const std::string
& pipeline_test_server
,
451 IOThread
* io_thread
) {
452 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
454 net::URLRequestContext
* url_request_context
=
455 io_thread
->globals()->system_request_context
.get();
456 if (!url_request_context
->proxy_service()->config().proxy_rules().empty()) {
457 // Pipelining with explicitly configured proxies is disabled for now.
461 const base::FieldTrial::Probability kDivisor
= 100;
462 base::FieldTrial::Probability probability_to_run_test
= 0;
464 const char* kTrialName
= "HttpPipeliningCompatibility";
465 base::FieldTrial
* trial
= base::FieldTrialList::Find(kTrialName
);
469 // After May 4, 2012, the trial will disable itself.
470 trial
= base::FieldTrialList::FactoryGetFieldTrial(
471 kTrialName
, kDivisor
, "disable_test", 2012, 5, 4,
472 base::FieldTrial::SESSION_RANDOMIZED
, NULL
);
474 chrome::VersionInfo::Channel channel
= chrome::VersionInfo::GetChannel();
475 if (channel
== chrome::VersionInfo::CHANNEL_CANARY
) {
476 probability_to_run_test
= 100;
477 } else if (channel
== chrome::VersionInfo::CHANNEL_DEV
) {
478 probability_to_run_test
= 100;
481 int collect_stats_group
= trial
->AppendGroup("enable_test",
482 probability_to_run_test
);
483 if (trial
->group() != collect_stats_group
) {
487 std::vector
<RequestInfo
> requests
;
490 info0
.filename
= "alphabet.txt";
491 info0
.expected_response
= "abcdefghijklmnopqrstuvwxyz";
492 requests
.push_back(info0
);
495 info1
.filename
= "cached.txt";
496 info1
.expected_response
= "azbycxdwevfugthsirjqkplomn";
497 requests
.push_back(info1
);
500 info2
.filename
= "reverse.txt";
501 info2
.expected_response
= "zyxwvutsrqponmlkjihgfedcba";
502 requests
.push_back(info2
);
505 info3
.filename
= "chunked.txt";
506 info3
.expected_response
= "chunkedencodingisfun";
507 requests
.push_back(info3
);
510 info4
.filename
= "cached.txt";
511 info4
.expected_response
= "azbycxdwevfugthsirjqkplomn";
512 requests
.push_back(info4
);
514 HttpPipeliningCompatibilityClient
* client
=
515 new HttpPipeliningCompatibilityClient(NULL
);
516 client
->Start(pipeline_test_server
, requests
,
517 HttpPipeliningCompatibilityClient::PIPE_TEST_CANARY_AND_STATS
,
518 base::Bind(&DeleteClient
, io_thread
),
519 url_request_context
);
520 io_thread
->globals()->http_pipelining_compatibility_client
.reset(client
);
523 } // anonymous namespace
525 void CollectPipeliningCapabilityStatsOnUIThread(
526 const std::string
& pipeline_test_server
, IOThread
* io_thread
) {
527 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
528 if (pipeline_test_server
.empty())
531 content::BrowserThread::PostTask(
532 content::BrowserThread::IO
,
534 base::Bind(&CollectPipeliningCapabilityStatsOnIOThread
,
535 pipeline_test_server
,
539 } // namespace chrome_browser_net