OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "content/public/test/test_download_request_handler.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/memory/weak_ptr.h" | |
9 #include "base/run_loop.h" | |
10 #include "content/public/browser/browser_thread.h" | |
11 #include "net/base/io_buffer.h" | |
12 #include "net/http/http_request_headers.h" | |
13 #include "net/http/http_response_headers.h" | |
14 #include "net/url_request/url_request_filter.h" | |
15 #include "net/url_request/url_request_interceptor.h" | |
16 | |
17 namespace content { | |
18 | |
19 namespace { | |
20 | |
21 using Parameters = TestDownloadRequestHandler::Parameters; | |
22 using InjectedError = TestDownloadRequestHandler::InjectedError; | |
23 | |
24 class Interceptor : public net::URLRequestInterceptor { | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
nit, suggestion: Use a more fully qualified name s
asanka
2015/11/13 21:40:01
Done.
| |
25 public: | |
26 static base::WeakPtr<Interceptor> Register(const GURL& url); | |
27 | |
28 using JobFactory = | |
29 base::Callback<net::URLRequestJob*(net::URLRequest*, | |
30 net::NetworkDelegate*, | |
31 base::WeakPtr<Interceptor>)>; | |
32 | |
33 ~Interceptor() override; | |
34 void Unregister(); | |
35 void SetJobFactory(const JobFactory& factory); | |
36 void GetAndResetCompletedRequests( | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
General comment throughout this file: Comments as
asanka
2015/11/13 21:40:01
Done.
| |
37 TestDownloadRequestHandler::CompletedRequests* requests); | |
38 void AddCompletedRequest( | |
39 const TestDownloadRequestHandler::CompletedRequest& request_info); | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
nit: Use parallel variable names for these two fun
asanka
2015/11/13 21:40:01
Done.
| |
40 | |
41 private: | |
42 Interceptor(const GURL& url); | |
43 | |
44 // net::URLRequestInterceptor | |
45 net::URLRequestJob* MaybeInterceptRequest( | |
46 net::URLRequest* request, | |
47 net::NetworkDelegate* network_delegate) const override; | |
48 | |
49 TestDownloadRequestHandler::CompletedRequests completed_requests_; | |
50 GURL url_; | |
51 JobFactory job_factory_; | |
52 mutable base::WeakPtrFactory<Interceptor> weak_ptr_factory_; | |
53 DISALLOW_COPY_AND_ASSIGN(Interceptor); | |
54 }; | |
55 | |
56 class PartialResponseJob : public net::URLRequestJob { | |
57 public: | |
58 static net::URLRequestJob* Factory(const Parameters& parameters, | |
59 net::URLRequest* request, | |
60 net::NetworkDelegate* delegate, | |
61 base::WeakPtr<Interceptor> interceptor); | |
62 | |
63 // URLRequestJob | |
64 void Start() override; | |
65 void GetResponseInfo(net::HttpResponseInfo* response_info) override; | |
66 int64 GetTotalReceivedBytes() const override; | |
67 bool GetMimeType(std::string* mime_type) const override; | |
68 int GetResponseCode() const override; | |
69 bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override; | |
70 | |
71 private: | |
72 PartialResponseJob(scoped_ptr<Parameters> parameters, | |
73 base::WeakPtr<Interceptor> interceptor, | |
74 net::URLRequest* url_request, | |
75 net::NetworkDelegate* network_delegate); | |
76 | |
77 ~PartialResponseJob() override; | |
78 void ReportCompletedRequest(int64_t transferred_content_length); | |
79 | |
80 scoped_ptr<Parameters> parameters_; | |
81 | |
82 base::WeakPtr<Interceptor> interceptor_; | |
83 net::HttpResponseInfo response_info_; | |
84 int64_t offset_ = -1; | |
85 int64_t offset_begin_ = -1; | |
86 int64_t offset_end_ = -1; | |
87 base::WeakPtrFactory<PartialResponseJob> weak_factory_; | |
88 | |
89 DISALLOW_COPY_AND_ASSIGN(PartialResponseJob); | |
90 }; | |
91 | |
92 class StaticResponseJob : public net::URLRequestJob { | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
nit: Single line comment as to purpose? (In the c
asanka
2015/11/13 21:40:02
This got removed.
| |
93 public: | |
94 static net::URLRequestJob* Factory(const std::string& headers, | |
95 net::URLRequest* request, | |
96 net::NetworkDelegate* delegate, | |
97 base::WeakPtr<Interceptor> interceptor); | |
98 // URLRequestJob | |
99 void Start() override; | |
100 void GetResponseInfo(net::HttpResponseInfo* response_info) override; | |
101 | |
102 private: | |
103 StaticResponseJob(const std::string& headers, | |
104 net::URLRequest* url_request, | |
105 net::NetworkDelegate* network_delegate); | |
106 ~StaticResponseJob() override; | |
107 | |
108 net::HttpResponseInfo response_info_; | |
109 base::WeakPtrFactory<StaticResponseJob> weak_factory_; | |
110 DISALLOW_COPY_AND_ASSIGN(StaticResponseJob); | |
111 }; | |
112 | |
113 // static | |
114 net::URLRequestJob* PartialResponseJob::Factory( | |
115 const Parameters& parameters, | |
116 net::URLRequest* request, | |
117 net::NetworkDelegate* delegate, | |
118 base::WeakPtr<Interceptor> interceptor) { | |
119 return new PartialResponseJob(make_scoped_ptr(new Parameters(parameters)), | |
120 interceptor, request, delegate); | |
121 } | |
122 | |
123 PartialResponseJob::PartialResponseJob(scoped_ptr<Parameters> parameters, | |
124 base::WeakPtr<Interceptor> interceptor, | |
125 net::URLRequest* request, | |
126 net::NetworkDelegate* network_delegate) | |
127 : net::URLRequestJob(request, network_delegate), | |
128 parameters_(parameters.Pass()), | |
129 interceptor_(interceptor), | |
130 weak_factory_(this) { | |
131 DCHECK(parameters_.get()); | |
132 DCHECK_LT(0, parameters_->size); | |
133 DCHECK_NE(-1, parameters_->pattern_generator_seed); | |
134 } | |
135 | |
136 PartialResponseJob::~PartialResponseJob() {} | |
137 | |
138 void PartialResponseJob::Start() { | |
139 DVLOG(1) << "Starting request for " << request()->url().spec(); | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
Just confirming that you wanted to land the log en
asanka
2015/11/13 21:40:01
Yeah. They are D* logs that only result in code fo
| |
140 SetStatus(net::URLRequestStatus()); | |
141 std::stringstream response_headers; | |
142 | |
143 const net::HttpRequestHeaders extra_headers = | |
144 request()->extra_request_headers(); | |
145 | |
146 DCHECK(request()->method() == "GET") << "PartialResponseJob only " | |
147 "knows how to respond to GET " | |
148 "requests"; | |
149 offset_begin_ = 0; | |
150 offset_end_ = parameters_->size - 1; | |
151 std::string value; | |
152 std::string range_header; | |
153 std::vector<net::HttpByteRange> byte_ranges; | |
154 | |
155 if (parameters_->support_byte_ranges && | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
I feel like a comment or two would go a long way h
asanka
2015/11/13 21:40:02
Yup. It looks a bit dense now that I look at it ag
| |
156 extra_headers.GetHeader(net::HttpRequestHeaders::kIfRange, &value) && | |
157 !value.empty() && value == parameters_->etag && | |
158 extra_headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) && | |
159 net::HttpUtil::ParseRangeHeader(range_header, &byte_ranges) && | |
160 byte_ranges.size() > 0) { | |
161 if (byte_ranges[0].ComputeBounds(parameters_->size)) { | |
162 offset_begin_ = byte_ranges[0].first_byte_position(); | |
163 offset_end_ = byte_ranges[0].last_byte_position(); | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
Does this need to be the min of the parameters_->s
asanka
2015/11/13 21:40:02
The HttpByteRange::ComputeBounds() method takes ca
| |
164 response_headers << "HTTP/1.1 206 Partial content\r\n"; | |
165 response_headers << "Content-Range: bytes " << offset_begin_ << "-" | |
166 << offset_end_ << "/" << parameters_->size << "\r\n"; | |
167 response_headers << "Content-Length: " << offset_end_ - offset_begin_ + 1 | |
168 << "\r\n"; | |
169 } else { | |
170 response_headers << "HTTP/1.1 416 Range not satisfiable\r\n"; | |
171 response_headers << "Content-Range: bytes */" << parameters_->size | |
172 << "\r\n"; | |
173 } | |
174 } else { | |
175 response_headers << "HTTP/1.1 200 Success\r\n"; | |
176 response_headers << "Content-Length: " << parameters_->size << "\r\n"; | |
177 } | |
178 if (!parameters_->content_type.empty()) | |
179 response_headers << "Content-Type: " << parameters_->content_type << "\r\n"; | |
180 | |
181 if (parameters_->support_byte_ranges) | |
182 response_headers << "Accept-Ranges: bytes\r\n"; | |
183 | |
184 if (!parameters_->etag.empty()) | |
185 response_headers << "ETag: " << parameters_->etag << "\r\n"; | |
186 | |
187 if (!parameters_->last_modified.empty()) | |
188 response_headers << "Last-Modified: " << parameters_->last_modified | |
189 << "\r\n"; | |
190 | |
191 std::string raw_headers = response_headers.str(); | |
192 response_info_.headers = | |
193 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( | |
194 raw_headers.c_str(), raw_headers.size())); | |
195 offset_ = offset_begin_; | |
196 | |
197 while (!parameters_->injected_errors.empty() && | |
198 parameters_->injected_errors.front().offset <= offset_) | |
199 parameters_->injected_errors.pop(); | |
200 | |
201 DVLOG(1) << "Notifying response headers complete with headers:\n" | |
202 << raw_headers; | |
203 base::MessageLoop::current()->PostTask( | |
204 FROM_HERE, base::Bind(&PartialResponseJob::NotifyHeadersComplete, | |
205 weak_factory_.GetWeakPtr())); | |
206 } | |
207 | |
208 void PartialResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { | |
209 *response_info = response_info_; | |
210 } | |
211 | |
212 int64 PartialResponseJob::GetTotalReceivedBytes() const { | |
213 return offset_ - offset_begin_; | |
214 } | |
215 | |
216 bool PartialResponseJob::GetMimeType(std::string* mime_type) const { | |
217 *mime_type = parameters_->content_type; | |
218 return !parameters_->content_type.empty(); | |
219 } | |
220 | |
221 int PartialResponseJob::GetResponseCode() const { | |
222 return response_info_.headers.get() ? response_info_.headers->response_code() | |
223 : 0; | |
224 } | |
225 | |
226 bool PartialResponseJob::ReadRawData(net::IOBuffer* buf, | |
227 int buf_size, | |
228 int* bytes_read) { | |
229 *bytes_read = 0; | |
230 DVLOG(1) << "Reading " << buf_size << " bytes"; | |
231 if (offset_ > offset_end_) { | |
232 ReportCompletedRequest(offset_end_ - offset_begin_ + 1); | |
233 DVLOG(1) << "Done reading."; | |
234 return true; | |
235 } | |
236 | |
237 int64_t range_end = std::min(offset_end_, offset_ + buf_size - 1); | |
238 if (!parameters_->injected_errors.empty()) { | |
239 const InjectedError& injected_error = parameters_->injected_errors.front(); | |
240 if (offset_ == injected_error.offset) { | |
241 int error = injected_error.error; | |
242 SetStatus(net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); | |
243 DVLOG(1) << "Returning error " << net::ErrorToString(error); | |
244 ReportCompletedRequest(injected_error.offset - offset_begin_); | |
245 parameters_->injected_errors.pop(); | |
246 return false; | |
247 } | |
248 | |
249 if (offset_ < injected_error.offset && injected_error.offset <= range_end) | |
250 range_end = injected_error.offset - 1; | |
251 } | |
252 int bytes_to_copy = (range_end - offset_) + 1; | |
253 | |
254 TestDownloadRequestHandler::GetPatternBytes( | |
255 parameters_->pattern_generator_seed, offset_, bytes_to_copy, buf->data()); | |
256 DVLOG(1) << "Read " << bytes_to_copy << " bytes at offset " << offset_; | |
257 offset_ += bytes_to_copy; | |
258 *bytes_read = bytes_to_copy; | |
259 return true; | |
260 } | |
261 | |
262 void PartialResponseJob::ReportCompletedRequest( | |
263 int64_t transferred_content_length) { | |
264 if (interceptor_.get()) { | |
265 TestDownloadRequestHandler::CompletedRequest request_info; | |
266 request_info.transferred_content_length = transferred_content_length; | |
267 request_info.request_headers = request()->extra_request_headers(); | |
268 interceptor_->AddCompletedRequest(request_info); | |
269 } | |
270 } | |
271 | |
272 // static | |
273 net::URLRequestJob* StaticResponseJob::Factory( | |
274 const std::string& headers, | |
275 net::URLRequest* request, | |
276 net::NetworkDelegate* delegate, | |
277 base::WeakPtr<Interceptor> interceptor) { | |
278 return new StaticResponseJob(headers, request, delegate); | |
279 } | |
280 | |
281 StaticResponseJob::StaticResponseJob(const std::string& headers, | |
282 net::URLRequest* url_request, | |
283 net::NetworkDelegate* network_delegate) | |
284 : URLRequestJob(url_request, network_delegate), weak_factory_(this) { | |
285 response_info_.headers = new net::HttpResponseHeaders( | |
286 net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); | |
287 } | |
288 | |
289 StaticResponseJob::~StaticResponseJob() {} | |
290 | |
291 void StaticResponseJob::Start() { | |
292 base::MessageLoop::current()->PostTask( | |
293 FROM_HERE, base::Bind(&StaticResponseJob::NotifyHeadersComplete, | |
294 weak_factory_.GetWeakPtr())); | |
295 return; | |
296 } | |
297 | |
298 void StaticResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { | |
299 *response_info = response_info_; | |
300 } | |
301 | |
302 template <class T> | |
303 void StoreValueAndInvokeClosure(const base::Closure& quit_closure, | |
304 T* value_receiver, | |
305 T value) { | |
306 *value_receiver = value; | |
307 quit_closure.Run(); | |
308 } | |
309 | |
310 // Xorshift* PRNG from https://en.wikipedia.org/wiki/Xorshift | |
311 uint64_t XorShift64StarWithIndex(uint64_t seed, uint64_t index) { | |
312 const uint64_t kMultiplier = UINT64_C(2685821657736338717); | |
313 uint64_t x = seed * kMultiplier + index; | |
314 x ^= x >> 12; | |
315 x ^= x << 25; | |
316 x ^= x >> 27; | |
317 return x * kMultiplier; | |
318 } | |
319 | |
320 // static | |
321 base::WeakPtr<Interceptor> Interceptor::Register(const GURL& url) { | |
322 DCHECK(url.is_valid()); | |
323 scoped_ptr<Interceptor> interceptor(new Interceptor(url)); | |
324 base::WeakPtr<Interceptor> weak_reference = | |
325 interceptor->weak_ptr_factory_.GetWeakPtr(); | |
326 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); | |
327 filter->AddUrlInterceptor(url, interceptor.Pass()); | |
328 return weak_reference; | |
329 } | |
330 | |
331 void Interceptor::Unregister() { | |
332 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); | |
333 filter->RemoveUrlHandler(url_); | |
334 } | |
335 | |
336 void Interceptor::SetJobFactory(const JobFactory& job_factory) { | |
337 job_factory_ = job_factory; | |
338 } | |
339 | |
340 void Interceptor::GetAndResetCompletedRequests( | |
341 TestDownloadRequestHandler::CompletedRequests* requests) { | |
342 requests->clear(); | |
343 completed_requests_.swap(*requests); | |
344 } | |
345 | |
346 void Interceptor::AddCompletedRequest( | |
347 const TestDownloadRequestHandler::CompletedRequest& request_info) { | |
348 completed_requests_.push_back(request_info); | |
349 } | |
350 | |
351 Interceptor::Interceptor(const GURL& url) | |
352 : url_(url), weak_ptr_factory_(this) {} | |
353 | |
354 Interceptor::~Interceptor() {} | |
355 | |
356 net::URLRequestJob* Interceptor::MaybeInterceptRequest( | |
357 net::URLRequest* request, | |
358 net::NetworkDelegate* network_delegate) const { | |
359 DVLOG(1) << "Intercepting request for " << request->url() | |
360 << " with headers:\n" << request->extra_request_headers().ToString(); | |
361 if (job_factory_.is_null()) | |
362 return nullptr; | |
363 return job_factory_.Run(request, network_delegate, | |
364 weak_ptr_factory_.GetWeakPtr()); | |
365 } | |
366 | |
367 } // namespace | |
368 | |
369 struct TestDownloadRequestHandler::InterceptorProxy { | |
370 base::WeakPtr<Interceptor> interceptor_; | |
371 }; | |
372 | |
373 TestDownloadRequestHandler::InjectedError::InjectedError(int64_t offset, | |
374 net::Error error) | |
375 : offset(offset), error(error) {} | |
376 | |
377 // static | |
378 Parameters TestDownloadRequestHandler::Parameters::WithSingleInterruption() { | |
379 Parameters parameters; | |
380 parameters.injected_errors.push( | |
381 InjectedError(parameters.size / 2, net::ERR_CONNECTION_RESET)); | |
382 return parameters; | |
383 } | |
384 | |
385 TestDownloadRequestHandler::Parameters::Parameters(const Parameters& other) | |
386 : etag(other.etag), | |
387 last_modified(other.last_modified), | |
388 content_type(other.content_type), | |
389 size(other.size), | |
390 pattern_generator_seed(other.pattern_generator_seed), | |
391 support_byte_ranges(other.support_byte_ranges), | |
392 injected_errors(other.injected_errors) {} | |
393 | |
394 TestDownloadRequestHandler::Parameters::Parameters() | |
395 : etag("abcd"), | |
396 last_modified("Tue, 15 Nov 1994 12:45:26 GMT"), | |
397 content_type("application/octet-stream"), | |
398 size(102400), | |
399 pattern_generator_seed(1), | |
400 support_byte_ranges(true) {} | |
401 | |
402 TestDownloadRequestHandler::Parameters::Parameters( | |
403 const std::string& etag, | |
404 const std::string& last_modified, | |
405 const std::string& content_type, | |
406 int64_t size, | |
407 int pattern_generator_seed, | |
408 bool support_byte_ranges) | |
409 : etag(etag), | |
410 last_modified(last_modified), | |
411 content_type(content_type), | |
412 size(size), | |
413 pattern_generator_seed(pattern_generator_seed), | |
414 support_byte_ranges(support_byte_ranges) {} | |
415 | |
416 TestDownloadRequestHandler::Parameters::~Parameters() {} | |
417 | |
418 void TestDownloadRequestHandler::Parameters::ClearInjectedErrors() { | |
419 std::queue<InjectedError> empty_error_list; | |
420 injected_errors.swap(empty_error_list); | |
421 } | |
422 | |
423 TestDownloadRequestHandler::TestDownloadRequestHandler() | |
424 : TestDownloadRequestHandler(GURL("http://example.com/download")) {} | |
425 | |
426 TestDownloadRequestHandler::TestDownloadRequestHandler(const GURL& url) | |
427 : url_(url) { | |
428 interceptor_proxy_.reset(new InterceptorProxy); | |
429 base::RunLoop run_loop; | |
430 BrowserThread::PostTaskAndReplyWithResult( | |
431 BrowserThread::IO, FROM_HERE, base::Bind(&Interceptor::Register, url_), | |
432 base::Bind(&StoreValueAndInvokeClosure<base::WeakPtr<Interceptor>>, | |
433 run_loop.QuitClosure(), &interceptor_proxy_->interceptor_)); | |
434 run_loop.Run(); | |
435 } | |
436 | |
437 void TestDownloadRequestHandler::StartServing(const Parameters& parameters) { | |
438 Interceptor::JobFactory job_factory = | |
439 base::Bind(&PartialResponseJob::Factory, parameters); | |
440 BrowserThread::PostTask( | |
Randy Smith (Not in Mondays)
2015/11/12 00:57:57
Comment about why a simple call to a setter starts
asanka
2015/11/13 21:40:01
It's a bit unclear, but StartServing(const Paramet
| |
441 BrowserThread::IO, FROM_HERE, | |
442 base::Bind(&Interceptor::SetJobFactory, interceptor_proxy_->interceptor_, | |
443 job_factory)); | |
444 } | |
445 | |
446 void TestDownloadRequestHandler::StartServingStaticResponse( | |
447 const base::StringPiece& headers) { | |
448 Interceptor::JobFactory job_factory = | |
449 base::Bind(&StaticResponseJob::Factory, headers.as_string()); | |
450 BrowserThread::PostTask( | |
451 BrowserThread::IO, FROM_HERE, | |
452 base::Bind(&Interceptor::SetJobFactory, interceptor_proxy_->interceptor_, | |
453 job_factory)); | |
454 } | |
455 | |
456 // static | |
457 void TestDownloadRequestHandler::GetPatternBytes(int seed, | |
458 int64_t starting_offset, | |
459 int length, | |
460 char* buffer) { | |
461 int64_t seed_offset = starting_offset / sizeof(int64_t); | |
462 int64_t first_byte_position = starting_offset % sizeof(int64_t); | |
463 while (length > 0) { | |
464 uint64_t data = XorShift64StarWithIndex(seed, seed_offset); | |
465 int length_to_copy = | |
466 std::min(length, static_cast<int>(sizeof(data) - first_byte_position)); | |
467 memcpy(buffer, reinterpret_cast<char*>(&data) + first_byte_position, | |
468 length_to_copy); | |
469 buffer += length_to_copy; | |
470 length -= length_to_copy; | |
471 ++seed_offset; | |
472 first_byte_position = 0; | |
473 } | |
474 } | |
475 | |
476 TestDownloadRequestHandler::~TestDownloadRequestHandler() { | |
477 BrowserThread::PostTask( | |
478 BrowserThread::IO, FROM_HERE, | |
479 base::Bind(&Interceptor::Unregister, interceptor_proxy_->interceptor_)); | |
480 } | |
481 | |
482 void TestDownloadRequestHandler::GetCompletedRequestInfo( | |
483 TestDownloadRequestHandler::CompletedRequests* requests) { | |
484 base::RunLoop run_loop; | |
485 BrowserThread::PostTaskAndReply( | |
486 BrowserThread::IO, FROM_HERE, | |
487 base::Bind(&Interceptor::GetAndResetCompletedRequests, | |
488 interceptor_proxy_->interceptor_, requests), | |
489 run_loop.QuitClosure()); | |
490 run_loop.Run(); | |
491 } | |
492 | |
493 } // namespace content | |
OLD | NEW |