Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(259)

Side by Side Diff: content/browser/blob_storage/blob_url_loader_factory.cc

Issue 2906543002: Add support for reading blobs when using the network service. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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/blob_storage/blob_url_loader_factory.h"
6
7 #include <stddef.h>
8 #include "base/bind.h"
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
14 #include "content/browser/storage_partition_impl.h"
15 #include "content/common/net_adapters.h"
16 #include "content/common/url_loader.mojom.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "mojo/public/cpp/system/simple_watcher.h"
19 #include "net/base/io_buffer.h"
20 #include "net/http/http_byte_range.h"
21 #include "net/http/http_request_headers.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_status_code.h"
24 #include "net/http/http_util.h"
25 #include "storage/browser/blob/blob_data_handle.h"
26 #include "storage/browser/blob/blob_reader.h"
27 #include "storage/browser/blob/blob_storage_context.h"
28 #include "storage/browser/blob/blob_url_request_job.h"
29 #include "storage/browser/fileapi/file_system_context.h"
30
31 namespace content {
32
33 namespace {
34 constexpr size_t kDefaultAllocationSize = 512 * 1024;
35
36 // Note: some of this code is duplicated from storage::BlobURLRequestJob.
37 class BlobURLLoader : public mojom::URLLoader {
dmurph 2017/05/25 21:37:10 Can you write threading expectations? I think we'r
Marijn Kruisselbrink 2017/05/25 22:12:22 I'm pretty sure it's created on and fully runs on
jam 2017/05/26 04:46:47 Correct, I added a dcheck as well.
38 public:
39 BlobURLLoader(mojom::URLLoaderAssociatedRequest url_loader_request,
40 const ResourceRequest& request,
41 mojom::URLLoaderClientPtr client,
42 storage::BlobStorageContext* blob_storage_context,
43 storage::FileSystemContext* file_system_context)
44 : binding_(this, std::move(url_loader_request)),
45 request_(request),
46 client_(std::move(client)),
47 byte_range_set_(false),
48 writable_handle_watcher_(FROM_HERE,
49 mojo::SimpleWatcher::ArmingPolicy::MANUAL),
50 peer_closed_handle_watcher_(FROM_HERE,
51 mojo::SimpleWatcher::ArmingPolicy::MANUAL),
52 weak_factory_(this) {
53 blob_handle_ = blob_storage_context->GetBlobDataFromPublicURL(request.url);
54
55 // PostTask since it might destruct.
56 base::ThreadTaskRunnerHandle::Get()->PostTask(
57 FROM_HERE,
58 base::Bind(&BlobURLLoader::Start, weak_factory_.GetWeakPtr(), request,
59 make_scoped_refptr(file_system_context)));
60 }
61
62 void Start(const ResourceRequest& request,
63 scoped_refptr<storage::FileSystemContext> file_system_context) {
64 if (!blob_handle_) {
65 NotifyCompleted(net::ERR_FILE_NOT_FOUND);
66 return;
67 }
68
69 base::SequencedTaskRunner* file_task_runner =
70 BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get();
Marijn Kruisselbrink 2017/05/25 22:12:22 nit: BrowserThread::FILE is deprecated, and you pr
jam 2017/05/26 04:46:47 Right, I wanted the same behavior. They can both b
71 blob_reader_ =
72 blob_handle_->CreateReader(file_system_context.get(), file_task_runner);
73
74 // We only support GET request per the spec.
75 if (request.method != "GET") {
76 NotifyCompleted(net::ERR_METHOD_NOT_SUPPORTED);
77 return;
78 }
79
80 if (blob_reader_->net_error()) {
81 NotifyCompleted(blob_reader_->net_error());
82 return;
83 }
84
85 net::HttpRequestHeaders request_headers;
86 request_headers.AddHeadersFromString(request.headers);
87 std::string range_header;
88 if (request_headers.GetHeader(net::HttpRequestHeaders::kRange,
89 &range_header)) {
90 // We only care about "Range" header here.
91 std::vector<net::HttpByteRange> ranges;
92 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
93 if (ranges.size() == 1) {
94 byte_range_set_ = true;
95 byte_range_ = ranges[0];
96 } else {
97 // We don't support multiple range requests in one single URL request,
98 // because we need to do multipart encoding here.
99 // TODO(jianli): Support multipart byte range requests.
100 NotifyCompleted(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
101 }
102 }
103 }
104
105 storage::BlobReader::Status size_status =
106 blob_reader_->CalculateSize(base::Bind(&BlobURLLoader::DidCalculateSize,
107 weak_factory_.GetWeakPtr()));
108 switch (size_status) {
109 case storage::BlobReader::Status::NET_ERROR:
110 NotifyCompleted(blob_reader_->net_error());
111 return;
112 case storage::BlobReader::Status::IO_PENDING:
113 return;
114 case storage::BlobReader::Status::DONE:
115 DidCalculateSize(net::OK);
116 return;
117 }
118 }
119
120 ~BlobURLLoader() override {}
121
122 private:
123 // mojom::URLLoader implementation:
124 void FollowRedirect() override { NOTREACHED(); }
125
126 void SetPriority(net::RequestPriority priority,
127 int32_t intra_priority_value) override {
128 NOTREACHED();
129 }
130
131 void NotifyCompleted(int error_code) {
132 ResourceRequestCompletionStatus request_complete_data;
133 request_complete_data.error_code = error_code;
134 client_->OnComplete(request_complete_data);
135
136 DeleteIfNeeded();
137 }
138
139 void DidCalculateSize(int result) {
140 if (result != net::OK) {
141 NotifyCompleted(result);
142 return;
143 }
144
145 // Apply the range requirement.
146 if (!byte_range_.ComputeBounds(blob_reader_->total_size())) {
147 NotifyCompleted(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
148 return;
149 }
150
151 DCHECK_LE(byte_range_.first_byte_position(),
152 byte_range_.last_byte_position() + 1);
153 uint64_t length =
154 base::checked_cast<uint64_t>(byte_range_.last_byte_position() -
155 byte_range_.first_byte_position() + 1);
156
157 if (byte_range_set_)
158 blob_reader_->SetReadRange(byte_range_.first_byte_position(), length);
159
160 net::HttpStatusCode status_code = net::HTTP_OK;
161 if (byte_range_set_ && byte_range_.IsValid()) {
162 status_code = net::HTTP_PARTIAL_CONTENT;
163 } else {
164 // TODO(horo): When the requester doesn't need the side data
165 // (ex:FileReader) we should skip reading the side data.
166 if (blob_reader_->has_side_data() &&
167 blob_reader_->ReadSideData(base::Bind(&BlobURLLoader::DidReadMetadata,
168 weak_factory_.GetWeakPtr())) ==
169 storage::BlobReader::Status::IO_PENDING) {
170 return;
171 }
172 }
173
174 HeadersCompleted(status_code);
175 }
176
177 void DidReadMetadata(storage::BlobReader::Status result) {
178 if (result != storage::BlobReader::Status::DONE) {
179 NotifyCompleted(blob_reader_->net_error());
180 return;
181 }
182 HeadersCompleted(net::HTTP_OK);
183 }
184
185 void HeadersCompleted(net::HttpStatusCode status_code) {
186 ResourceResponseHead response;
187 response.content_length = 0;
188 response.headers = storage::BlobURLRequestJob::GenerateHeaders(
189 status_code, blob_handle_.get(), blob_reader_.get(), &byte_range_,
190 &response.content_length);
191
192 std::string mime_type;
193 response.headers->GetMimeType(&mime_type);
194 // Match logic in StreamURLRequestJob::HeadersCompleted.
195 if (mime_type.empty())
196 mime_type = "text/plain";
197 response.mime_type = mime_type;
198
199 // TODO(jam): some of this code can be shared with
200 // content/network/url_loader_impl.h
201 client_->OnReceiveResponse(response, base::nullopt, nullptr);
202
203 net::IOBufferWithSize* metadata = blob_reader_->side_data();
204 if (metadata) {
205 const uint8_t* data = reinterpret_cast<const uint8_t*>(metadata->data());
206 client_->OnReceiveCachedMetadata(
207 std::vector<uint8_t>(data, data + metadata->size()));
208 }
209
210 mojo::DataPipe data_pipe(kDefaultAllocationSize);
dmurph 2017/05/25 21:37:10 This part seems to make sense to me - I don't know
jam 2017/05/26 04:46:47 This is pretty much copied from content/common/url
211 response_body_stream_ = std::move(data_pipe.producer_handle);
212 response_body_consumer_handle_ = std::move(data_pipe.consumer_handle);
213 peer_closed_handle_watcher_.Watch(
214 response_body_stream_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
215 base::Bind(&BlobURLLoader::OnResponseBodyStreamClosed,
216 base::Unretained(this)));
217 peer_closed_handle_watcher_.ArmOrNotify();
218
219 writable_handle_watcher_.Watch(
220 response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
221 base::Bind(&BlobURLLoader::OnResponseBodyStreamReady,
222 base::Unretained(this)));
223
224 // Start reading...
225 ReadMore();
226 }
227
228 void ReadMore() {
229 DCHECK(!pending_write_.get());
230
231 uint32_t num_bytes;
232 // TODO: we should use the abstractions in MojoAsyncResourceHandler.
233 MojoResult result = NetToMojoPendingBuffer::BeginWrite(
234 &response_body_stream_, &pending_write_, &num_bytes);
235 if (result == MOJO_RESULT_SHOULD_WAIT) {
236 // The pipe is full. We need to wait for it to have more space.
237 writable_handle_watcher_.ArmOrNotify();
238 return;
239 } else if (result != MOJO_RESULT_OK) {
240 // The response body stream is in a bad state. Bail.
241 // TODO: How should this be communicated to our client?
dmurph 2017/05/25 21:37:10 NotifyComplete(error)? Or are you unsure what the
jam 2017/05/26 04:46:46 Done
242 writable_handle_watcher_.Cancel();
243 response_body_stream_.reset();
244 DeleteIfNeeded();
245 return;
246 }
247
248 CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes);
249 scoped_refptr<net::IOBuffer> buf(
250 new NetToMojoIOBuffer(pending_write_.get()));
251 int bytes_read;
252 // url_request_->Read(buf.get(), static_cast<int>(num_bytes), &bytes_read);
253 storage::BlobReader::Status read_status = blob_reader_->Read(
254 buf.get(), static_cast<int>(num_bytes), &bytes_read,
255 base::Bind(&BlobURLLoader::DidRead, weak_factory_.GetWeakPtr(), false));
256 switch (read_status) {
257 case storage::BlobReader::Status::NET_ERROR:
258 NotifyCompleted(blob_reader_->net_error());
259 DeleteIfNeeded();
dmurph 2017/05/25 21:37:10 Redundant line - NotifyCompleted calls this. Or, m
jam 2017/05/26 04:46:46 Done.
260 return;
261 case storage::BlobReader::Status::IO_PENDING:
262 // Wait for DidRead.
263 return;
264 case storage::BlobReader::Status::DONE:
265 if (bytes_read > 0) {
266 DidRead(true, bytes_read);
267 } else {
268 NotifyCompleted(net::OK);
269 writable_handle_watcher_.Cancel();
270 pending_write_->Complete(0);
271 pending_write_ = nullptr; // This closes the data pipe.
272 DeleteIfNeeded();
273 return;
274 }
275 }
276 }
277
278 void DidRead(bool completed_synchronously, int num_bytes) {
279 if (response_body_consumer_handle_.is_valid()) {
280 // Send the data pipe on the first OnReadCompleted call.
281 client_->OnStartLoadingResponseBody(
282 std::move(response_body_consumer_handle_));
Marijn Kruisselbrink 2017/05/25 22:12:22 nit: mojo::ScopedHandleBase is not documented as c
jam 2017/05/26 04:46:47 The header says "// Scoper for the actual handle t
283 }
284 response_body_stream_ = pending_write_->Complete(num_bytes);
285 pending_write_ = nullptr;
286 if (completed_synchronously) {
287 base::ThreadTaskRunnerHandle::Get()->PostTask(
288 FROM_HERE,
289 base::Bind(&BlobURLLoader::ReadMore, weak_factory_.GetWeakPtr()));
290 } else {
291 ReadMore();
292 }
293 }
294
295 void OnResponseBodyStreamClosed(MojoResult result) {
296 response_body_stream_.reset();
297 pending_write_ = nullptr;
298 DeleteIfNeeded();
299 }
300
301 void OnResponseBodyStreamReady(MojoResult result) {
302 // TODO: Handle a bad |result| value.
dmurph 2017/05/25 21:37:10 I'm imagining this would: 1. Close the stream 2. C
jam 2017/05/26 04:46:47 Yep (this TODO is copied from the other implementa
303 DCHECK_EQ(result, MOJO_RESULT_OK);
304 ReadMore();
305 }
306
307 void DeleteIfNeeded() {
308 bool has_data_pipe =
309 pending_write_.get() || response_body_stream_.is_valid();
310 if (!has_data_pipe)
311 delete this;
312 }
313
314 mojo::AssociatedBinding<mojom::URLLoader> binding_;
315 ResourceRequest request_;
316 mojom::URLLoaderClientPtr client_;
317
318 bool byte_range_set_;
319 net::HttpByteRange byte_range_;
320
321 std::unique_ptr<storage::BlobDataHandle> blob_handle_;
322 std::unique_ptr<storage::BlobReader> blob_reader_;
323
324 // TODO(jam): share with URLLoaderImpl
325 mojo::ScopedDataPipeProducerHandle response_body_stream_;
326 mojo::ScopedDataPipeConsumerHandle response_body_consumer_handle_;
327 scoped_refptr<NetToMojoPendingBuffer> pending_write_;
328 mojo::SimpleWatcher writable_handle_watcher_;
329 mojo::SimpleWatcher peer_closed_handle_watcher_;
330
331 base::WeakPtrFactory<BlobURLLoader> weak_factory_;
332
333 DISALLOW_COPY_AND_ASSIGN(BlobURLLoader);
dmurph 2017/05/25 21:37:10 I double checked that the logic/contents for this
jam 2017/05/26 04:46:46 In general, I prefer higher level tests so that th
334 };
335
336 } // namespace
337
338 BlobURLLoaderFactory::BlobURLLoaderFactory(
339 StoragePartitionImpl* storage_partition)
340 : blob_storage_context_(ChromeBlobStorageContext::GetFor(
341 storage_partition->browser_context())),
342 file_system_context_(storage_partition->GetFileSystemContext()) {
343 DCHECK_CURRENTLY_ON(BrowserThread::UI);
344 }
345
346 mojom::URLLoaderFactoryPtr BlobURLLoaderFactory::CreateFactory() {
347 DCHECK_CURRENTLY_ON(BrowserThread::UI);
348 mojom::URLLoaderFactoryPtr factory;
349 mojom::URLLoaderFactoryRequest request = mojo::MakeRequest(&factory);
350 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
351 base::BindOnce(&BlobURLLoaderFactory::BindOnIO, this,
352 std::move(request)));
353
354 return factory;
355 }
356
357 BlobURLLoaderFactory::~BlobURLLoaderFactory() {}
358
359 void BlobURLLoaderFactory::BindOnIO(mojom::URLLoaderFactoryRequest request) {
360 DCHECK_CURRENTLY_ON(BrowserThread::IO);
361
362 loader_factory_bindings_.AddBinding(this, std::move(request));
363 }
364
365 void BlobURLLoaderFactory::CreateLoaderAndStart(
366 mojom::URLLoaderAssociatedRequest loader,
367 int32_t routing_id,
368 int32_t request_id,
369 uint32_t options,
370 const ResourceRequest& request,
371 mojom::URLLoaderClientPtr client) {
372 DCHECK_CURRENTLY_ON(BrowserThread::IO);
373 new BlobURLLoader(std::move(loader), request, std::move(client),
374 blob_storage_context_->context(),
375 file_system_context_.get());
376 }
377
378 void BlobURLLoaderFactory::SyncLoad(int32_t routing_id,
379 int32_t request_id,
380 const ResourceRequest& request,
381 SyncLoadCallback callback) {
382 NOTREACHED();
383 }
384
385 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698