v1
This commit is contained in:
commit
af684cc837
12 changed files with 667 additions and 0 deletions
54
CMakeLists.txt
Normal file
54
CMakeLists.txt
Normal file
|
@ -0,0 +1,54 @@
|
|||
cmake_minimum_required(VERSION 3.27)
|
||||
project(cserver VERSION 1)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CXX_EXTENSIONS NO)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(Boost 1.84.0 REQUIRED COMPONENTS url)
|
||||
find_package(utempl REQUIRED)
|
||||
find_package(llhttp REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
add_library(cserver INTERFACE)
|
||||
target_include_directories(
|
||||
cserver
|
||||
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||
target_link_libraries(cserver INTERFACE utempl::utempl ${Boost_LIBRARIES} llhttp uring ${OPENSSL_LIBRARIES} )
|
||||
|
||||
target_compile_features(cserver INTERFACE cxx_std_20)
|
||||
|
||||
install(TARGETS cserver
|
||||
EXPORT cserverTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file("cserverConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/cserverConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/cserverConfig.cmake"
|
||||
INSTALL_DESTINATION
|
||||
${CMAKE_INSTALL_DATAROOTDIR}/cserver/cmake)
|
||||
|
||||
install(EXPORT cserverTargets
|
||||
FILE cserverTargets.cmake
|
||||
NAMESPACE cserver::
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cserver/cmake)
|
||||
|
||||
install(FILES "${PROJECT_BINARY_DIR}/cserverConfig.cmake"
|
||||
"${PROJECT_BINARY_DIR}/cserverConfigVersion.cmake"
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cserver/cmake)
|
||||
|
||||
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/cserver DESTINATION include)
|
||||
|
4
cmake/cserverConfig.cmake.in
Normal file
4
cmake/cserverConfig.cmake.in
Normal file
|
@ -0,0 +1,4 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
86
include/cserver/clients/http/component.hpp
Normal file
86
include/cserver/clients/http/component.hpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
#include <cserver/clients/http/request.hpp>
|
||||
#include <cserver/server/http/http_response.hpp>
|
||||
#include <cserver/engine/components.hpp>
|
||||
#include <cserver/engine/coroutine.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <utempl/constexpr_string.hpp>
|
||||
|
||||
namespace cserver::server::http {
|
||||
|
||||
inline constexpr auto ParseHttpHeader(std::string header) -> std::pair<std::string, std::string> {
|
||||
size_t pos = header.find(":");
|
||||
std::string key = header.substr(0, pos);
|
||||
std::string value = header.substr(pos + 2);
|
||||
return std::make_pair(key, value);
|
||||
|
||||
};
|
||||
} // namespace cserver::server::http
|
||||
namespace cserver::components {
|
||||
template <typename TaskProcessor = int>
|
||||
struct HttpClient {
|
||||
TaskProcessor& taskProcessor;
|
||||
boost::asio::ssl::context ctx;
|
||||
boost::asio::ip::tcp::resolver resolver;
|
||||
static constexpr utempl::ConstexprString kName = "httpClient";
|
||||
inline constexpr auto CreateRequest() {
|
||||
return clients::http::Request{*this};
|
||||
};
|
||||
inline constexpr HttpClient(auto, auto& context) :
|
||||
taskProcessor(context.template FindComponent<"basicTaskProcessor">()),
|
||||
ctx(boost::asio::ssl::context::method::sslv23_client),
|
||||
resolver(this->taskProcessor.ioContext) {}
|
||||
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
static consteval auto Adder(const T& context) {
|
||||
using Type = std::remove_cvref_t<decltype(context.template FindComponent<"basicTaskProcessor">())>;
|
||||
return context.TransformComponents(
|
||||
[&](const ComponentConfig<name, HttpClient<>>&) -> ComponentConfig<name, HttpClient<Type>> {
|
||||
return {};
|
||||
});
|
||||
};
|
||||
template <typename T>
|
||||
inline auto PerformRequest(T&& request) -> cserver::Task<server::http::HTTPResponse> {
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(this->taskProcessor.ioContext, this->ctx);
|
||||
boost::asio::ip::tcp::resolver::iterator endpoint =
|
||||
co_await this->resolver.async_resolve({request.request.url.host(), request.request.url.has_port() ? request.request.url.port() : request.request.url.scheme()}, boost::asio::use_awaitable);
|
||||
co_await boost::asio::async_connect(socket.lowest_layer(), endpoint, boost::asio::use_awaitable);
|
||||
co_await socket.async_handshake(boost::asio::ssl::stream_base::client, boost::asio::use_awaitable);
|
||||
std::string req(request.request.ToString());
|
||||
co_await boost::asio::async_write(socket, boost::asio::buffer(req.data(), req.size()), boost::asio::use_awaitable);
|
||||
std::string serverResponse;
|
||||
co_await boost::asio::async_read_until(socket, boost::asio::dynamic_buffer(serverResponse), "\r\n\r\n", boost::asio::use_awaitable);
|
||||
server::http::HTTPResponse response;
|
||||
std::istringstream responseStream(std::move(serverResponse));
|
||||
std::string httpVersion;
|
||||
responseStream >> httpVersion >> response.statusCode;
|
||||
std::getline(responseStream, response.statusMessage);
|
||||
std::string header;
|
||||
while(std::getline(responseStream, header) && header.find(":") != std::string::npos) {
|
||||
response.headers.insert(server::http::ParseHttpHeader(std::move(header)));
|
||||
};
|
||||
if(response.statusCode != 200) {
|
||||
co_return response;
|
||||
};
|
||||
if(response.headers.contains("Content-Length")) {
|
||||
auto size = std::stoi(response.headers["Content-Length"]);
|
||||
response.body.reserve(size);
|
||||
co_await boost::asio::async_read(socket, boost::asio::dynamic_buffer(response.body), boost::asio::transfer_at_least(size), boost::asio::use_awaitable);
|
||||
co_return response;
|
||||
};
|
||||
for(;;) {
|
||||
auto [ec, n] = co_await boost::asio::async_read(socket, boost::asio::dynamic_buffer(response.body), boost::asio::transfer_at_least(1), boost::asio::as_tuple(boost::asio::use_awaitable));
|
||||
if(ec && ec == boost::asio::error::eof) {
|
||||
break;
|
||||
};
|
||||
if(ec) {
|
||||
throw ec;
|
||||
};
|
||||
};
|
||||
co_return response;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace cserver::clients::http
|
92
include/cserver/clients/http/request.hpp
Normal file
92
include/cserver/clients/http/request.hpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
#include <cserver/server/http/http_request.hpp>
|
||||
#include <cserver/server/http/http_response.hpp>
|
||||
#include <cserver/engine/coroutine.hpp>
|
||||
|
||||
namespace cserver::clients::http {
|
||||
template <typename HttpClient>
|
||||
struct Request {
|
||||
HttpClient& client;
|
||||
server::http::HTTPRequest request;
|
||||
Request(HttpClient& client) :
|
||||
client(client) {
|
||||
this->AddHeader("User-Agent", "cserver/1");
|
||||
};
|
||||
inline constexpr auto Post() && -> Request<HttpClient>&& {
|
||||
this->request.method = "POST";
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto Post() & -> Request<HttpClient>& {
|
||||
this->request.method = "POST";
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto Get() && -> Request<HttpClient>&& {
|
||||
this->request.method = "GET";
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto Get() & -> Request<HttpClient>& {
|
||||
this->request.method = "GET";
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto Put() && -> Request<HttpClient>&& {
|
||||
this->request.method = "PUT";
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto Put() & -> Request<HttpClient>& {
|
||||
this->request.method = "PUT";
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto SetCustomMethod(std::string method) & -> Request<HttpClient>& {
|
||||
this->request.method = std::move(method);
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto SetCustomMethod(std::string method) && -> Request<HttpClient>&& {
|
||||
this->request.method = std::move(method);
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto AddHeader(std::string first, std::string second) && -> Request<HttpClient>&& {
|
||||
this->request.headers.emplace(std::move(first), std::move(second));
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto AddHeader(std::string first, std::string second) & -> Request<HttpClient>& {
|
||||
this->request.headers.emplace(std::move(first), std::move(second));
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto AddHeaderIfNotExists(std::string check, std::string first, std::string second) & -> Request<HttpClient>& {
|
||||
if(!this->request.headers.contains(std::move(check))) {
|
||||
return this->AddHeader(std::move(first), std::move(second));
|
||||
};
|
||||
return *this;
|
||||
};
|
||||
inline constexpr auto AddHeaderIfNotExists(std::string check, std::string first, std::string second) && -> Request<HttpClient>&& {
|
||||
if(!this->request.headers.contains(std::move(check))) {
|
||||
return std::move(*this).AddHeader(std::move(first), std::move(second));
|
||||
};
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto Url(std::string url) & -> Request<HttpClient>& {
|
||||
this->request.url = boost::urls::url{std::move(url)};
|
||||
auto authority = this->request.url.authority();
|
||||
return this->AddHeader("Host", std::string{authority.data(), authority.size()});
|
||||
};
|
||||
inline constexpr auto Url(std::string url) && -> Request<HttpClient>&& {
|
||||
this->request.url = boost::urls::url{std::move(url)};
|
||||
auto authority = this->request.url.authority();
|
||||
return std::move(*this).AddHeader("Host", std::string{authority.data(), authority.size()});
|
||||
};
|
||||
inline constexpr auto Data(std::string data) && -> Request<HttpClient>&& {
|
||||
this->request.body = std::move(data);
|
||||
return std::move(*this);
|
||||
};
|
||||
inline constexpr auto ToString() const -> std::string {
|
||||
return this->request.ToString();
|
||||
};
|
||||
inline auto Perform() && -> cserver::Task<server::http::HTTPResponse> {
|
||||
co_return co_await client.PerformRequest(std::move(*this).AddHeaderIfNotExists("Transfer-Encoding", "Content-Length", std::to_string(this->request.body.size())));
|
||||
};
|
||||
inline auto Perform() & -> cserver::Task<server::http::HTTPResponse> {
|
||||
co_return co_await client.PerformRequest(this->AddHeaderIfNotExists("Transfer-Encoding", "Content-Length", std::to_string(this->request.body.size())));
|
||||
};
|
||||
};
|
||||
|
||||
};
|
37
include/cserver/engine/basic/task_processor.hpp
Normal file
37
include/cserver/engine/basic/task_processor.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
#include <cserver/engine/components.hpp>
|
||||
#include <cserver/engine/coroutine.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace cserver::engine::basic {
|
||||
|
||||
template <std::size_t Size = 0>
|
||||
struct TaskProcessor {
|
||||
boost::asio::io_context ioContext;
|
||||
std::array<std::jthread, Size> pool;
|
||||
static constexpr utempl::ConstexprString kName = "basicTaskProcessor";
|
||||
inline constexpr TaskProcessor(auto, auto&) :
|
||||
ioContext{},
|
||||
pool{} {
|
||||
};
|
||||
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
static consteval auto Adder(const T& context) {
|
||||
constexpr std::size_t Count = T::kConfig.template Get<name>().template Get<"threadPoolSize">();
|
||||
return context.TransformComponents(
|
||||
[&](const ComponentConfig<name, TaskProcessor<>>&) -> ComponentConfig<name, TaskProcessor<Count>> {
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
inline auto Run() {
|
||||
for(auto& thread : this->pool) {
|
||||
thread = std::jthread([&]{
|
||||
boost::asio::executor_work_guard<decltype(this->ioContext.get_executor())> guard{this->ioContext.get_executor()};
|
||||
this->ioContext.run();
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
131
include/cserver/engine/components.hpp
Normal file
131
include/cserver/engine/components.hpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
#include <utempl/utils.hpp>
|
||||
#include <thread>
|
||||
|
||||
namespace cserver {
|
||||
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
struct NamedValue {
|
||||
T value;
|
||||
};
|
||||
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
struct ComponentConfig {};
|
||||
|
||||
template <typename T>
|
||||
inline constexpr auto TransformIfOk(T&& value, auto&& f) {
|
||||
if constexpr(requires{f(std::forward<T>(value));}) {
|
||||
return f(std::forward<T>(value));
|
||||
} else {
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
template <typename... Ts>
|
||||
struct ConstexprConfig {
|
||||
utempl::Tuple<Ts...> data;
|
||||
template <utempl::ConstexprString Key>
|
||||
inline constexpr auto Get() const -> auto {
|
||||
return [&]<typename... TTs, utempl::ConstexprString... names>(const ConstexprConfig<NamedValue<names, TTs>...>&){
|
||||
constexpr auto list = utempl::TypeList<utempl::Wrapper<names>...>{};
|
||||
constexpr std::size_t I = Find<utempl::Wrapper<Key>>(list);
|
||||
if constexpr(I < sizeof...(Ts)) {
|
||||
return utempl::Get<I>(this->data).value;
|
||||
};
|
||||
}(*this);
|
||||
};
|
||||
template <utempl::ConstexprString Key, typename T>
|
||||
inline constexpr auto Append(T&& value) const -> ConstexprConfig<Ts..., NamedValue<Key, std::remove_cvref_t<T>>> {
|
||||
return {.data = TupleCat(this->data, utempl::MakeTuple(NamedValue<Key, std::remove_cvref_t<T>>{std::forward<T>(value)}))};
|
||||
};
|
||||
};
|
||||
|
||||
template <ConstexprConfig config, auto names, typename... Ts>
|
||||
struct ServiceContext {
|
||||
utempl::Tuple<Ts...> storage;
|
||||
static constexpr auto kConfig = config;
|
||||
static constexpr auto kList = utempl::TypeList<Ts...>{};
|
||||
inline constexpr ServiceContext() :
|
||||
storage{
|
||||
[&]<auto... Is>(std::index_sequence<Is...>) -> utempl::Tuple<Ts...> {
|
||||
return {[&]<auto I>(utempl::Wrapper<I>) {
|
||||
return [&]() -> std::remove_cvref_t<decltype(Get<I>(storage))> {
|
||||
return {decltype(Get<I>(names)){}, *this};
|
||||
};
|
||||
}(utempl::Wrapper<Is>{})...};
|
||||
}(std::index_sequence_for<Ts...>())
|
||||
} {};
|
||||
inline constexpr auto Run() {
|
||||
[&]<auto... Is>(std::index_sequence<Is...>) {
|
||||
([](auto& component){
|
||||
if constexpr(requires{component.Run();}) {
|
||||
component.Run();
|
||||
};
|
||||
}(Get<Is>(this->storage)), ...);
|
||||
}(std::index_sequence_for<Ts...>());
|
||||
};
|
||||
template <utempl::ConstexprString name>
|
||||
inline constexpr auto FindComponent() -> auto& {
|
||||
constexpr auto I = utempl::Find<utempl::Wrapper<name>>(names);
|
||||
return Get<I>(this->storage);
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr auto FindComponent() -> T& {
|
||||
constexpr auto I = utempl::Find<std::remove_cvref_t<T>>(utempl::kTypeList<Ts...>);
|
||||
return Get<I>(this->storage);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <ConstexprConfig config = {}, typename... Ts>
|
||||
struct ServiceContextBuilder {
|
||||
static constexpr auto kList = utempl::kTypeList<Ts...>;
|
||||
static constexpr auto kConfig = config;
|
||||
template <typename T, utempl::ConstexprString name = T::kName>
|
||||
static consteval auto Append() -> decltype(T::template Adder<name>(ServiceContextBuilder<config, Ts..., ComponentConfig<name, T>>{})) {
|
||||
return {};
|
||||
};
|
||||
|
||||
template <typename T, utempl::ConstexprString name = T::kName>
|
||||
static consteval auto Append() -> ServiceContextBuilder<config, Ts..., ComponentConfig<name, T>>
|
||||
requires (!requires(ServiceContextBuilder<config, Ts..., ComponentConfig<name, T>> builder) {T::template Adder<name>(builder);}) {
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
template <utempl::ConstexprString Key, auto Value>
|
||||
static consteval auto AppendConfigParam() -> ServiceContextBuilder<config.template Append<Key>(Value), Ts...> {
|
||||
return {};
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
static consteval auto TransformComponents(F&& f) -> ServiceContextBuilder<config, decltype(TransformIfOk(Ts{}, f))...> {
|
||||
return {};
|
||||
};
|
||||
template <utempl::ConstexprString name>
|
||||
static consteval auto FindComponent() {
|
||||
return []<typename... TTs, utempl::ConstexprString... names>
|
||||
(const ServiceContextBuilder<config,ComponentConfig<names, TTs>...>&)
|
||||
-> decltype(utempl::Get<Find<utempl::Wrapper<name>>(utempl::TypeList<utempl::Wrapper<names>...>{})>(utempl::TypeList<TTs...>{})) {
|
||||
std::unreachable();
|
||||
}(ServiceContextBuilder<config, Ts...>{});
|
||||
};
|
||||
|
||||
static consteval auto Config() {
|
||||
return config;
|
||||
};
|
||||
|
||||
static constexpr auto Run() -> void {
|
||||
[]<utempl::ConstexprString... names, typename... TTs>(utempl::TypeList<ComponentConfig<names, TTs>...>) {
|
||||
ServiceContext<config, utempl::kTypeList<utempl::Wrapper<names>...>, TTs...> context;
|
||||
context.Run();
|
||||
for(;;) {
|
||||
std::this_thread::sleep_for(std::chrono::minutes(1));
|
||||
};
|
||||
}(utempl::TypeList<Ts...>{});
|
||||
};
|
||||
};
|
||||
} // namespace cserver
|
9
include/cserver/engine/coroutine.hpp
Normal file
9
include/cserver/engine/coroutine.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace cserver {
|
||||
|
||||
template <typename T>
|
||||
using Task = boost::asio::awaitable<T>;
|
||||
|
||||
} // namespace cserver
|
34
include/cserver/server/handlers/http_handler_base.hpp
Normal file
34
include/cserver/server/handlers/http_handler_base.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <cserver/engine/components.hpp>
|
||||
#include <cserver/server/http/http_request.hpp>
|
||||
#include <cserver/server/http/http_response.hpp>
|
||||
#include <cserver/engine/coroutine.hpp>
|
||||
|
||||
namespace cserver::server::handlers {
|
||||
|
||||
template <typename T>
|
||||
struct HTTPHandlerBase {
|
||||
template <utempl::ConstexprString name>
|
||||
static consteval auto Adder(const auto& context) {
|
||||
return context.TransformComponents([]<typename TT>(const ComponentConfig<T::kHandlerManagerName, TT>) {
|
||||
return ComponentConfig<T::kHandlerManagerName, typename TT::template AddHandler<ComponentConfig<name, T>>>{};
|
||||
});
|
||||
};
|
||||
inline auto HandleRequest(cserver::server::http::HTTPRequest&& request,
|
||||
cserver::server::http::HTTPResponse& response) -> cserver::Task<std::string> {
|
||||
try {
|
||||
co_return co_await static_cast<T&>(*this).HandleRequestThrow(std::move(request), response);
|
||||
} catch(const std::exception& err) {
|
||||
fmt::println("Error in {}: {}", __PRETTY_FUNCTION__, err.what());
|
||||
} catch(...) {
|
||||
fmt::println("Error in {}: Unknown Error", __PRETTY_FUNCTION__);
|
||||
};
|
||||
response.statusCode = 500;
|
||||
response.statusMessage = "Internal Server Error";
|
||||
co_return "Internal Server Error";
|
||||
};
|
||||
inline constexpr HTTPHandlerBase(auto, auto&) {};
|
||||
|
||||
};
|
||||
|
||||
} // namespace cserver::server::handlers
|
25
include/cserver/server/http/http_request.hpp
Normal file
25
include/cserver/server/http/http_request.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include <fmt/format.h>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <boost/url.hpp>
|
||||
|
||||
namespace cserver::server::http {
|
||||
|
||||
struct HTTPRequest {
|
||||
std::string method = {};
|
||||
boost::urls::url url = {};
|
||||
std::unordered_map<std::string, std::string> headers = {};
|
||||
std::string body = {};
|
||||
inline auto ToString() const -> std::string {
|
||||
std::ostringstream stream;
|
||||
stream << fmt::format("{} {} HTTP/1.1\r\n", this->method, this->url.path());
|
||||
for(const auto& header : this->headers) {
|
||||
stream << fmt::format("{}: {}\r\n", header.first, header.second);
|
||||
};
|
||||
stream << fmt::format("\r\n{}\r\n", this->body);
|
||||
return stream.str();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace cserver::server::http
|
71
include/cserver/server/http/http_request_parser.hpp
Normal file
71
include/cserver/server/http/http_request_parser.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
#include <cserver/server/http/http_request.hpp>
|
||||
#include <llhttp.h>
|
||||
|
||||
namespace cserver::server::http {
|
||||
|
||||
struct HTTPRequestParser : private llhttp_t, public HTTPRequest {
|
||||
bool err = false;
|
||||
bool done = false;
|
||||
std::string headerField = {};
|
||||
std::string headerValue = {};
|
||||
std::string urlString = {};
|
||||
inline HTTPRequestParser(std::string_view data) {
|
||||
llhttp_settings_t settings;
|
||||
llhttp_settings_init(&settings);
|
||||
settings.on_method = HTTPRequestParser::OnMethod;
|
||||
settings.on_url = HTTPRequestParser::OnUrl;
|
||||
settings.on_url_complete = HTTPRequestParser::OnUrlComplete;
|
||||
settings.on_header_field = HTTPRequestParser::OnHeaderField;
|
||||
settings.on_header_value = HTTPRequestParser::OnHeaderValue;
|
||||
settings.on_headers_complete = HTTPRequestParser::OnHeaderComplete;
|
||||
settings.on_body = HTTPRequestParser::OnBody;
|
||||
settings.on_message_complete = HTTPRequestParser::OnMessageComplete;
|
||||
llhttp_init(this, HTTP_BOTH, &settings);
|
||||
llhttp_execute(this, data.data(), data.size());
|
||||
};
|
||||
static inline auto OnMethod(llhttp_t* parser, const char* data, std::size_t size) -> int {
|
||||
auto* self = static_cast<HTTPRequest*>(static_cast<HTTPRequestParser*>(parser));
|
||||
self->method.append(data, size);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnUrl(llhttp_t* parser, const char* data, std::size_t size) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->urlString.append(data, size);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnUrlComplete(llhttp_t* parser) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->url = boost::urls::url(self->urlString);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnHeaderField(llhttp_t* parser, const char* data, std::size_t size) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->headerField.append(data, size);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnHeaderValue(llhttp_t* parser, const char* data, std::size_t size) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->headerValue.append(data, size);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnHeaderComplete(llhttp_t* parser) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->headers.emplace(std::move(self->headerField), std::move(self->headerValue));
|
||||
self->headerValue.clear();
|
||||
self->headerField.clear();
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnBody(llhttp_t* parser, const char* data, std::size_t size) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->body.append(data, size);
|
||||
return 0;
|
||||
};
|
||||
static inline auto OnMessageComplete(llhttp_t* parser) -> int {
|
||||
auto* self = static_cast<HTTPRequestParser*>(parser);
|
||||
self->done = true;
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace cserver::server::http
|
26
include/cserver/server/http/http_response.hpp
Normal file
26
include/cserver/server/http/http_response.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace cserver::server::http {
|
||||
|
||||
struct HTTPResponse {
|
||||
unsigned short statusCode = 200;
|
||||
std::string statusMessage = "OK";
|
||||
std::unordered_map<std::string, std::string> headers = {};
|
||||
std::string body = {};
|
||||
inline auto ToString() const -> std::string {
|
||||
std::ostringstream stream;
|
||||
stream << fmt::format("HTTP/1.1 {} {}\r\n", this->statusCode, this->statusMessage);
|
||||
for(const auto& header : this->headers) {
|
||||
stream << fmt::format("{}: {}\r\n", header.first, header.second);
|
||||
};
|
||||
if(!this->body.empty()) {
|
||||
stream << fmt::format("\r\n{}\r\n", this->body);
|
||||
};
|
||||
return stream.str();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace cserver::server::http
|
98
include/cserver/server/server/server.hpp
Normal file
98
include/cserver/server/server/server.hpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
#pragma once
|
||||
#include <cserver/engine/components.hpp>
|
||||
#include <cserver/server/http/http_request_parser.hpp>
|
||||
#include <cserver/server/http/http_response.hpp>
|
||||
#include <cserver/engine/coroutine.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
|
||||
namespace cserver::server::server {
|
||||
namespace impl {
|
||||
template <typename T>
|
||||
using GetTypeFromComponentConfig = decltype([]<utempl::ConstexprString name, typename TT>(const ComponentConfig<name, TT>&) -> TT {}(std::declval<T>()));
|
||||
|
||||
template <typename T>
|
||||
inline constexpr utempl::ConstexprString kNameFromComponentConfig =
|
||||
decltype([]<utempl::ConstexprString name, typename TT>(const ComponentConfig<name, TT>&) {
|
||||
return utempl::Wrapper<name>{};
|
||||
} (std::declval<T>()))::kValue;
|
||||
} // namespace impl
|
||||
|
||||
template <utempl::ConstexprString TPName = "basicTaskProcessor", typename TaskProcessor = int, typename... Ts>
|
||||
struct Server {
|
||||
TaskProcessor& taskProcessor;
|
||||
utempl::Tuple<impl::GetTypeFromComponentConfig<Ts>&...> handlers;
|
||||
static constexpr utempl::ConstexprString kName = "server";
|
||||
unsigned short port;
|
||||
static constexpr utempl::Tuple kNames = {impl::kNameFromComponentConfig<Ts>...};
|
||||
static constexpr utempl::Tuple kPaths = {impl::GetTypeFromComponentConfig<Ts>::kPath...};
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
static consteval auto Adder(const T& context) {
|
||||
constexpr utempl::ConstexprString tpName = [&]{
|
||||
if constexpr(requires{T::kConfig.template Get<name>().template Get<"taskProcessor">();}) {
|
||||
return T::kConfig.template Get<name>().template Get<"taskProcessor">();
|
||||
} else {
|
||||
return TPName;
|
||||
};
|
||||
}();
|
||||
using TP = decltype(context.template FindComponent<tpName>());
|
||||
return context.TransformComponents(
|
||||
[&](const ComponentConfig<name, Server<TPName, int, Ts...>>&) -> ComponentConfig<name, Server<tpName, TP, Ts...>> {
|
||||
return {};
|
||||
});
|
||||
};
|
||||
template <utempl::ConstexprString name, typename T>
|
||||
inline constexpr Server(utempl::Wrapper<name>, T& context) :
|
||||
taskProcessor(context.template FindComponent<TPName>()),
|
||||
handlers{
|
||||
[&]<auto... Is>(std::index_sequence<Is...>) -> utempl::Tuple<impl::GetTypeFromComponentConfig<Ts>&...> {
|
||||
return {context.template FindComponent<Get<Is>(kNames)>()...};
|
||||
}(std::index_sequence_for<Ts...>())
|
||||
},
|
||||
port(T::kConfig.template Get<name>().template Get<"port">()) {
|
||||
};
|
||||
auto Reader(boost::asio::ip::tcp::socket socket) -> Task<void> {
|
||||
std::string buffer;
|
||||
buffer.reserve(socket.available());
|
||||
co_await boost::asio::async_read_until(socket, boost::asio::dynamic_buffer(buffer), "\r\n\r\n", boost::asio::use_awaitable);
|
||||
http::HTTPRequest request = http::HTTPRequestParser{buffer};
|
||||
bool flag = false;
|
||||
co_await [&]<auto... Is>(std::index_sequence<Is...>) -> cserver::Task<void> {
|
||||
(co_await [&]<auto I>(utempl::Wrapper<I>) -> cserver::Task<void> {
|
||||
if(request.url.path().substr(0, Get<I>(kPaths).size()) == Get<I>(kPaths)) {
|
||||
flag = true;
|
||||
http::HTTPResponse response{};
|
||||
response.body = co_await Get<I>(this->handlers).HandleRequest(std::move(request), response);
|
||||
|
||||
response.headers["Content-Length"] = std::to_string(response.body.size());
|
||||
response.headers["Server"] = "cserver/1";
|
||||
auto data = response.ToString();
|
||||
co_await boost::asio::async_write(socket, boost::asio::buffer(data.data(), data.size()), boost::asio::use_awaitable);
|
||||
socket.close();
|
||||
};
|
||||
}(utempl::Wrapper<Is>{}), ...);
|
||||
}(std::index_sequence_for<Ts...>());
|
||||
constexpr std::string_view error404 = "HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"\r\n";
|
||||
if(!flag) {
|
||||
co_await boost::asio::async_write(socket, boost::asio::buffer(error404.data(), error404.size()), boost::asio::use_awaitable);
|
||||
};
|
||||
};
|
||||
auto Task() -> cserver::Task<void> {
|
||||
auto executor = co_await boost::asio::this_coro::executor;
|
||||
boost::asio::ip::tcp::acceptor acceptor{executor, {boost::asio::ip::tcp::v6(), this->port}};
|
||||
for(;;) {
|
||||
auto socket = co_await acceptor.async_accept(boost::asio::use_awaitable);
|
||||
boost::asio::co_spawn(executor, this->Reader(std::move(socket)), boost::asio::detached);
|
||||
};
|
||||
};
|
||||
inline constexpr auto Run() -> void {
|
||||
boost::asio::co_spawn(this->taskProcessor.ioContext, this->Task(), boost::asio::detached);
|
||||
};
|
||||
inline ~Server() {
|
||||
};
|
||||
template <typename Handler>
|
||||
using AddHandler = Server<TPName, TaskProcessor, Ts..., Handler>;
|
||||
};
|
||||
} // namespace cserver::server::server
|
Loading…
Reference in a new issue