|
|
@@ -1,6 +1,9 @@
|
|
|
|
#include <ligo/http.hpp>
|
|
|
|
#include <ligo/http.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <cctype>
|
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cassert>
|
|
|
|
#include <chrono>
|
|
|
|
#include <chrono>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdint>
|
|
|
@@ -118,6 +121,67 @@ auto host_header(std::string const &host, std::uint16_t port) -> std::string {
|
|
|
|
return value;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto lower(std::string value) -> std::string {
|
|
|
|
|
|
|
|
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) { return static_cast<char>(std::tolower(character)); });
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto decode_query(std::string_view value) -> std::string {
|
|
|
|
|
|
|
|
auto output = std::string{};
|
|
|
|
|
|
|
|
auto hex = [](char character) -> int {
|
|
|
|
|
|
|
|
if (character >= '0' && character <= '9') {
|
|
|
|
|
|
|
|
return character - '0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (character >= 'a' && character <= 'f') {
|
|
|
|
|
|
|
|
return character - 'a' + 10;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (character >= 'A' && character <= 'F') {
|
|
|
|
|
|
|
|
return character - 'A' + 10;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
for (std::size_t index = 0; index < value.size(); ++index) {
|
|
|
|
|
|
|
|
if (value[index] == '%') {
|
|
|
|
|
|
|
|
if (index + 2 >= value.size() || hex(value[index + 1]) < 0 || hex(value[index + 2]) < 0) {
|
|
|
|
|
|
|
|
throw std::invalid_argument("Invalid query escape");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
auto character = static_cast<char>(hex(value[index + 1]) * 16 + hex(value[index + 2]));
|
|
|
|
|
|
|
|
if (character == '\0') {
|
|
|
|
|
|
|
|
throw std::invalid_argument("Invalid query character");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
output += character;
|
|
|
|
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
output += value[index] == '+' ? ' ' : value[index];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto parse_query(ligo::request_context &context) -> void {
|
|
|
|
|
|
|
|
auto position = context.route.find('?');
|
|
|
|
|
|
|
|
if (position == std::string::npos) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
auto query = context.route.substr(position + 1);
|
|
|
|
|
|
|
|
context.route.resize(position);
|
|
|
|
|
|
|
|
auto offset = std::size_t{};
|
|
|
|
|
|
|
|
while (offset < query.size()) {
|
|
|
|
|
|
|
|
auto end = query.find('&', offset);
|
|
|
|
|
|
|
|
auto part = std::string_view{query}.substr(offset, end == std::string::npos ? query.size() - offset : end - offset);
|
|
|
|
|
|
|
|
auto equal = part.find('=');
|
|
|
|
|
|
|
|
auto key = decode_query(part.substr(0, equal));
|
|
|
|
|
|
|
|
auto value = equal == std::string_view::npos ? std::string{} : decode_query(part.substr(equal + 1));
|
|
|
|
|
|
|
|
if (key.empty() || !context.query.emplace(std::move(key), std::move(value)).second) {
|
|
|
|
|
|
|
|
throw std::invalid_argument("Empty or duplicate query key");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (end == std::string::npos) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = end + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto remote_peer(boost::beast::tcp_stream &stream) -> std::string {
|
|
|
|
auto remote_peer(boost::beast::tcp_stream &stream) -> std::string {
|
|
|
|
auto endpoint_error = boost::system::error_code{};
|
|
|
|
auto endpoint_error = boost::system::error_code{};
|
|
|
|
auto remote_endpoint = stream.socket().remote_endpoint(endpoint_error);
|
|
|
|
auto remote_endpoint = stream.socket().remote_endpoint(endpoint_error);
|
|
|
@@ -196,9 +260,33 @@ auto handle_session(STREAM &stream, std::shared_ptr<ligo::service_router const>
|
|
|
|
.method = std::string{http_request.method_string()},
|
|
|
|
.method = std::string{http_request.method_string()},
|
|
|
|
.route = std::string{http_request.target()},
|
|
|
|
.route = std::string{http_request.target()},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
for (auto const &field : http_request.base()) {
|
|
|
|
|
|
|
|
auto name = lower(std::string{field.name_string()});
|
|
|
|
|
|
|
|
auto value = std::string{field.value()};
|
|
|
|
|
|
|
|
if (context.headers.contains(name)) {
|
|
|
|
|
|
|
|
context.headers[name] += (name == "cookie" ? "; " : ", ") + value;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
context.headers.emplace(std::move(name), std::move(value));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
auto query_error = std::string{};
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (options.parse_query) {
|
|
|
|
|
|
|
|
parse_query(context);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (std::invalid_argument const &error) {
|
|
|
|
|
|
|
|
query_error = error.what();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query_error.empty()) {
|
|
|
|
|
|
|
|
co_await write_http_failure_response(stream, ligo::detail::make_error(ligo::detail::error_invalid_argument, query_error), options.timeout, options.on_error);
|
|
|
|
|
|
|
|
co_return;
|
|
|
|
|
|
|
|
}
|
|
|
|
auto result = co_await ligo::detail::call_response_handler(*router, std::move(context), std::move(payload));
|
|
|
|
auto result = co_await ligo::detail::call_response_handler(*router, std::move(context), std::move(payload));
|
|
|
|
if (result) {
|
|
|
|
if (result) {
|
|
|
|
response.result(boost::beast::http::status::ok);
|
|
|
|
response.result(result->status >= 200 && result->status <= 599 ? result->status : 500);
|
|
|
|
|
|
|
|
for (auto const &[name, value] : result->headers) {
|
|
|
|
|
|
|
|
response.insert(name, value);
|
|
|
|
|
|
|
|
}
|
|
|
|
response.set(boost::beast::http::field::content_type, result->content_type);
|
|
|
|
response.set(boost::beast::http::field::content_type, result->content_type);
|
|
|
|
response.body() = std::move(result->body);
|
|
|
|
response.body() = std::move(result->body);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@@ -326,12 +414,18 @@ auto make_http_request_message(ligo::http::endpoint const &endpoint, ligo::http:
|
|
|
|
http_request.set(boost::beast::http::field::content_type, "application/json");
|
|
|
|
http_request.set(boost::beast::http::field::content_type, "application/json");
|
|
|
|
http_request.body() = boost::json::serialize(request.payload);
|
|
|
|
http_request.body() = boost::json::serialize(request.payload);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request.raw_body) {
|
|
|
|
|
|
|
|
http_request.body() = *request.raw_body;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto const &[name, value] : request.headers) {
|
|
|
|
|
|
|
|
http_request.set(name, value);
|
|
|
|
|
|
|
|
}
|
|
|
|
http_request.prepare_payload();
|
|
|
|
http_request.prepare_payload();
|
|
|
|
return http_request;
|
|
|
|
return http_request;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename STREAM>
|
|
|
|
template <typename STREAM>
|
|
|
|
auto read_http_response(STREAM &stream, std::chrono::milliseconds timeout, std::uint64_t max_body_size) -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>> {
|
|
|
|
auto read_http_response(STREAM &stream, std::chrono::milliseconds timeout, std::uint64_t max_body_size) -> boost::asio::awaitable<std::expected<ligo::http::response, modulus::error>> {
|
|
|
|
auto buffer = boost::beast::flat_buffer{};
|
|
|
|
auto buffer = boost::beast::flat_buffer{};
|
|
|
|
auto parser = boost::beast::http::response_parser<boost::beast::http::string_body>{};
|
|
|
|
auto parser = boost::beast::http::response_parser<boost::beast::http::string_body>{};
|
|
|
|
parser.body_limit(max_body_size);
|
|
|
|
parser.body_limit(max_body_size);
|
|
|
@@ -342,36 +436,42 @@ auto read_http_response(STREAM &stream, std::chrono::milliseconds timeout, std::
|
|
|
|
if (read_error) {
|
|
|
|
if (read_error) {
|
|
|
|
if (http_body_limit_exceeded(read_error)) {
|
|
|
|
if (http_body_limit_exceeded(read_error)) {
|
|
|
|
auto failure_status = http_body_limit_status();
|
|
|
|
auto failure_status = http_body_limit_status();
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto failure_status = ligo::detail::operation_failure(read_error, read_deadline, "HTTP request timed out");
|
|
|
|
auto failure_status = ligo::detail::operation_failure(read_error, read_deadline, "HTTP request timed out");
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto http_response = parser.release();
|
|
|
|
auto http_response = parser.release();
|
|
|
|
auto parse_error = boost::system::error_code{};
|
|
|
|
auto response = ligo::http::response{};
|
|
|
|
auto body = boost::json::parse(http_response.body(), parse_error);
|
|
|
|
response.status = static_cast<std::uint16_t>(http_response.result_int());
|
|
|
|
if (parse_error) {
|
|
|
|
response.body = std::move(http_response.body());
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(ligo::detail::error_invalid_argument, parse_error.message());
|
|
|
|
for (auto const &field : http_response.base()) {
|
|
|
|
|
|
|
|
auto name = lower(std::string{field.name_string()});
|
|
|
|
|
|
|
|
if (response.headers.contains(name)) {
|
|
|
|
|
|
|
|
response.headers[name] += ", " + std::string{field.value()};
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
response.headers.emplace(std::move(name), std::string{field.value()});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
co_return ligo::detail::unwrap_response_envelope(std::move(body), "HTTP");
|
|
|
|
co_return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto call_https_endpoint(ligo::http::endpoint endpoint, boost::beast::http::request<boost::beast::http::string_body> http_request, ligo::http::client_options options) -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>> {
|
|
|
|
auto call_https_endpoint(ligo::http::endpoint endpoint, boost::beast::http::request<boost::beast::http::string_body> http_request, ligo::http::client_options options) -> boost::asio::awaitable<std::expected<ligo::http::response, modulus::error>> {
|
|
|
|
auto context = ligo::make_tls_client_context(options.security);
|
|
|
|
auto context = ligo::make_tls_client_context(options.security);
|
|
|
|
if (!context) {
|
|
|
|
if (!context) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(context.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(context.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto executor = co_await boost::asio::this_coro::executor;
|
|
|
|
auto executor = co_await boost::asio::this_coro::executor;
|
|
|
|
auto endpoints = co_await ligo::detail::resolve_tcp_endpoints(executor, endpoint.host, endpoint.port, options.timeout, "HTTP request timed out");
|
|
|
|
auto endpoints = co_await ligo::detail::resolve_tcp_endpoints(executor, endpoint.host, endpoint.port, options.timeout, "HTTP request timed out");
|
|
|
|
if (!endpoints) {
|
|
|
|
if (!endpoints) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(endpoints.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(endpoints.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto tcp_stream = boost::beast::tcp_stream{executor};
|
|
|
|
auto tcp_stream = boost::beast::tcp_stream{executor};
|
|
|
|
tcp_stream.expires_after(options.timeout);
|
|
|
|
tcp_stream.expires_after(options.timeout);
|
|
|
|
auto [connect_error, _] = co_await tcp_stream.async_connect(*endpoints, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
auto [connect_error, _] = co_await tcp_stream.async_connect(*endpoints, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
if (connect_error) {
|
|
|
|
if (connect_error) {
|
|
|
|
auto failure_status = ligo::detail::operation_failure(connect_error, "HTTP request timed out");
|
|
|
|
auto failure_status = ligo::detail::operation_failure(connect_error, "HTTP request timed out");
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto stream = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>{
|
|
|
|
auto stream = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>{
|
|
|
|
tcp_stream.release_socket(),
|
|
|
|
tcp_stream.release_socket(),
|
|
|
@@ -379,11 +479,11 @@ auto call_https_endpoint(ligo::http::endpoint endpoint, boost::beast::http::requ
|
|
|
|
};
|
|
|
|
};
|
|
|
|
auto verification = ligo::configure_tls_client_stream(stream, endpoint.host, options.security);
|
|
|
|
auto verification = ligo::configure_tls_client_stream(stream, endpoint.host, options.security);
|
|
|
|
if (!verification) {
|
|
|
|
if (!verification) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(verification.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(verification.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto handshake = co_await ligo::detail::async_tls_handshake(stream, boost::asio::ssl::stream_base::client, options.timeout, "HTTP TLS handshake timed out");
|
|
|
|
auto handshake = co_await ligo::detail::async_tls_handshake(stream, boost::asio::ssl::stream_base::client, options.timeout, "HTTP TLS handshake timed out");
|
|
|
|
if (!handshake) {
|
|
|
|
if (!handshake) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(handshake.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(handshake.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto write_deadline = ligo::detail::start_stream_deadline(stream, options.timeout);
|
|
|
|
auto write_deadline = ligo::detail::start_stream_deadline(stream, options.timeout);
|
|
|
|
auto [write_error, written] = co_await boost::beast::http::async_write(stream, http_request, ligo::detail::deadline_token(write_deadline));
|
|
|
|
auto [write_error, written] = co_await boost::beast::http::async_write(stream, http_request, ligo::detail::deadline_token(write_deadline));
|
|
|
@@ -391,36 +491,36 @@ auto call_https_endpoint(ligo::http::endpoint endpoint, boost::beast::http::requ
|
|
|
|
static_cast<void>(written);
|
|
|
|
static_cast<void>(written);
|
|
|
|
if (write_error) {
|
|
|
|
if (write_error) {
|
|
|
|
auto failure_status = ligo::detail::operation_failure(write_error, write_deadline, "HTTP request timed out");
|
|
|
|
auto failure_status = ligo::detail::operation_failure(write_error, write_deadline, "HTTP request timed out");
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto response = co_await read_http_response(stream, options.timeout, options.max_body_size);
|
|
|
|
auto response = co_await read_http_response(stream, options.timeout, options.max_body_size);
|
|
|
|
static_cast<void>(co_await ligo::detail::async_tls_shutdown(stream, options.timeout, "HTTP TLS shutdown timed out"));
|
|
|
|
static_cast<void>(co_await ligo::detail::async_tls_shutdown(stream, options.timeout, "HTTP TLS shutdown timed out"));
|
|
|
|
co_return response;
|
|
|
|
co_return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto call_http_endpoint(ligo::http::endpoint endpoint, std::shared_ptr<ligo::http::request const> request, ligo::http::client_options options) -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>> {
|
|
|
|
auto call_http_endpoint(ligo::http::endpoint endpoint, std::shared_ptr<ligo::http::request const> request, ligo::http::client_options options) -> boost::asio::awaitable<std::expected<ligo::http::response, modulus::error>> {
|
|
|
|
assert(request != nullptr);
|
|
|
|
assert(request != nullptr);
|
|
|
|
modulus::log_debug("ligo HTTP client call to " + endpoint.host + ":" + std::to_string(endpoint.port));
|
|
|
|
modulus::log_debug("ligo HTTP client call to " + endpoint.host + ":" + std::to_string(endpoint.port));
|
|
|
|
auto valid_timeout = ligo::detail::validate_positive_timeout(options.timeout, "HTTP timeout");
|
|
|
|
auto valid_timeout = ligo::detail::validate_positive_timeout(options.timeout, "HTTP timeout");
|
|
|
|
if (!valid_timeout) {
|
|
|
|
if (!valid_timeout) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(valid_timeout.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(valid_timeout.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (endpoint.port == 0) {
|
|
|
|
if (endpoint.port == 0) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(ligo::detail::error_invalid_argument, "HTTP port must be non-zero");
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(ligo::detail::error_invalid_argument, "HTTP port must be non-zero");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (request->target.empty()) {
|
|
|
|
if (request->target.empty()) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(ligo::detail::error_invalid_argument, "HTTP target must not be empty");
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(ligo::detail::error_invalid_argument, "HTTP target must not be empty");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (request->target.front() != '/') {
|
|
|
|
if (request->target.front() != '/') {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(ligo::detail::error_invalid_argument, "HTTP target must start with /");
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(ligo::detail::error_invalid_argument, "HTTP target must start with /");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto verb = http_verb(request->method);
|
|
|
|
auto verb = http_verb(request->method);
|
|
|
|
if (!verb) {
|
|
|
|
if (!verb) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(verb.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(verb.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (options.security.mode == ligo::security_mode::DTLS) {
|
|
|
|
if (options.security.mode == ligo::security_mode::DTLS) {
|
|
|
|
auto status = ligo::unsupported_security("HTTP client", options.security.mode);
|
|
|
|
auto status = ligo::unsupported_security("HTTP client", options.security.mode);
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto http_request = make_http_request_message(endpoint, *request, *verb);
|
|
|
|
auto http_request = make_http_request_message(endpoint, *request, *verb);
|
|
|
|
if (options.security.mode == ligo::security_mode::TLS) {
|
|
|
|
if (options.security.mode == ligo::security_mode::TLS) {
|
|
|
@@ -430,20 +530,20 @@ auto call_http_endpoint(ligo::http::endpoint endpoint, std::shared_ptr<ligo::htt
|
|
|
|
auto stream = boost::beast::tcp_stream{executor};
|
|
|
|
auto stream = boost::beast::tcp_stream{executor};
|
|
|
|
auto endpoints = co_await ligo::detail::resolve_tcp_endpoints(executor, endpoint.host, endpoint.port, options.timeout, "HTTP request timed out");
|
|
|
|
auto endpoints = co_await ligo::detail::resolve_tcp_endpoints(executor, endpoint.host, endpoint.port, options.timeout, "HTTP request timed out");
|
|
|
|
if (!endpoints) {
|
|
|
|
if (!endpoints) {
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(endpoints.error());
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(endpoints.error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
auto [connect_error, _] = co_await stream.async_connect(*endpoints, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
auto [connect_error, _] = co_await stream.async_connect(*endpoints, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
if (connect_error) {
|
|
|
|
if (connect_error) {
|
|
|
|
auto failure_status = ligo::detail::operation_failure(connect_error, "HTTP request timed out");
|
|
|
|
auto failure_status = ligo::detail::operation_failure(connect_error, "HTTP request timed out");
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
auto [write_error, written] = co_await boost::beast::http::async_write(stream, http_request, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
auto [write_error, written] = co_await boost::beast::http::async_write(stream, http_request, boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
|
|
static_cast<void>(written);
|
|
|
|
static_cast<void>(written);
|
|
|
|
if (write_error) {
|
|
|
|
if (write_error) {
|
|
|
|
auto failure_status = ligo::detail::operation_failure(write_error, "HTTP request timed out");
|
|
|
|
auto failure_status = ligo::detail::operation_failure(write_error, "HTTP request timed out");
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(failure_status);
|
|
|
|
co_return ligo::detail::fail<ligo::http::response>(failure_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
stream.expires_after(options.timeout);
|
|
|
|
co_return co_await read_http_response(stream, options.timeout, options.max_body_size);
|
|
|
|
co_return co_await read_http_response(stream, options.timeout, options.max_body_size);
|
|
|
@@ -458,6 +558,19 @@ ligo::http::client::client(ligo::http::endpoint endpoint, ligo::http::client_opt
|
|
|
|
: endpoint_{std::move(endpoint)}, options_{options} {}
|
|
|
|
: endpoint_{std::move(endpoint)}, options_{options} {}
|
|
|
|
|
|
|
|
|
|
|
|
auto ligo::http::client::call(ligo::http::request const &request) const -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>> {
|
|
|
|
auto ligo::http::client::call(ligo::http::request const &request) const -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>> {
|
|
|
|
|
|
|
|
auto response = co_await exchange(request);
|
|
|
|
|
|
|
|
if (!response) {
|
|
|
|
|
|
|
|
co_return std::unexpected(response.error());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
auto error = boost::system::error_code{};
|
|
|
|
|
|
|
|
auto body = boost::json::parse(response->body, error);
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
|
|
co_return ligo::detail::fail<boost::json::value>(ligo::detail::error_invalid_argument, error.message());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
co_return ligo::detail::unwrap_response_envelope(std::move(body), "HTTP");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto ligo::http::client::exchange(ligo::http::request const &request) const -> boost::asio::awaitable<std::expected<ligo::http::response, modulus::error>> {
|
|
|
|
return call_http_endpoint(endpoint_, std::make_shared<ligo::http::request const>(request), options_);
|
|
|
|
return call_http_endpoint(endpoint_, std::make_shared<ligo::http::request const>(request), options_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|