Skip to content

Commit

Permalink
Merge pull request #17 from 5cript/async-client
Browse files Browse the repository at this point in the history
Async client Additions
  • Loading branch information
5cript authored Oct 23, 2023
2 parents e1d3c85 + 7173b16 commit 2f264f1
Show file tree
Hide file tree
Showing 16 changed files with 464 additions and 366 deletions.
24 changes: 14 additions & 10 deletions docs_source/sphinx/http/server.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HTTP Server
# HTTP Server

## Tutorial

Expand Down Expand Up @@ -28,13 +28,17 @@ int main()
Roar::Server server{{.executor = pool.executor()}};
// SSL Server (you can provide your own SSL Context for more options.)
//
// auto sslContext = SslServerContext{
// .certificate = "certificate string here", // or filesystem::path to cert
// .privateKey = "private key here", // or filesystem::path to key
// .password = "passwordHereIfApplicable",
// };
// initializeServerSslContext(sslContext);
//
// Roar::Server server{{
// .executor = pool.executor()
// .sslContext = makeSslContext(SslContextCreationParameters{
// .certificate = std::string_view{"certificate string here"},
// .privateKey = std::string_view{"private key here"},
// .password = keyPassphrase,
// })
// .sslContext = std::move(sslContext),
// }};
// stop the thread_pool on scope exit to guarantee that all asynchronous tasks are finished before the server is
Expand All @@ -51,7 +55,7 @@ int main()
std::cin.get();
}
```
In this example we are creating an asio thread pool for the asynchronous actions of our server,
In this example we are creating an asio thread pool for the asynchronous actions of our server,
the server itself and then start the server by binding and accepting on port 8081.
```{warning}
Important: The Roar::ScopeExit construct ensures that the threads are shutdown before the server or request listeners are destroyed.
Expand Down Expand Up @@ -141,7 +145,7 @@ int main()
pool.stop();
pool.join();
}};
server.installRequestListener<RequestListener>();
// Start server and bind on port "port".
Expand Down Expand Up @@ -215,7 +219,7 @@ class MyRequestListener
BOOST_DESCRIBE_CLASS(MyRequestListener, (), (), (), (roar_index, roar_images))
};
```
Available options for routes are documented here:
Available options for routes are documented here:
<a href="/roar/doxygen/structRoar_1_1RouteInfo.html">RouteInfo</a>.

```{admonition} Head Requests
Expand Down Expand Up @@ -366,7 +370,7 @@ class FileServer
// Allow DELETE requests to recursively delete directories? Defaults to false.
.allowDeleteOfNonEmptyDirectories = false,
// If this option is set, requests to directories do not try to serve an index.html,
// If this option is set, requests to directories do not try to serve an index.html,
// but give a table with all existing files instead. Defaults to true.
.allowListing = true,
Expand Down
91 changes: 68 additions & 23 deletions include/roar/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <functional>
#include <type_traits>
#include <future>
#include <any>

namespace Roar
{
Expand All @@ -34,21 +35,25 @@ namespace Roar
public:
constexpr static std::chrono::seconds defaultTimeout{10};

struct ConstructionArguments
struct SslOptions
{
/// Required io executor for boost::asio.
boost::asio::any_io_executor executor;

/// Supply for SSL support.
std::optional<boost::asio::ssl::context> sslContext;
boost::asio::ssl::context sslContext;

/// SSL verify mode:
boost::asio::ssl::verify_mode sslVerifyMode = boost::asio::ssl::verify_none;
boost::asio::ssl::verify_mode sslVerifyMode = boost::asio::ssl::verify_peer;

/**
* @brief sslVerifyCallback, you can use boost::asio::ssl::rfc2818_verification(host) most of the time.
*/
std::function<bool(bool, boost::asio::ssl::verify_context&)> sslVerifyCallback = {};
std::function<bool(bool, boost::asio::ssl::verify_context&)> sslVerifyCallback;
};

struct ConstructionArguments
{
/// Required io executor for boost::asio.
boost::asio::any_io_executor executor;
std::optional<SslOptions> sslOptions = std::nullopt;
};

Client(ConstructionArguments&& args);
Expand All @@ -75,6 +80,54 @@ namespace Roar
Detail::PromiseTypeBindFail<Error>>
read(std::chrono::seconds timeout = defaultTimeout);

/**
* @brief Attach some state to the client lifetime.
*
* @param tag A tag name, to retrieve it back with.
* @param state The state.
*/
template <typename T>
void attachState(std::string const& tag, T&& state)
{
attachedState_[tag] = std::forward<T>(state);
}

/**
* @brief Create state in place.
*
* @tparam ConstructionArgs
* @param tag
* @param args
*/
template <typename T, typename... ConstructionArgs>
void emplaceState(std::string const& tag, ConstructionArgs&&... args)
{
attachedState_[tag] = std::make_any<T>(std::forward<ConstructionArgs>(args)...);
}

/**
* @brief Retrieve attached state by tag.
*
* @tparam T Type of the attached state.
* @param tag The tag of the state.
* @return T& Returns a reference to the held state.
*/
template <typename T>
T& state(std::string const& tag)
{
return std::any_cast<T&>(attachedState_.at(tag));
}

/**
* @brief Remove attached state.
*
* @param tag The tag of the state to remove.
*/
void removeState(std::string const& tag)
{
attachedState_.erase(tag);
}

/**
* @brief Connects the client to a server and performs a request
*
Expand All @@ -86,22 +139,6 @@ namespace Roar
request(Request<BodyT>&& request, std::chrono::seconds timeout = defaultTimeout)
{
return promise::newPromise([&, this](promise::Defer d) mutable {
if (std::holds_alternative<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_))
{
auto& sslSocket = std::get<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_);
if (!SSL_ctrl(
sslSocket.native_handle(),
SSL_CTRL_SET_TLSEXT_HOSTNAME,
TLSEXT_NAMETYPE_host_name,
// yikes openssl you make me do this
const_cast<void*>(reinterpret_cast<void const*>(request.host().c_str()))))
{
boost::beast::error_code ec{
static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()};
return d.reject(Error{.error = ec, .additionalInfo = "SSL_set_tlsext_host_name failed."});
}
}

const auto host = request.host();
const auto port = request.port();
doResolve(
Expand Down Expand Up @@ -324,6 +361,10 @@ namespace Roar
{
if (std::holds_alternative<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_))
{
auto maybeError = setupSsl(request.host());
if (maybeError)
return d.reject(*maybeError);

auto& sslSocket = std::get<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_);
withLowerLayerDo([&](auto& socket) {
socket.expires_after(timeout);
Expand Down Expand Up @@ -371,8 +412,12 @@ namespace Roar
});
}

std::optional<Error> setupSsl(std::string const& host);

private:
std::optional<SslOptions> sslOptions_;
std::variant<boost::beast::ssl_stream<boost::beast::tcp_stream>, boost::beast::tcp_stream> socket_;
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint_;
std::unordered_map<std::string, std::any> attachedState_;
};
}
3 changes: 2 additions & 1 deletion include/roar/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <roar/routing/request_listener.hpp>
#include <roar/standard_response_provider.hpp>
#include <roar/standard_text_response_provider.hpp>
#include <roar/ssl/make_ssl_context.hpp>
#include <roar/filesystem/jail.hpp>

#include <boost/describe/modifiers.hpp>
Expand Down Expand Up @@ -37,7 +38,7 @@ namespace Roar
boost::asio::any_io_executor executor;

/// Supply for SSL support.
std::optional<boost::asio::ssl::context> sslContext;
std::optional<SslServerContext> sslContext;

/// Called when an error occurs in an asynchronous routine.
std::function<void(Error&&)> onError = [](auto&&) {};
Expand Down
3 changes: 2 additions & 1 deletion include/roar/session/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <roar/beast/forward.hpp>
#include <roar/detail/pimpl_special_functions.hpp>
#include <roar/ssl/make_ssl_context.hpp>
#include <roar/standard_response_provider.hpp>
#include <roar/error.hpp>

Expand All @@ -23,7 +24,7 @@ namespace Roar
constexpr static std::chrono::seconds sslDetectionTimeout{10};

public:
Factory(std::optional<boost::asio::ssl::context>& sslContext, std::function<void(Error&&)> onError);
Factory(std::optional<SslServerContext>& sslContext, std::function<void(Error&&)> onError);
ROAR_PIMPL_SPECIAL_FUNCTIONS(Factory);

/**
Expand Down
5 changes: 3 additions & 2 deletions include/roar/session/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <boost/beast/http/read.hpp>
#include <boost/beast/ssl/ssl_stream.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <roar/ssl/make_ssl_context.hpp>
#include <promise-cpp/promise.hpp>

#include <memory>
Expand Down Expand Up @@ -49,7 +50,7 @@ namespace Roar
Session(
boost::asio::basic_stream_socket<boost::asio::ip::tcp>&& socket,
boost::beast::basic_flat_buffer<std::allocator<char>>&& buffer,
std::optional<boost::asio::ssl::context>& sslContext,
std::optional<SslServerContext>& sslContext,
bool isSecure,
std::function<void(Error&&)> onError,
std::weak_ptr<Router> router,
Expand Down Expand Up @@ -743,7 +744,7 @@ namespace Roar
std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>>& parser();
boost::beast::flat_buffer& buffer();
StandardResponseProvider const& standardResponseProvider();
void startup();
void startup(bool immediate = true);

private:
struct Implementation;
Expand Down
18 changes: 10 additions & 8 deletions include/roar/ssl/make_ssl_context.hpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
#pragma once

#include <boost/asio/ssl/context.hpp>

#include <string_view>
#include <string>
#include <filesystem>
#include <variant>

namespace Roar
{
struct SslContextCreationParameters
struct SslServerContext
{
boost::asio::ssl::context::method method = boost::asio::ssl::context::tls_server;
std::variant<std::string_view, std::filesystem::path> certificate;
std::variant<std::string_view, std::filesystem::path> privateKey;
std::string_view diffieHellmanParameters = "";
std::string_view password = "";
boost::asio::ssl::context ctx = boost::asio::ssl::context{boost::asio::ssl::context::tls_server};
std::variant<std::string, std::filesystem::path> certificate;
std::variant<std::string, std::filesystem::path> privateKey;
std::string diffieHellmanParameters = "";
std::string password = "";
};

/**
Expand All @@ -22,5 +24,5 @@ namespace Roar
* @param settings
* @return boost::asio::ssl::context
*/
boost::asio::ssl::context makeSslContext(SslContextCreationParameters settings);
void initializeServerSslContext(SslServerContext& ctx);
}
Loading

0 comments on commit 2f264f1

Please sign in to comment.