| // 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 "src/cobalt/bin/utils/fuchsia_http_client.h" |
| |
| #include <fuchsia/net/http/cpp/fidl.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/status.h> |
| |
| #include "src/lib/fsl/socket/strings.h" |
| #include "src/lib/fsl/vmo/strings.h" |
| |
| namespace cobalt { |
| namespace utils { |
| |
| using lib::clearcut::HTTPClient; |
| using lib::clearcut::HTTPRequest; |
| using lib::clearcut::HTTPResponse; |
| using lib::statusor::StatusOr; |
| |
| namespace { |
| |
| fuchsia::net::http::Request MakeRequest(const lib::clearcut::HTTPRequest& request, |
| zx::time deadline) { |
| fuchsia::net::http::Request fx_request; |
| fx_request.set_method("POST"); |
| fx_request.set_url(request.url); |
| |
| fsl::SizedVmo data; |
| auto result = fsl::VmoFromString(request.body, &data); |
| FX_CHECK(result); |
| |
| fx_request.mutable_body()->set_buffer(std::move(data).ToTransport()); |
| |
| for (const auto& header : request.headers) { |
| fuchsia::net::http::Header hdr; |
| std::vector<uint8_t> name(header.first.begin(), header.first.end()); |
| hdr.name = name; |
| |
| std::vector<uint8_t> value(header.second.begin(), header.second.end()); |
| hdr.value = value; |
| fx_request.mutable_headers()->push_back(std::move(hdr)); |
| } |
| |
| fx_request.set_deadline(deadline.get()); |
| return fx_request; |
| } |
| |
| StatusOr<HTTPResponse> ReadResponse(fuchsia::net::http::Response fx_response, zx::time deadline) { |
| HTTPResponse response; |
| |
| if (fx_response.has_error()) { |
| std::ostringstream ss; |
| ss << "Got error while making HTTP request: "; |
| auto status_code = util::StatusCode::INTERNAL; |
| switch (fx_response.error()) { |
| case fuchsia::net::http::Error::INTERNAL: |
| ss << "Internal Error"; |
| status_code = util::StatusCode::INTERNAL; |
| break; |
| case fuchsia::net::http::Error::UNABLE_TO_PARSE: |
| ss << "Unable to parse HTTP data"; |
| status_code = util::StatusCode::INVALID_ARGUMENT; |
| break; |
| case fuchsia::net::http::Error::CHANNEL_CLOSED: |
| ss << "Channel closed"; |
| status_code = util::StatusCode::ABORTED; |
| break; |
| case fuchsia::net::http::Error::CONNECT: |
| ss << "Error occurred while connecting"; |
| status_code = util::StatusCode::UNAVAILABLE; |
| break; |
| case fuchsia::net::http::Error::DEADLINE_EXCEEDED: |
| ss << "Deadline exceeded while waiting for response"; |
| status_code = util::StatusCode::DEADLINE_EXCEEDED; |
| break; |
| } |
| return util::Status(status_code, ss.str()); |
| } |
| |
| response.http_code = fx_response.status_code(); |
| |
| if (fx_response.has_headers()) { |
| for (auto& header : fx_response.headers()) { |
| std::string name(header.name.begin(), header.name.end()); |
| std::string value(header.value.begin(), header.value.end()); |
| response.headers.emplace(name, value); |
| } |
| } |
| |
| if (fx_response.has_body()) { |
| std::vector<char> buffer(64 * 1024); |
| size_t num_bytes = 0; |
| bool more_data = true; |
| while (more_data) { |
| auto status = fx_response.mutable_body()->read(0, buffer.data(), buffer.size(), &num_bytes); |
| switch (status) { |
| case ZX_OK: |
| response.response.append(static_cast<const char*>(buffer.data()), num_bytes); |
| continue; |
| case ZX_ERR_SHOULD_WAIT: |
| // Wait until there is more to read. |
| status = fx_response.mutable_body()->wait_one( |
| ZX_SOCKET_READABLE | ZX_SOCKET_PEER_WRITE_DISABLED | ZX_SOCKET_PEER_CLOSED, deadline, |
| /*observed_signals=*/nullptr); |
| if (status != ZX_OK) { |
| switch (status) { |
| case ZX_ERR_TIMED_OUT: |
| FX_LOGS(ERROR) << "Exceeded deadline while waiting on the socket"; |
| return util::Status(util::StatusCode::DEADLINE_EXCEEDED, |
| "Deadline exceeded while waiting on socket"); |
| default: |
| FX_LOGS(ERROR) << "Unhandled zx_status_t: " << status; |
| return util::Status(util::StatusCode::UNAVAILABLE, "Unhandled zx error"); |
| } |
| } |
| continue; |
| case ZX_ERR_PEER_CLOSED: |
| // The sending side of the channel has closed. There is no more data to read. |
| more_data = false; |
| break; |
| default: |
| FX_LOGS(ERROR) << "Unhandled zx_status_t: " << status; |
| return util::Status(util::StatusCode::UNAVAILABLE, "Unhandled zx error"); |
| } |
| } |
| } |
| |
| return response; |
| } |
| |
| } // namespace |
| |
| FuchsiaHTTPClient::FuchsiaHTTPClient( |
| fit::function<::fuchsia::net::http::LoaderSyncPtr()> loader_factory) |
| : loader_factory_(std::move(loader_factory)) {} |
| |
| StatusOr<HTTPResponse> FuchsiaHTTPClient::PostSync(HTTPRequest request, |
| std::chrono::steady_clock::time_point deadline) { |
| if (!loader_.is_bound()) { |
| loader_ = loader_factory_(); |
| } |
| |
| auto zx_deadline = |
| zx::deadline_after(zx::duration(std::chrono::duration_cast<std::chrono::nanoseconds>( |
| deadline - std::chrono::steady_clock::now()) |
| .count())); |
| |
| fuchsia::net::http::Response fx_response; |
| auto status = loader_->Fetch(MakeRequest(std::move(request), zx_deadline), &fx_response); |
| |
| if (status != ZX_OK) { |
| std::ostringstream ss; |
| ss << "Connection to HTTP Loader service lost, Perhaps it crashed? (ZX STATUS: " |
| << zx_status_get_string(status) << ")"; |
| |
| std::string s = ss.str(); |
| FX_LOGS(WARNING) << s; |
| |
| loader_.Unbind(); |
| return util::Status(util::StatusCode::UNAVAILABLE, s); |
| } |
| |
| return ReadResponse(std::move(fx_response), zx_deadline); |
| } |
| |
| } // namespace utils |
| } // namespace cobalt |