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