blob: 6d5a9d7c4a08f396856ebee72852a861a6a3eb21 [file] [log] [blame]
yhirano72f62272016-08-13 12:50:061// Copyright 2016 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/browser/loader/mojo_async_resource_handler.h"
6
7#include <utility>
8
9#include "base/command_line.h"
10#include "base/containers/hash_tables.h"
11#include "base/logging.h"
12#include "base/macros.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/time/time.h"
15#include "content/browser/loader/netlog_observer.h"
16#include "content/browser/loader/resource_dispatcher_host_impl.h"
17#include "content/browser/loader/resource_request_info_impl.h"
18#include "content/common/resource_request_completion_status.h"
yhirano59393702016-11-16 06:10:2419#include "content/public/browser/global_request_id.h"
yhirano72f62272016-08-13 12:50:0620#include "content/public/browser/resource_dispatcher_host_delegate.h"
21#include "content/public/common/resource_response.h"
22#include "mojo/public/c/system/data_pipe.h"
23#include "mojo/public/cpp/system/data_pipe.h"
24#include "net/base/io_buffer.h"
25#include "net/base/load_flags.h"
26#include "net/base/mime_sniffer.h"
yhirano72f62272016-08-13 12:50:0627#include "net/url_request/redirect_info.h"
28
29namespace content {
30namespace {
31
32int g_allocation_size = MojoAsyncResourceHandler::kDefaultAllocationSize;
33
34// MimeTypeResourceHandler *implicitly* requires that the buffer size
35// returned from OnWillRead should be larger than certain size.
36// TODO(yhirano): Fix MimeTypeResourceHandler.
37constexpr size_t kMinAllocationSize = 2 * net::kMaxBytesToSniff;
38
39constexpr size_t kMaxChunkSize = 32 * 1024;
40
41void GetNumericArg(const std::string& name, int* result) {
42 const std::string& value =
43 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name);
44 if (!value.empty())
45 base::StringToInt(value, result);
46}
47
48void InitializeResourceBufferConstants() {
49 static bool did_init = false;
50 if (did_init)
51 return;
52 did_init = true;
53
54 GetNumericArg("resource-buffer-size", &g_allocation_size);
55}
56
57} // namespace
58
59// This class is for sharing the ownership of a ScopedDataPipeProducerHandle
60// between WriterIOBuffer and MojoAsyncResourceHandler.
61class MojoAsyncResourceHandler::SharedWriter final
62 : public base::RefCountedThreadSafe<SharedWriter> {
63 public:
64 explicit SharedWriter(mojo::ScopedDataPipeProducerHandle writer)
65 : writer_(std::move(writer)) {}
66 mojo::DataPipeProducerHandle writer() { return writer_.get(); }
67
68 private:
69 friend class base::RefCountedThreadSafe<SharedWriter>;
70 ~SharedWriter() {}
71
72 const mojo::ScopedDataPipeProducerHandle writer_;
73
74 DISALLOW_COPY_AND_ASSIGN(SharedWriter);
75};
76
77// This class is a IOBuffer subclass for data gotten from a
78// ScopedDataPipeProducerHandle.
79class MojoAsyncResourceHandler::WriterIOBuffer final
80 : public net::IOBufferWithSize {
81 public:
82 // |data| and |size| should be gotten from |writer| via BeginWriteDataRaw.
83 // They will be accesible via IOBuffer methods. As |writer| is stored in this
84 // instance, |data| will be kept valid as long as the following conditions
85 // hold:
86 // 1. |data| is not invalidated via EndWriteDataRaw.
87 // 2. |this| instance is alive.
88 WriterIOBuffer(scoped_refptr<SharedWriter> writer, void* data, size_t size)
89 : net::IOBufferWithSize(static_cast<char*>(data), size),
90 writer_(std::move(writer)) {}
91
92 private:
93 ~WriterIOBuffer() override {
94 // Avoid deleting |data_| in the IOBuffer destructor.
95 data_ = nullptr;
96 }
97
98 // This member is for keeping the writer alive.
99 scoped_refptr<SharedWriter> writer_;
100
101 DISALLOW_COPY_AND_ASSIGN(WriterIOBuffer);
102};
103
104MojoAsyncResourceHandler::MojoAsyncResourceHandler(
105 net::URLRequest* request,
106 ResourceDispatcherHostImpl* rdh,
yhiranofe95d9b2016-11-07 04:17:47107 mojom::URLLoaderAssociatedRequest mojo_request,
108 mojom::URLLoaderClientAssociatedPtr url_loader_client)
yhirano72f62272016-08-13 12:50:06109 : ResourceHandler(request),
110 rdh_(rdh),
111 binding_(this, std::move(mojo_request)),
112 url_loader_client_(std::move(url_loader_client)) {
113 DCHECK(url_loader_client_);
114 InitializeResourceBufferConstants();
yhirano59393702016-11-16 06:10:24115 // This unretained pointer is safe, because |binding_| is owned by |this| and
116 // the callback will never be called after |this| is destroyed.
117 binding_.set_connection_error_handler(
118 base::Bind(&MojoAsyncResourceHandler::Cancel, base::Unretained(this)));
yhirano72f62272016-08-13 12:50:06119}
120
121MojoAsyncResourceHandler::~MojoAsyncResourceHandler() {
122 if (has_checked_for_sufficient_resources_)
123 rdh_->FinishedWithResourcesForRequest(request());
124}
125
126bool MojoAsyncResourceHandler::OnRequestRedirected(
127 const net::RedirectInfo& redirect_info,
128 ResourceResponse* response,
129 bool* defer) {
130 // Not implemented.
131 return false;
132}
133
134bool MojoAsyncResourceHandler::OnResponseStarted(ResourceResponse* response,
135 bool* defer) {
136 const ResourceRequestInfoImpl* info = GetRequestInfo();
137
138 if (rdh_->delegate()) {
139 rdh_->delegate()->OnResponseStarted(request(), info->GetContext(),
140 response);
141 }
142
143 NetLogObserver::PopulateResponseInfo(request(), response);
144
145 response->head.request_start = request()->creation_time();
146 response->head.response_start = base::TimeTicks::Now();
147 sent_received_response_message_ = true;
148 url_loader_client_->OnReceiveResponse(response->head);
149 return true;
150}
151
152bool MojoAsyncResourceHandler::OnWillStart(const GURL& url, bool* defer) {
153 return true;
154}
155
156bool MojoAsyncResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf,
157 int* buf_size,
158 int min_size) {
159 DCHECK_EQ(-1, min_size);
160
161 if (!CheckForSufficientResource())
162 return false;
163
164 if (!shared_writer_) {
165 MojoCreateDataPipeOptions options;
166 options.struct_size = sizeof(MojoCreateDataPipeOptions);
167 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
168 options.element_num_bytes = 1;
169 options.capacity_num_bytes = g_allocation_size;
170 mojo::DataPipe data_pipe(options);
171
172 url_loader_client_->OnStartLoadingResponseBody(
173 std::move(data_pipe.consumer_handle));
174 if (!data_pipe.producer_handle.is_valid())
175 return false;
176
177 shared_writer_ = new SharedWriter(std::move(data_pipe.producer_handle));
178 handle_watcher_.Start(shared_writer_->writer(), MOJO_HANDLE_SIGNAL_WRITABLE,
179 base::Bind(&MojoAsyncResourceHandler::OnWritable,
180 base::Unretained(this)));
181
182 bool defer = false;
183 scoped_refptr<net::IOBufferWithSize> buffer;
184 if (!AllocateWriterIOBuffer(&buffer, &defer))
185 return false;
186 if (!defer) {
187 if (static_cast<size_t>(buffer->size()) >= kMinAllocationSize) {
188 *buf = buffer_ = buffer;
189 *buf_size = buffer_->size();
190 return true;
191 }
192
193 // The allocated buffer is too small.
194 if (EndWrite(0) != MOJO_RESULT_OK)
195 return false;
196 }
197 DCHECK(!is_using_io_buffer_not_from_writer_);
198 is_using_io_buffer_not_from_writer_ = true;
199 buffer_ = new net::IOBufferWithSize(kMinAllocationSize);
200 }
201
202 DCHECK_EQ(0u, buffer_offset_);
203 *buf = buffer_;
204 *buf_size = buffer_->size();
205 return true;
206}
207
208bool MojoAsyncResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
209 DCHECK_GE(bytes_read, 0);
210 DCHECK(buffer_);
211
212 if (!bytes_read)
213 return true;
214
215 if (is_using_io_buffer_not_from_writer_) {
216 // Couldn't allocate a buffer on the data pipe in OnWillRead.
217 DCHECK_EQ(0u, buffer_bytes_read_);
218 buffer_bytes_read_ = bytes_read;
219 if (!CopyReadDataToDataPipe(defer))
220 return false;
221 if (*defer)
222 OnDefer();
223 return true;
224 }
225
226 if (EndWrite(bytes_read) != MOJO_RESULT_OK)
227 return false;
228 // Allocate a buffer for the next OnWillRead call here, because OnWillRead
229 // doesn't have |defer| parameter.
230 if (!AllocateWriterIOBuffer(&buffer_, defer))
231 return false;
232 if (*defer)
233 OnDefer();
234 return true;
235}
236
237void MojoAsyncResourceHandler::OnDataDownloaded(int bytes_downloaded) {
tzik6e20b742016-11-08 09:14:12238 int64_t total_received_bytes = request()->GetTotalReceivedBytes();
239 int64_t bytes_to_report =
240 total_received_bytes - reported_total_received_bytes_;
241 reported_total_received_bytes_ = total_received_bytes;
242 DCHECK_LE(0, bytes_to_report);
243
244 url_loader_client_->OnDataDownloaded(bytes_downloaded, bytes_to_report);
yhirano72f62272016-08-13 12:50:06245}
246
247void MojoAsyncResourceHandler::FollowRedirect() {
248 NOTIMPLEMENTED();
249}
250
yhirano72f62272016-08-13 12:50:06251void MojoAsyncResourceHandler::ResumeForTesting() {
252 Resume();
253}
254
255void MojoAsyncResourceHandler::SetAllocationSizeForTesting(size_t size) {
256 g_allocation_size = size;
257}
258
259MojoResult MojoAsyncResourceHandler::BeginWrite(void** data,
260 uint32_t* available) {
261 MojoResult result = mojo::BeginWriteDataRaw(
262 shared_writer_->writer(), data, available, MOJO_WRITE_DATA_FLAG_NONE);
263 if (result == MOJO_RESULT_OK)
264 *available = std::min(*available, static_cast<uint32_t>(kMaxChunkSize));
265 return result;
266}
267
268MojoResult MojoAsyncResourceHandler::EndWrite(uint32_t written) {
269 return mojo::EndWriteDataRaw(shared_writer_->writer(), written);
270}
271
272void MojoAsyncResourceHandler::OnResponseCompleted(
273 const net::URLRequestStatus& status,
yhirano72f62272016-08-13 12:50:06274 bool* defer) {
275 shared_writer_ = nullptr;
276 buffer_ = nullptr;
277 handle_watcher_.Cancel();
278
279 const ResourceRequestInfoImpl* info = GetRequestInfo();
280
281 // TODO(gavinp): Remove this CHECK when we figure out the cause of
282 // http://crbug.com/124680 . This check mirrors closely check in
283 // WebURLLoaderImpl::OnCompletedRequest that routes this message to a WebCore
284 // ResourceHandleInternal which asserts on its state and crashes. By crashing
285 // when the message is sent, we should get better crash reports.
286 CHECK(status.status() != net::URLRequestStatus::SUCCESS ||
287 sent_received_response_message_);
288
289 int error_code = status.error();
290 bool was_ignored_by_handler = info->WasIgnoredByHandler();
291
292 DCHECK_NE(status.status(), net::URLRequestStatus::IO_PENDING);
293 // If this check fails, then we're in an inconsistent state because all
294 // requests ignored by the handler should be canceled (which should result in
295 // the ERR_ABORTED error code).
296 DCHECK(!was_ignored_by_handler || error_code == net::ERR_ABORTED);
297
298 ResourceRequestCompletionStatus request_complete_data;
299 request_complete_data.error_code = error_code;
300 request_complete_data.was_ignored_by_handler = was_ignored_by_handler;
301 request_complete_data.exists_in_cache = request()->response_info().was_cached;
yhirano72f62272016-08-13 12:50:06302 request_complete_data.completion_time = base::TimeTicks::Now();
303 request_complete_data.encoded_data_length =
304 request()->GetTotalReceivedBytes();
305
306 url_loader_client_->OnComplete(request_complete_data);
307}
308
309bool MojoAsyncResourceHandler::CopyReadDataToDataPipe(bool* defer) {
310 while (true) {
311 scoped_refptr<net::IOBufferWithSize> dest;
312 if (!AllocateWriterIOBuffer(&dest, defer))
313 return false;
314 if (*defer)
315 return true;
316 if (buffer_bytes_read_ == 0) {
317 // All bytes are copied. Save the buffer for the next OnWillRead call.
318 buffer_ = std::move(dest);
319 return true;
320 }
321
322 size_t copied_size =
323 std::min(buffer_bytes_read_, static_cast<size_t>(dest->size()));
324 memcpy(dest->data(), buffer_->data() + buffer_offset_, copied_size);
325 buffer_offset_ += copied_size;
326 buffer_bytes_read_ -= copied_size;
327 if (EndWrite(copied_size) != MOJO_RESULT_OK)
328 return false;
329
330 if (buffer_bytes_read_ == 0) {
331 // All bytes are copied.
332 buffer_offset_ = 0;
333 is_using_io_buffer_not_from_writer_ = false;
334 }
335 }
336}
337
338bool MojoAsyncResourceHandler::AllocateWriterIOBuffer(
339 scoped_refptr<net::IOBufferWithSize>* buf,
340 bool* defer) {
341 void* data = nullptr;
342 uint32_t available = 0;
343 MojoResult result = BeginWrite(&data, &available);
344 if (result == MOJO_RESULT_SHOULD_WAIT) {
345 *defer = true;
346 return true;
347 }
348 if (result != MOJO_RESULT_OK)
349 return false;
350 *buf = new WriterIOBuffer(shared_writer_, data, available);
351 return true;
352}
353
354void MojoAsyncResourceHandler::Resume() {
355 if (!did_defer_)
356 return;
mmenkef12e05a2016-11-08 11:50:54357 did_defer_ = false;
358
yhirano72f62272016-08-13 12:50:06359 if (is_using_io_buffer_not_from_writer_) {
360 // |buffer_| is set to a net::IOBufferWithSize. Write the buffer contents
361 // to the data pipe.
362 DCHECK_GT(buffer_bytes_read_, 0u);
mmenkef12e05a2016-11-08 11:50:54363 if (!CopyReadDataToDataPipe(&did_defer_)) {
yhirano72f62272016-08-13 12:50:06364 controller()->CancelWithError(net::ERR_FAILED);
365 return;
366 }
367 } else {
368 // Allocate a buffer for the next OnWillRead call here.
mmenkef12e05a2016-11-08 11:50:54369 if (!AllocateWriterIOBuffer(&buffer_, &did_defer_)) {
yhirano72f62272016-08-13 12:50:06370 controller()->CancelWithError(net::ERR_FAILED);
371 return;
372 }
373 }
374
mmenkef12e05a2016-11-08 11:50:54375 if (did_defer_) {
yhirano72f62272016-08-13 12:50:06376 // Continue waiting.
377 return;
378 }
yhirano72f62272016-08-13 12:50:06379 request()->LogUnblocked();
380 controller()->Resume();
381}
382
383void MojoAsyncResourceHandler::OnDefer() {
384 request()->LogBlockedBy("MojoAsyncResourceHandler");
385 did_defer_ = true;
386}
387
388bool MojoAsyncResourceHandler::CheckForSufficientResource() {
389 if (has_checked_for_sufficient_resources_)
390 return true;
391 has_checked_for_sufficient_resources_ = true;
392
393 if (rdh_->HasSufficientResourcesForRequest(request()))
394 return true;
395
396 controller()->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
397 return false;
398}
399
400void MojoAsyncResourceHandler::OnWritable(MojoResult unused) {
401 Resume();
402}
403
yhirano59393702016-11-16 06:10:24404void MojoAsyncResourceHandler::Cancel() {
405 const ResourceRequestInfoImpl* info = GetRequestInfo();
406 ResourceDispatcherHostImpl::Get()->CancelRequestFromRenderer(
407 GlobalRequestID(info->GetChildID(), info->GetRequestID()));
408}
409
yhirano72f62272016-08-13 12:50:06410} // namespace content