From 2bb0d2ab46bf8104ab6e0b96fdefbeb20aa4c9e4 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Thu, 12 Jan 2023 15:50:15 +0100 Subject: Move https.* to http.* --- Makefile | 4 - http.cpp | 463 +++++++++++++++++++++++++++++++++++++++++++++++ http.h | 22 +++ https.cpp | 463 ----------------------------------------------- https.h | 22 --- server.cpp | 1 - tests/Makefile | 4 - tests/test-https.cpp | 28 --- tests/test-webserver.cpp | 2 + 9 files changed, 487 insertions(+), 522 deletions(-) create mode 100644 http.cpp create mode 100644 http.h delete mode 100644 https.cpp delete mode 100644 https.h delete mode 100644 tests/test-https.cpp diff --git a/Makefile b/Makefile index 20b1072..fe2f7a0 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ PROGSRC=\ config.cpp \ error.cpp \ http.cpp \ - https.cpp \ plugin.cpp \ privileges.cpp \ response.cpp \ @@ -155,8 +154,6 @@ DISTFILES= \ error.h \ http.cpp \ http.h \ - https.cpp \ - https.h \ install-webserver.sh \ main.cpp \ plugin.cpp \ @@ -215,7 +212,6 @@ DISTFILES= \ tests/test-config.cpp \ tests/test-environment.cpp \ tests/test-http.cpp \ - tests/test-https.cpp \ tests/test-plugin.cpp \ tests/test-privileges.cpp \ tests/test-response.cpp \ diff --git a/http.cpp b/http.cpp new file mode 100644 index 0000000..868e739 --- /dev/null +++ b/http.cpp @@ -0,0 +1,463 @@ +#include "http.h" + +#include "config.h" +#include "error.h" +#include "server.h" +#include "response.h" +#include "websocket.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = beast::websocket; +using tcp = boost::asio::ip::tcp; // from +using namespace Reichwein; + +namespace { + +// Handles an HTTP server connection +template +class session +{ +private: + Derived& derived() + { + return static_cast(*this); + } + + boost::asio::io_context& ioc_; + beast::flat_buffer buffer_; + Server& server_; + std::optional> parser_; // need to reset parser every time, no other mechanism currently + request_type req_; + std::shared_ptr res_; + + void handle_request() + { + beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(300)); // timeout on write by server much longer than read timeout from client + auto sp = std::make_shared(response::generate_response(req_, server_)); + + res_ = sp; + + // Write the response + http::async_write( + derived().stream(), + *sp, + beast::bind_front_handler( + &session::on_write, + derived().shared_from_this(), + sp->need_eof())); + } + + void handle_websocket() + { + beast::get_lowest_layer(derived().stream()).expires_never(); + make_websocket_session(ioc_, std::move(derived().stream()), response::get_websocket_address(req_, server_), parser_->release()); + } + +public: + explicit + session( + boost::asio::io_context& ioc, + Server& server): + ioc_(ioc), + server_(server) + { + } + + // Start the asynchronous operation + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. + net::dispatch( + derived().stream().get_executor(), + beast::bind_front_handler( + &Derived::on_run, + derived().shared_from_this())); + } + + void + do_read() + { + // Make the request empty before reading, + // otherwise the operation behavior is undefined. + req_ = {}; + + // this is the way to reset the parser. it's necessary. + // https://github.com/boostorg/beast/issues/927 + parser_.emplace(); + parser_->body_limit(1000000000); // 1GB limit + + // Set the timeout. + beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(30)); + + // Read a request + http::async_read(derived().stream(), buffer_, *parser_, + beast::bind_front_handler( + &session::on_read, + derived().shared_from_this())); + } + + void + on_read( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + // This means they closed the connection + if (ec == http::error::end_of_stream) + return derived().do_close(); + + if (ec == http::error::partial_message) + return; // ignore + + if (ec) + return fail(ec, "http read"); + + req_ = parser_->get(); + + if (websocket::is_upgrade(req_)) + { + handle_websocket(); + return; + } + + // Send the response + handle_request(); + } + + void + on_write( + bool close, + beast::error_code ec, + std::size_t bytes_transferred + ) + { + boost::ignore_unused(bytes_transferred); + + if (ec) + return fail(ec, "http write"); + + if (close) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return derived().do_close(); + } + + // We're done with the response so delete it + res_ = nullptr; + + // Read another request + do_read(); + } + +}; + +class plain_session: + public session, + public std::enable_shared_from_this +{ + beast::tcp_stream stream_; + +public: + explicit plain_session( + boost::asio::io_context& ioc, + tcp::socket&& socket, + Server& server): + session(ioc, server), + stream_(std::move(socket)) + { + } + + void on_run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Skip ssl handshake for plain http. + net::dispatch(stream_.get_executor(), + beast::bind_front_handler( + &session::do_read, + shared_from_this())); + } + + void + do_close() + { + // Send a TCP shutdown + beast::error_code ec; + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + // At this point the connection is closed gracefully + } + + beast::tcp_stream& stream() + { + return stream_; + } + +}; // class + +class ssl_session: + public session, + public std::enable_shared_from_this +{ + beast::ssl_stream stream_; +public: + explicit ssl_session( + boost::asio::io_context& ioc, + tcp::socket&& socket, + ssl::context& ctx, + Server& server): + session(ioc, server), + stream_(std::move(socket), ctx) + { + } + + void on_run() + { + // Set the timeout + beast::get_lowest_layer(stream_).expires_after( + std::chrono::seconds(30)); + + // Perform the SSL handshake + stream_.async_handshake( + ssl::stream_base::server, + beast::bind_front_handler( + &ssl_session::on_handshake, + shared_from_this())); + } + + void + on_handshake(beast::error_code ec) + { + if (ec) + return fail(ec, "https handshake"); + + do_read(); + } + + void + do_close() + { + // Set the timeout. + beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + // Perform the SSL shutdown + stream_.async_shutdown( + beast::bind_front_handler( + &ssl_session::on_shutdown, + shared_from_this())); + } + + void + on_shutdown(beast::error_code ec) + { + if (ec) + return fail(ec, "https shutdown"); + + // At this point the connection is closed gracefully + } + + beast::ssl_stream& stream() + { + return stream_; + } + +}; // class + +// Accepts incoming connections and launches the sessions +template +class listener +{ +private: + Derived& derived() + { + return static_cast(*this); + } + +protected: + net::io_context& ioc_; + tcp::acceptor acceptor_; + ::Server& server_; + +public: + explicit listener( + net::io_context& ioc, + tcp::endpoint endpoint, + Server& server): + ioc_(ioc), + acceptor_(ioc), + server_(server) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if (ec) + { + fail(ec, "http listener open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if (ec) + { + fail(ec, "http listener set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if (ec) + { + fail(ec, "http listener bind"); + return; + } + + // Start listening for connections + acceptor_.listen(net::socket_base::max_listen_connections, ec); + if (ec) + { + fail(ec, "http listener listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + do_accept(); + } + +protected: + void + do_accept() + { + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &Derived::on_accept, + derived().shared_from_this())); + } +}; // class + +class plain_listener: + public listener, + public std::enable_shared_from_this +{ +public: + explicit plain_listener( + net::io_context& ioc, + tcp::endpoint endpoint, + Server& server): + listener(ioc, endpoint, server) + { + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if (ec) { + fail(ec, "plain listener accept"); + } else { + // Create the session and run it + std::make_shared( + ioc_, + std::move(socket), + server_)->run(); + } + + // Accept another connection + do_accept(); + } +}; // class + +class ssl_listener: + public listener, + public std::enable_shared_from_this +{ + ssl::context& ctx_; + +public: + explicit ssl_listener( + net::io_context& ioc, + ssl::context& ctx, + tcp::endpoint endpoint, + Server& server): + listener(ioc, endpoint, server), + ctx_(ctx) + { + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if (ec) { + fail(ec, "ssl listener accept"); + } else { + // Create the session and run it + std::make_shared( + ioc_, + std::move(socket), + ctx_, + server_)->run(); + } + + // Accept another connection + do_accept(); + } +}; // class + +} // namespace + +void make_listener(net::io_context& ioc, net::ip::address address, unsigned short port, Server& server) +{ + std::make_shared( + ioc, + tcp::endpoint{address, port}, + server)->run(); +} + +void make_listener(net::io_context& ioc, ssl::context& ctx, net::ip::address address, unsigned short port, Server& server) +{ + std::make_shared( + ioc, + ctx, + tcp::endpoint{address, port}, + server)->run(); +} + diff --git a/http.h b/http.h new file mode 100644 index 0000000..226490e --- /dev/null +++ b/http.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "config.h" +#include "server.h" + +// plain / http +void make_listener(boost::asio::io_context& ioc, boost::asio::ip::address address, unsigned short port, Server& server); + +// ssl / https +void make_listener(boost::asio::io_context& ioc, boost::asio::ssl::context& ctx, boost::asio::ip::address address, unsigned short port, Server& server); diff --git a/https.cpp b/https.cpp deleted file mode 100644 index 20fab61..0000000 --- a/https.cpp +++ /dev/null @@ -1,463 +0,0 @@ -#include "https.h" - -#include "config.h" -#include "error.h" -#include "server.h" -#include "response.h" -#include "websocket.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; -namespace beast = boost::beast; // from -namespace http = beast::http; // from -namespace net = boost::asio; // from -namespace ssl = boost::asio::ssl; // from -namespace websocket = beast::websocket; -using tcp = boost::asio::ip::tcp; // from -using namespace Reichwein; - -namespace { - -// Handles an HTTP server connection -template -class session -{ -private: - Derived& derived() - { - return static_cast(*this); - } - - boost::asio::io_context& ioc_; - beast::flat_buffer buffer_; - Server& server_; - std::optional> parser_; // need to reset parser every time, no other mechanism currently - request_type req_; - std::shared_ptr res_; - - void handle_request() - { - beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(300)); // timeout on write by server much longer than read timeout from client - auto sp = std::make_shared(response::generate_response(req_, server_)); - - res_ = sp; - - // Write the response - http::async_write( - derived().stream(), - *sp, - beast::bind_front_handler( - &session::on_write, - derived().shared_from_this(), - sp->need_eof())); - } - - void handle_websocket() - { - beast::get_lowest_layer(derived().stream()).expires_never(); - make_websocket_session(ioc_, std::move(derived().stream()), response::get_websocket_address(req_, server_), parser_->release()); - } - -public: - explicit - session( - boost::asio::io_context& ioc, - Server& server): - ioc_(ioc), - server_(server) - { - } - - // Start the asynchronous operation - void - run() - { - // We need to be executing within a strand to perform async operations - // on the I/O objects in this session. - net::dispatch( - derived().stream().get_executor(), - beast::bind_front_handler( - &Derived::on_run, - derived().shared_from_this())); - } - - void - do_read() - { - // Make the request empty before reading, - // otherwise the operation behavior is undefined. - req_ = {}; - - // this is the way to reset the parser. it's necessary. - // https://github.com/boostorg/beast/issues/927 - parser_.emplace(); - parser_->body_limit(1000000000); // 1GB limit - - // Set the timeout. - beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(30)); - - // Read a request - http::async_read(derived().stream(), buffer_, *parser_, - beast::bind_front_handler( - &session::on_read, - derived().shared_from_this())); - } - - void - on_read( - beast::error_code ec, - std::size_t bytes_transferred) - { - boost::ignore_unused(bytes_transferred); - - // This means they closed the connection - if (ec == http::error::end_of_stream) - return derived().do_close(); - - if (ec == http::error::partial_message) - return; // ignore - - if (ec) - return fail(ec, "http read"); - - req_ = parser_->get(); - - if (websocket::is_upgrade(req_)) - { - handle_websocket(); - return; - } - - // Send the response - handle_request(); - } - - void - on_write( - bool close, - beast::error_code ec, - std::size_t bytes_transferred - ) - { - boost::ignore_unused(bytes_transferred); - - if (ec) - return fail(ec, "http write"); - - if (close) - { - // This means we should close the connection, usually because - // the response indicated the "Connection: close" semantic. - return derived().do_close(); - } - - // We're done with the response so delete it - res_ = nullptr; - - // Read another request - do_read(); - } - -}; - -class plain_session: - public session, - public std::enable_shared_from_this -{ - beast::tcp_stream stream_; - -public: - explicit plain_session( - boost::asio::io_context& ioc, - tcp::socket&& socket, - Server& server): - session(ioc, server), - stream_(std::move(socket)) - { - } - - void on_run() - { - // We need to be executing within a strand to perform async operations - // on the I/O objects in this session. Skip ssl handshake for plain http. - net::dispatch(stream_.get_executor(), - beast::bind_front_handler( - &session::do_read, - shared_from_this())); - } - - void - do_close() - { - // Send a TCP shutdown - beast::error_code ec; - stream_.socket().shutdown(tcp::socket::shutdown_send, ec); - // At this point the connection is closed gracefully - } - - beast::tcp_stream& stream() - { - return stream_; - } - -}; // class - -class ssl_session: - public session, - public std::enable_shared_from_this -{ - beast::ssl_stream stream_; -public: - explicit ssl_session( - boost::asio::io_context& ioc, - tcp::socket&& socket, - ssl::context& ctx, - Server& server): - session(ioc, server), - stream_(std::move(socket), ctx) - { - } - - void on_run() - { - // Set the timeout - beast::get_lowest_layer(stream_).expires_after( - std::chrono::seconds(30)); - - // Perform the SSL handshake - stream_.async_handshake( - ssl::stream_base::server, - beast::bind_front_handler( - &ssl_session::on_handshake, - shared_from_this())); - } - - void - on_handshake(beast::error_code ec) - { - if (ec) - return fail(ec, "https handshake"); - - do_read(); - } - - void - do_close() - { - // Set the timeout. - beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); - - // Perform the SSL shutdown - stream_.async_shutdown( - beast::bind_front_handler( - &ssl_session::on_shutdown, - shared_from_this())); - } - - void - on_shutdown(beast::error_code ec) - { - if (ec) - return fail(ec, "https shutdown"); - - // At this point the connection is closed gracefully - } - - beast::ssl_stream& stream() - { - return stream_; - } - -}; // class - -// Accepts incoming connections and launches the sessions -template -class listener -{ -private: - Derived& derived() - { - return static_cast(*this); - } - -protected: - net::io_context& ioc_; - tcp::acceptor acceptor_; - ::Server& server_; - -public: - explicit listener( - net::io_context& ioc, - tcp::endpoint endpoint, - Server& server): - ioc_(ioc), - acceptor_(ioc), - server_(server) - { - beast::error_code ec; - - // Open the acceptor - acceptor_.open(endpoint.protocol(), ec); - if (ec) - { - fail(ec, "http listener open"); - return; - } - - // Allow address reuse - acceptor_.set_option(net::socket_base::reuse_address(true), ec); - if (ec) - { - fail(ec, "http listener set_option"); - return; - } - - // Bind to the server address - acceptor_.bind(endpoint, ec); - if (ec) - { - fail(ec, "http listener bind"); - return; - } - - // Start listening for connections - acceptor_.listen(net::socket_base::max_listen_connections, ec); - if (ec) - { - fail(ec, "http listener listen"); - return; - } - } - - // Start accepting incoming connections - void - run() - { - do_accept(); - } - -protected: - void - do_accept() - { - // The new connection gets its own strand - acceptor_.async_accept( - net::make_strand(ioc_), - beast::bind_front_handler( - &Derived::on_accept, - derived().shared_from_this())); - } -}; // class - -class plain_listener: - public listener, - public std::enable_shared_from_this -{ -public: - explicit plain_listener( - net::io_context& ioc, - tcp::endpoint endpoint, - Server& server): - listener(ioc, endpoint, server) - { - } - - void - on_accept(beast::error_code ec, tcp::socket socket) - { - if (ec) { - fail(ec, "plain listener accept"); - } else { - // Create the session and run it - std::make_shared( - ioc_, - std::move(socket), - server_)->run(); - } - - // Accept another connection - do_accept(); - } -}; // class - -class ssl_listener: - public listener, - public std::enable_shared_from_this -{ - ssl::context& ctx_; - -public: - explicit ssl_listener( - net::io_context& ioc, - ssl::context& ctx, - tcp::endpoint endpoint, - Server& server): - listener(ioc, endpoint, server), - ctx_(ctx) - { - } - - void - on_accept(beast::error_code ec, tcp::socket socket) - { - if (ec) { - fail(ec, "ssl listener accept"); - } else { - // Create the session and run it - std::make_shared( - ioc_, - std::move(socket), - ctx_, - server_)->run(); - } - - // Accept another connection - do_accept(); - } -}; // class - -} // namespace - -void make_listener(net::io_context& ioc, net::ip::address address, unsigned short port, Server& server) -{ - std::make_shared( - ioc, - tcp::endpoint{address, port}, - server)->run(); -} - -void make_listener(net::io_context& ioc, ssl::context& ctx, net::ip::address address, unsigned short port, Server& server) -{ - std::make_shared( - ioc, - ctx, - tcp::endpoint{address, port}, - server)->run(); -} - diff --git a/https.h b/https.h deleted file mode 100644 index 226490e..0000000 --- a/https.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include "config.h" -#include "server.h" - -// plain / http -void make_listener(boost::asio::io_context& ioc, boost::asio::ip::address address, unsigned short port, Server& server); - -// ssl / https -void make_listener(boost::asio::io_context& ioc, boost::asio::ssl::context& ctx, boost::asio::ip::address address, unsigned short port, Server& server); diff --git a/server.cpp b/server.cpp index 3f6183b..26b4292 100644 --- a/server.cpp +++ b/server.cpp @@ -22,7 +22,6 @@ #include "server.h" #include "http.h" -#include "https.h" #include "privileges.h" #include "statistics.h" diff --git a/tests/Makefile b/tests/Makefile index 14af291..c04cbcd 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -38,7 +38,6 @@ UNITS=\ config.cpp \ error.cpp \ http.cpp \ - https.cpp \ plugin.cpp \ privileges.cpp \ response.cpp \ @@ -52,7 +51,6 @@ TESTSRC=\ test-config.cpp \ test-environment.cpp \ test-http.cpp \ - test-https.cpp \ test-plugin.cpp \ test-privileges.cpp \ test-response.cpp \ @@ -91,8 +89,6 @@ error.o: ../error.cpp $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@ http.o: ../http.cpp $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@ -https.o: ../https.cpp - $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@ plugin.o: ../plugin.cpp $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@ privileges.o: ../privileges.cpp diff --git a/tests/test-https.cpp b/tests/test-https.cpp deleted file mode 100644 index cb917e9..0000000 --- a/tests/test-https.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include "https.h" - -using namespace std::string_literals; - -class HTTPSFixture -{ -public: - HTTPSFixture(){} - ~HTTPSFixture(){} - void setup(){} - void teardown(){} -}; - -BOOST_FIXTURE_TEST_CASE(https, HTTPSFixture) -{ -} - diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp index 727356d..85859df 100644 --- a/tests/test-webserver.cpp +++ b/tests/test-webserver.cpp @@ -1113,6 +1113,7 @@ echo -ne "HTTP_HOST: $HTTP_HOST\r\n" BOOST_FIXTURE_TEST_CASE(empty_config, Fixture) { WebserverProcess serverProcess{""}; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); BOOST_REQUIRE_EQUAL(serverProcess.is_running(), false); } @@ -1123,5 +1124,6 @@ BOOST_FIXTURE_TEST_CASE(incomplete_config, Fixture) www-data )CONFIG"}; WebserverProcess serverProcess{webserver_config}; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); BOOST_REQUIRE_EQUAL(serverProcess.is_running(), false); } -- cgit v1.2.3