Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
http_plugin.cpp
Go to the documentation of this file.
5
6#include <fc/network/ip.hpp>
9#include <fc/io/json.hpp>
10#include <fc/io/raw.hpp>
11#include <fc/crypto/openssl.hpp>
12
13#include <boost/asio.hpp>
14#include <boost/optional.hpp>
15
22
23#include <thread>
24#include <memory>
25#include <regex>
26
27const fc::string logger_name("http_plugin");
29
30namespace sysio {
31
32 static appbase::abstract_plugin& _http_plugin = app().register_plugin<http_plugin>();
33
34 namespace asio = boost::asio;
35
36 using std::map;
37 using std::vector;
38 using std::set;
39 using std::string;
40 using std::regex;
41 using boost::optional;
42 using boost::asio::ip::tcp;
43 using boost::asio::ip::address_v4;
44 using boost::asio::ip::address_v6;
45 using std::shared_ptr;
47
52
53 static http_plugin_defaults current_http_plugin_defaults;
54
56 current_http_plugin_defaults = config;
57 }
58
59 namespace detail {
60
61 template<class T>
64 typedef asio base;
65
66 typedef base::concurrency_type concurrency_type;
67
68 typedef base::request_type request_type;
69 typedef base::response_type response_type;
70
71 typedef base::message_type message_type;
72 typedef base::con_msg_manager_type con_msg_manager_type;
73 typedef base::endpoint_msg_manager_type endpoint_msg_manager_type;
74
77
78 typedef base::rng_type rng_type;
79
88
91
92 static const long timeout_open_handshake = 0;
93 };
94
97 typedef asio base;
98
99 typedef base::concurrency_type concurrency_type;
100
101 typedef base::request_type request_type;
102 typedef base::response_type response_type;
103
104 typedef base::message_type message_type;
105 typedef base::con_msg_manager_type con_msg_manager_type;
106 typedef base::endpoint_msg_manager_type endpoint_msg_manager_type;
107
110
111 typedef base::rng_type rng_type;
112
121
123
124 static const long timeout_open_handshake = 0;
125 };
126
131 virtual ~abstract_conn() {}
132 virtual bool verify_max_bytes_in_flight() = 0;
133 virtual void handle_exception() = 0;
134 virtual void send_response(std::string, int) = 0;
135 };
136
137 using abstract_conn_ptr = std::shared_ptr<abstract_conn>;
138
139 template<typename T>
141
145 using internal_url_handler = std::function<void(abstract_conn_ptr, string, string, url_response_callback)>;
146
152 static size_t in_flight_sizeof( const string& s ) {
153 return s.size();
154 }
155
163 static size_t in_flight_sizeof( const fc::variant& v ) {
164 try {
165 return fc::raw::pack_size( v );
166 } catch(...) {}
167 return 0;
168 }
169 }
170
174 using ssl_context_ptr = websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>;
175 using http_plugin_impl_ptr = std::shared_ptr<class http_plugin_impl>;
176
177 static bool verbose_http_errors = false;
178
179class http_plugin_impl : public std::enable_shared_from_this<http_plugin_impl> {
180 public:
181 http_plugin_impl() = default;
182
185
188
189 // key -> priority, url_handler
190 map<string,detail::internal_url_handler> url_handlers;
191 std::optional<tcp::endpoint> listen_endpoint;
196 size_t max_body_size{2*1024*1024};
197
199
201 std::optional<sysio::chain::named_thread_pool> thread_pool;
202 std::atomic<size_t> bytes_in_flight{0};
205
206 std::optional<tcp::endpoint> https_listen_endpoint;
208 string https_key;
210
212
213 std::optional<asio::local::stream_protocol::endpoint> unix_endpoint;
215
216 bool validate_host = true;
217 set<string> valid_hosts;
218
219 bool host_port_is_valid( const std::string& header_host_port, const string& endpoint_local_host_port ) {
220 return !validate_host || header_host_port == endpoint_local_host_port || valid_hosts.find(header_host_port) != valid_hosts.end();
221 }
222
223 bool host_is_valid( const std::string& host, const string& endpoint_local_host_port, bool secure) {
224 if (!validate_host) {
225 return true;
226 }
227
228 // normalise the incoming host so that it always has the explicit port
229 static auto has_port_expr = regex("[^:]:[0-9]+$");
230 if (std::regex_search(host, has_port_expr)) {
231 return host_port_is_valid( host, endpoint_local_host_port );
232 } else {
233 // according to RFC 2732 ipv6 addresses should always be enclosed with brackets so we shouldn't need to special case here
234 return host_port_is_valid( host + ":" + std::to_string(secure ? websocketpp::uri_default_secure_port : websocketpp::uri_default_port ), endpoint_local_host_port);
235 }
236 }
237
239 ssl_context_ptr ctx = websocketpp::lib::make_shared<websocketpp::lib::asio::ssl::context>(asio::ssl::context::sslv23_server);
240
241 try {
242 ctx->set_options(asio::ssl::context::default_workarounds |
243 asio::ssl::context::no_sslv2 |
244 asio::ssl::context::no_sslv3 |
245 asio::ssl::context::no_tlsv1 |
246 asio::ssl::context::no_tlsv1_1 |
247 asio::ssl::context::single_dh_use);
248
249 ctx->use_certificate_chain_file(https_cert_chain);
250 ctx->use_private_key_file(https_key, asio::ssl::context::pem);
251
252 //going for the A+! Do a few more things on the native context to get ECDH in use
253
254 fc::ec_key ecdh = EC_KEY_new_by_curve_name(https_ecdh_curve == SECP384R1 ? NID_secp384r1 : NID_X9_62_prime256v1);
255 if (!ecdh)
256 SYS_THROW(chain::http_exception, "Failed to set NID_secp384r1");
257 if(SSL_CTX_set_tmp_ecdh(ctx->native_handle(), (EC_KEY*)ecdh) != 1)
258 SYS_THROW(chain::http_exception, "Failed to set ECDH PFS");
259
260 if(SSL_CTX_set_cipher_list(ctx->native_handle(), \
261 "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:AES256:" \
262 "!DHE:!RSA:!AES128:!RC4:!DES:!3DES:!DSS:!SRP:!PSK:!EXP:!MD5:!LOW:!aNULL:!eNULL") != 1)
263 SYS_THROW(chain::http_exception, "Failed to set HTTPS cipher list");
264 } catch (const fc::exception& e) {
265 fc_elog( logger, "https server initialization error: ${w}", ("w", e.to_detail_string()) );
266 } catch(std::exception& e) {
267 fc_elog( logger, "https server initialization error: ${w}", ("w", e.what()) );
268 }
269
270 return ctx;
271 }
272
273 template<class T>
275 string err = "Internal Service error, http: ";
277 try {
279 try {
280 throw;
281 } catch (const fc::exception& e) {
282 err += e.to_detail_string();
283 fc_elog( logger, "${e}", ("e", err));
285 "Internal Service Error", error_results::error_info( e, verbose_http_errors )};
286 con->set_body( fc::json::to_string( results, deadline ));
287 } catch (const std::exception& e) {
288 err += e.what();
289 fc_elog( logger, "${e}", ("e", err));
291 "Internal Service Error",
293 verbose_http_errors )};
294 con->set_body( fc::json::to_string( results, deadline ));
295 } catch (...) {
296 err += "Unknown Exception";
298 "Internal Service Error",
300 fc::exception( FC_LOG_MESSAGE( error, "Unknown Exception" )),
301 verbose_http_errors )};
302 con->set_body( fc::json::to_string( results, deadline ));
303 }
304 } catch (fc::timeout_exception& e) {
305 con->set_body( R"xxx({"message": "Internal Server Error"})xxx" );
306 fc_elog( logger, "Timeout exception ${te} attempting to handle exception: ${e}", ("te", e.to_detail_string())("e", err) );
307 } catch (...) {
308 con->set_body( R"xxx({"message": "Internal Server Error"})xxx" );
309 fc_elog( logger, "Exception attempting to handle exception: ${e}", ("e", err) );
310 }
311 con->send_http_response();
312 }
313
314 template<class T>
315 bool allow_host(const typename T::request_type& req, detail::connection_ptr<T> con) {
316 bool is_secure = con->get_uri()->get_secure();
317 const auto& local_endpoint = con->get_socket().lowest_layer().local_endpoint();
318 auto local_socket_host_port = local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port());
319
320 const auto& host_str = req.get_header("Host");
321 if (host_str.empty() || !host_is_valid(host_str, local_socket_host_port, is_secure)) {
323 return false;
324 }
325 return true;
326 }
327
328 template<typename T>
329 bool verify_max_bytes_in_flight( const T& con ) {
330 auto bytes_in_flight_size = bytes_in_flight.load();
331 if( bytes_in_flight_size > max_bytes_in_flight ) {
332 fc_dlog( logger, "429 - too many bytes in flight: ${bytes}", ("bytes", bytes_in_flight_size) );
335 ei.name = "Busy";
336 ei.what = "Too many bytes in flight: " + std::to_string( bytes_in_flight_size );
340 con->send_http_response();
341 return false;
342 }
343
344 return true;
345 }
346
353 template<typename T>
356 :_conn(std::move(conn))
357 ,_impl(std::move(impl))
358 {}
359
360 // No copy constructor and no move
365
366 ~abstract_conn_impl() = default;
367
371
375
376 void send_response(std::string body, int code) override {
377 _conn->set_body(std::move(body));
378 _conn->set_status( websocketpp::http::status_code::value( code ) );
379 _conn->send_http_response();
380 }
381
384 };
385
393 template<typename T>
395 return std::make_shared<abstract_conn_impl<T>>(std::move(conn), std::move(impl));
396 }
397
404 template<typename T>
405 struct in_flight {
407 :_object(std::move(object))
408 ,_impl(std::move(impl))
409 {
410 _count = detail::in_flight_sizeof(_object);
411 _impl->bytes_in_flight += _count;
412 }
413
415 if (_count) {
416 _impl->bytes_in_flight -= _count;
417 }
418 }
419
420 // No copy constructor, but allow move
421 in_flight(const in_flight&) = delete;
423 :_object(std::move(from._object))
424 ,_count(from._count)
425 ,_impl(std::move(from._impl))
426 {
427 from._count = 0;
428 }
429
430 // No copy assignment, but allow move
431 in_flight& operator=(const in_flight&) = delete;
433 _object = std::move(from._object);
434 _count = from._count;
435 _impl = std::move(from._impl);
436 from._count = 0;
437 }
438
443 const T& operator* () const {
444 return _object;
445 }
446
452 return _object;
453 }
454
456 size_t _count;
458 };
459
463 template<typename T>
464 static auto make_in_flight(T&& object, http_plugin_impl_ptr impl) {
465 return std::make_shared<in_flight<T>>(std::forward<T>(object), std::move(impl));
466 }
467
479 auto next_ptr = std::make_shared<url_handler>(std::move(next));
480 return [my=std::move(my), priority, next_ptr=std::move(next_ptr)]
481 ( detail::abstract_conn_ptr conn, string r, string b, url_response_callback then ) {
482 auto tracked_b = make_in_flight<string>(std::move(b), my);
483 if (!conn->verify_max_bytes_in_flight()) {
484 return;
485 }
486
487 url_response_callback wrapped_then = [tracked_b, then=std::move(then)](int code, fc::variant resp) {
488 then(code, std::move(resp));
489 };
490
491 // post to the app thread taking shared ownership of next (via std::shared_ptr),
492 // sole ownership of the tracked body and the passed in parameters
493 app().post( priority, [next_ptr, conn=std::move(conn), r=std::move(r), tracked_b, wrapped_then=std::move(wrapped_then)]() mutable {
494 try {
495 // call the `next` url_handler and wrap the response handler
496 (*next_ptr)( std::move( r ), std::move(*(*tracked_b)), std::move(wrapped_then)) ;
497 } catch( ... ) {
498 conn->handle_exception();
499 }
500 } );
501 };
502 }
503
512 return [next=std::move(next)]( detail::abstract_conn_ptr conn, string r, string b, url_response_callback then ) {
513 try {
514 next(std::move(r), std::move(b), std::move(then));
515 } catch( ... ) {
516 conn->handle_exception();
517 }
518 };
519 }
520
528 template<typename T>
530 return [my=shared_from_this(), abstract_conn_ptr]( int code, fc::variant response ) {
531 auto tracked_response = make_in_flight(std::move(response), my);
532 if (!abstract_conn_ptr->verify_max_bytes_in_flight()) {
533 return;
534 }
535
536 // post back to an HTTP thread to to allow the response handler to be called from any thread
537 boost::asio::post( my->thread_pool->get_executor(),
538 [my, abstract_conn_ptr, code, tracked_response=std::move(tracked_response)]() {
539 try {
540 std::string json = fc::json::to_string( *(*tracked_response), fc::time_point::now() + my->max_response_time );
541 auto tracked_json = make_in_flight(std::move(json), my);
542 abstract_conn_ptr->send_response(std::move(*(*tracked_json)), code);
543 } catch( ... ) {
544 abstract_conn_ptr->handle_exception();
545 }
546 });
547 };
548 }
549
550 template<class T>
552 try {
553 auto& req = con->get_request();
554
555 if(!allow_host<T>(req, con))
556 return;
557
558 if( !access_control_allow_origin.empty()) {
559 con->append_header( "Access-Control-Allow-Origin", access_control_allow_origin );
560 }
561 if( !access_control_allow_headers.empty()) {
562 con->append_header( "Access-Control-Allow-Headers", access_control_allow_headers );
563 }
564 if( !access_control_max_age.empty()) {
565 con->append_header( "Access-Control-Max-Age", access_control_max_age );
566 }
567 if( access_control_allow_credentials ) {
568 con->append_header( "Access-Control-Allow-Credentials", "true" );
569 }
570
571 if(req.get_method() == "OPTIONS") {
572 con->set_status(websocketpp::http::status_code::ok);
573 return;
574 }
575
576 con->append_header( "Content-type", "application/json" );
577 con->defer_http_response();
578
579 auto abstract_conn_ptr = make_abstract_conn_ptr<T>(con, shared_from_this());
580 if( !verify_max_bytes_in_flight( con )) return;
581
582 std::string resource = con->get_uri()->get_resource();
583 auto handler_itr = url_handlers.find( resource );
584 if( handler_itr != url_handlers.end()) {
585 std::string body = con->get_request_body();
586 handler_itr->second( abstract_conn_ptr, std::move( resource ), std::move( body ), make_http_response_handler<T>(abstract_conn_ptr) );
587 } else {
588 fc_dlog( logger, "404 - not found: ${ep}", ("ep", resource) );
590 "Not Found", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Endpoint" )), verbose_http_errors )};
591 con->set_body( fc::json::to_string( results, fc::time_point::now() + max_response_time ));
593 con->send_http_response();
594 }
595 } catch( ... ) {
596 handle_exception<T>( con );
597 }
598 }
599
600 template<class T>
602 try {
603 ws.clear_access_channels(websocketpp::log::alevel::all);
604 ws.init_asio( &thread_pool->get_executor() );
605 ws.set_reuse_addr(true);
606 ws.set_max_http_body_size(max_body_size);
607 // captures `this` & ws, my needs to live as long as server is handling requests
608 ws.set_http_handler([&](connection_hdl hdl) {
609 handle_http_request<detail::asio_with_stub_log<T>>(ws.get_con_from_hdl(hdl));
610 });
611 } catch ( const fc::exception& e ){
612 fc_elog( logger, "http: ${e}", ("e", e.to_detail_string()) );
613 } catch ( const std::exception& e ){
614 fc_elog( logger, "http: ${e}", ("e", e.what()) );
615 } catch (...) {
616 fc_elog( logger, "error thrown from http io service" );
617 }
618 }
619
620 void add_aliases_for_endpoint( const tcp::endpoint& ep, string host, string port ) {
621 auto resolved_port_str = std::to_string(ep.port());
622 valid_hosts.emplace(host + ":" + port);
623 valid_hosts.emplace(host + ":" + resolved_port_str);
624 }
625 };
626
627 template<>
628 bool http_plugin_impl::allow_host<detail::asio_local_with_stub_log>(const detail::asio_local_with_stub_log::request_type& req, websocketpp::server<detail::asio_local_with_stub_log>::connection_ptr con) {
629 return true;
630 }
631
632 http_plugin::http_plugin():my(new http_plugin_impl()){
634 }
636
637 void http_plugin::set_program_options(options_description&, options_description& cfg) {
638 if(current_http_plugin_defaults.default_unix_socket_path.length())
639 cfg.add_options()
640 ("unix-socket-path", bpo::value<string>()->default_value(current_http_plugin_defaults.default_unix_socket_path),
641 "The filename (relative to data-dir) to create a unix socket for HTTP RPC; set blank to disable.");
642 else
643 cfg.add_options()
644 ("unix-socket-path", bpo::value<string>(),
645 "The filename (relative to data-dir) to create a unix socket for HTTP RPC; set blank to disable.");
646
647 if(current_http_plugin_defaults.default_http_port)
648 cfg.add_options()
649 ("http-server-address", bpo::value<string>()->default_value("127.0.0.1:" + std::to_string(current_http_plugin_defaults.default_http_port)),
650 "The local IP and port to listen for incoming http connections; set blank to disable.");
651 else
652 cfg.add_options()
653 ("http-server-address", bpo::value<string>(),
654 "The local IP and port to listen for incoming http connections; leave blank to disable.");
655
656 cfg.add_options()
657 ("https-server-address", bpo::value<string>(),
658 "The local IP and port to listen for incoming https connections; leave blank to disable.")
659
660 ("https-certificate-chain-file", bpo::value<string>(),
661 "Filename with the certificate chain to present on https connections. PEM format. Required for https.")
662
663 ("https-private-key-file", bpo::value<string>(),
664 "Filename with https private key in PEM format. Required for https")
665
666 ("https-ecdh-curve", bpo::value<https_ecdh_curve_t>()->notifier([this](https_ecdh_curve_t c) {
667 my->https_ecdh_curve = c;
668 })->default_value(SECP384R1),
669 "Configure https ECDH curve to use: secp384r1 or prime256v1")
670
671 ("access-control-allow-origin", bpo::value<string>()->notifier([this](const string& v) {
672 my->access_control_allow_origin = v;
673 fc_ilog( logger, "configured http with Access-Control-Allow-Origin: ${o}",
674 ("o", my->access_control_allow_origin) );
675 }),
676 "Specify the Access-Control-Allow-Origin to be returned on each request.")
677
678 ("access-control-allow-headers", bpo::value<string>()->notifier([this](const string& v) {
679 my->access_control_allow_headers = v;
680 fc_ilog( logger, "configured http with Access-Control-Allow-Headers : ${o}",
681 ("o", my->access_control_allow_headers) );
682 }),
683 "Specify the Access-Control-Allow-Headers to be returned on each request.")
684
685 ("access-control-max-age", bpo::value<string>()->notifier([this](const string& v) {
686 my->access_control_max_age = v;
687 fc_ilog( logger, "configured http with Access-Control-Max-Age : ${o}",
688 ("o", my->access_control_max_age) );
689 }),
690 "Specify the Access-Control-Max-Age to be returned on each request.")
691
692 ("access-control-allow-credentials",
693 bpo::bool_switch()->notifier([this](bool v) {
694 my->access_control_allow_credentials = v;
695 if( v ) fc_ilog( logger, "configured http with Access-Control-Allow-Credentials: true" );
696 })->default_value(false),
697 "Specify if Access-Control-Allow-Credentials: true should be returned on each request.")
698 ("max-body-size", bpo::value<uint32_t>()->default_value(my->max_body_size),
699 "The maximum body size in bytes allowed for incoming RPC requests")
700 ("http-max-bytes-in-flight-mb", bpo::value<int64_t>()->default_value(500),
701 "Maximum size in megabytes http_plugin should use for processing http requests. -1 for unlimited. 429 error response when exceeded." )
702 ("http-max-response-time-ms", bpo::value<uint32_t>()->default_value(30),
703 "Maximum time for processing a request.")
704 ("verbose-http-errors", bpo::bool_switch()->default_value(false),
705 "Append the error log to HTTP responses")
706 ("http-validate-host", boost::program_options::value<bool>()->default_value(true),
707 "If set to false, then any incoming \"Host\" header is considered valid")
708 ("http-alias", bpo::value<std::vector<string>>()->composing(),
709 "Additionaly acceptable values for the \"Host\" header of incoming HTTP requests, can be specified multiple times. Includes http/s_server_address by default.")
710 ("http-threads", bpo::value<uint16_t>()->default_value( my->thread_pool_size ),
711 "Number of worker threads in http thread pool")
712 ;
713 }
714
715 void http_plugin::plugin_initialize(const variables_map& options) {
716 try {
717 my->max_body_size = options.at( "max-body-size" ).as<uint32_t>();
718 verbose_http_errors = options.at( "verbose-http-errors" ).as<bool>();
719
720 my->thread_pool_size = options.at( "http-threads" ).as<uint16_t>();
721 SYS_ASSERT( my->thread_pool_size > 0, chain::plugin_config_exception,
722 "http-threads ${num} must be greater than 0", ("num", my->thread_pool_size));
723
724 auto max_bytes_mb = options.at( "http-max-bytes-in-flight-mb" ).as<int64_t>();
725 SYS_ASSERT( (max_bytes_mb >= -1 && max_bytes_mb < std::numeric_limits<int64_t>::max() / (1024 * 1024)), chain::plugin_config_exception,
726 "http-max-bytes-in-flight-mb (${max_bytes_mb}) must be equal to or greater than -1 and less than ${max}", ("max_bytes_mb", max_bytes_mb) ("max", std::numeric_limits<int64_t>::max() / (1024 * 1024)) );
727 if ( max_bytes_mb == -1 ) {
728 my->max_bytes_in_flight = std::numeric_limits<size_t>::max();
729 } else {
730 my->max_bytes_in_flight = max_bytes_mb * 1024 * 1024;
731 }
732 my->max_response_time = fc::microseconds( options.at("http-max-response-time-ms").as<uint32_t>() * 1000 );
733
734 my->validate_host = options.at("http-validate-host").as<bool>();
735 if( options.count( "http-alias" )) {
736 const auto& aliases = options["http-alias"].as<vector<string>>();
737 my->valid_hosts.insert(aliases.begin(), aliases.end());
738 }
739
740 tcp::resolver resolver( app().get_io_service());
741 if( options.count( "http-server-address" ) && options.at( "http-server-address" ).as<string>().length()) {
742 string lipstr = options.at( "http-server-address" ).as<string>();
743 string host = lipstr.substr( 0, lipstr.find( ':' ));
744 string port = lipstr.substr( host.size() + 1, lipstr.size());
745 tcp::resolver::query query( tcp::v4(), host.c_str(), port.c_str());
746 try {
747 my->listen_endpoint = *resolver.resolve( query );
748 ilog( "configured http to listen on ${h}:${p}", ("h", host)( "p", port ));
749 } catch ( const boost::system::system_error& ec ) {
750 elog( "failed to configure http to listen on ${h}:${p} (${m})",
751 ("h", host)( "p", port )( "m", ec.what()));
752 }
753
754 // add in resolved hosts and ports as well
755 if (my->listen_endpoint) {
756 my->add_aliases_for_endpoint(*my->listen_endpoint, host, port);
757 }
758 }
759
760 if( options.count( "unix-socket-path" ) && !options.at( "unix-socket-path" ).as<string>().empty()) {
761 boost::filesystem::path sock_path = options.at("unix-socket-path").as<string>();
762 if (sock_path.is_relative())
763 sock_path = app().data_dir() / sock_path;
764 my->unix_endpoint = asio::local::stream_protocol::endpoint(sock_path.string());
765 }
766
767 if( options.count( "https-server-address" ) && options.at( "https-server-address" ).as<string>().length()) {
768 if( !options.count( "https-certificate-chain-file" ) ||
769 options.at( "https-certificate-chain-file" ).as<string>().empty()) {
770 elog( "https-certificate-chain-file is required for HTTPS" );
771 return;
772 }
773 if( !options.count( "https-private-key-file" ) ||
774 options.at( "https-private-key-file" ).as<string>().empty()) {
775 elog( "https-private-key-file is required for HTTPS" );
776 return;
777 }
778
779 string lipstr = options.at( "https-server-address" ).as<string>();
780 string host = lipstr.substr( 0, lipstr.find( ':' ));
781 string port = lipstr.substr( host.size() + 1, lipstr.size());
782 tcp::resolver::query query( tcp::v4(), host.c_str(), port.c_str());
783 try {
784 my->https_listen_endpoint = *resolver.resolve( query );
785 ilog( "configured https to listen on ${h}:${p} (TLS configuration will be validated momentarily)",
786 ("h", host)( "p", port ));
787 my->https_cert_chain = options.at( "https-certificate-chain-file" ).as<string>();
788 my->https_key = options.at( "https-private-key-file" ).as<string>();
789 } catch ( const boost::system::system_error& ec ) {
790 elog( "failed to configure https to listen on ${h}:${p} (${m})",
791 ("h", host)( "p", port )( "m", ec.what()));
792 }
793
794 // add in resolved hosts and ports as well
795 if (my->https_listen_endpoint) {
796 my->add_aliases_for_endpoint(*my->https_listen_endpoint, host, port);
797 }
798 }
799
800 //watch out for the returns above when adding new code here
802 }
803
805
806 handle_sighup(); // setup logging
807 app().post(appbase::priority::high, [this] ()
808 {
809 try {
810 my->thread_pool.emplace( "http", my->thread_pool_size );
811 if(my->listen_endpoint) {
812 try {
813 my->create_server_for_endpoint(*my->listen_endpoint, my->server);
814
815 fc_ilog( logger, "start listening for http requests" );
816 my->server.listen(*my->listen_endpoint);
817 my->server.start_accept();
818 } catch ( const fc::exception& e ){
819 fc_elog( logger, "http service failed to start: ${e}", ("e", e.to_detail_string()) );
820 throw;
821 } catch ( const std::exception& e ){
822 fc_elog( logger, "http service failed to start: ${e}", ("e", e.what()) );
823 throw;
824 } catch (...) {
825 fc_elog( logger, "error thrown from http io service" );
826 throw;
827 }
828 }
829
830 if(my->unix_endpoint) {
831 try {
832 my->unix_server.clear_access_channels(websocketpp::log::alevel::all);
833 my->unix_server.init_asio( &my->thread_pool->get_executor() );
834 my->unix_server.set_max_http_body_size(my->max_body_size);
835 my->unix_server.listen(*my->unix_endpoint);
836 // captures `this`, my needs to live as long as unix_server is handling requests
837 my->unix_server.set_http_handler([this](connection_hdl hdl) {
838 my->handle_http_request<detail::asio_local_with_stub_log>( my->unix_server.get_con_from_hdl(hdl));
839 });
840 my->unix_server.start_accept();
841 } catch ( const fc::exception& e ){
842 fc_elog( logger, "unix socket service (${path}) failed to start: ${e}", ("e", e.to_detail_string())("path",my->unix_endpoint->path()) );
843 throw;
844 } catch ( const std::exception& e ){
845 fc_elog( logger, "unix socket service (${path}) failed to start: ${e}", ("e", e.what())("path",my->unix_endpoint->path()) );
846 throw;
847 } catch (...) {
848 fc_elog( logger, "error thrown from unix socket (${path}) io service", ("path",my->unix_endpoint->path()) );
849 throw;
850 }
851 }
852
853 if(my->https_listen_endpoint) {
854 try {
855 my->create_server_for_endpoint(*my->https_listen_endpoint, my->https_server);
856 my->https_server.set_tls_init_handler([this](websocketpp::connection_hdl hdl) -> ssl_context_ptr{
857 return my->on_tls_init(hdl);
858 });
859
860 fc_ilog( logger, "start listening for https requests" );
861 my->https_server.listen(*my->https_listen_endpoint);
862 my->https_server.start_accept();
863 } catch ( const fc::exception& e ){
864 fc_elog( logger, "https service failed to start: ${e}", ("e", e.to_detail_string()) );
865 throw;
866 } catch ( const std::exception& e ){
867 fc_elog( logger, "https service failed to start: ${e}", ("e", e.what()) );
868 throw;
869 } catch (...) {
870 fc_elog( logger, "error thrown from https io service" );
871 throw;
872 }
873 }
874
875 add_api({{
876 std::string("/v1/node/get_supported_apis"),
877 [&](string, string body, url_response_callback cb) mutable {
878 try {
879 if (body.empty()) body = "{}";
880 auto result = (*this).get_supported_apis();
881 cb(200, fc::variant(result));
882 } catch (...) {
883 handle_exception("node", "get_supported_apis", body, cb);
884 }
885 }
886 }});
887 } catch (...) {
888 fc_elog(logger, "http_plugin startup fails, shutting down");
889 app().shutdown();
890 }
891 });
892 }
893
897
899 if(my->server.is_listening())
900 my->server.stop_listening();
901 if(my->https_server.is_listening())
902 my->https_server.stop_listening();
903 if(my->unix_server.is_listening())
904 my->unix_server.stop_listening();
905
906 if( my->thread_pool ) {
907 my->thread_pool->stop();
908 my->thread_pool.reset();
909 }
910
911 // release http_plugin_impl_ptr shared_ptrs captured in url handlers
912 my->url_handlers.clear();
913
914 app().post( 0, [me = my](){} ); // keep my pointer alive until queue is drained
915 fc_ilog( logger, "exit shutdown");
916 }
917
918 void http_plugin::add_handler(const string& url, const url_handler& handler, int priority) {
919 fc_ilog( logger, "add api url: ${c}", ("c", url) );
920 my->url_handlers[url] = my->make_app_thread_url_handler(priority, handler, my);
921 }
922
923 void http_plugin::add_async_handler(const string& url, const url_handler& handler) {
924 fc_ilog( logger, "add api url: ${c}", ("c", url) );
925 my->url_handlers[url] = my->make_http_thread_url_handler(handler);
926 }
927
928 void http_plugin::handle_exception( const char *api_name, const char *call_name, const string& body, url_response_callback cb ) {
929 try {
930 try {
931 throw;
932 } catch (chain::unknown_block_exception& e) {
933 error_results results{400, "Unknown Block", error_results::error_info(e, verbose_http_errors)};
934 cb( 400, fc::variant( results ));
935 } catch (chain::invalid_http_request& e) {
936 error_results results{400, "Invalid Request", error_results::error_info(e, verbose_http_errors)};
937 cb( 400, fc::variant( results ));
938 } catch (chain::unsatisfied_authorization& e) {
939 error_results results{401, "UnAuthorized", error_results::error_info(e, verbose_http_errors)};
940 cb( 401, fc::variant( results ));
941 } catch (chain::tx_duplicate& e) {
942 error_results results{409, "Conflict", error_results::error_info(e, verbose_http_errors)};
943 cb( 409, fc::variant( results ));
944 } catch (fc::eof_exception& e) {
945 error_results results{422, "Unprocessable Entity", error_results::error_info(e, verbose_http_errors)};
946 cb( 422, fc::variant( results ));
947 fc_elog( logger, "Unable to parse arguments to ${api}.${call}", ("api", api_name)( "call", call_name ) );
948 fc_dlog( logger, "Bad arguments: ${args}", ("args", body) );
949 } catch (fc::exception& e) {
950 error_results results{500, "Internal Service Error", error_results::error_info(e, verbose_http_errors)};
951 cb( 500, fc::variant( results ));
952 fc_dlog( logger, "Exception while processing ${api}.${call}: ${e}",
953 ("api", api_name)( "call", call_name )("e", e.to_detail_string()) );
954 } catch (std::exception& e) {
955 error_results results{500, "Internal Service Error", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, e.what())), verbose_http_errors)};
956 cb( 500, fc::variant( results ));
957 fc_elog( logger, "STD Exception encountered while processing ${api}.${call}",
958 ("api", api_name)( "call", call_name ) );
959 fc_dlog( logger, "Exception Details: ${e}", ("e", e.what()) );
960 } catch (...) {
961 error_results results{500, "Internal Service Error",
962 error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Exception" )), verbose_http_errors)};
963 cb( 500, fc::variant( results ));
964 fc_elog( logger, "Unknown Exception encountered while processing ${api}.${call}",
965 ("api", api_name)( "call", call_name ) );
966 }
967 } catch (...) {
968 std::cerr << "Exception attempting to handle exception for " << api_name << "." << call_name << std::endl;
969 }
970 }
971
973 return (!my->listen_endpoint || my->listen_endpoint->address().is_loopback()) && (!my->https_listen_endpoint || my->https_listen_endpoint->address().is_loopback());
974 }
975
977 return (!my->listen_endpoint || my->listen_endpoint->address().is_loopback());
978 }
979
981 return verbose_http_errors;
982 }
983
986
987 for (const auto& handler : my->url_handlers) {
988 if (handler.first != "/v1/node/get_supported_apis")
989 result.apis.emplace_back(handler.first);
990 }
991
992 return result;
993 }
994
996 return my->max_response_time;
997 }
998
999 std::istream& operator>>(std::istream& in, https_ecdh_curve_t& curve) {
1000 std::string s;
1001 in >> s;
1002 if (s == "secp384r1")
1003 curve = SECP384R1;
1004 else if (s == "prime256v1")
1005 curve = PRIME256V1;
1006 else
1007 in.setstate(std::ios_base::failbit);
1008 return in;
1009 }
1010
1011 std::ostream& operator<<(std::ostream& osm, https_ecdh_curve_t curve) {
1012 if (curve == SECP384R1) {
1013 osm << "secp384r1";
1014 } else if (curve == PRIME256V1) {
1015 osm << "prime256v1";
1016 }
1017
1018 return osm;
1019 }
1020}
std::string ws(int const level)
const mie::Vuint & r
Definition bn.cpp:28
#define SYS_THROW(exc_type, FORMAT,...)
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Definition exceptions.hpp:7
bfs::path data_dir() const
Get data directory.
auto post(int priority, Func &&func)
Used to generate a useful error report when an exception is thrown.
Definition exception.hpp:58
static constexpr fc::microseconds format_time_limit
Definition exception.hpp:60
std::string to_detail_string(log_level ll=log_level::all) const
const char * what() const noexcept override
static string to_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
Definition json.cpp:674
static void update(const fc::string &name, logger &log)
Definition logger.cpp:92
static time_point now()
Definition time.cpp:14
static constexpr time_point maximum()
Definition time.hpp:46
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object's.
Definition variant.hpp:191
http_plugin_impl & operator=(const http_plugin_impl &)=delete
ssl_context_ptr on_tls_init(websocketpp::connection_hdl hdl)
websocket_server_tls_type https_server
http_plugin_impl(http_plugin_impl &&)=delete
void add_aliases_for_endpoint(const tcp::endpoint &ep, string host, string port)
std::optional< asio::local::stream_protocol::endpoint > unix_endpoint
http_plugin_impl(const http_plugin_impl &)=delete
bool host_is_valid(const std::string &host, const string &endpoint_local_host_port, bool secure)
void create_server_for_endpoint(const tcp::endpoint &ep, websocketpp::server< detail::asio_with_stub_log< T > > &ws)
fc::microseconds max_response_time
websocket_server_type server
static void handle_exception(detail::connection_ptr< T > con)
static detail::abstract_conn_ptr make_abstract_conn_ptr(detail::connection_ptr< T > conn, http_plugin_impl_ptr impl)
static detail::internal_url_handler make_app_thread_url_handler(int priority, url_handler next, http_plugin_impl_ptr my)
std::optional< sysio::chain::named_thread_pool > thread_pool
auto make_http_response_handler(detail::abstract_conn_ptr abstract_conn_ptr)
std::atomic< size_t > bytes_in_flight
static auto make_in_flight(T &&object, http_plugin_impl_ptr impl)
std::optional< tcp::endpoint > https_listen_endpoint
static detail::internal_url_handler make_http_thread_url_handler(url_handler next)
websocket_local_server_type unix_server
bool host_port_is_valid(const std::string &header_host_port, const string &endpoint_local_host_port)
map< string, detail::internal_url_handler > url_handlers
std::optional< tcp::endpoint > listen_endpoint
void handle_http_request(detail::connection_ptr< T > con)
http_plugin_impl & operator=(http_plugin_impl &&)=delete
https_ecdh_curve_t https_ecdh_curve
bool allow_host(const typename T::request_type &req, detail::connection_ptr< T > con)
bool verify_max_bytes_in_flight(const T &con)
void add_api(const api_description &api, int priority=appbase::priority::medium_low)
void add_handler(const string &url, const url_handler &, int priority=appbase::priority::medium_low)
void add_async_handler(const string &url, const url_handler &handler)
bool is_secure() const
virtual void set_program_options(options_description &, options_description &cfg) override
bool verbose_errors() const
get_supported_apis_result get_supported_apis() const
fc::microseconds get_max_response_time() const
bool is_on_loopback() const
static void set_defaults(const http_plugin_defaults config)
void plugin_initialize(const variables_map &options)
static void handle_exception(const char *api_name, const char *call_name, const string &body, url_response_callback cb)
void handle_sighup() override
Stub logger that ignores all input.
Definition stub.hpp:41
Asio based endpoint transport component.
Definition endpoint.hpp:53
Asio based endpoint transport component.
#define FC_LOG_AND_RETHROW()
fc::logger logger
const fc::string logger_name("http_plugin")
#define FC_LOG_MESSAGE(LOG_LEVEL, FORMAT,...)
A helper method for generating log messages.
#define fc_ilog(LOGGER, FORMAT,...)
Definition logger.hpp:83
#define ilog(FORMAT,...)
Definition logger.hpp:118
#define elog(FORMAT,...)
Definition logger.hpp:130
#define fc_elog(LOGGER, FORMAT,...)
Definition logger.hpp:95
#define fc_dlog(LOGGER, FORMAT,...)
Definition logger.hpp:77
application & app()
size_t pack_size(const T &v)
Definition raw.hpp:671
std::string string
Definition string.hpp:10
datastream< ST > & operator<<(datastream< ST > &s, const sysio::chain::may_not_exist< T > &v)
Definition abi_def.hpp:146
datastream< ST > & operator>>(datastream< ST > &s, sysio::chain::may_not_exist< T > &v)
Definition abi_def.hpp:152
Definition name.hpp:106
std::function< void(abstract_conn_ptr, string, string, url_response_callback)> internal_url_handler
std::shared_ptr< abstract_conn > abstract_conn_ptr
typename websocketpp::server< T >::connection_ptr connection_ptr
websocketpp::lib::shared_ptr< websocketpp::lib::asio::ssl::context > ssl_context_ptr
std::function< void(string, string, url_response_callback)> url_handler
Callback type for a URL handler.
std::function< void(int, fc::variant)> url_response_callback
A callback function provided to a URL handler to allow it to specify the HTTP response code and body.
const fc::string logger_name("net_plugin_impl")
std::shared_ptr< class http_plugin_impl > http_plugin_impl_ptr
https_ecdh_curve_t
lib::weak_ptr< void > connection_hdl
A handle to uniquely identify a connection.
#define T(meth, val, expected)
string url
Definition main.cpp:166
unsigned short uint16_t
Definition stdint.h:125
signed __int64 int64_t
Definition stdint.h:135
unsigned int uint32_t
Definition stdint.h:126
static constexpr int high
virtual bool verify_max_bytes_in_flight()=0
virtual void handle_exception()=0
virtual void send_response(std::string, int)=0
websocketpp::transport::asio::basic_socket::local_endpoint socket_type
base::con_msg_manager_type con_msg_manager_type
base::concurrency_type concurrency_type
websocketpp::transport::asio::local_endpoint< transport_config > transport_type
base::endpoint_msg_manager_type endpoint_msg_manager_type
base::con_msg_manager_type con_msg_manager_type
websocketpp::log::stub alog_type
websocketpp::transport::asio::endpoint< transport_config > transport_type
base::endpoint_msg_manager_type endpoint_msg_manager_type
websocketpp::log::stub elog_type
base::response_type response_type
base::concurrency_type concurrency_type
static const long timeout_open_handshake
Structure used to create JSON error responses.
abstract_conn_impl(abstract_conn_impl &&)=delete
abstract_conn_impl(const abstract_conn_impl &)=delete
void send_response(std::string body, int code) override
abstract_conn_impl(detail::connection_ptr< T > conn, http_plugin_impl_ptr impl)
abstract_conn_impl & operator=(abstract_conn_impl &&) noexcept=default
abstract_conn_impl & operator=(const abstract_conn_impl &)=delete
in_flight & operator=(const in_flight &)=delete
in_flight(const in_flight &)=delete
in_flight & operator=(in_flight &&from)
in_flight(T &&object, http_plugin_impl_ptr impl)
Server config with asio transport and TLS disabled.
static level const all
Special aggregate value representing "all levels".
Definition levels.hpp:152
char * s
yh_object_descriptor object