blob: 06451f0827c618ee0b80f30e349405ab55c4cb03 [file] [log] [blame]
// Copyright 2017 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 <fuchsia/net/oldhttp/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <unordered_map>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <zx/time.h>
#include "lib/component/cpp/startup_context.h"
#include "lib/fidl/cpp/binding_set.h"
#include "lib/fidl/cpp/optional.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/memory/weak_ptr.h"
#include "lib/fxl/time/time_delta.h"
namespace {
namespace http = ::fuchsia::net::oldhttp;
class RetryingLoader {
public:
RetryingLoader(http::URLLoaderPtr url_loader, const std::string& url,
fuchsia::sys::Loader::LoadUrlCallback callback)
: url_loader_(std::move(url_loader)),
url_(url),
callback_(std::move(callback)),
// TODO(rosswang): deadline support
quiet_tries_(5),
// TODO(rosswang): randomness
retry_delay_(fxl::TimeDelta::FromSeconds(1)),
weak_ptr_factory_(this) {}
void Attempt() {
url_loader_->Start(NewRequest(),
[weak_this = weak_ptr_factory_.GetWeakPtr()](
const http::URLResponse& response) {
if (weak_this) {
weak_this->ProcessResponse(response);
}
});
}
void SetDeleter(fit::closure fn) { deleter_ = std::move(fn); }
private:
// Need to create a new request each time because a URLRequest's body can
// potentially contain a VMO handle and so can't be cloned.
http::URLRequest NewRequest() const {
http::URLRequest request;
request.method = "GET";
request.url = url_;
request.auto_follow_redirects = true;
request.response_body_mode = http::ResponseBodyMode::BUFFER;
return request;
}
void ProcessResponse(const http::URLResponse& response) {
if (response.status_code == 200) {
auto package = fuchsia::sys::Package::New();
package->data = fidl::MakeOptional(std::move(response.body->buffer()));
package->resolved_url = std::move(response.url);
SendResponse(std::move(package));
} else if (response.error) {
Retry(response);
} else {
FXL_LOG(WARNING) << "Failed to load application from " << url_ << ": "
<< response.status_line << " (" << response.status_code
<< ")";
SendResponse(nullptr);
}
}
void Retry(const http::URLResponse& response) {
async::PostDelayedTask(
async_get_default_dispatcher(),
[weak_this = weak_ptr_factory_.GetWeakPtr()] {
if (weak_this) {
weak_this->Attempt();
}
},
zx::nsec(retry_delay_.ToNanoseconds()));
if (quiet_tries_ > 0) {
FXL_VLOG(2) << "Retrying load of " << url_ << " due to "
<< response.error->description << " (" << response.error->code
<< ")";
quiet_tries_--;
// TODO(rosswang): Randomness, and factor out the delay fn.
retry_delay_ =
fxl::TimeDelta::FromSecondsF(retry_delay_.ToSecondsF() * 1.5f);
} else if (quiet_tries_ == 0) {
FXL_LOG(WARNING) << "Error while attempting to load application from "
<< url_ << ": " << response.error->description << " ("
<< response.error->code
<< "); continuing to retry every "
<< retry_delay_.ToSeconds() << " s.";
quiet_tries_ = -1;
}
}
void SendResponse(fuchsia::sys::PackagePtr package) {
FXL_DCHECK(!package || !package->resolved_url.empty());
callback_(std::move(package));
deleter_();
}
const http::URLLoaderPtr url_loader_;
const std::string url_;
const fuchsia::sys::Loader::LoadUrlCallback callback_;
fit::closure deleter_;
// Tries before an error is printed. No errors will be printed afterwards
// either.
int quiet_tries_;
fxl::TimeDelta retry_delay_;
fxl::WeakPtrFactory<RetryingLoader> weak_ptr_factory_;
};
class NetworkLoader : public fuchsia::sys::Loader {
public:
NetworkLoader()
: context_(component::StartupContext::CreateFromStartupInfo()) {
context_->outgoing().AddPublicService(bindings_.GetHandler(this));
context_->ConnectToEnvironmentService(http_.NewRequest());
}
void LoadUrl(std::string url, LoadUrlCallback callback) override {
http::URLLoaderPtr loader;
http_->CreateURLLoader(loader.NewRequest());
auto retrying_loader = std::make_unique<RetryingLoader>(
std::move(loader), url, std::move(callback));
RetryingLoader* ref = retrying_loader.get();
loaders_.emplace(ref, std::move(retrying_loader));
ref->SetDeleter([this, ref] { loaders_.erase(ref); });
ref->Attempt();
}
private:
std::unique_ptr<component::StartupContext> context_;
fidl::BindingSet<fuchsia::sys::Loader> bindings_;
http::HttpServicePtr http_;
std::unordered_map<RetryingLoader*, std::unique_ptr<RetryingLoader>> loaders_;
};
} // namespace
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
NetworkLoader app;
loop.Run();
return 0;
}