| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mojo/services/network/url_loader_impl.h" |
| |
| #include "base/memory/scoped_vector.h" |
| #include "base/message_loop/message_loop.h" |
| #include "mojo/common/common_type_converters.h" |
| #include "mojo/services/network/network_context.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/base/upload_data_stream.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/redirect_info.h" |
| #include "net/url_request/url_request_context.h" |
| |
| namespace mojo { |
| namespace { |
| |
| const uint32_t kMaxReadSize = 64 * 1024; |
| |
| // Generates an URLResponsePtr from the response state of a net::URLRequest. |
| URLResponsePtr MakeURLResponse(const net::URLRequest* url_request) { |
| URLResponsePtr response(URLResponse::New()); |
| response->url = String::From(url_request->url()); |
| |
| const net::HttpResponseHeaders* headers = url_request->response_headers(); |
| if (headers) { |
| response->status_code = headers->response_code(); |
| response->status_line = headers->GetStatusLine(); |
| |
| std::vector<String> header_lines; |
| void* iter = NULL; |
| std::string name, value; |
| while (headers->EnumerateHeaderLines(&iter, &name, &value)) |
| header_lines.push_back(name + ": " + value); |
| if (!header_lines.empty()) |
| response->headers.Swap(&header_lines); |
| } |
| |
| std::string mime_type; |
| url_request->GetMimeType(&mime_type); |
| response->mime_type = mime_type; |
| |
| std::string charset; |
| url_request->GetCharset(&charset); |
| response->charset = charset; |
| |
| return response.Pass(); |
| } |
| |
| NetworkErrorPtr MakeNetworkError(int error_code) { |
| NetworkErrorPtr error = NetworkError::New(); |
| error->code = error_code; |
| error->description = net::ErrorToString(error_code); |
| return error.Pass(); |
| } |
| |
| // Reads the request body upload data from a DataPipe. |
| class UploadDataPipeElementReader : public net::UploadElementReader { |
| public: |
| UploadDataPipeElementReader(ScopedDataPipeConsumerHandle pipe) |
| : pipe_(pipe.Pass()), num_bytes_(0) {} |
| virtual ~UploadDataPipeElementReader() {} |
| |
| // UploadElementReader overrides: |
| virtual int Init(const net::CompletionCallback& callback) OVERRIDE { |
| offset_ = 0; |
| ReadDataRaw(pipe_.get(), NULL, &num_bytes_, MOJO_READ_DATA_FLAG_QUERY); |
| return net::OK; |
| } |
| virtual uint64 GetContentLength() const OVERRIDE { |
| return num_bytes_; |
| } |
| virtual uint64 BytesRemaining() const OVERRIDE { |
| return num_bytes_ - offset_; |
| } |
| virtual bool IsInMemory() const OVERRIDE { |
| return false; |
| } |
| virtual int Read(net::IOBuffer* buf, |
| int buf_length, |
| const net::CompletionCallback& callback) OVERRIDE { |
| uint32_t bytes_read = |
| std::min(static_cast<uint32_t>(BytesRemaining()), |
| static_cast<uint32_t>(buf_length)); |
| if (bytes_read > 0) { |
| ReadDataRaw(pipe_.get(), buf->data(), &bytes_read, |
| MOJO_READ_DATA_FLAG_NONE); |
| } |
| |
| offset_ += bytes_read; |
| return bytes_read; |
| } |
| |
| private: |
| ScopedDataPipeConsumerHandle pipe_; |
| uint32_t num_bytes_; |
| uint32_t offset_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UploadDataPipeElementReader); |
| }; |
| |
| } // namespace |
| |
| // Keeps track of a pending two-phase write on a DataPipeProducerHandle. |
| class URLLoaderImpl::PendingWriteToDataPipe : |
| public base::RefCountedThreadSafe<PendingWriteToDataPipe> { |
| public: |
| explicit PendingWriteToDataPipe(ScopedDataPipeProducerHandle handle) |
| : handle_(handle.Pass()), |
| buffer_(NULL) { |
| } |
| |
| MojoResult BeginWrite(uint32_t* num_bytes) { |
| MojoResult result = BeginWriteDataRaw(handle_.get(), &buffer_, num_bytes, |
| MOJO_WRITE_DATA_FLAG_NONE); |
| if (*num_bytes > kMaxReadSize) |
| *num_bytes = kMaxReadSize; |
| |
| return result; |
| } |
| |
| ScopedDataPipeProducerHandle Complete(uint32_t num_bytes) { |
| EndWriteDataRaw(handle_.get(), num_bytes); |
| buffer_ = NULL; |
| return handle_.Pass(); |
| } |
| |
| char* buffer() { return static_cast<char*>(buffer_); } |
| |
| private: |
| friend class base::RefCountedThreadSafe<PendingWriteToDataPipe>; |
| |
| ~PendingWriteToDataPipe() { |
| if (handle_.is_valid()) |
| EndWriteDataRaw(handle_.get(), 0); |
| } |
| |
| ScopedDataPipeProducerHandle handle_; |
| void* buffer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PendingWriteToDataPipe); |
| }; |
| |
| // Takes ownership of a pending two-phase write on a DataPipeProducerHandle, |
| // and makes its buffer available as a net::IOBuffer. |
| class URLLoaderImpl::DependentIOBuffer : public net::WrappedIOBuffer { |
| public: |
| DependentIOBuffer(PendingWriteToDataPipe* pending_write) |
| : net::WrappedIOBuffer(pending_write->buffer()), |
| pending_write_(pending_write) { |
| } |
| private: |
| virtual ~DependentIOBuffer() {} |
| scoped_refptr<PendingWriteToDataPipe> pending_write_; |
| }; |
| |
| URLLoaderImpl::URLLoaderImpl(NetworkContext* context) |
| : context_(context), |
| response_body_buffer_size_(0), |
| auto_follow_redirects_(true), |
| weak_ptr_factory_(this) { |
| } |
| |
| URLLoaderImpl::~URLLoaderImpl() { |
| } |
| |
| void URLLoaderImpl::Start(URLRequestPtr request, |
| const Callback<void(URLResponsePtr)>& callback) { |
| if (url_request_) { |
| SendError(net::ERR_UNEXPECTED, callback); |
| return; |
| } |
| |
| if (!request) { |
| SendError(net::ERR_INVALID_ARGUMENT, callback); |
| return; |
| } |
| |
| url_request_ = context_->url_request_context()->CreateRequest( |
| GURL(request->url), |
| net::DEFAULT_PRIORITY, |
| this, |
| NULL); |
| url_request_->set_method(request->method); |
| if (request->headers) { |
| net::HttpRequestHeaders headers; |
| for (size_t i = 0; i < request->headers.size(); ++i) |
| headers.AddHeaderFromString(request->headers[i].To<base::StringPiece>()); |
| url_request_->SetExtraRequestHeaders(headers); |
| } |
| if (request->body) { |
| ScopedVector<net::UploadElementReader> element_readers; |
| for (size_t i = 0; i < request->body.size(); ++i) { |
| element_readers.push_back( |
| new UploadDataPipeElementReader(request->body[i].Pass())); |
| } |
| url_request_->set_upload(make_scoped_ptr( |
| new net::UploadDataStream(element_readers.Pass(), 0))); |
| } |
| if (request->bypass_cache) |
| url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE); |
| |
| callback_ = callback; |
| response_body_buffer_size_ = request->response_body_buffer_size; |
| auto_follow_redirects_ = request->auto_follow_redirects; |
| |
| url_request_->Start(); |
| } |
| |
| void URLLoaderImpl::FollowRedirect( |
| const Callback<void(URLResponsePtr)>& callback) { |
| if (!url_request_) { |
| SendError(net::ERR_UNEXPECTED, callback); |
| return; |
| } |
| |
| if (auto_follow_redirects_) { |
| DLOG(ERROR) << "Spurious call to FollowRedirect"; |
| SendError(net::ERR_UNEXPECTED, callback); |
| return; |
| } |
| |
| // TODO(darin): Verify that it makes sense to call FollowDeferredRedirect. |
| url_request_->FollowDeferredRedirect(); |
| } |
| |
| void URLLoaderImpl::QueryStatus( |
| const Callback<void(URLLoaderStatusPtr)>& callback) { |
| URLLoaderStatusPtr status(URLLoaderStatus::New()); |
| if (url_request_) { |
| status->is_loading = url_request_->is_pending(); |
| if (!url_request_->status().is_success()) |
| status->error = MakeNetworkError(url_request_->status().error()); |
| } else { |
| status->is_loading = false; |
| } |
| // TODO(darin): Populate more status fields. |
| callback.Run(status.Pass()); |
| } |
| |
| void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request, |
| const net::RedirectInfo& redirect_info, |
| bool* defer_redirect) { |
| DCHECK(url_request == url_request_.get()); |
| DCHECK(url_request->status().is_success()); |
| |
| if (auto_follow_redirects_) |
| return; |
| |
| // Send the redirect response to the client, allowing them to inspect it and |
| // optionally follow the redirect. |
| *defer_redirect = true; |
| |
| URLResponsePtr response = MakeURLResponse(url_request); |
| response->redirect_method = redirect_info.new_method; |
| response->redirect_url = String::From(redirect_info.new_url); |
| |
| SendResponse(response.Pass()); |
| } |
| |
| void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request) { |
| DCHECK(url_request == url_request_.get()); |
| |
| if (!url_request->status().is_success()) { |
| SendError(url_request->status().error(), callback_); |
| callback_ = Callback<void(URLResponsePtr)>(); |
| return; |
| } |
| |
| // TODO(darin): Add support for optional MIME sniffing. |
| |
| DataPipe data_pipe; |
| // TODO(darin): Honor given buffer size. |
| |
| URLResponsePtr response = MakeURLResponse(url_request); |
| response->body = data_pipe.consumer_handle.Pass(); |
| response_body_stream_ = data_pipe.producer_handle.Pass(); |
| |
| SendResponse(response.Pass()); |
| |
| // Start reading... |
| ReadMore(); |
| } |
| |
| void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request, |
| int bytes_read) { |
| DCHECK(url_request == url_request_.get()); |
| |
| if (url_request->status().is_success()) { |
| DidRead(static_cast<uint32_t>(bytes_read), false); |
| } else { |
| pending_write_ = NULL; // This closes the data pipe. |
| } |
| } |
| |
| void URLLoaderImpl::SendError( |
| int error_code, |
| const Callback<void(URLResponsePtr)>& callback) { |
| URLResponsePtr response(URLResponse::New()); |
| if (url_request_) |
| response->url = String::From(url_request_->url()); |
| response->error = MakeNetworkError(error_code); |
| callback.Run(response.Pass()); |
| } |
| |
| void URLLoaderImpl::SendResponse(URLResponsePtr response) { |
| Callback<void(URLResponsePtr)> callback; |
| std::swap(callback_, callback); |
| callback.Run(response.Pass()); |
| } |
| |
| void URLLoaderImpl::OnResponseBodyStreamReady(MojoResult result) { |
| // TODO(darin): Handle a bad |result| value. |
| ReadMore(); |
| } |
| |
| void URLLoaderImpl::WaitToReadMore() { |
| handle_watcher_.Start(response_body_stream_.get(), |
| MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_DEADLINE_INDEFINITE, |
| base::Bind(&URLLoaderImpl::OnResponseBodyStreamReady, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void URLLoaderImpl::ReadMore() { |
| DCHECK(!pending_write_.get()); |
| |
| pending_write_ = new PendingWriteToDataPipe(response_body_stream_.Pass()); |
| |
| uint32_t num_bytes; |
| MojoResult result = pending_write_->BeginWrite(&num_bytes); |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| // The pipe is full. We need to wait for it to have more space. |
| response_body_stream_ = pending_write_->Complete(num_bytes); |
| pending_write_ = NULL; |
| WaitToReadMore(); |
| return; |
| } |
| if (result != MOJO_RESULT_OK) { |
| // The response body stream is in a bad state. Bail. |
| // TODO(darin): How should this be communicated to our client? |
| return; |
| } |
| CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes); |
| |
| scoped_refptr<net::IOBuffer> buf = |
| new DependentIOBuffer(pending_write_.get()); |
| |
| int bytes_read; |
| url_request_->Read(buf.get(), static_cast<int>(num_bytes), &bytes_read); |
| |
| // Drop our reference to the buffer. |
| buf = NULL; |
| |
| if (url_request_->status().is_io_pending()) { |
| // Wait for OnReadCompleted. |
| } else if (url_request_->status().is_success() && bytes_read > 0) { |
| DidRead(static_cast<uint32_t>(bytes_read), true); |
| } else { |
| pending_write_->Complete(0); |
| pending_write_ = NULL; // This closes the data pipe. |
| } |
| } |
| |
| void URLLoaderImpl::DidRead(uint32_t num_bytes, bool completed_synchronously) { |
| DCHECK(url_request_->status().is_success()); |
| |
| response_body_stream_ = pending_write_->Complete(num_bytes); |
| pending_write_ = NULL; |
| |
| if (completed_synchronously) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&URLLoaderImpl::ReadMore, weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| ReadMore(); |
| } |
| } |
| |
| } // namespace mojo |