16#include <boost/algorithm/string.hpp>
17#include <boost/asio.hpp>
18#include <boost/asio/ssl.hpp>
25#include <boost/asio/ssl/rfc2818_verification.hpp>
28using boost::asio::ip::tcp;
35 boost::asio::io_service
ios;
50 endpoints.reserve(
url.resolved_addresses.size());
51 for (
const auto& addr:
url.resolved_addresses) {
52 endpoints.emplace_back(boost::asio::ip::make_address(addr),
url.resolved_port);
54 boost::asio::connect(sock, endpoints);
58 std::string
do_txrx(
T& socket, boost::asio::streambuf& request_buff,
unsigned int& status_code) {
60 boost::asio::write(socket, request_buff);
65 boost::asio::streambuf response;
66 boost::asio::read_until(socket, response,
"\r\n");
69 std::istream response_stream(&response);
70 std::string http_version;
71 response_stream >> http_version;
72 response_stream >> status_code;
74 std::string status_message;
75 std::getline(response_stream, status_message);
76 SYS_ASSERT( !(!response_stream || http_version.substr(0, 5) !=
"HTTP/"), invalid_http_response,
"Invalid Response" );
79 boost::asio::read_until(socket, response,
"\r\n\r\n");
83 int response_content_length = -1;
84 std::regex clregex(R
"xx(^content-length:\s+(\d+))xx", std::regex_constants::icase);
85 while (std::getline(response_stream, header) && header !=
"\r") {
87 if(std::regex_search(header, match, clregex))
88 response_content_length = std::stoi(match[1]);
93 if( response_content_length != -1 ) {
94 response_content_length -= response.size();
95 if( response_content_length > 0 )
96 boost::asio::read(socket, response, boost::asio::transfer_exactly(response_content_length));
98 boost::system::error_code ec;
99 boost::asio::read(socket, response, boost::asio::transfer_all(), ec);
100 SYS_ASSERT(!ec || ec == boost::asio::ssl::error::stream_truncated, http_exception,
"Unable to read http response: ${err}", (
"err",ec.message()));
103 std::stringstream re;
112 if(boost::algorithm::starts_with(server_url,
"unix://")) {
114 res.
server = server_url.substr(strlen(
"unix://"));
120 std::regex rgx(R
"xx(^(([^:/?#]+):)?(//([^:/?#]*)(:(\d+))?)?([^?#]*)(\?([^#]*))?(#(.*))?)xx");
122 if(std::regex_search(server_url.begin(), server_url.end(), match, rgx)) {
129 SYS_THROW(fail_to_resolve_host,
"Unrecognized URL scheme (${s}) in URL \"${u}\"", (
"s", res.
scheme)(
"u", server_url));
131 SYS_THROW(fail_to_resolve_host,
"No server parsed from URL \"${u}\"", (
"u", server_url));
133 res.
port = res.
scheme ==
"http" ?
"80" :
"443";
134 boost::trim_right_if(res.
path, boost::is_any_of(
"/"));
139 if(
url.scheme ==
"unix")
142 tcp::resolver resolver(
context->ios);
143 boost::system::error_code ec;
144 auto result = resolver.resolve(tcp::v4(),
url.server,
url.
port, ec);
146 SYS_THROW(fail_to_resolve_host,
"Error resolving \"${server}:${port}\" : ${m}", (
"server",
url.server)(
"port",
url.
port)(
"m",ec.message()));
151 resolved_addresses.reserve(result.size());
152 std::optional<uint16_t> resolved_port;
153 bool is_loopback =
true;
155 for(
const auto&
r : result) {
156 const auto& addr =
r.endpoint().address();
157 if (addr.is_v6())
continue;
159 resolved_addresses.emplace_back(addr.to_string());
160 is_loopback = is_loopback && addr.is_loopback();
163 SYS_ASSERT(*resolved_port == port, resolved_to_multiple_ports,
"Service name \"${port}\" resolved to multiple ports and this is not supported!", (
"port",
url.
port));
165 resolved_port = port;
169 return resolved_url(
url, std::move(resolved_addresses), *resolved_port, is_loopback);
175 (
url.scheme ==
"https" &&
url.resolved_port == 443) ||
176 (
url.scheme ==
"http" &&
url.resolved_port == 80)
188 std::string postjson;
195 boost::asio::streambuf request;
196 std::ostream request_stream(&request);
198 request_stream <<
"POST " <<
url.
path <<
" HTTP/1.0\r\n";
199 request_stream <<
"Host: " << host_header_value <<
"\r\n";
200 request_stream <<
"content-length: " << postjson.size() <<
"\r\n";
201 request_stream <<
"Accept: */*\r\n";
202 request_stream <<
"Connection: close\r\n";
204 std::vector<string>::iterator itr;
206 request_stream << *itr <<
"\r\n";
208 request_stream <<
"\r\n";
209 request_stream << postjson;
212 string s(request.size(),
'\0');
213 buffer_copy(boost::asio::buffer(
s), request.data());
214 std::cerr <<
"REQUEST:" << std::endl
215 <<
"---------------------" << std::endl
217 <<
"---------------------" << std::endl;
220 unsigned int status_code;
224 if(
url.scheme ==
"unix") {
225 boost::asio::local::stream_protocol::socket unix_socket(cp.
context->ios);
226 unix_socket.connect(boost::asio::local::stream_protocol::endpoint(
url.server));
227 re =
do_txrx(unix_socket, request, status_code);
229 else if(
url.scheme ==
"http") {
230 tcp::socket socket(cp.
context->ios);
232 re =
do_txrx(socket, request, status_code);
235 boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23_client);
238 boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(cp.
context->ios, ssl_context);
239 SSL_set_tlsext_host_name(socket.native_handle(),
url.server.c_str());
241 socket.set_verify_mode(boost::asio::ssl::verify_peer);
242 socket.set_verify_callback(boost::asio::ssl::rfc2818_verification(
url.server));
245 socket.handshake(boost::asio::ssl::stream_base::client);
246 re =
do_txrx(socket, request, status_code);
248 try {socket.shutdown();}
catch(...) {}
250 }
catch ( invalid_http_request& e ) {
252 e.append_log(
FC_LOG_MESSAGE( info,
"If the condition persists, please contact the RPC server administrator for ${server}!", (
"server",
url.server) ) );
258 std::cerr <<
"RESPONSE:" << std::endl
259 <<
"---------------------" << std::endl
261 <<
"---------------------" << std::endl;
263 if( status_code == 200 || status_code == 201 || status_code == 202 ) {
264 return response_result;
265 }
else if( status_code == 404 ) {
268 throw chain::missing_chain_api_plugin_exception(
FC_LOG_MESSAGE(error,
"Chain API plugin is not enabled"));
270 throw chain::missing_wallet_api_plugin_exception(
FC_LOG_MESSAGE(error,
"Wallet is not available"));
272 throw chain::missing_history_api_plugin_exception(
FC_LOG_MESSAGE(error,
"History API plugin is not enabled"));
274 throw chain::missing_net_api_plugin_exception(
FC_LOG_MESSAGE(error,
"Net API plugin is not enabled"));
276 if ( re.find(
"Trace API:") == string::npos ) {
277 throw chain::missing_trace_api_plugin_exception(
FC_LOG_MESSAGE(error,
"Trace API plugin is not enabled"));
283 const auto &error_details = error_info.details;
286 for (
auto itr = error_details.begin(); itr != error_details.end(); itr++) {
291 throw fc::exception(logs, error_info.code, error_info.name, error_info.what);
294 SYS_ASSERT( status_code == 200, http_request_fail,
"Error code ${c}\n: ${msg}\n", (
"c", status_code)(
"msg", re) );
295 return response_result;
#define SYS_THROW(exc_type, FORMAT,...)
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Used to generate a useful error report when an exception is thrown.
static string to_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
static string to_pretty_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
static variant from_string(const string &utf8_str, const parse_type ptype=parse_type::legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
provides information about where and when a log message was generated.
aggregates a message along with the context and associated meta-information.
static constexpr time_point maximum()
std::optional< uint16_t > port() const
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object's.
boost::asio::io_service ios
#define FC_LOG_MESSAGE(LOG_LEVEL, FORMAT,...)
A helper method for generating log messages.
std::vector< log_message > log_messages
void add_platform_root_cas_to_context(boost::asio::ssl::context &ctx)
std::string do_txrx(T &socket, boost::asio::streambuf &request_buff, unsigned int &status_code)
fc::variant do_http_call(const connection_param &cp, const fc::variant &postdata, bool print_request, bool print_response)
const string trace_api_func_base
std::unique_ptr< detail::http_context_impl, detail::http_context_deleter > http_context
void do_connect(tcp::socket &sock, const resolved_url &url)
const string chain_func_base
http_context create_http_context()
parsed_url parse_url(const string &server_url)
const string wallet_func_base
string format_host_header(const resolved_url &url)
const string net_func_base
const string history_func_base
resolved_url resolve_url(const http_context &context, const parsed_url &url)
#define T(meth, val, expected)
std::vector< string > & headers
const http_context & context
Structure used to create JSON error responses.