blob: 9b0cce9d5257f76121a34c779126d836aa6e00d2 [file] [log] [blame]
// 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 "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/http/http_response_headers.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 = url_request->url().spec();
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);
}
return response.Pass();
}
} // 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) {
}
bool 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 == MOJO_RESULT_OK;
}
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),
auto_follow_redirects_(true),
weak_ptr_factory_(this) {
}
URLLoaderImpl::~URLLoaderImpl() {
}
void URLLoaderImpl::OnConnectionError() {
delete this;
}
void URLLoaderImpl::Start(URLRequestPtr request,
ScopedDataPipeProducerHandle response_body_stream) {
// Do not allow starting another request.
if (url_request_) {
SendError(net::ERR_UNEXPECTED);
url_request_.reset();
response_body_stream_.reset();
return;
}
if (!request) {
SendError(net::ERR_INVALID_ARGUMENT);
return;
}
response_body_stream_ = response_body_stream.Pass();
GURL url(request->url);
url_request_.reset(
new net::URLRequest(url,
net::DEFAULT_PRIORITY,
this,
context_->url_request_context()));
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->bypass_cache)
url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE);
// TODO(darin): Handle request body.
auto_follow_redirects_ = request->auto_follow_redirects;
url_request_->Start();
}
void URLLoaderImpl::FollowRedirect() {
if (auto_follow_redirects_) {
DLOG(ERROR) << "Spurious call to FollowRedirect";
} else {
if (url_request_)
url_request_->FollowDeferredRedirect();
}
}
void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request,
const GURL& new_url,
bool* defer_redirect) {
DCHECK(url_request == url_request_.get());
DCHECK(url_request->status().is_success());
URLResponsePtr response = MakeURLResponse(url_request);
std::string redirect_method =
net::URLRequest::ComputeMethodForRedirect(url_request->method(),
response->status_code);
client()->OnReceivedRedirect(
response.Pass(), new_url.spec(), redirect_method);
*defer_redirect = !auto_follow_redirects_;
}
void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request) {
DCHECK(url_request == url_request_.get());
if (!url_request->status().is_success()) {
SendError(url_request->status().error());
return;
}
client()->OnReceivedResponse(MakeURLResponse(url_request));
// Start reading...
ReadMore();
}
void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request,
int bytes_read) {
if (url_request->status().is_success()) {
DidRead(static_cast<uint32_t>(bytes_read), false);
} else {
pending_write_ = NULL; // This closes the data pipe.
// TODO(darin): Perhaps we should communicate this error to our client.
}
}
void URLLoaderImpl::SendError(int error_code) {
NetworkErrorPtr error(NetworkError::New());
error->code = error_code;
error->description = net::ErrorToString(error_code);
client()->OnReceivedError(error.Pass());
}
void URLLoaderImpl::ReadMore() {
DCHECK(!pending_write_);
pending_write_ = new PendingWriteToDataPipe(response_body_stream_.Pass());
uint32_t num_bytes;
if (!pending_write_->BeginWrite(&num_bytes))
CHECK(false); // Oops! TODO(darin): crbug/386877: The pipe might be full!
if (num_bytes > static_cast<uint32_t>(std::numeric_limits<int>::max()))
CHECK(false); // Oops!
scoped_refptr<net::IOBuffer> buf = new DependentIOBuffer(pending_write_);
int bytes_read;
url_request_->Read(buf, 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.
if (bytes_read == 0) {
client()->OnReceivedEndOfResponseBody();
} else {
DCHECK(!url_request_->status().is_success());
SendError(url_request_->status().error());
}
}
}
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