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