blob: b9a3c15193a4289a372a465b7df878a11a6bcbfb [file] [log] [blame]
// Copyright 2018 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 "components/cronet/native/url_request.h"
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "components/cronet/native/engine.h"
#include "components/cronet/native/generated/cronet.idl_impl_struct.h"
#include "components/cronet/native/include/cronet_c.h"
#include "components/cronet/native/io_buffer_with_cronet_buffer.h"
#include "components/cronet/native/runnables.h"
#include "net/base/io_buffer.h"
namespace {
net::RequestPriority ConvertRequestPriority(
Cronet_UrlRequestParams_REQUEST_PRIORITY priority) {
switch (priority) {
case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_IDLE:
return net::IDLE;
case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOWEST:
return net::LOWEST;
case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOW:
return net::LOW;
case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_MEDIUM:
return net::MEDIUM;
case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_HIGHEST:
return net::HIGHEST;
}
return net::DEFAULT_PRIORITY;
}
std::unique_ptr<Cronet_UrlResponseInfo> CreateCronet_UrlResponseInfo(
const std::vector<std::string>& url_chain,
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) {
auto response_info = std::make_unique<Cronet_UrlResponseInfo>();
response_info->url = url_chain.back();
response_info->url_chain = url_chain;
response_info->http_status_code = http_status_code;
response_info->http_status_text = http_status_text;
// |headers| could be nullptr.
if (headers != nullptr) {
size_t iter = 0;
std::string header_name;
std::string header_value;
while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
Cronet_HttpHeader header;
header.name = header_name;
header.value = header_value;
response_info->all_headers_list.push_back(std::move(header));
}
}
response_info->was_cached = was_cached;
response_info->negotiated_protocol = negotiated_protocol;
response_info->proxy_server = proxy_server;
response_info->received_byte_count = received_byte_count;
return response_info;
}
Cronet_Error_ERROR_CODE NetErrorToCronetErrorCode(int net_error) {
switch (net_error) {
case net::ERR_NAME_NOT_RESOLVED:
return Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED;
case net::ERR_INTERNET_DISCONNECTED:
return Cronet_Error_ERROR_CODE_ERROR_INTERNET_DISCONNECTED;
case net::ERR_NETWORK_CHANGED:
return Cronet_Error_ERROR_CODE_ERROR_NETWORK_CHANGED;
case net::ERR_TIMED_OUT:
return Cronet_Error_ERROR_CODE_ERROR_TIMED_OUT;
case net::ERR_CONNECTION_CLOSED:
return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_CLOSED;
case net::ERR_CONNECTION_TIMED_OUT:
return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_TIMED_OUT;
case net::ERR_CONNECTION_REFUSED:
return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_REFUSED;
case net::ERR_CONNECTION_RESET:
return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_RESET;
case net::ERR_ADDRESS_UNREACHABLE:
return Cronet_Error_ERROR_CODE_ERROR_ADDRESS_UNREACHABLE;
case net::ERR_QUIC_PROTOCOL_ERROR:
return Cronet_Error_ERROR_CODE_ERROR_QUIC_PROTOCOL_FAILED;
default:
return Cronet_Error_ERROR_CODE_ERROR_OTHER;
}
}
bool IsCronetErrorImmediatelyRetryable(Cronet_Error_ERROR_CODE error_code) {
switch (error_code) {
case Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED:
case Cronet_Error_ERROR_CODE_ERROR_INTERNET_DISCONNECTED:
case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_REFUSED:
case Cronet_Error_ERROR_CODE_ERROR_ADDRESS_UNREACHABLE:
case Cronet_Error_ERROR_CODE_ERROR_OTHER:
default:
return false;
case Cronet_Error_ERROR_CODE_ERROR_NETWORK_CHANGED:
case Cronet_Error_ERROR_CODE_ERROR_TIMED_OUT:
case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_CLOSED:
case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_TIMED_OUT:
case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_RESET:
return true;
}
}
std::unique_ptr<Cronet_Error> CreateCronet_Error(
int net_error,
int quic_error,
const std::string& error_string) {
auto error = std::make_unique<Cronet_Error>();
error->error_code = NetErrorToCronetErrorCode(net_error);
error->message = error_string;
error->internal_error_code = net_error;
error->quic_detailed_error_code = quic_error;
error->immediately_retryable =
IsCronetErrorImmediatelyRetryable(error->error_code);
return error;
}
} // namespace
namespace cronet {
// Callback is owned by CronetURLRequest. It is constructed on client thread,
// but invoked and deleted on the network thread.
class Cronet_UrlRequestImpl::Callback : public CronetURLRequest::Callback {
public:
Callback(const std::string& url,
Cronet_UrlRequestImpl* url_request,
Cronet_UrlRequestCallbackPtr callback,
Cronet_ExecutorPtr executor);
~Callback() override = default;
// CronetURLRequest::Callback implementations:
void OnReceivedRedirect(const std::string& new_location,
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) override;
void OnResponseStarted(int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) override;
void OnReadCompleted(scoped_refptr<net::IOBuffer> buffer,
int bytes_read,
int64_t received_byte_count) override;
void OnSucceeded(int64_t received_byte_count) override;
void OnError(int net_error,
int quic_error,
const std::string& error_string,
int64_t received_byte_count) override;
void OnCanceled() override;
void OnDestroyed() override;
void OnMetricsCollected(const base::Time& request_start_time,
const base::TimeTicks& request_start,
const base::TimeTicks& dns_start,
const base::TimeTicks& dns_end,
const base::TimeTicks& connect_start,
const base::TimeTicks& connect_end,
const base::TimeTicks& ssl_start,
const base::TimeTicks& ssl_end,
const base::TimeTicks& send_start,
const base::TimeTicks& send_end,
const base::TimeTicks& push_start,
const base::TimeTicks& push_end,
const base::TimeTicks& receive_headers_end,
const base::TimeTicks& request_end,
bool socket_reused,
int64_t sent_bytes_count,
int64_t received_bytes_count) override;
private:
void PostTaskToExecutor(base::OnceClosure task);
// The UrlRequest which owns context that owns the callback.
Cronet_UrlRequestImpl* url_request_ = nullptr;
// Application callback interface, used, but not owned, by |this|.
Cronet_UrlRequestCallbackPtr callback_ = nullptr;
// Executor for application callback, used, but not owned, by |this|.
Cronet_ExecutorPtr executor_ = nullptr;
// URL chain contains the URL currently being requested, and
// all URLs previously requested. New URLs are added before
// Cronet_UrlRequestCallback::OnRedirectReceived is called.
std::vector<std::string> url_chain_;
// All methods except constructor are invoked on the network thread.
THREAD_CHECKER(network_thread_checker_);
DISALLOW_COPY_AND_ASSIGN(Callback);
};
Cronet_UrlRequestImpl::Cronet_UrlRequestImpl() = default;
Cronet_UrlRequestImpl::~Cronet_UrlRequestImpl() {
base::AutoLock lock(lock_);
// Request may already be destroyed if it hasn't started or got canceled.
if (request_)
request_->Destroy(false);
}
Cronet_RESULT Cronet_UrlRequestImpl::InitWithParams(
Cronet_EnginePtr engine,
Cronet_String url,
Cronet_UrlRequestParamsPtr params,
Cronet_UrlRequestCallbackPtr callback,
Cronet_ExecutorPtr executor) {
CHECK(engine);
engine_ = reinterpret_cast<Cronet_EngineImpl*>(engine);
if (!url || std::string(url).empty())
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_URL);
if (!params)
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_PARAMS);
if (!callback)
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_CALLBACK);
if (!executor)
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_EXECUTOR);
VLOG(1) << "New Cronet_UrlRequest: " << url;
// Tests call InitWithParams() repeatedly on the same Cronet_UrlRequestPtr,
// rather than Destroy()ing and creating a new one, so ensure that any prior
// |request_| is not leaked (see https://crbug.com/829077).
{
base::AutoLock lock(lock_);
if (request_)
request_->Destroy(false);
}
request_ = new CronetURLRequest(
engine_->cronet_url_request_context(),
std::make_unique<Callback>(url, this, callback, executor), GURL(url),
ConvertRequestPriority(params->priority), params->disable_cache,
true /* params->disableConnectionMigration */,
false /* params->enableMetrics */,
// TODO(pauljensen): Consider exposing TrafficStats API via C++ API.
false /* traffic_stats_tag_set */, 0 /* traffic_stats_tag */,
false /* traffic_stats_uid_set */, 0 /* traffic_stats_uid */);
if (!params->http_method.empty() &&
!request_->SetHttpMethod(params->http_method)) {
return engine_->CheckResult(
Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_METHOD);
}
for (const auto& request_header : params->request_headers) {
if (request_header.name.empty())
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_HEADER_NAME);
if (request_header.value.empty())
return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_HEADER_VALUE);
if (!request_->AddRequestHeader(request_header.name,
request_header.value)) {
return engine_->CheckResult(
Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_HEADER);
}
}
return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}
Cronet_RESULT Cronet_UrlRequestImpl::Start() {
base::AutoLock lock(lock_);
if (started_) {
return engine_->CheckResult(
Cronet_RESULT_ILLEGAL_STATE_REQUEST_ALREADY_STARTED);
}
if (!request_) {
return engine_->CheckResult(
Cronet_RESULT_ILLEGAL_STATE_REQUEST_NOT_INITIALIZED);
}
request_->Start();
started_ = true;
return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}
Cronet_RESULT Cronet_UrlRequestImpl::FollowRedirect() {
base::AutoLock lock(lock_);
if (!waiting_on_redirect_) {
return engine_->CheckResult(
Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_REDIRECT);
}
waiting_on_redirect_ = false;
if (!IsDoneLocked())
request_->FollowDeferredRedirect();
return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}
Cronet_RESULT Cronet_UrlRequestImpl::Read(Cronet_BufferPtr buffer) {
base::AutoLock lock(lock_);
if (!waiting_on_read_)
return engine_->CheckResult(Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_READ);
waiting_on_read_ = false;
if (IsDoneLocked())
return engine_->CheckResult(Cronet_RESULT_SUCCESS);
// Create IOBuffer that will own |buffer| while it is used by |request_|.
net::IOBuffer* io_buffer = new IOBufferWithCronet_Buffer(buffer);
if (request_->ReadData(io_buffer, Cronet_Buffer_GetSize(buffer)))
return engine_->CheckResult(Cronet_RESULT_SUCCESS);
return engine_->CheckResult(Cronet_RESULT_ILLEGAL_STATE_READ_FAILED);
}
void Cronet_UrlRequestImpl::Cancel() {
base::AutoLock lock(lock_);
if (started_) {
// If request has posted callbacks to client executor, then it is possible
// that |request_| will be destroyed before callback is executed. The
// callback runnable uses IsDone() to avoid calling client callback in this
// case.
DestroyRequestUnlessDoneLocked(
Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED);
}
}
bool Cronet_UrlRequestImpl::IsDone() {
base::AutoLock lock(lock_);
return IsDoneLocked();
}
bool Cronet_UrlRequestImpl::IsDoneLocked() {
lock_.AssertAcquired();
return started_ && request_ == nullptr;
}
bool Cronet_UrlRequestImpl::DestroyRequestUnlessDone(
Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason) {
base::AutoLock lock(lock_);
return DestroyRequestUnlessDoneLocked(finished_reason);
}
bool Cronet_UrlRequestImpl::DestroyRequestUnlessDoneLocked(
Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason) {
lock_.AssertAcquired();
if (request_ == nullptr)
return true;
DCHECK(error_ == nullptr ||
finished_reason == Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED);
request_->Destroy(finished_reason ==
Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED);
// Request can no longer be used as CronetURLRequest::Destroy() will
// eventually delete |request_| from the network thread, so setting |request_|
// to nullptr doesn't introduce a memory leak.
request_ = nullptr;
return false;
}
void Cronet_UrlRequestImpl::GetStatus(
Cronet_UrlRequestStatusListenerPtr listener) {
NOTIMPLEMENTED();
}
Cronet_UrlRequestImpl::Callback::Callback(const std::string& url,
Cronet_UrlRequestImpl* url_request,
Cronet_UrlRequestCallbackPtr callback,
Cronet_ExecutorPtr executor)
: url_request_(url_request),
callback_(callback),
executor_(executor),
url_chain_({url}) {
DETACH_FROM_THREAD(network_thread_checker_);
DCHECK(url_request);
DCHECK(callback);
DCHECK(executor);
}
// CronetURLRequest::Callback implementations:
void Cronet_UrlRequestImpl::Callback::OnReceivedRedirect(
const std::string& new_location,
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
base::AutoLock lock(url_request_->lock_);
url_request_->waiting_on_redirect_ = true;
url_request_->response_info_ = CreateCronet_UrlResponseInfo(
url_chain_, http_status_code, http_status_text, headers, was_cached,
negotiated_protocol, proxy_server, received_byte_count);
// Have to do this after creating responseInfo.
url_chain_.push_back(new_location);
// Invoke Cronet_UrlRequestCallback_OnRedrectReceived using OnceClosure.
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request) {
if (url_request->IsDone())
return;
Cronet_UrlRequestCallback_OnRedirectReceived(
callback, url_request, url_request->response_info_.get(),
url_request->response_info_->url_chain.front().c_str());
},
callback_, url_request_));
}
void Cronet_UrlRequestImpl::Callback::OnResponseStarted(
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
base::AutoLock lock(url_request_->lock_);
url_request_->waiting_on_read_ = true;
url_request_->response_info_ = CreateCronet_UrlResponseInfo(
url_chain_, http_status_code, http_status_text, headers, was_cached,
negotiated_protocol, proxy_server, received_byte_count);
// Invoke Cronet_UrlRequestCallback_OnResponseStarted using OnceClosure.
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request) {
if (url_request->IsDone())
return;
Cronet_UrlRequestCallback_OnResponseStarted(
callback, url_request, url_request->response_info_.get());
},
callback_, url_request_));
}
void Cronet_UrlRequestImpl::Callback::OnReadCompleted(
scoped_refptr<net::IOBuffer> buffer,
int bytes_read,
int64_t received_byte_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
IOBufferWithCronet_Buffer* io_buffer =
reinterpret_cast<IOBufferWithCronet_Buffer*>(buffer.get());
std::unique_ptr<Cronet_Buffer> cronet_buffer(io_buffer->Release());
base::AutoLock lock(url_request_->lock_);
url_request_->waiting_on_read_ = true;
url_request_->response_info_->received_byte_count = received_byte_count;
// Invoke Cronet_UrlRequestCallback_OnReadCompleted using OnceClosure.
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request,
std::unique_ptr<Cronet_Buffer> cronet_buffer, int bytes_read) {
if (url_request->IsDone())
return;
Cronet_UrlRequestCallback_OnReadCompleted(
callback, url_request, url_request->response_info_.get(),
cronet_buffer.release(), bytes_read);
},
callback_, url_request_, std::move(cronet_buffer), bytes_read));
}
void Cronet_UrlRequestImpl::Callback::OnSucceeded(int64_t received_byte_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
base::AutoLock lock(url_request_->lock_);
url_request_->response_info_->received_byte_count = received_byte_count;
// Invoke Cronet_UrlRequestCallback_OnSucceeded using OnceClosure.
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request) {
if (url_request->DestroyRequestUnlessDone(
Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED)) {
return;
}
Cronet_UrlRequestCallback_OnSucceeded(
callback, url_request, url_request->response_info_.get());
},
callback_, url_request_));
}
void Cronet_UrlRequestImpl::Callback::OnError(int net_error,
int quic_error,
const std::string& error_string,
int64_t received_byte_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
base::AutoLock lock(url_request_->lock_);
if (url_request_->response_info_)
url_request_->response_info_->received_byte_count = received_byte_count;
url_request_->error_ =
CreateCronet_Error(net_error, quic_error, error_string);
// Invoke Cronet_UrlRequestCallback_OnFailed using OnceClosure.
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request) {
if (url_request->DestroyRequestUnlessDone(
Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED)) {
return;
}
Cronet_UrlRequestCallback_OnFailed(callback, url_request,
url_request->response_info_.get(),
url_request->error_.get());
},
callback_, url_request_));
}
void Cronet_UrlRequestImpl::Callback::OnCanceled() {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
PostTaskToExecutor(base::BindOnce(
[](Cronet_UrlRequestCallback* callback,
Cronet_UrlRequestImpl* url_request) {
Cronet_UrlRequestCallback_OnCanceled(callback, url_request,
url_request->response_info_.get());
},
callback_, url_request_));
}
void Cronet_UrlRequestImpl::Callback::OnDestroyed() {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
DCHECK(url_request_);
}
void Cronet_UrlRequestImpl::Callback::OnMetricsCollected(
const base::Time& request_start_time,
const base::TimeTicks& request_start,
const base::TimeTicks& dns_start,
const base::TimeTicks& dns_end,
const base::TimeTicks& connect_start,
const base::TimeTicks& connect_end,
const base::TimeTicks& ssl_start,
const base::TimeTicks& ssl_end,
const base::TimeTicks& send_start,
const base::TimeTicks& send_end,
const base::TimeTicks& push_start,
const base::TimeTicks& push_end,
const base::TimeTicks& receive_headers_end,
const base::TimeTicks& request_end,
bool socket_reused,
int64_t sent_bytes_count,
int64_t received_bytes_count) {
DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
}
void Cronet_UrlRequestImpl::Callback::PostTaskToExecutor(
base::OnceClosure task) {
Cronet_RunnablePtr runnable =
new cronet::OnceClosureRunnable(std::move(task));
// |runnable| is passed to executor, which destroys it after execution.
Cronet_Executor_Execute(executor_, runnable);
}
}; // namespace cronet
CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_Create() {
return new cronet::Cronet_UrlRequestImpl();
}