| // 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 <fuchsia/net/oldhttp/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/async/default.h> |
| |
| #include "garnet/bin/cobalt/utils/fuchsia_http_client.h" |
| #include "lib/fsl/socket/strings.h" |
| #include "lib/fsl/vmo/strings.h" |
| #include "lib/fxl/memory/ref_counted.h" |
| |
| namespace cobalt { |
| namespace utils { |
| |
| namespace http = ::fuchsia::net::oldhttp; |
| |
| using clearcut::HTTPClient; |
| using clearcut::HTTPRequest; |
| using clearcut::HTTPResponse; |
| using tensorflow_statusor::StatusOr; |
| |
| namespace { |
| |
| http::URLRequest MakeRequest(fxl::RefPtr<NetworkRequest> network_request) { |
| http::URLRequest fx_request; |
| fx_request.url = network_request->request().url; |
| fx_request.method = "POST"; |
| fx_request.auto_follow_redirects = true; |
| fx_request.body = http::URLBody::New(); |
| |
| fsl::SizedVmo data; |
| auto result = fsl::VmoFromString(network_request->request().body, &data); |
| FXL_CHECK(result); |
| |
| fx_request.body->set_buffer(std::move(data).ToTransport()); |
| for (const auto& header : network_request->request().headers) { |
| http::HttpHeader hdr; |
| hdr.name = header.first; |
| hdr.value = header.second; |
| fx_request.headers.push_back(std::move(hdr)); |
| } |
| return fx_request; |
| } |
| |
| } // namespace |
| |
| void NetworkRequest::ReadResponse(async_dispatcher_t* dispatcher, |
| fxl::RefPtr<NetworkRequest> self, |
| uint32_t http_code, zx::socket source) { |
| // Store a reference to myself, so that I don't get deleted while reading from |
| // the socket. |
| self_ = self; |
| http_code_ = http_code; |
| socket_drainer_ = std::make_unique<fsl::SocketDrainer>(this, dispatcher); |
| socket_drainer_->Start(std::move(source)); |
| } |
| |
| void NetworkRequest::OnDataAvailable(const void* data, size_t num_bytes) { |
| response_.append(static_cast<const char*>(data), num_bytes); |
| } |
| |
| void NetworkRequest::OnDataComplete() { |
| HTTPResponse response; |
| response.response = response_; |
| response.http_code = http_code_; |
| SetValueAndCleanUp(std::move(response)); |
| } |
| |
| FuchsiaHTTPClient::FuchsiaHTTPClient( |
| network_wrapper::NetworkWrapper* network_wrapper, |
| async_dispatcher_t* dispatcher) |
| : network_wrapper_(network_wrapper), dispatcher_(dispatcher) {} |
| |
| void FuchsiaHTTPClient::HandleResponse(fxl::RefPtr<NetworkRequest> req, |
| http::URLResponse fx_response) { |
| req->CancelCallbacks(); |
| if (fx_response.error) { |
| std::ostringstream ss; |
| ss << fx_response.url << " error " << fx_response.error->description; |
| req->SetValueAndCleanUp(util::Status(util::StatusCode::INTERNAL, ss.str())); |
| return; |
| } |
| if (fx_response.body) { |
| FXL_DCHECK(fx_response.body->is_stream()); |
| req->ReadResponse(dispatcher_, req, fx_response.status_code, |
| std::move(fx_response.body->stream())); |
| } else { |
| HTTPResponse response; |
| response.response = ""; |
| response.http_code = fx_response.status_code; |
| req->SetValueAndCleanUp(std::move(response)); |
| } |
| } |
| |
| void FuchsiaHTTPClient::HandleDeadline(fxl::RefPtr<NetworkRequest> req) { |
| req->CancelCallbacks(); |
| req->SetValueAndCleanUp( |
| util::Status(util::StatusCode::DEADLINE_EXCEEDED, |
| "Deadline exceeded while waiting for network request")); |
| } |
| |
| void FuchsiaHTTPClient::SendRequest( |
| fxl::RefPtr<NetworkRequest> network_request) { |
| network_request->SetNetworkWrapperCancel(network_wrapper_->Request( |
| std::bind(&MakeRequest, network_request), |
| [this, network_request](http::URLResponse fx_response) { |
| HandleResponse(network_request, std::move(fx_response)); |
| })); |
| } |
| |
| void NetworkRequest::CancelCallbacks() { |
| if (network_wrapper_cancel_) { |
| network_wrapper_cancel_->Cancel(); |
| } |
| if (deadline_task_) { |
| deadline_task_->Cancel(); |
| } |
| } |
| |
| void NetworkRequest::SetValueAndCleanUp(StatusOr<HTTPResponse> value) { |
| promise_.set_value(std::move(value)); |
| |
| // Clean up stored references so NetworkRequest can be freed. |
| if (network_wrapper_cancel_) { |
| network_wrapper_cancel_ = nullptr; |
| } |
| if (deadline_task_) { |
| deadline_task_ = nullptr; |
| } |
| if (socket_drainer_) { |
| socket_drainer_ = nullptr; |
| } |
| self_ = nullptr; |
| } |
| |
| std::future<StatusOr<HTTPResponse>> FuchsiaHTTPClient::Post( |
| HTTPRequest request, std::chrono::steady_clock::time_point deadline) { |
| ZX_ASSERT_MSG( |
| async_get_default_dispatcher() != dispatcher_, |
| "Post should not be called from the same thread as dispatcher_, as " |
| "this may cause deadlocks"); |
| auto network_request = |
| fxl::MakeRefCounted<NetworkRequest>(std::move(request)); |
| network_request->SetDeadlineTask(std::make_unique<async::TaskClosure>( |
| [this, network_request] { HandleDeadline(network_request); })); |
| |
| async::PostTask(dispatcher_, |
| [this, network_request]() { SendRequest(network_request); }); |
| |
| auto duration = zx::nsec(std::chrono::duration_cast<std::chrono::nanoseconds>( |
| deadline - std::chrono::steady_clock::now()) |
| .count()); |
| network_request->ScheduleDeadline(dispatcher_, duration); |
| |
| return network_request->get_future(); |
| } |
| |
| } // namespace utils |
| } // namespace cobalt |