diff --git a/examples/client/CMakeLists.txt b/examples/client/CMakeLists.txt index e45eb43..609c152 100644 --- a/examples/client/CMakeLists.txt +++ b/examples/client/CMakeLists.txt @@ -3,6 +3,13 @@ project(basic_client) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++11") +SET(ENABLE_SSL ON) + +if (ENABLE_SSL) + add_definitions(-DCINATRA_ENABLE_SSL) + message(STATUS "Use SSL") +endif() + find_package(Boost COMPONENTS system REQUIRED) include_directories( "../../include" @@ -10,4 +17,9 @@ include_directories( ) add_executable(basic_client main.cpp) -target_link_libraries(basic_client ${Boost_LIBRARIES}) + +if (ENABLE_SSL) + target_link_libraries(basic_client ${Boost_LIBRARIES} -lssl -lcrypto -lpthread) +else() + target_link_libraries(basic_client ${Boost_LIBRARIES}) +endif() \ No newline at end of file diff --git a/examples/client/basic_client.vcxproj b/examples/client/basic_client.vcxproj index 1af657f..4b18278 100644 --- a/examples/client/basic_client.vcxproj +++ b/examples/client/basic_client.vcxproj @@ -97,6 +97,7 @@ true true 4996 + %(PreprocessorDefinitions) @@ -123,6 +124,7 @@ true true 4996 + %(PreprocessorDefinitions) true diff --git a/examples/client/main.cpp b/examples/client/main.cpp index 0d09436..778ac03 100644 --- a/examples/client/main.cpp +++ b/examples/client/main.cpp @@ -549,6 +549,58 @@ void test_threads() { std::cin >> str; } +void test_ssl() { + bool is_ssl = true; + rpc_client client; + client.set_error_callback([](auto ec) { + std::cout << ec.message() << "\n"; + }); + +#ifdef CINATRA_ENABLE_SSL + client.set_ssl_context_callback([](boost::asio::ssl::context& ctx) { + ctx.set_verify_mode(boost::asio::ssl::context::verify_peer); + ctx.load_verify_file("server.crt"); + }); +#endif + + bool r = client.connect("127.0.0.1", 9000, is_ssl); + if (!r) { + return; + } + + for (size_t i = 0; i < 100; i++) { + try { + auto result = client.call("echo", "purecpp"); + std::cout << result << " sync\n"; + } + catch (const std::exception& e) { + std::cout << e.what() << " sync\n"; + } + + auto future = client.async_call("echo", "purecpp"); + auto status = future.wait_for(std::chrono::milliseconds(5000)); + if (status == std::future_status::timeout) { + std::cout << "timeout future\n"; + } + else { + auto result1 = future.get(); + std::cout << result1.as() << " future\n"; + } + + client.async_call("echo", [](auto ec, auto data) { + if (ec) { + std::cout << ec.message() <<" "<< data << "\n"; + return; + } + + auto result = as(data); + std::cout << result << " async\n"; + }, "purecpp"); + } + + std::getchar(); +} + int main() { test_sub1(); test_connect(); diff --git a/examples/server/CMakeLists.txt b/examples/server/CMakeLists.txt index 4e5d730..3f1e146 100644 --- a/examples/server/CMakeLists.txt +++ b/examples/server/CMakeLists.txt @@ -3,11 +3,23 @@ project(basic_server) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++11") -find_package(Boost COMPONENTS system REQUIRED) +SET(ENABLE_SSL OFF) + +if (ENABLE_SSL) + add_definitions(-DCINATRA_ENABLE_SSL) + message(STATUS "Use SSL") +endif() + +find_package(Boost COMPONENTS system filesystem REQUIRED) include_directories( "../../include" "../../third/msgpack/include" ) add_executable(basic_server main.cpp) -target_link_libraries(basic_server ${Boost_LIBRARIES}) + +if (ENABLE_SSL) + target_link_libraries(basic_server ${Boost_LIBRARIES} -lssl -lcrypto -lpthread) +else() + target_link_libraries(basic_server ${Boost_LIBRARIES}) +endif() diff --git a/examples/server/basic_server.vcxproj b/examples/server/basic_server.vcxproj index 58a393d..139e6f8 100644 --- a/examples/server/basic_server.vcxproj +++ b/examples/server/basic_server.vcxproj @@ -97,6 +97,7 @@ true true 4996 + %(PreprocessorDefinitions) @@ -123,6 +124,7 @@ true true 4996 + %(PreprocessorDefinitions) true diff --git a/examples/server/main.cpp b/examples/server/main.cpp index be2733f..41da501 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -91,6 +91,13 @@ int get_int(rpc_conn conn, int val) { return val; } +void test_ssl() { + rpc_server server(9000, std::thread::hardware_concurrency(), { "server.crt", "server.key" }); + server.register_handler("hello", hello); + server.register_handler("echo", echo); + server.run(); +} + int main() { rpc_server server(9000, std::thread::hardware_concurrency()); diff --git a/include/connection.h b/include/connection.h index 8f2ffb0..b08e70e 100644 --- a/include/connection.h +++ b/include/connection.h @@ -8,11 +8,17 @@ #include "use_asio.hpp" #include "const_vars.h" #include "router.h" +#include "cplusplus_14.h" using boost::asio::ip::tcp; namespace rest_rpc { namespace rpc_service { + struct ssl_configure { + std::string cert_file; + std::string key_file; + }; + class connection : public std::enable_shared_from_this, private asio::noncopyable { public: connection(boost::asio::io_service& io_service, std::size_t timeout_seconds) @@ -23,9 +29,18 @@ namespace rest_rpc { has_closed_(false) { } - ~connection() { close(); } + ~connection() { + close(); + } - void start() { read_head(); } + void start() { + if (is_ssl() && !has_shake_) { + async_handshake(); + } + else { + read_head(); + } + } tcp::socket& socket() { return socket_; } @@ -53,15 +68,6 @@ namespace rest_rpc { response(req_id, std::move(result)); } - void close() { - has_closed_ = true; - if (socket_.is_open()) { - boost::system::error_code ignored_ec; - socket_.shutdown(tcp::socket::shutdown_both, ignored_ec); - socket_.close(ignored_ec); - } - } - void set_conn_id(int64_t id) { conn_id_ = id; } int64_t conn_id() const { return conn_id_; } @@ -87,13 +93,39 @@ namespace rest_rpc { callback_ = std::move(callback); } + void init_ssl_context(const ssl_configure& ssl_conf) { +#ifdef CINATRA_ENABLE_SSL + unsigned long ssl_options = boost::asio::ssl::context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use; + try { + boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); + ssl_context.set_options(ssl_options); + ssl_context.set_password_callback([](std::size_t size, + boost::asio::ssl::context_base::password_purpose purpose) {return "123456"; }); + + boost::system::error_code ec; + if (fs::exists(ssl_conf.cert_file, ec)) { + ssl_context.use_certificate_chain_file(ssl_conf.cert_file); + } + + if (fs::exists(ssl_conf.key_file, ec)) + ssl_context.use_private_key_file(ssl_conf.key_file, boost::asio::ssl::context::pem); + + //ssl_context_callback(ssl_context); + ssl_stream_ = std::make_unique>(socket_, ssl_context); + } + catch (const std::exception& e) { + print(e); + } +#endif + } + private: void read_head() { reset_timer(); auto self(this->shared_from_this()); - boost::asio::async_read( - socket_, boost::asio::buffer(head_), - [this, self](boost::system::error_code ec, std::size_t length) { + async_read_head([this, self](boost::system::error_code ec, std::size_t length) { if (!socket_.is_open()) { //LOG(INFO) << "socket already closed"; return; @@ -117,12 +149,12 @@ namespace rest_rpc { read_head(); } else { - //LOG(INFO) << "invalid body len"; + print("invalid body len"); close(); } } else { - //LOG(INFO) << ec.message(); + print(ec); close(); } }); @@ -130,9 +162,7 @@ namespace rest_rpc { void read_body(std::size_t size) { auto self(this->shared_from_this()); - boost::asio::async_read( - socket_, boost::asio::buffer(body_.data(), size), - [this, self](boost::system::error_code ec, std::size_t length) { + async_read(size, [this, self](boost::system::error_code ec, std::size_t length) { cancel_timer(); if (!socket_.is_open()) { @@ -153,7 +183,7 @@ namespace rest_rpc { callback_(std::move(std::get<0>(p)), std::move(std::get<1>(p)), this->shared_from_this()); } catch (const std::exception& ex) { - std::cout << ex.what() << "\n"; + print(ex); } } } @@ -173,8 +203,7 @@ namespace rest_rpc { write_buffers[3] = boost::asio::buffer(msg.content->data(), write_size_); auto self = this->shared_from_this(); - boost::asio::async_write( - socket_, write_buffers, + async_write(write_buffers, [this, self](boost::system::error_code ec, std::size_t length) { on_write(ec, length); }); @@ -182,8 +211,8 @@ namespace rest_rpc { void on_write(boost::system::error_code ec, std::size_t length) { if (ec) { - std::cout << ec.value() << " " << ec.message() << std::endl; - close(); + print(ec); + close(false); return; } @@ -197,6 +226,67 @@ namespace rest_rpc { } } + void async_handshake() { +#ifdef CINATRA_ENABLE_SSL + auto self = this->shared_from_this(); + ssl_stream_->async_handshake(boost::asio::ssl::stream_base::server, + [this, self](const boost::system::error_code& error) { + if (error) { + print(error); + close(); + return; + } + + has_shake_ = true; + read_head(); + }); +#endif + } + + bool is_ssl() const { +#ifdef CINATRA_ENABLE_SSL + return ssl_stream_ != nullptr; +#else + return false; +#endif + } + + template + void async_read_head(Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_read(*ssl_stream_, boost::asio::buffer(head_, HEAD_LEN), std::move(handler)); +#endif + } + else { + boost::asio::async_read(socket_, boost::asio::buffer(head_, HEAD_LEN), std::move(handler)); + } + } + + template + void async_read(size_t size_to_read, Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_read(*ssl_stream_, boost::asio::buffer(body_.data(), size_to_read), std::move(handler)); +#endif + } + else { + boost::asio::async_read(socket_, boost::asio::buffer(body_.data(), size_to_read), std::move(handler)); + } + } + + template + void async_write(const BufferType& buffers, Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_write(*ssl_stream_, buffers, std::move(handler)); +#endif + } + else { + boost::asio::async_write(socket_, buffers, std::move(handler)); + } + } + void reset_timer() { if (timeout_seconds_ == 0) { return; } @@ -208,7 +298,7 @@ namespace rest_rpc { if (ec) { return; } //LOG(INFO) << "rpc connection timeout"; - close(); + close(false); }); } @@ -218,7 +308,46 @@ namespace rest_rpc { timer_.cancel(); } + void close(bool close_ssl = true) { +#ifdef CINATRA_ENABLE_SSL + if (close_ssl && ssl_stream_) { + boost::system::error_code ec; + ssl_stream_->shutdown(ec); + ssl_stream_ = nullptr; + } +#endif + if (has_closed_) { + return; + } + + boost::system::error_code ignored_ec; + socket_.shutdown(tcp::socket::shutdown_both, ignored_ec); + socket_.close(ignored_ec); + has_closed_ = true; + has_shake_ = false; + } + + template + void print(Args... args) { +#ifdef _DEBUG + std::initializer_list{( std::cout << args << ' ', 0)...}; + std::cout << "\n"; +#endif + } + + void print(const boost::system::error_code& ec) { + print(ec.value(), ec.message()); + } + + void print(const std::exception& ex) { + print(ex.what()); + } + tcp::socket socket_; +#ifdef CINATRA_ENABLE_SSL + std::unique_ptr> ssl_stream_ = nullptr; +#endif + bool has_shake_ = false; char head_[HEAD_LEN]; std::vector body_; std::uint64_t req_id_; diff --git a/include/rpc_client.hpp b/include/rpc_client.hpp index d79d159..cb8883e 100644 --- a/include/rpc_client.hpp +++ b/include/rpc_client.hpp @@ -52,7 +52,7 @@ namespace rest_rpc { deadline_(ios_), body_(INIT_BUF_SIZE) { thd_ = std::make_shared([this] { ios_.run(); - }); + }); } rpc_client(const std::string& host, unsigned short port) : socket_(ios_), work_(ios_), @@ -62,9 +62,9 @@ namespace rest_rpc { }); } - ~rpc_client() { + ~rpc_client() { close(); - stop(); + stop(); } void run(){ @@ -79,22 +79,25 @@ namespace rest_rpc { reconnect_cnt_ = reconnect_count; } - bool connect(size_t timeout = 1) { + bool connect(size_t timeout = 3, bool is_ssl = false) { if (has_connected_) return true; assert(port_ != 0); + if (is_ssl) { + upgrade_to_ssl(); + } async_connect(); return wait_conn(timeout); } - bool connect(const std::string& host, unsigned short port, size_t timeout = 1) { + bool connect(const std::string& host, unsigned short port, bool is_ssl = false, size_t timeout = 3) { if (port_==0) { host_ = host; port_ = port; } - return connect(timeout); + return connect(timeout, is_ssl); } void async_connect(const std::string& host, unsigned short port) { @@ -137,15 +140,25 @@ namespace rest_rpc { port_ = port; } - void close() { - has_connected_ = false; - if (socket_.is_open()) { - boost::system::error_code ignored_ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); - socket_.close(ignored_ec); - } - clear_cache(); - } + void close(bool close_ssl = true) { + boost::system::error_code ec; + if (close_ssl) { +#ifdef CINATRA_ENABLE_SSL + if (ssl_stream_) { + ssl_stream_->shutdown(ec); + ssl_stream_ = nullptr; + } +#endif + } + + if (!has_connected_) + return; + + has_connected_ = false; + socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + socket_.close(ec); + clear_cache(); + } void set_error_callback(std::function f) { err_cb_ = std::move(f); @@ -237,7 +250,8 @@ namespace rest_rpc { template void async_call(const std::string& rpc_name, std::function cb, Args&& ... args) { if (!has_connected_) { - error_callback(boost::asio::error::make_error_code(boost::asio::error::not_connected)); + if(cb) + cb(boost::asio::error::make_error_code(boost::asio::error::not_connected), "not connected"); return; } @@ -306,6 +320,12 @@ namespace rest_rpc { call("publish_by_token", std::move(key), std::move(token), std::string(buf.data(), buf.size())); } +#ifdef CINATRA_ENABLE_SSL + void set_ssl_context_callback(std::function ssl_context_callback) { + ssl_context_callback_ = std::move(ssl_context_callback); + } +#endif + private: void async_connect() { assert(port_ != 0); @@ -331,8 +351,13 @@ namespace rest_rpc { async_reconnect(); } else { - //std::cout<<"connected ok"< write_buffers; - write_buffers[0] = boost::asio::buffer(&write_size_, sizeof(int32_t)); - write_buffers[1] = boost::asio::buffer(&msg.req_id, sizeof(uint64_t)); - write_buffers[2] = boost::asio::buffer(&msg.req_type, sizeof(request_type)); - write_buffers[3] = boost::asio::buffer((char*)msg.content.data(), write_size_); + std::array write_buffers; + write_buffers[0] = boost::asio::buffer(&write_size_, sizeof(int32_t)); + write_buffers[1] = boost::asio::buffer(&msg.req_id, sizeof(uint64_t)); + write_buffers[2] = boost::asio::buffer(&msg.req_type, sizeof(request_type)); + write_buffers[3] = boost::asio::buffer((char*)msg.content.data(), write_size_); - boost::asio::async_write(socket_, write_buffers, + async_write(write_buffers, [this](const boost::system::error_code& ec, const size_t length) { if (ec) { has_connected_ = false; - close(); + close(false); error_callback(ec); return; @@ -410,8 +435,7 @@ namespace rest_rpc { } void do_read() { - boost::asio::async_read(socket_, boost::asio::buffer(head_), - [this](const boost::system::error_code& ec, const size_t length) { + async_read_head([this](const boost::system::error_code& ec, const size_t length) { if (!socket_.is_open()) { //LOG(INFO) << "socket already closed"; has_connected_ = false; @@ -438,17 +462,14 @@ namespace rest_rpc { } } else { - //LOG(INFO) << ec.message(); - has_connected_ = false; - close(); + close(false); error_callback(ec); } }); } void read_body(std::uint64_t req_id, request_type req_type, size_t body_len) { - boost::asio::async_read( - socket_, boost::asio::buffer(body_.data(), body_len), + async_read(body_len, [this, req_id, req_type, body_len](boost::system::error_code ec, std::size_t length) { //cancel_timer(); @@ -552,9 +573,8 @@ namespace rest_rpc { it->second(data); } - catch (const std::exception& ex) { + catch (const std::exception& /*ex*/) { error_callback(asio::error::make_error_code(asio::error::invalid_argument)); - std::cout << ex.what() << "\n"; } } @@ -645,8 +665,95 @@ namespace rest_rpc { }; } + bool is_ssl() const { +#ifdef CINATRA_ENABLE_SSL + return ssl_stream_ != nullptr; +#else + return false; +#endif + } + + + void handshake() { +#ifdef CINATRA_ENABLE_SSL + ssl_stream_->async_handshake(boost::asio::ssl::stream_base::client, + [this](const boost::system::error_code& ec) { + if (!ec) { + has_connected_ = true; + do_read(); + resend_subscribe(); + if (has_wait_) + conn_cond_.notify_one(); + } + else { + error_callback(ec); + close(); + } + }); +#endif + } + + void upgrade_to_ssl() { +#ifdef CINATRA_ENABLE_SSL + if (ssl_stream_) + return; + + boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); + ssl_context.set_default_verify_paths(); + boost::system::error_code ec; + ssl_context.set_options(boost::asio::ssl::context::default_workarounds, ec); + if (ssl_context_callback_) { + ssl_context_callback_(ssl_context); + } + ssl_stream_ = std::make_unique>(socket_, ssl_context); + //verify peer TODO +#else + assert(is_ssl());//please add definition CINATRA_ENABLE_SSL, not allowed coming in this branch +#endif + } + + template + void async_read_head(Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_read(*ssl_stream_, boost::asio::buffer(head_, HEAD_LEN), std::move(handler)); +#endif + } + else { + boost::asio::async_read(socket_, boost::asio::buffer(head_, HEAD_LEN), std::move(handler)); + } + } + + template + void async_read(size_t size_to_read, Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_read(*ssl_stream_, boost::asio::buffer(body_.data(), size_to_read), std::move(handler)); +#endif + } + else { + boost::asio::async_read(socket_, boost::asio::buffer(body_.data(), size_to_read), std::move(handler)); + } + } + + template + void async_write(const BufferType& buffers, Handler handler) { + if (is_ssl()) { +#ifdef CINATRA_ENABLE_SSL + boost::asio::async_write(*ssl_stream_, buffers, std::move(handler)); +#endif + } + else { + boost::asio::async_write(socket_, buffers, std::move(handler)); + } + } + boost::asio::io_service ios_; asio::ip::tcp::socket socket_; +#ifdef CINATRA_ENABLE_SSL + std::unique_ptr> ssl_stream_; + std::function ssl_context_callback_; +#endif boost::asio::io_service::work work_; std::shared_ptr thd_ = nullptr; diff --git a/include/rpc_server.h b/include/rpc_server.h index 1329f55..9a9a1a2 100644 --- a/include/rpc_server.h +++ b/include/rpc_server.h @@ -11,7 +11,7 @@ using boost::asio::ip::tcp; namespace rest_rpc { - namespace rpc_service { + namespace rpc_service { using rpc_conn = std::weak_ptr; class rpc_server : private asio::noncopyable { public: @@ -25,6 +25,15 @@ namespace rest_rpc { pub_sub_thread_ = std::make_shared([this] { clean_sub_pub(); }); } + rpc_server(short port, size_t size, ssl_configure ssl_conf, size_t timeout_seconds = 15, size_t check_seconds = 10) : + rpc_server(port, size, timeout_seconds, check_seconds) { +#ifdef CINATRA_ENABLE_SSL + ssl_conf_ = std::move(ssl_conf); +#else + assert(false);//please add definition CINATRA_ENABLE_SSL, not allowed coming in this branch +#endif + } + ~rpc_server() { { std::unique_lock lock(mtx_); @@ -99,6 +108,11 @@ namespace rest_rpc { //LOG(INFO) << "acceptor error: " << ec.message(); } else { +#ifdef CINATRA_ENABLE_SSL + if (!ssl_conf_.cert_file.empty()) { + conn_->init_ssl_context(ssl_conf_); + } +#endif conn_->start(); std::unique_lock lock(mtx_); conn_->set_conn_id(conn_id_); @@ -204,6 +218,8 @@ namespace rest_rpc { std::shared_ptr pub_sub_thread_; bool stop_check_pub_sub_ = false; + + ssl_configure ssl_conf_; }; } // namespace rpc_service } // namespace rest_rpc diff --git a/include/use_asio.hpp b/include/use_asio.hpp index d297d3a..c7b5028 100644 --- a/include/use_asio.hpp +++ b/include/use_asio.hpp @@ -44,3 +44,21 @@ using namespace nonstd; using string_view = boost::string_view; #endif #endif + +#if __cplusplus > 201402L +#if defined (__GNUC__) +#if __GNUC__ < 8 +#include +namespace fs = std::experimental::filesystem; +#else +#include +namespace fs = std::filesystem; +#endif +#else +#include +namespace fs = boost::filesystem; +#endif +#else +#include +namespace fs = boost::filesystem; +#endif