blob: 693da1a3ff89cf2d0cc3c74025e0f1e2ae679353 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "lib/network_wrapper/network_wrapper_impl.h"
#include <utility>
#include "lib/callback/cancellable_helper.h"
#include "lib/callback/destruction_sentinel.h"
#include "lib/callback/trace_callback.h"
#include "src/lib/fxl/strings/ascii.h"
namespace network_wrapper {
namespace http = ::fuchsia::net::oldhttp;
const uint32_t kMaxRedirectCount = 32;
const int32_t kTooManyRedirectErrorCode = -310;
const int32_t kInvalidResponseErrorCode = -320;
class NetworkWrapperImpl::RunningRequest {
public:
explicit RunningRequest(fit::function<http::URLRequest()> request_factory)
: request_factory_(std::move(request_factory)), redirect_count_(0u) {}
void Cancel() {
FXL_DCHECK(on_empty_callback_);
on_empty_callback_();
}
// Set the network service to use. This will start (or restart) the request.
void SetHttpService(http::HttpService* http_service) {
http_service_ = http_service;
if (http_service) {
// Restart the request, as any fidl callback is now pending forever.
Start();
}
}
void set_callback(fit::function<void(http::URLResponse)> callback) {
// Once this class calls its callback, it must notify its container.
callback_ = [this, callback = std::move(callback)](
http::URLResponse response) mutable {
FXL_DCHECK(on_empty_callback_);
if (destruction_sentinel_.DestructedWhile(
[callback = std::move(callback), &response] {
callback(std::move(response));
})) {
return;
}
on_empty_callback_();
};
}
void set_on_empty(fit::closure on_empty_callback) {
on_empty_callback_ = std::move(on_empty_callback);
}
private:
void Start() {
// Cancel any pending request.
url_loader_.Unbind();
// If no network service has been set, bail out and wait to be called again.
if (!http_service_)
return;
auto request = request_factory_();
// If last response was a redirect, follow it.
if (!next_url_.empty())
request.url = next_url_;
http_service_->CreateURLLoader(url_loader_.NewRequest());
const std::string& url = request.url;
const std::string& method = request.method;
url_loader_->Start(
std::move(request),
TRACE_CALLBACK(
[this](http::URLResponse response) {
url_loader_.Unbind();
if (response.error) {
callback_(std::move(response));
return;
}
// 307 and 308 are redirects for which the HTTP
// method must not change.
if (response.status_code == 307 || response.status_code == 308) {
HandleRedirect(std::move(response));
return;
}
callback_(std::move(response));
return;
},
"network_wrapper", "network_url_loader_start", "url", url, "method",
method));
url_loader_.set_error_handler([this](zx_status_t status) {
// If the connection to the url loader failed, restart the request.
// TODO(qsr): LE-77: Handle multiple failures with:
// 1) backoff.
// 2) notification to the user.
Start();
});
}
void HandleRedirect(http::URLResponse response) {
// Follow the redirect if a Location header is found.
for (const auto& header : *response.headers) {
if (fxl::EqualsCaseInsensitiveASCII(header.name, "location")) {
++redirect_count_;
if (redirect_count_ >= kMaxRedirectCount) {
callback_(NewErrorResponse(kTooManyRedirectErrorCode,
"Too many redirects."));
return;
}
next_url_ = header.value;
Start();
return;
}
}
// Return an error otherwise.
callback_(
NewErrorResponse(kInvalidResponseErrorCode, "No Location header."));
// |this| might be deleted withing the callback, don't reference member
// variables afterwards.
}
http::URLResponse NewErrorResponse(int32_t code, std::string reason) {
http::URLResponse response;
response.error = http::HttpError::New();
response.error->code = code;
response.error->description = reason;
return response;
}
fit::function<http::URLRequest()> request_factory_;
fit::function<void(http::URLResponse)> callback_;
fit::closure on_empty_callback_;
std::string next_url_;
uint32_t redirect_count_;
http::HttpService* http_service_;
http::URLLoaderPtr url_loader_;
callback::DestructionSentinel destruction_sentinel_;
};
NetworkWrapperImpl::NetworkWrapperImpl(
async_dispatcher_t* dispatcher, std::unique_ptr<backoff::Backoff> backoff,
fit::function<http::HttpServicePtr()> http_service_factory)
: backoff_(std::move(backoff)),
http_service_factory_(std::move(http_service_factory)),
task_runner_(dispatcher) {}
NetworkWrapperImpl::~NetworkWrapperImpl() {}
fxl::RefPtr<callback::Cancellable> NetworkWrapperImpl::Request(
fit::function<http::URLRequest()> request_factory,
fit::function<void(http::URLResponse)> callback) {
RunningRequest& request =
running_requests_.emplace(std::move(request_factory));
auto cancellable =
callback::CancellableImpl::Create([&request]() { request.Cancel(); });
request.set_callback(cancellable->WrapCallback(TRACE_CALLBACK(
std::move(callback), "network_wrapper", "network_request")));
if (!in_backoff_) {
request.SetHttpService(GetHttpService());
}
return cancellable;
}
http::HttpService* NetworkWrapperImpl::GetHttpService() {
if (!http_service_) {
http_service_ = http_service_factory_();
http_service_.set_error_handler([this](zx_status_t status) {
FXL_LOG(WARNING) << "Network service crashed or not configured "
<< "in environment, trying to reconnect.";
FXL_DCHECK(!in_backoff_);
in_backoff_ = true;
for (auto& request : running_requests_) {
request.SetHttpService(nullptr);
}
http_service_.Unbind();
task_runner_.PostDelayedTask([this] { RetryGetHttpService(); },
backoff_->GetNext());
});
}
return http_service_.get();
}
void NetworkWrapperImpl::RetryGetHttpService() {
in_backoff_ = false;
if (running_requests_.empty()) {
return;
}
http::HttpService* http_service = GetHttpService();
for (auto& request : running_requests_) {
request.SetHttpService(http_service);
}
}
} // namespace network_wrapper