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/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" 27 28 namespace chrome_browser_net { 29 30 static const int kCanaryRequestId = 999; 31 32 namespace { 33 34 // There is one Request per RequestInfo passed in to Start() above. 35 class Request : public internal::PipelineTestRequest, 36 public net::URLRequest::Delegate { 37 public: 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); 43 44 virtual ~Request() {} 45 46 virtual void Start() OVERRIDE; 47 48 protected: 49 // Called when this request has determined its result. Returns the result to 50 // the |client_|. 51 virtual void Finished(internal::PipelineTestRequest::Status result); 52 53 const std::string& response() const { return response_; } 54 55 internal::PipelineTestRequest::Delegate* delegate() { return delegate_; } 56 57 private: 58 // Called when a response can be read. Reads bytes into |response_| until it 59 // consumes the entire response or it encounters an error. 60 void DoRead(); 61 62 // Called when all bytes have been received. Compares the |response_| to 63 // |info_|'s expected response. 64 virtual void DoReadFinished(); 65 66 // net::URLRequest::Delegate interface 67 virtual void OnReceivedRedirect(net::URLRequest* request, 68 const GURL& new_url, 69 bool* defer_redirect) OVERRIDE; 70 virtual void OnSSLCertificateError(net::URLRequest* request, 71 const net::SSLInfo& ssl_info, 72 bool fatal) OVERRIDE; 73 virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; 74 virtual void OnReadCompleted(net::URLRequest* request, 75 int bytes_read) OVERRIDE; 76 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_; 83 int response_code_; 84 }; 85 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 + 94 info.filename), 95 net::DEFAULT_PRIORITY, 96 this)), 97 info_(info), 98 response_code_(0) { 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); 105 } 106 107 void Request::Start() { 108 url_request_->Start(); 109 } 110 111 void Request::OnReceivedRedirect( 112 net::URLRequest* request, 113 const GURL& new_url, 114 bool* defer_redirect) { 115 *defer_redirect = true; 116 request->Cancel(); 117 Finished(STATUS_REDIRECTED); 118 } 119 120 void Request::OnSSLCertificateError( 121 net::URLRequest* request, 122 const net::SSLInfo& ssl_info, 123 bool fatal) { 124 Finished(STATUS_CERT_ERROR); 125 } 126 127 void Request::OnResponseStarted(net::URLRequest* request) { 128 response_code_ = request->GetResponseCode(); 129 if (response_code_ != 200) { 130 Finished(STATUS_BAD_RESPONSE_CODE); 131 return; 132 } 133 const net::HttpVersion required_version(1, 1); 134 if (request->response_info().headers->GetParsedHttpVersion() < 135 required_version) { 136 Finished(STATUS_BAD_HTTP_VERSION); 137 return; 138 } 139 read_buffer_ = new net::IOBuffer(info_.expected_response.length()); 140 DoRead(); 141 } 142 143 void Request::OnReadCompleted(net::URLRequest* request, int bytes_read) { 144 if (bytes_read == 0) { 145 DoReadFinished(); 146 } else if (bytes_read < 0) { 147 Finished(STATUS_NETWORK_ERROR); 148 } else { 149 response_.append(read_buffer_->data(), bytes_read); 150 if (response_.length() <= info_.expected_response.length()) { 151 DoRead(); 152 } else if (response_.find(info_.expected_response) == 0) { 153 Finished(STATUS_TOO_LARGE); 154 } else { 155 Finished(STATUS_CONTENT_MISMATCH); 156 } 157 } 158 } 159 160 void Request::DoRead() { 161 int bytes_read = 0; 162 if (url_request_->Read(read_buffer_.get(), info_.expected_response.length(), 163 &bytes_read)) { 164 OnReadCompleted(url_request_.get(), bytes_read); 165 } 166 } 167 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); 172 } else { 173 Finished(STATUS_CONTENT_MISMATCH); 174 } 175 } else if (response_ == info_.expected_response) { 176 Finished(STATUS_SUCCESS); 177 } else { 178 Finished(STATUS_CONTENT_MISMATCH); 179 } 180 } 181 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_); 187 } 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); 195 } else { 196 delegate()->OnRequestFinished(request_id_, result); 197 } 198 // WARNING: We may be deleted at this point. 199 } 200 201 // A special non-pipelined request sent before pipelining begins to test basic 202 // HTTP connectivity. 203 class CanaryRequest : public Request { 204 public: 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) { 211 } 212 213 virtual ~CanaryRequest() {} 214 215 private: 216 virtual void Finished( 217 internal::PipelineTestRequest::Status result) OVERRIDE { 218 delegate()->OnCanaryFinished(result); 219 } 220 }; 221 222 // A special request that parses a /stats.txt response from the test server. 223 class StatsRequest : public Request { 224 public: 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) { 233 } 234 235 virtual ~StatsRequest() {} 236 237 private: 238 virtual void DoReadFinished() OVERRIDE { 239 internal::PipelineTestRequest::Status status = 240 internal::ProcessStatsResponse(response()); 241 Finished(status); 242 } 243 }; 244 245 class RequestFactory : public internal::PipelineTestRequest::Factory { 246 public: 247 virtual internal::PipelineTestRequest* NewRequest( 248 int request_id, 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); 258 259 case internal::PipelineTestRequest::TYPE_CANARY: 260 return new CanaryRequest(request_id, base_url, info, delegate, 261 url_request_context); 262 263 case internal::PipelineTestRequest::TYPE_STATS: 264 return new StatsRequest(request_id, base_url, info, delegate, 265 url_request_context); 266 267 default: 268 NOTREACHED(); 269 return NULL; 270 } 271 } 272 }; 273 274 } // anonymous namespace 275 276 HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient( 277 internal::PipelineTestRequest::Factory* factory) 278 : factory_(factory), 279 num_finished_(0), 280 num_succeeded_(0) { 281 if (!factory_.get()) { 282 factory_.reset(new RequestFactory); 283 } 284 } 285 286 HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() { 287 } 288 289 void HttpPipeliningCompatibilityClient::Start( 290 const std::string& base_url, 291 std::vector<RequestInfo>& requests, 292 Options options, 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())); 303 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()); 308 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)); 314 } 315 if (options == PIPE_TEST_COLLECT_SERVER_STATS || 316 options == PIPE_TEST_CANARY_AND_STATS) { 317 RequestInfo info; 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 321 // exact length. 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)); 327 } 328 if (options == PIPE_TEST_RUN_CANARY_REQUEST || 329 options == PIPE_TEST_CANARY_AND_STATS) { 330 RequestInfo info; 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(); 342 } else { 343 StartTestRequests(); 344 } 345 } 346 347 void HttpPipeliningCompatibilityClient::StartTestRequests() { 348 for (size_t i = 0; i < requests_.size(); ++i) { 349 requests_[i]->Start(); 350 } 351 } 352 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); 358 if (success) { 359 StartTestRequests(); 360 } else { 361 finished_callback_.Run(0); 362 } 363 } 364 365 void HttpPipeliningCompatibilityClient::OnRequestFinished( 366 int request_id, internal::PipelineTestRequest::Status status) { 367 // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric 368 // names. 369 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"), 370 status, 371 internal::PipelineTestRequest::STATUS_MAX); 372 373 ++num_finished_; 374 if (status == internal::PipelineTestRequest::STATUS_SUCCESS) { 375 ++num_succeeded_; 376 } 377 if (num_finished_ == requests_.size()) { 378 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success", 379 num_succeeded_ == requests_.size()); 380 finished_callback_.Run(0); 381 } 382 } 383 384 void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id, 385 int error_code) { 386 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "NetworkError"), 387 -error_code, 900); 388 } 389 390 void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id, 391 int response_code) { 392 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "ResponseCode"), 393 response_code, 600); 394 } 395 396 std::string HttpPipeliningCompatibilityClient::GetMetricName( 397 int request_id, const char* description) { 398 return base::StringPrintf("NetConnectivity.Pipeline.%d.%s", 399 request_id, description); 400 } 401 402 namespace internal { 403 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; 408 409 std::vector<std::pair<std::string, std::string> > kv_pairs; 410 base::SplitStringIntoKeyValuePairs(response, ':', ',', &kv_pairs); 411 412 if (kv_pairs.size() != 2) { 413 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 414 } 415 416 for (size_t i = 0; i < kv_pairs.size(); ++i) { 417 const std::string& key = kv_pairs[i].first; 418 int value; 419 if (!base::StringToInt(kv_pairs[i].second, &value)) { 420 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 421 } 422 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; 427 } else { 428 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS; 429 } 430 } 431 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); 436 437 return internal::PipelineTestRequest::STATUS_SUCCESS; 438 } 439 440 } // namespace internal 441 442 namespace { 443 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(); 447 } 448 449 void CollectPipeliningCapabilityStatsOnIOThread( 450 const std::string& pipeline_test_server, 451 IOThread* io_thread) { 452 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 453 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. 458 return; 459 } 460 461 const base::FieldTrial::Probability kDivisor = 100; 462 base::FieldTrial::Probability probability_to_run_test = 0; 463 464 const char* kTrialName = "HttpPipeliningCompatibility"; 465 base::FieldTrial* trial = base::FieldTrialList::Find(kTrialName); 466 if (trial) { 467 return; 468 } 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); 473 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; 479 } 480 481 int collect_stats_group = trial->AppendGroup("enable_test", 482 probability_to_run_test); 483 if (trial->group() != collect_stats_group) { 484 return; 485 } 486 487 std::vector<RequestInfo> requests; 488 489 RequestInfo info0; 490 info0.filename = "alphabet.txt"; 491 info0.expected_response = "abcdefghijklmnopqrstuvwxyz"; 492 requests.push_back(info0); 493 494 RequestInfo info1; 495 info1.filename = "cached.txt"; 496 info1.expected_response = "azbycxdwevfugthsirjqkplomn"; 497 requests.push_back(info1); 498 499 RequestInfo info2; 500 info2.filename = "reverse.txt"; 501 info2.expected_response = "zyxwvutsrqponmlkjihgfedcba"; 502 requests.push_back(info2); 503 504 RequestInfo info3; 505 info3.filename = "chunked.txt"; 506 info3.expected_response = "chunkedencodingisfun"; 507 requests.push_back(info3); 508 509 RequestInfo info4; 510 info4.filename = "cached.txt"; 511 info4.expected_response = "azbycxdwevfugthsirjqkplomn"; 512 requests.push_back(info4); 513 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); 521 } 522 523 } // anonymous namespace 524 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()) 529 return; 530 531 content::BrowserThread::PostTask( 532 content::BrowserThread::IO, 533 FROM_HERE, 534 base::Bind(&CollectPipeliningCapabilityStatsOnIOThread, 535 pipeline_test_server, 536 io_thread)); 537 } 538 539 } // namespace chrome_browser_net 540