From 82734f2218d52bdf59c7ebcaaa2b9fe8b8310701 Mon Sep 17 00:00:00 2001 From: Russel Waters Date: Thu, 31 Jan 2019 17:49:10 -0500 Subject: [PATCH] Support the HTTP OPTIONS verb for RPC (#1667) Useful for preflighting requests when using CORS. --- nano/core_test/rpc.cpp | 16 +++++++++---- nano/node/rpc.cpp | 49 ++++++++++++++++++++++++++++------------ nano/node/rpc.hpp | 3 ++- nano/node/rpc_secure.cpp | 38 ++++++++++++++++++++++++------- 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index d0af9ff1..c34c6e86 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -1757,10 +1757,18 @@ TEST (rpc, version) ASSERT_EQ (boost::str (boost::format ("Nano %1%") % NANO_MAJOR_MINOR_RC_VERSION), response1.json.get ("node_vendor")); } auto headers (response1.resp.base ()); - auto allowed_origin (headers.at ("Access-Control-Allow-Origin")); - auto allowed_headers (headers.at ("Access-Control-Allow-Headers")); - ASSERT_EQ ("*", allowed_origin); - ASSERT_EQ ("Accept, Accept-Language, Content-Language, Content-Type", allowed_headers); + auto allow (headers.at ("Allow")); + auto content_type (headers.at ("Content-Type")); + auto access_control_allow_origin (headers.at ("Access-Control-Allow-Origin")); + auto access_control_allow_methods (headers.at ("Access-Control-Allow-Methods")); + auto access_control_allow_headers (headers.at ("Access-Control-Allow-Headers")); + auto connection (headers.at ("Connection")); + ASSERT_EQ ("POST, OPTIONS", allow); + ASSERT_EQ ("application/json", content_type); + ASSERT_EQ ("*", access_control_allow_origin); + ASSERT_EQ (allow, access_control_allow_methods); + ASSERT_EQ ("Accept, Accept-Language, Content-Language, Content-Type", access_control_allow_headers); + ASSERT_EQ ("close", connection); } TEST (rpc, work_generate) diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index 44d72b7c..d6a17241 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -4038,17 +4038,24 @@ void nano::rpc_connection::parse_connection () read (); } -void nano::rpc_connection::write_result (std::string body, unsigned version) +void nano::rpc_connection::prepare_head (unsigned version, boost::beast::http::status status) +{ + res.version (version); + res.result (status); + res.set (boost::beast::http::field::allow, "POST, OPTIONS"); + res.set (boost::beast::http::field::content_type, "application/json"); + res.set (boost::beast::http::field::access_control_allow_origin, "*"); + res.set (boost::beast::http::field::access_control_allow_methods, "POST, OPTIONS"); + res.set (boost::beast::http::field::access_control_allow_headers, "Accept, Accept-Language, Content-Language, Content-Type"); + res.set (boost::beast::http::field::connection, "close"); +} + +void nano::rpc_connection::write_result (std::string body, unsigned version, boost::beast::http::status status) { if (!responded.test_and_set ()) { - res.set ("Content-Type", "application/json"); - res.set ("Access-Control-Allow-Origin", "*"); - res.set ("Access-Control-Allow-Headers", "Accept, Accept-Language, Content-Language, Content-Type"); - res.set ("Connection", "close"); - res.result (boost::beast::http::status::ok); + prepare_head (version, status); res.body () = body; - res.version (version); res.prepare_payload (); } else @@ -4082,14 +4089,28 @@ void nano::rpc_connection::read () BOOST_LOG (this_l->node->log) << boost::str (boost::format ("RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () % request_id); } }); - if (this_l->request.method () == boost::beast::http::verb::post) + auto method = this_l->request.method (); + switch (method) { - auto handler (std::make_shared (*this_l->node, this_l->rpc, this_l->request.body (), request_id, response_handler)); - handler->process_request (); - } - else - { - error_response (response_handler, "Can only POST requests"); + case boost::beast::http::verb::post: + { + auto handler (std::make_shared (*this_l->node, this_l->rpc, this_l->request.body (), request_id, response_handler)); + handler->process_request (); + break; + } + case boost::beast::http::verb::options: + { + this_l->prepare_head (version); + this_l->res.prepare_payload (); + boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + }); + break; + } + default: + { + error_response (response_handler, "Can only POST requests"); + break; + } } }); } diff --git a/nano/node/rpc.hpp b/nano/node/rpc.hpp index c787073e..e61f61cb 100644 --- a/nano/node/rpc.hpp +++ b/nano/node/rpc.hpp @@ -96,7 +96,8 @@ public: virtual ~rpc_connection () = default; virtual void parse_connection (); virtual void read (); - virtual void write_result (std::string body, unsigned version); + virtual void prepare_head (unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); + virtual void write_result (std::string body, unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); std::shared_ptr node; nano::rpc & rpc; boost::asio::ip::tcp::socket socket; diff --git a/nano/node/rpc_secure.cpp b/nano/node/rpc_secure.cpp index ca4abcdf..8999a38b 100644 --- a/nano/node/rpc_secure.cpp +++ b/nano/node/rpc_secure.cpp @@ -176,15 +176,37 @@ void nano::rpc_connection_secure::read () BOOST_LOG (this_l->node->log) << boost::str (boost::format ("TLS: RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () % request_id); } }); + auto method = this_l->request.method (); + switch (method) + { + case boost::beast::http::verb::post: + { + auto handler (std::make_shared (*this_l->node, this_l->rpc, this_l->request.body (), request_id, response_handler)); + handler->process_request (); + break; + } + case boost::beast::http::verb::options: + { + this_l->prepare_head (version); + this_l->res.set (boost::beast::http::field::allow, "POST, OPTIONS"); + this_l->res.prepare_payload (); + boost::beast::http::async_write (this_l->stream, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { - if (this_l->request.method () == boost::beast::http::verb::post) - { - auto handler (std::make_shared (*this_l->node, this_l->rpc, this_l->request.body (), request_id, response_handler)); - handler->process_request (); - } - else - { - error_response (response_handler, "Can only POST requests"); + // Perform the SSL shutdown + this_l->stream.async_shutdown ( + std::bind ( + &rai::rpc_connection_secure::on_shutdown, + this_l, + std::placeholders::_1)); + + }); + break; + } + default: + { + error_response (response_handler, "Can only POST requests"); + break; + } } }); }