2021-03-13 19:33:01 +08:00
|
|
|
#ifndef _CLIENT_HPP
|
|
|
|
#define _CLIENT_HPP
|
|
|
|
|
|
|
|
#include <boost/beast.hpp>
|
2021-08-07 20:56:19 +08:00
|
|
|
#include <boost/asio/spawn.hpp>
|
2021-03-13 19:33:01 +08:00
|
|
|
#include <boost/asio.hpp>
|
2021-08-07 20:56:19 +08:00
|
|
|
#include <boost/json.hpp>
|
2021-03-13 19:33:01 +08:00
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <string>
|
|
|
|
#include <cstddef>
|
|
|
|
#include <future>
|
|
|
|
#include <memory>
|
|
|
|
#include <chrono>
|
|
|
|
#include <exception>
|
|
|
|
|
|
|
|
#include "logging.hpp"
|
|
|
|
|
|
|
|
namespace bserv {
|
|
|
|
|
|
|
|
namespace beast = boost::beast;
|
|
|
|
namespace http = beast::http;
|
|
|
|
namespace asio = boost::asio;
|
|
|
|
namespace json = boost::json;
|
|
|
|
using asio::ip::tcp;
|
|
|
|
|
2021-07-14 14:16:00 +08:00
|
|
|
using request_type = http::request<http::string_body>;
|
|
|
|
using response_type = http::response<http::string_body>;
|
|
|
|
|
2021-03-13 19:33:01 +08:00
|
|
|
class request_failed_exception
|
|
|
|
: public std::exception {
|
|
|
|
private:
|
2021-08-07 20:56:19 +08:00
|
|
|
const std::string msg_;
|
2021-03-13 19:33:01 +08:00
|
|
|
public:
|
|
|
|
request_failed_exception(const std::string& msg) : msg_{msg} {}
|
|
|
|
const char* what() const noexcept { return msg_.c_str(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
// https://www.boost.org/doc/libs/1_75_0/libs/beast/example/http/client/async/http_client_async.cpp
|
2021-08-07 20:56:19 +08:00
|
|
|
// https://www.boost.org/doc/libs/1_75_0/libs/beast/example/http/client/coro/http_client_coro.cpp
|
2021-03-13 19:33:01 +08:00
|
|
|
|
|
|
|
// sends one async request to a remote server
|
2021-08-07 20:56:19 +08:00
|
|
|
inline http::response<http::string_body> http_client_send(
|
2021-03-13 19:33:01 +08:00
|
|
|
asio::io_context& ioc,
|
2021-08-07 20:56:19 +08:00
|
|
|
asio::yield_context& yield,
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
2021-08-07 20:56:19 +08:00
|
|
|
const std::string& port,
|
|
|
|
const http::request<http::string_body>& req) {
|
|
|
|
beast::error_code ec;
|
|
|
|
tcp::resolver resolver{ioc};
|
|
|
|
const auto results = resolver.async_resolve(host, port, yield[ec]);
|
|
|
|
if (ec) {
|
|
|
|
throw request_failed_exception{"http_client_session::resolver resolve: " + ec.message()};
|
|
|
|
}
|
|
|
|
beast::tcp_stream stream{ioc};
|
|
|
|
// sets a timeout on the operation
|
|
|
|
stream.expires_after(std::chrono::seconds(EXPIRY_TIME));
|
|
|
|
// makes the connection on the IP address we get from a lookup
|
|
|
|
stream.async_connect(results, yield[ec]);
|
|
|
|
if (ec) {
|
|
|
|
throw request_failed_exception{"http_client_session::stream connect: " + ec.message()};
|
|
|
|
}
|
|
|
|
// sets a timeout on the operation
|
|
|
|
stream.expires_after(std::chrono::seconds(EXPIRY_TIME));
|
|
|
|
// sends the HTTP request to the remote host
|
|
|
|
http::async_write(stream, req, yield[ec]);
|
|
|
|
if (ec) {
|
|
|
|
throw request_failed_exception{"http_client_session::stream write: " + ec.message()};
|
|
|
|
}
|
|
|
|
beast::flat_buffer buffer;
|
|
|
|
http::response<http::string_body> res;
|
|
|
|
// receives the HTTP response
|
|
|
|
http::async_read(stream, buffer, res, yield[ec]);
|
|
|
|
if (ec) {
|
|
|
|
throw request_failed_exception{"http_client_session::stream read: " + ec.message()};
|
|
|
|
}
|
|
|
|
// gracefully close the socket
|
|
|
|
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
|
|
|
|
// `not_connected` happens sometimes so don't bother reporting it
|
|
|
|
if (ec && ec != beast::errc::not_connected) {
|
|
|
|
// reports the error to the log!
|
|
|
|
fail(ec, "http_client_session::stream::socket shutdown");
|
|
|
|
// return;
|
|
|
|
}
|
|
|
|
// if we get here then the connection is closed gracefully
|
|
|
|
return res;
|
|
|
|
}
|
2021-03-13 19:33:01 +08:00
|
|
|
|
2021-08-07 20:56:19 +08:00
|
|
|
inline request_type get_request(
|
2021-07-14 14:16:00 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& target,
|
|
|
|
const http::verb& method,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
2021-07-14 14:16:00 +08:00
|
|
|
request_type req;
|
|
|
|
req.method(method);
|
|
|
|
req.target(target);
|
|
|
|
req.set(http::field::host, host);
|
|
|
|
req.set(http::field::user_agent, NAME);
|
|
|
|
req.set(http::field::content_type, "application/json");
|
2021-08-07 20:56:19 +08:00
|
|
|
req.body() = boost::json::serialize(val);
|
2021-07-14 14:16:00 +08:00
|
|
|
req.prepare_payload();
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
|
|
|
|
class http_client {
|
2021-03-13 19:33:01 +08:00
|
|
|
private:
|
|
|
|
asio::io_context& ioc_;
|
2021-08-07 20:56:19 +08:00
|
|
|
asio::yield_context& yield_;
|
2021-03-13 19:33:01 +08:00
|
|
|
public:
|
2021-08-07 20:56:19 +08:00
|
|
|
http_client(asio::io_context& ioc, asio::yield_context& yield)
|
|
|
|
: ioc_{ioc}, yield_{yield} {}
|
|
|
|
http::response<http::string_body> request(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const http::request<http::string_body>& req) {
|
2021-08-07 20:56:19 +08:00
|
|
|
return http_client_send(ioc_, yield_, host, port, req);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value request_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const http::request<http::string_body>& req) {
|
2021-08-07 20:56:19 +08:00
|
|
|
return boost::json::parse(request(host, port, req).body());
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
|
|
|
|
2021-08-07 20:56:19 +08:00
|
|
|
response_type send(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
|
|
|
const http::verb& method,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
request_type req = get_request(host, target, method, val);
|
2021-07-14 14:16:00 +08:00
|
|
|
return request(host, port, req);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value send_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
|
|
|
const http::verb& method,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
request_type req = get_request(host, target, method, val);
|
|
|
|
return request_for_value(host, port, req);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
|
|
|
|
response_type get(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send(host, port, target, http::verb::get, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value get_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send_for_value(host, port, target, http::verb::get, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
response_type put(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send(host, port, target, http::verb::put, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value put_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send_for_value(host, port, target, http::verb::put, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
response_type post(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send(host, port, target, http::verb::post, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value post_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send_for_value(host, port, target, http::verb::post, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
response_type delete_(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send(host, port, target, http::verb::delete_, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-08-07 20:56:19 +08:00
|
|
|
boost::json::value delete_for_value(
|
2021-03-13 19:33:01 +08:00
|
|
|
const std::string& host,
|
|
|
|
const std::string& port,
|
|
|
|
const std::string& target,
|
2021-08-07 20:56:19 +08:00
|
|
|
const boost::json::value& val) {
|
|
|
|
return send_for_value(host, port, target, http::verb::delete_, val);
|
2021-03-13 19:33:01 +08:00
|
|
|
}
|
2021-07-14 14:16:00 +08:00
|
|
|
};
|
2021-03-13 19:33:01 +08:00
|
|
|
|
|
|
|
} // bserv
|
|
|
|
|
|
|
|
#endif // _CLIENT_HPP
|