blob: e4d2f1753abd280fb4ef62d3c287095105c97a51 [file] [log] [blame]
// 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