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. 4 5 #include "chrome/browser/net/http_pipelining_compatibility_client.h" 6 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/disk_cache/histogram_macros.h" 18 #include "net/http/http_network_layer.h" 19 #include "net/http/http_network_session.h" 20 #include "net/http/http_response_headers.h" 21 #include "net/http/http_version.h" 22 #include "net/proxy/proxy_config.h" 23 #include "net/proxy/proxy_service.h" 24 #include "net/url_request/url_request_context.h" 25 #include "net/url_request/url_request_context_getter.h" 26 27 namespace chrome_browser_net { 28 29 static const int kCanaryRequestId = 999; 30 31 namespace { 32 33 // There is one Request per RequestInfo passed in to Start() above. 34 class Request : public internal::PipelineTestRequest, 35 public net::URLRequest::Delegate { 36 public: 37 Request(int request_id, 38 const std::string& base_url, 39 const RequestInfo& info, 40 internal::PipelineTestRequest::Delegate* delegate, 41 net::URLRequestContext* url_request_context); 42 43 virtual ~Request() {} 44 45 virtual void Start() OVERRIDE; 46 47 protected: 48 // Called when this request has determined its result. Returns the result to 49 // the |client_|. 50 virtual void Finished(internal::PipelineTestRequest::Status result); 51 52 const std::string& response() const { return response_; } 53 54 internal::PipelineTestRequest::Delegate* delegate() { return delegate_; } 55 56 private: 57 // Called when a response can be read. Reads bytes into |response_| until it 58 // consumes the entire response or it encounters an error. 59 void DoRead(); 60 61 // Called when all bytes have been received. Compares the |response_| to 62 // |info_|'s expected response. 63 virtual void DoReadFinished(); 64 65 // net::URLRequest::Delegate interface 66 virtual void OnReceivedRedirect(net::URLRequest* request, 67 const GURL& new_url, 68 bool* defer_redirect) OVERRIDE; 69 virtual void OnSSLCertificateError(net::URLRequest* request, 70 const net::SSLInfo& ssl_info, 71 bool fatal) OVERRIDE; 72 virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; 73 virtual void OnReadCompleted(net::URLRequest* request, 74 int bytes_read) OVERRIDE; 75 76 internal::PipelineTestRequest::Delegate* delegate_; 77 const int request_id_; 78 scoped_ptr<net::URLRequest> url_request_; 79 const RequestInfo info_; 80 scoped_refptr<net::IOBuffer> read_buffer_; 81 std::string response_; 82 int response_code_; 83 }; 84 85 Request::Request(int request_id, 86 const std::string& base_url, 87 const RequestInfo& info, 88 internal::PipelineTestRequest::Delegate* delegate, 89 net::URLRequestContext* url_request_context) 90 : delegate_(delegate), 91 request_id_(request_id), 92 url_request_(url_request_context->CreateRequest( 93 GURL(base_url + info.filename), this)), 94 info_(info), 95 response_code_(0) { 96 url_request_->set_load_flags(net::LOAD_BYPASS_CACHE | 97 net::LOAD_DISABLE_CACHE | 98 net::LOAD_DO_NOT_SAVE_COOKIES | 99 net::LOAD_DO_NOT_SEND_COOKIES | 100 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN | 101 net::LOAD_DO_NOT_SEND_AUTH_DATA); 102 } 103 104 void Request::Start() { 105 url_request_->Start(); 106 } 107 108 void Request::OnReceivedRedirect( 109 net::URLRequest* request, 110 const GURL& new_url, 111 bool* defer_redirect) { 112 *defer_redirect = true; 113 request->Cancel(); 114 Finished(STATUS_REDIRECTED); 115 } 116 117 void Request::OnSSLCertificateError( 118 net::URLRequest* request, 119 const net::SSLInfo& ssl_info, 120 bool fatal) { 121 Finished(STATUS_CERT_ERROR); 122 } 123 124 void Request::OnResponseStarted(net::URLRequest* request) { 125 response_code_ = request->GetResponseCode(); 126 if (response_code_ != 200) { 127 Finished(STATUS_BAD_RESPONSE_CODE); 128 return; 129 } 130 const net::HttpVersion required_version(1, 1); 131 if (request->response_info().headers->GetParsedHttpVersion() < 132 required_version) { 133 Finished(STATUS_BAD_HTTP_VERSION); 134 return; 135 } 136 read_buffer_ = new net::IOBuffer(info_.expected_response.length()); 137 DoRead(); 138 } 139 140 void Request::OnReadCompleted(net::URLRequest* request, int bytes_read) { 141 if (bytes_read == 0) { 142 DoReadFinished(); 143 } else if (bytes_read < 0) { 144 Finished(STATUS_NETWORK_ERROR); 145 } else { 146 response_.append(read_buffer_->data(), bytes_read); 147 if (response_.length() <= info_.expected_response.length()) { 148 DoRead(); 149 } else if (response_.find(info_.expected_response) == 0) { 150 Finished(STATUS_TOO_LARGE); 151 } else { 152 Finished(STATUS_CONTENT_MISMATCH); 153 } 154 } 155 } 156 157 void Request::DoRead() { 158 int bytes_read = 0; 159 if (url_request_->Read(read_buffer_.get(), info_.expected_response.length(), 160 &bytes_read)) { 161 OnReadCompleted(url_request_.get(), bytes_read); 162 } 163 } 164 165 void Request::DoReadFinished() { 166 if (response_.length() != info_.expected_response.length()) { 167 if (info_.expected_response.find(response_) == 0) { 168 Finished(STATUS_TOO_SMALL); 169 } else { 170 Finished(STATUS_CONTENT_MISMATCH); 171 } 172 } else if (response_ == info_.expected_response) { 173 Finished(STATUS_SUCCESS); 174 } else { 175 Finished(STATUS_CONTENT_MISMATCH); 176 } 177 } 178 179 void Request::Finished(internal::PipelineTestRequest::Status result) { 180 const net::URLRequestStatus status = url_request_->status(); 181 url_request_.reset(); 182 if (response_code_ > 0) { 183 delegate()->ReportResponseCode(request_id_, response_code_); 184 } 185 if (status.status() == net::URLRequestStatus::FAILED) { 186 // Network errors trump all other status codes, because network errors can 187 // be detected by the network stack even with real content. If we determine 188 // that all pipelining errors can be detected by the network stack, then we 189 // don't need to worry about broken proxies. 190 delegate()->ReportNetworkError(request_id_, status.error()); 191 delegate()->OnRequestFinished(request_id_, STATUS_NETWORK_ERROR); 192 } else { 193 delegate()->OnRequestFinished(request_id_, result); 194 } 195 // WARNING: We may be deleted at this point. 196 } 197 198 // A special non-pipelined request sent before pipelining begins to test basic 199 // HTTP connectivity. 200 class CanaryRequest : public Request { 201 public: 202 CanaryRequest(int request_id, 203 const std::string& base_url, 204 const RequestInfo& info, 205 internal::PipelineTestRequest::Delegate* delegate, 206 net::URLRequestContext* url_request_context) 207 : Request(request_id, base_url, info, delegate, url_request_context) { 208 } 209 210 virtual ~CanaryRequest() {} 211 212 private: 213 virtual void Finished( 214 internal::PipelineTestRequest::Status result) OVERRIDE { 215 delegate()->OnCanaryFinished(result); 216 } 217 }; 218 219 // A special request that parses a /stats.txt response from the test server. 220 class StatsRequest : public Request { 221 public: 222 // Note that |info.expected_response| is only used to determine the correct 223 // length of the response. The exact string content isn't used. 224 StatsRequest(int request_id, 225 const std::string& base_url, 226 const RequestInfo& info, 227 internal::PipelineTestRequest::Delegate* delegate, 228 net::URLRequestContext* url_request_context) 229 : Request(request_id, base_url, info, delegate, url_request_context) { 230 } 231 232 virtual ~StatsRequest() {} 233 234 private: 235 virtual void DoReadFinished() OVERRIDE { 236 internal::PipelineTestRequest::Status status = 237 internal::ProcessStatsResponse(response()); 238 Finished(status); 239 } 240 }; 241 242 class RequestFactory : public internal::PipelineTestRequest::Factory { 243 public: 244 virtual internal::PipelineTestRequest* NewRequest( 245 int request_id, 246 const std::string& base_url, 247 const RequestInfo& info, 248 internal::PipelineTestRequest::Delegate* delegate, 249 net::URLRequestContext* url_request_context, 250 internal::PipelineTestRequest::Type request_type) OVERRIDE { 251 switch (request_type) { 252 case internal::PipelineTestRequest::TYPE_PIPELINED: 253 return new Request(request_id, base_url, info, delegate, 254 url_request_context); 255 256 case internal::PipelineTestRequest::TYPE_CANARY: 257 return new CanaryRequest(request_id, base_url, info, delegate, 258 url_request_context); 259 260 case internal::PipelineTestRequest::TYPE_STATS: 261 return new StatsRequest(request_id, base_url, info, delegate, 262 url_request_context); 263 264 default: 265 NOTREACHED(); 266 return NULL; 267 } 268 } 269 }; 270 271 } // anonymous namespace 272 273 HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient( 274 internal::PipelineTestRequest::Factory* factory) 275 : factory_(factory), 276 num_finished_(0), 277 num_succeeded_(0) { 278 if (!factory_.get()) { 279 factory_.reset(new RequestFactory); 280 } 281 } 282 283 HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() { 284 } 285 286 void HttpPipeliningCompatibilityClient::Start( 287 const std::string& base_url, 288 std::vector<RequestInfo>& requests, 289 Options options, 290 const net::CompletionCallback& callback, 291 net::URLRequestContext* url_request_context) { 292 net::HttpNetworkSession* old_session = 293 url_request_context->http_transaction_factory()->GetSession(); 294 net::HttpNetworkSession::Params params = old_session->params(); 295 params.force_http_pipelining = true; 296 scoped_refptr<net::HttpNetworkSession> session = 297 new net::HttpNetworkSession(params); 298 http_transaction_factory_.reset( 299 net::HttpNetworkLayer::CreateFactory(session.get())); 300 301 url_request_context_.reset(new net::URLRequestContext); 302 url_request_context_->CopyFrom(url_request_context); 303 url_request_context_->set_http_transaction_factory( 304 http_transaction_factory_.get()); 305 306 finished_callback_ = callback; 307 for (size_t i = 0; i < requests.size(); ++i) { 308 requests_.push_back(factory_->NewRequest( 309 i, base_url, requests[i], this, url_request_context_.get(), 310 internal::PipelineTestRequest::TYPE_PIPELINED)); 311 } 312 if (options == PIPE_TEST_COLLECT_SERVER_STATS || 313 options == PIPE_TEST_CANARY_AND_STATS) { 314 RequestInfo info; 315 info.filename = "stats.txt"; 316 // This is just to determine the expected length of the response. 317 // StatsRequest doesn't expect this exact value, but it does expect this 318 // exact length. 319 info.expected_response = 320 "were_all_requests_http_1_1:1,max_pipeline_depth:5"; 321 requests_.push_back(factory_->NewRequest( 322 requests.size(), base_url, info, this, url_request_context_.get(), 323 internal::PipelineTestRequest::TYPE_STATS)); 324 } 325 if (options == PIPE_TEST_RUN_CANARY_REQUEST || 326 options == PIPE_TEST_CANARY_AND_STATS) { 327 RequestInfo info; 328 info.filename = "index.html"; 329 info.expected_response = 330 "\nThis is a test server operated by Google. It's used by Google " 331 "Chrome to test\nproxies for compatibility with HTTP pipelining. More " 332 "information can be found\nhere:\n\nhttp://dev.chromium.org/developers/" 333 "design-documents/network-stack/http-pipelining\n\nSource code can be " 334 "found here:\n\nhttp://code.google.com/p/http-pipelining-test/\n"; 335 canary_request_.reset(factory_->NewRequest( 336 kCanaryRequestId, base_url, info, this, url_request_context, 337 internal::PipelineTestRequest::TYPE_CANARY)); 338 canary_request_->Start(); 339 } else { 340 StartTestRequests(); 341 } 342 } 343 344 void HttpPipeliningCompatibilityClient::StartTestRequests() { 345 for (size_t i = 0; i < requests_.size(); ++i) { 346 requests_[i]->Start(); 347 } 348 } 349 350 void HttpPipeliningCompatibilityClient::OnCanaryFinished( 351 internal::PipelineTestRequest::Status status) { 352 canary_request_.reset(); 353 bool success = (status == internal::PipelineTestRequest::STATUS_SUCCESS); 354 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.CanarySuccess", success); 355 if (success) { 356 StartTestRequests(); 357 } else { 358 finished_callback_.Run(0); 359 } 360 } 361 362 void HttpPipeliningCompatibilityClient::OnRequestFinished( 363 int request_id, internal::PipelineTestRequest::Status status) { 364 // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric 365 // names. 366 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"), 367 status, 368 internal::PipelineTestRequest::STATUS_MAX); 369 370 ++num_finished_; 371 if (status == internal::PipelineTestRequest::STATUS_SUCCESS) { 372 ++num_succeeded_; 373 } 374 if (num_finished_ == requests_.size()) { 375 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success", 376 num_succeeded_ == requests_.size()); 377 finished_callback_.Run(0); 378 } 379 } 380 381 void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id, 382 int error_code) { 383 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "NetworkError"), 384 -error_code, 900); 385 } 386 387 void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id, 388 int response_code) { 389 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "ResponseCode"), 390 response_code, 600); 391 } 392 393 std::string HttpPipeliningCompatibilityClient::GetMetricName( 394 int request_id, const char* description) { 395 return base::StringPrintf("NetConnectivity.Pipeline.%d.%s", 396 request_id, description); 397 } 398 399 namespace internal { 400 401 internal::PipelineTestRequest::Status ProcessStatsResponse( 402 const std::string& response) { 403 bool were_all_requests_http_1_1 = false; 404 int max_pipeline_depth = 0; 405 406 std::vector<std::pair<std::string, std::string> > kv_pairs; 407 base::SplitStringIntoKeyValuePairs(response, ':', ',', &kv_pairs); 408 409 if (kv_pairs.size() != 2) { 410 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 411 } 412 413 for (size_t i = 0; i < kv_pairs.size(); ++i) { 414 const std::string& key = kv_pairs[i].first; 415 int value; 416 if (!base::StringToInt(kv_pairs[i].second, &value)) { 417 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 418 } 419 420 if (key == "were_all_requests_http_1_1") { 421 were_all_requests_http_1_1 = (value == 1); 422 } else if (key == "max_pipeline_depth") { 423 max_pipeline_depth = value; 424 } else { 425 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 426 } 427 } 428 429 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.AllHTTP11", 430 were_all_requests_http_1_1); 431 UMA_HISTOGRAM_ENUMERATION("NetConnectivity.Pipeline.Depth", 432 max_pipeline_depth, 6); 433 434 return internal::PipelineTestRequest::STATUS_SUCCESS; 435 } 436 437 } // namespace internal 438 439 namespace { 440 441 void DeleteClient(IOThread* io_thread, int /* rv */) { 442 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 443 io_thread->globals()->http_pipelining_compatibility_client.reset(); 444 } 445 446 void CollectPipeliningCapabilityStatsOnIOThread( 447 const std::string& pipeline_test_server, 448 IOThread* io_thread) { 449 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 450 451 net::URLRequestContext* url_request_context = 452 io_thread->globals()->system_request_context.get(); 453 if (!url_request_context->proxy_service()->config().proxy_rules().empty()) { 454 // Pipelining with explicitly configured proxies is disabled for now. 455 return; 456 } 457 458 const base::FieldTrial::Probability kDivisor = 100; 459 base::FieldTrial::Probability probability_to_run_test = 0; 460 461 const char* kTrialName = "HttpPipeliningCompatibility"; 462 base::FieldTrial* trial = base::FieldTrialList::Find(kTrialName); 463 if (trial) { 464 return; 465 } 466 // After May 4, 2012, the trial will disable itself. 467 trial = base::FieldTrialList::FactoryGetFieldTrial( 468 kTrialName, kDivisor, "disable_test", 2012, 5, 4, 469 base::FieldTrial::SESSION_RANDOMIZED, NULL); 470 471 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 472 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 473 probability_to_run_test = 100; 474 } else if (channel == chrome::VersionInfo::CHANNEL_DEV) { 475 probability_to_run_test = 100; 476 } 477 478 int collect_stats_group = trial->AppendGroup("enable_test", 479 probability_to_run_test); 480 if (trial->group() != collect_stats_group) { 481 return; 482 } 483 484 std::vector<RequestInfo> requests; 485 486 RequestInfo info0; 487 info0.filename = "alphabet.txt"; 488 info0.expected_response = "abcdefghijklmnopqrstuvwxyz"; 489 requests.push_back(info0); 490 491 RequestInfo info1; 492 info1.filename = "cached.txt"; 493 info1.expected_response = "azbycxdwevfugthsirjqkplomn"; 494 requests.push_back(info1); 495 496 RequestInfo info2; 497 info2.filename = "reverse.txt"; 498 info2.expected_response = "zyxwvutsrqponmlkjihgfedcba"; 499 requests.push_back(info2); 500 501 RequestInfo info3; 502 info3.filename = "chunked.txt"; 503 info3.expected_response = "chunkedencodingisfun"; 504 requests.push_back(info3); 505 506 RequestInfo info4; 507 info4.filename = "cached.txt"; 508 info4.expected_response = "azbycxdwevfugthsirjqkplomn"; 509 requests.push_back(info4); 510 511 HttpPipeliningCompatibilityClient* client = 512 new HttpPipeliningCompatibilityClient(NULL); 513 client->Start(pipeline_test_server, requests, 514 HttpPipeliningCompatibilityClient::PIPE_TEST_CANARY_AND_STATS, 515 base::Bind(&DeleteClient, io_thread), 516 url_request_context); 517 io_thread->globals()->http_pipelining_compatibility_client.reset(client); 518 } 519 520 } // anonymous namespace 521 522 void CollectPipeliningCapabilityStatsOnUIThread( 523 const std::string& pipeline_test_server, IOThread* io_thread) { 524 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 525 if (pipeline_test_server.empty()) 526 return; 527 528 content::BrowserThread::PostTask( 529 content::BrowserThread::IO, 530 FROM_HERE, 531 base::Bind(&CollectPipeliningCapabilityStatsOnIOThread, 532 pipeline_test_server, 533 io_thread)); 534 } 535 536 } // namespace chrome_browser_net 537