Skip to content

Commit

Permalink
Add Ichor HttpHost/Client implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Oipo committed Nov 9, 2024
1 parent 23b4539 commit 99a9ae7
Show file tree
Hide file tree
Showing 45 changed files with 1,366 additions and 194 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ set(FMT_SOURCES ${ICHOR_EXTERNAL_DIR}/fmt/src/format.cc ${ICHOR_EXTERNAL_DIR}/fm
file(GLOB_RECURSE ICHOR_FRAMEWORK_SOURCES ${ICHOR_TOP_DIR}/src/ichor/*.cpp)
file(GLOB_RECURSE ICHOR_OPTIONAL_ETCD_SOURCES ${ICHOR_TOP_DIR}/src/services/etcd/*.cpp)
file(GLOB_RECURSE ICHOR_LOGGING_SOURCES ${ICHOR_TOP_DIR}/src/services/logging/*.cpp)
file(GLOB_RECURSE ICHOR_TCP_SOURCES ${ICHOR_TOP_DIR}/src/services/network/tcp/*.cpp)
file(GLOB_RECURSE ICHOR_HTTP_SOURCES ${ICHOR_TOP_DIR}/src/services/network/http/*.cpp)
file(GLOB_RECURSE ICHOR_BOOST_BEAST_SOURCES ${ICHOR_TOP_DIR}/src/services/network/boost/*.cpp)
file(GLOB_RECURSE ICHOR_METRICS_SOURCES ${ICHOR_TOP_DIR}/src/services/metrics/*.cpp)
file(GLOB_RECURSE ICHOR_TIMER_SOURCES ${ICHOR_TOP_DIR}/src/services/timer/Timer.cpp ${ICHOR_TOP_DIR}/src/services/timer/TimerFactoryFactory.cpp)
Expand All @@ -137,7 +137,7 @@ if(ICHOR_USE_MIMALLOC AND NOT ICHOR_USE_SYSTEM_MIMALLOC)
set(ICHOR_FRAMEWORK_SOURCES ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_TOP_DIR}/external/mimalloc/src/static.c)
endif()

add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_TCP_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES} ${ICHOR_STL_SOURCES})
add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_TCP_SOURCES} ${ICHOR_HTTP_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES} ${ICHOR_STL_SOURCES})

if(ICHOR_ENABLE_INTERNAL_DEBUGGING)
target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_DEBUGGING)
Expand Down
2 changes: 1 addition & 1 deletion docs/02-DependencyInjection.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ e.g. here is a service that adds a REST API endpoint to the running HTTP host se
class BasicService final {
public:
BasicService(IHttpHostService *hostService) {
_routeRegistration = hostService->addRoute(HttpMethod::get, "/basic", [this, serializer](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistration = hostService->addRoute(HttpMethod::get, "/basic", [this, serializer](HttpRequest &req) -> Task<HttpResponse> {
co_return HttpResponse{HttpStatus::ok, "application/text, "<html><body>This is my basic webpage</body></html>", {}};
});
}
Expand Down
6 changes: 3 additions & 3 deletions docs/09-HttpServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) {
class UsingHttpService final {
UsingHttpService(IHttpHostService *host) {
_routeRegistrations.emplace_back(host->addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistrations.emplace_back(host->addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> Task<HttpResponse> {
co_return HttpResponse{HttpStatus::ok, "application/text", "<html><body>This is my basic webpage</body></html>", {}};
}));
}
Expand All @@ -47,7 +47,7 @@ To add routes that capture parts of the URL, Ichor provides a regex route matche

class UsingHttpService final {
UsingHttpService(IHttpHostService *host) {
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<RegexRouteMatch<R"(\/user\/(\d{1,2})\?*(.*))">>(), [this](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<RegexRouteMatch<R"(\/user\/(\d{1,2})\?*(.*))">>(), [this](HttpRequest &req) -> Task<HttpResponse> {
std::string user_id = req.regex_params[0];
if(req.regex_params.size() > 1) {
std::string query_params = req.regex_params[1]; // e.g. param1=one&param2=two, parsing string is left to the user for now, though Ichor does provide a string_view split function in stl/StringUtils.h
Expand Down Expand Up @@ -87,7 +87,7 @@ struct CustomRouteMatcher final : public RouteMatcher {
And use that with the route registration:

```c++
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<CustomRouteMatcher>(), [this](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<CustomRouteMatcher>(), [this](HttpRequest &req) -> Task<HttpResponse> {
co_return HttpResponse{HttpStatus::ok, "text/plain", {}, {}};
}));
```
7 changes: 5 additions & 2 deletions examples/common/DebugService.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ class DebugService final : public AdvancedService<DebugService> {
auto &_timer = _timerFactory->createTimer();
_timer.setCallback([this]() {
printServices();
std::terminate();
});
_timer.setChronoInterval(500ms);
_timer.setChronoInterval(1000ms);
_timer.startTimer();

printServices();
// printServices();

co_return {};
}
Expand Down Expand Up @@ -69,6 +70,8 @@ class DebugService final : public AdvancedService<DebugService> {

ICHOR_LOG_INFO(_logger, "");
}

ICHOR_LOG_INFO(_logger, "\n===================================\n");
}

void addDependencyInstance(ILogger &logger, IService &) {
Expand Down
1 change: 1 addition & 0 deletions examples/factory_example/FactoryService.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class FactoryService final {

if(runtimeService == end(_scopedRuntimeServices)) {
auto newProps = *evt.properties.value();
newProps.erase("Filter");
// `Filter` is a magic keyword that Ichor uses to determine if this service is global or if Ichor should use its filtering logic.
// In this case, we tell Ichor to only insert this service if the requesting service has a matching scope
newProps.emplace("Filter", Ichor::make_any<Filter>(ScopeFilterEntry{scope}));
Expand Down
4 changes: 2 additions & 2 deletions examples/http_example/UsingHttpService.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ class UsingHttpService final : public AdvancedService<UsingHttpService> {

void addDependencyInstance(IHttpHostService &svc, IService&) {
ICHOR_LOG_INFO(_logger, "Inserted IHttpHostService");
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> Task<HttpResponse> {
auto msg = _serializer->deserialize(req.body);
ICHOR_LOG_WARN(_logger, "received request on route {} {} with testmsg {} - {}", (int)req.method, req.route, msg->id, msg->val);
co_return HttpResponse{HttpStatus::ok, "application/json", _serializer->serialize(TestMsg{11, "hello"}), {}};
}));
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<RegexRouteMatch<R"(\/regex_test\/([a-zA-Z0-9]*)\?*([a-zA-Z0-9]+=[a-zA-Z0-9]+)*&*([a-zA-Z0-9]+=[a-zA-Z0-9]+)*)">>(), [this](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique<RegexRouteMatch<R"(\/regex_test\/([a-zA-Z0-9]*)\?*([a-zA-Z0-9]+=[a-zA-Z0-9]+)*&*([a-zA-Z0-9]+=[a-zA-Z0-9]+)*)">>(), [this](HttpRequest &req) -> Task<HttpResponse> {
ICHOR_LOG_WARN(_logger, "received request on route {} {} with params:", (int)req.method, req.route);
for(auto const &param : req.regex_params) {
ICHOR_LOG_WARN(_logger, "{}", param);
Expand Down
4 changes: 2 additions & 2 deletions examples/http_ping_pong/PongService.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
#include <ichor/services/network/http/IHttpConnectionService.h>
#include <ichor/services/network/http/IHttpHostService.h>
#include <ichor/services/serialization/ISerializer.h>
#include <ichor/coroutines/AsyncGenerator.h>
#include <ichor/coroutines/Task.h>
#include "PingMsg.h"

using namespace Ichor;

class PongService final {
public:
PongService(ILogger *logger, ISerializer<PingMsg> *serializer, IHttpHostService *hostService) : _logger(logger) {
_routeRegistration = hostService->addRoute(HttpMethod::post, "/ping", [this, serializer](HttpRequest &req) -> AsyncGenerator<HttpResponse> {
_routeRegistration = hostService->addRoute(HttpMethod::post, "/ping", [this, serializer](HttpRequest &req) -> Task<HttpResponse> {
ICHOR_LOG_INFO(_logger, "received request from {} with body {} ", req.address, std::string_view{reinterpret_cast<char*>(req.body.data()), req.body.size()});
auto msg = serializer->deserialize(req.body);
ICHOR_LOG_INFO(_logger, "received request from {} on route {} {} with PingMsg {}", req.address, (int) req.method, req.route, msg->sequence);
Expand Down
2 changes: 1 addition & 1 deletion examples/tcp_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) {
dm.createServiceManager<LoggerFactory<LOGGER_TYPE>, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any<LogLevel>(LogLevel::LOG_TRACE)}}, priorityToEnsureHostStartingFirst);
dm.createServiceManager<TestMsgGlazeSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<HOSTIMPL, IHostService>(Properties{{"Address", Ichor::make_any<std::string>("127.0.0.1"s)}, {"Port", Ichor::make_any<uint16_t>(static_cast<uint16_t>(8001))}}, priorityToEnsureHostStartingFirst);
dm.createServiceManager<ClientFactory<CONNIMPL>, IClientFactory>();
dm.createServiceManager<ClientFactory<CONNIMPL<IClientConnectionService>>, IClientFactory>();
#ifndef URING_EXAMPLE
dm.createServiceManager<TimerFactoryFactory>(Properties{}, priorityToEnsureHostStartingFirst);
#endif
Expand Down
8 changes: 8 additions & 0 deletions include/ichor/Concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <ichor/stl/NeverAlwaysNull.h>
#include <concepts>

#include "Concepts.h"

namespace Ichor {

struct DependencyRegister;
Expand All @@ -25,6 +27,12 @@ namespace Ichor {
template <class T, class U>
concept Derived = std::is_base_of<U, T>::value;

template <class T, class U, class V>
concept DerivedEither = std::is_base_of<U, T>::value || std::is_base_of<V, T>::value;

template <class T, class U, class V, class X>
concept DerivedAny = std::is_base_of<U, T>::value || std::is_base_of<V, T>::value;

template <class T, template <class> class U>
concept DerivedTemplated = std::is_base_of<U<T>, T>::value;

Expand Down
4 changes: 2 additions & 2 deletions include/ichor/coroutines/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace Ichor
{
template<typename T> class ICHOR_CORO_AWAIT_ELIDABLE ICHOR_CORO_LIFETIME_BOUND ICHOR_CORO_RETURN_TYPE Task;
template<typename T> class ICHOR_CORO_AWAIT_ELIDABLE ICHOR_CORO_LIFETIME_BOUND Task;

namespace Detail
{
Expand Down Expand Up @@ -250,7 +250,7 @@ namespace Ichor
/// caller. Execution of the coroutine body does not start until the
/// coroutine is first co_await'ed.
template<typename T = void>
class [[nodiscard]] ICHOR_CORO_AWAIT_ELIDABLE ICHOR_CORO_LIFETIME_BOUND ICHOR_CORO_RETURN_TYPE Task
class [[nodiscard]] ICHOR_CORO_AWAIT_ELIDABLE ICHOR_CORO_LIFETIME_BOUND Task
{
public:

Expand Down
5 changes: 3 additions & 2 deletions include/ichor/events/InternalEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ichor/ConstevalHash.h>
#include <ichor/dependency_management/Dependency.h>
#include <ichor/Callbacks.h>
#include <ichor/stl/NeverAlwaysNull.h>
#include <tl/optional.h>

namespace Ichor {
Expand Down Expand Up @@ -44,7 +45,7 @@ namespace Ichor {

/// When a new service gets created that requests dependencies, each dependency it requests adds this event
struct DependencyRequestEvent final : public Event {
explicit DependencyRequestEvent(uint64_t _id, ServiceIdType _originatingService, uint64_t _priority, Dependency _dependency, tl::optional<Properties const *> _properties) noexcept :
explicit DependencyRequestEvent(uint64_t _id, ServiceIdType _originatingService, uint64_t _priority, Dependency _dependency, tl::optional<NeverNull<Properties const *>> _properties) noexcept :
Event(_id, _originatingService, _priority), dependency(_dependency), properties{_properties} {}
~DependencyRequestEvent() final = default;

Expand All @@ -56,7 +57,7 @@ namespace Ichor {
}

Dependency dependency;
tl::optional<Properties const *> properties;
tl::optional<NeverNull<Properties const *>> properties;
static constexpr NameHashType TYPE = typeNameHash<DependencyRequestEvent>();
static constexpr std::string_view NAME = typeName<DependencyRequestEvent>();
};
Expand Down
4 changes: 3 additions & 1 deletion include/ichor/services/logging/LoggerFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Ichor {
class LoggerFactory final : public ILoggerFactory, public AdvancedService<LoggerFactory<LogT>> {
public:
LoggerFactory(DependencyRegister &reg, Properties props) : AdvancedService<LoggerFactory<LogT>>(std::move(props)) {
reg.registerDependency<IFrameworkLogger>(this, DependencyFlags::NONE);
reg.registerDependency<IFrameworkLogger>(this, DependencyFlags::REQUIRED);

auto logLevelProp = AdvancedService<LoggerFactory<LogT>>::getProperties().find("DefaultLogLevel");
if(logLevelProp != end(AdvancedService<LoggerFactory<LogT>>::getProperties())) {
Expand Down Expand Up @@ -65,10 +65,12 @@ namespace Ichor {
}

Properties props{};
props.reserve(2);
props.template emplace<>("Filter", Ichor::make_any<Filter>(ServiceIdFilterEntry{evt.originatingService}));
props.template emplace<>("LogLevel", Ichor::make_any<LogLevel>(requestedLevel));
auto newLogger = GetThreadLocalManager().template createServiceManager<LogT, ILogger>(std::move(props), evt.priority);
_loggers.emplace(evt.originatingService, newLogger->getServiceId());
ICHOR_LOG_TRACE(_logger, "created logger for svcid {}", evt.originatingService);
} else {
ICHOR_LOG_TRACE(_logger, "svcid {} already has logger", evt.originatingService);
}
Expand Down
14 changes: 10 additions & 4 deletions include/ichor/services/network/ClientFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ namespace Ichor {
class ClientFactory final : public IClientFactory, public AdvancedService<ClientFactory<NetworkType, NetworkInterfaceType>> {
public:
ClientFactory(DependencyRegister &reg, Properties properties) : AdvancedService<ClientFactory<NetworkType, NetworkInterfaceType>>(std::move(properties)) {
reg.registerDependency<ILogger>(this, DependencyFlags::NONE, AdvancedService<ClientFactory<NetworkType, NetworkInterfaceType>>::getProperties());
reg.registerDependency<ILogger>(this, DependencyFlags::REQUIRED, AdvancedService<ClientFactory<NetworkType, NetworkInterfaceType>>::getProperties());
}
~ClientFactory() final = default;

uint64_t createNewConnection(NeverNull<IService*> requestingSvc, Properties properties) final {
properties.erase("Filter");
properties.emplace("Filter", Ichor::make_any<Filter>(ServiceIdFilterEntry{requestingSvc->getServiceId()}));
ConnectionCounterType count = _connectionCounter++;

Expand Down Expand Up @@ -74,19 +75,24 @@ namespace Ichor {

AsyncGenerator<IchorBehaviour> handleDependencyRequest(AlwaysNull<NetworkInterfaceType*>, DependencyRequestEvent const &evt) {
if(!evt.properties.has_value()) {
throw std::runtime_error("Missing properties");
ICHOR_LOG_TRACE(_logger, "Missing properties when creating new connection {}", evt.originatingService);
co_return {};
}

if(!evt.properties.value()->contains("Address")) {
throw std::runtime_error("Missing address");
ICHOR_LOG_TRACE(_logger, "Missing address when creating new connection {}", evt.originatingService);
co_return {};
}

if(!evt.properties.value()->contains("Port")) {
throw std::runtime_error("Missing port");
ICHOR_LOG_TRACE(_logger, "Missing port when creating new connection {}", evt.originatingService);
co_return {};
}

if(!_connections.contains(evt.originatingService)) {
fmt::println("{} creating {} for {}", typeName<ClientFactory<NetworkType, NetworkInterfaceType>>(), typeName<NetworkInterfaceType>(), evt.originatingService);
auto newProps = *evt.properties.value();
newProps.erase("Filter");
newProps.emplace("Filter", Ichor::make_any<Filter>(ServiceIdFilterEntry{evt.originatingService}));

unordered_map<ConnectionCounterType, ServiceIdType> newMap;
Expand Down
15 changes: 15 additions & 0 deletions include/ichor/services/network/IConnectionService.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <ichor/stl/ErrnoUtils.h>
#include <tl/expected.h>
#include <vector>
#include <span>

namespace Ichor {
class IConnectionService {
Expand Down Expand Up @@ -41,4 +42,18 @@ namespace Ichor {
protected:
~IConnectionService() = default;
};

struct IHostConnectionService : public IConnectionService {
using IConnectionService::IConnectionService;

protected:
~IHostConnectionService() = default;
};

struct IClientConnectionService : public IConnectionService {
using IConnectionService::IConnectionService;

protected:
~IClientConnectionService() = default;
};
}
6 changes: 3 additions & 3 deletions include/ichor/services/network/boost/HttpHostService.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ namespace Ichor::Boost {
HttpHostService(DependencyRegister &reg, Properties props);
~HttpHostService() final = default;

HttpRouteRegistration addRoute(HttpMethod method, std::string_view route, std::function<AsyncGenerator<HttpResponse>(HttpRequest&)> handler) final;
HttpRouteRegistration addRoute(HttpMethod method, std::unique_ptr<RouteMatcher> matcher, std::function<AsyncGenerator<HttpResponse>(HttpRequest&)> handler) final;
HttpRouteRegistration addRoute(HttpMethod method, std::string_view route, std::function<Task<HttpResponse>(HttpRequest&)> handler) final;
HttpRouteRegistration addRoute(HttpMethod method, std::unique_ptr<RouteMatcher> matcher, std::function<Task<HttpResponse>(HttpRequest&)> handler) final;
void removeRoute(HttpMethod method, RouteIdType id) final;

void setPriority(uint64_t priority) final;
Expand Down Expand Up @@ -94,7 +94,7 @@ namespace Ichor::Boost {
bool _debug{};
std::atomic<ILogger*> _logger{};
IAsioContextService *_asioContextService{};
unordered_map<HttpMethod, unordered_map<std::unique_ptr<RouteMatcher>, std::function<AsyncGenerator<HttpResponse>(HttpRequest&)>>> _handlers{};
unordered_map<HttpMethod, unordered_map<std::unique_ptr<RouteMatcher>, std::function<Task<HttpResponse>(HttpRequest&)>>> _handlers{};
AsyncManualResetEvent _startStopEvent{};
IEventQueue *_queue;
};
Expand Down
2 changes: 1 addition & 1 deletion include/ichor/services/network/boost/WsConnectionService.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace Ichor::Boost {
};
}

class WsConnectionService final : public IConnectionService, public AdvancedService<WsConnectionService> {
class WsConnectionService final : public IConnectionService, public IHostConnectionService, public IClientConnectionService, public AdvancedService<WsConnectionService> {
public:
WsConnectionService(DependencyRegister &reg, Properties props);
~WsConnectionService() final = default;
Expand Down
Loading

0 comments on commit 99a9ae7

Please sign in to comment.