| // 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; |
| } |