Skip to content

Commit 202dd99

Browse files
Reimplement REST interface using HTTP/2
Instead of using the httplib-based HTTP server using blocking I/O in a thread per client, implement a nghttp2-based HTTP/2 server, using single-threaded non-blocking I/O directly integrated into the Broker zmq_poll() loop. Signed-off-by: Frank Osterfeld <[email protected]>
1 parent 85351ec commit 202dd99

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4303
-2444
lines changed

CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ else()
108108
option(OPENCMW_ENABLE_COVERAGE "Enable Coverage" OFF)
109109
endif()
110110
option(OPENCMW_ENABLE_CONCEPTS "Enable Concepts Builds" ${opencmw_MASTER_PROJECT})
111+
option(OPENCMW_DEBUG_HTTP "Enable verbose HTTP output for debugging" OFF)
112+
option(OPENCMW_PROFILE_HTTP "Enable verbose HTTP output for profiling" OFF)
111113

112114
# Very basic PCH example
113115
option(ENABLE_PCH "Enable Precompiled Headers" OFF)
@@ -124,6 +126,14 @@ if(ENABLE_PCH)
124126
<utility>)
125127
endif()
126128

129+
if(OPENCMW_DEBUG_HTTP)
130+
target_compile_definitions(opencmw_project_options INTERFACE -DOPENCMW_DEBUG_HTTP)
131+
endif()
132+
133+
if(OPENCMW_PROFILE_HTTP)
134+
target_compile_definitions(opencmw_project_options INTERFACE -DOPENCMW_PROFILE_HTTP)
135+
endif()
136+
127137
if(OPENCMW_ENABLE_TESTING)
128138
enable_testing()
129139
message("Building Tests.")

cmake/DependenciesNative.cmake

+26
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,29 @@ FetchContent_Declare(
6565
FetchContent_MakeAvailable(cpp-httplib zeromq openssl-source)
6666

6767
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) # replace contrib by extras for catch2 v3.x.x
68+
69+
option(ENABLE_NGHTTP2_DEBUG "Enable verbose nghttp2 debug output" OFF)
70+
71+
include(ExternalProject)
72+
ExternalProject_Add(Nghttp2Project
73+
GIT_REPOSITORY https://github.com/nghttp2/nghttp2
74+
GIT_TAG v1.65.0
75+
GIT_SHALLOW ON
76+
BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/nghttp2-install/lib/libnghttp2.a
77+
CMAKE_ARGS
78+
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/nghttp2-install
79+
-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
80+
-DENABLE_LIB_ONLY:BOOL=ON
81+
-DENABLE_HTTP3:BOOL=OFF
82+
-DENABLE_DEBUG:BOOL=${ENABLE_NGHTTP2_DEBUG}
83+
-DBUILD_STATIC_LIBS:BOOL=ON
84+
-BUILD_SHARED_LIBS:BOOL=OFF
85+
-DENABLE_DOC:BOOL=OFF
86+
)
87+
88+
add_library(nghttp2-static STATIC IMPORTED STATIC GLOBAL)
89+
set_target_properties(nghttp2-static PROPERTIES
90+
IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/nghttp2-install/lib/libnghttp2.a"
91+
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR}/nghttp2-install/include"
92+
)
93+
add_dependencies(nghttp2-static Nghttp2Project)

concepts/client/CMakeLists.txt

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
if(NOT EMSCRIPTEN)
2-
add_executable(RestSubscription_example RestSubscription_example.cpp)
3-
target_link_libraries(
4-
RestSubscription_example
5-
PRIVATE core
6-
client
7-
opencmw_project_options
8-
opencmw_project_warnings
9-
assets::rest)
10-
endif()
1+
add_executable(LoadTest_client LoadTest_client.cpp)
2+
target_link_libraries(
3+
LoadTest_client
4+
PRIVATE core
5+
client
6+
opencmw_project_options
7+
opencmw_project_warnings)
118

129
add_executable(RestSubscription_client RestSubscription_client.cpp)
1310
target_link_libraries(

concepts/client/LoadTest_client.cpp

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <atomic>
2+
#include <chrono>
3+
#include <string_view>
4+
#include <thread>
5+
6+
#include <MIME.hpp>
7+
#include <RestClient.hpp>
8+
#include <URI.hpp>
9+
10+
#include "ClientCommon.hpp"
11+
#include "helpers.hpp"
12+
13+
using namespace std::chrono_literals;
14+
15+
std::string schema() {
16+
if (auto env = ::getenv("DISABLE_REST_HTTPS"); env != nullptr && std::string_view(env) == "1") {
17+
return "http";
18+
} else {
19+
return "https";
20+
}
21+
}
22+
23+
24+
int main() {
25+
constexpr auto kServerPort = 8080;
26+
constexpr auto kNClients = 80UZ;
27+
constexpr auto kNSubscriptions = 10UZ;
28+
constexpr auto kNUpdates = 5000UZ;
29+
constexpr auto kIntervalMs = 40UZ;
30+
constexpr auto kPayloadSize = 4096UZ;
31+
32+
std::array<std::unique_ptr<opencmw::client::RestClient>, kNClients> clients;
33+
for (std::size_t i = 0; i < clients.size(); i++) {
34+
clients[i] = std::make_unique<opencmw::client::RestClient>(opencmw::client::DefaultContentTypeHeader(opencmw::MIME::BINARY), opencmw::client::VerifyServerCertificates(false));
35+
}
36+
std::atomic<std::size_t> responseCount = 0;
37+
38+
const auto start = std::chrono::system_clock::now();
39+
40+
for (std::size_t i = 0; i < kNClients; i++) {
41+
for (std::size_t j = 0; j < kNSubscriptions; j++) {
42+
opencmw::client::Command cmd;
43+
cmd.command = opencmw::mdp::Command::Subscribe;
44+
cmd.serviceName = "/loadTest";
45+
cmd.topic = opencmw::URI<>(fmt::format("{}://localhost:{}/loadTest?initialDelayMs=1000&topic={}&intervalMs={}&payloadSize={}&nUpdates={}", schema(), kServerPort, /*i,*/ j, kIntervalMs, kPayloadSize, kNUpdates));
46+
cmd.callback = [&responseCount](const auto &msg) {
47+
responseCount++;
48+
};
49+
clients[i]->request(std::move(cmd));
50+
}
51+
}
52+
53+
constexpr auto expectedResponses = kNClients * kNSubscriptions * kNUpdates;
54+
55+
std::uint64_t counter = 0;
56+
while (responseCount < expectedResponses) {
57+
counter += 50;
58+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
59+
if (counter % 20 == 0) {
60+
fmt::println("Received {} of {} responses", responseCount.load(), expectedResponses);
61+
}
62+
}
63+
64+
const auto end = std::chrono::system_clock::now();
65+
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
66+
fmt::println("Elapsed time: {} ms", elapsed);
67+
return 0;
68+
}

concepts/client/RestSubscription_client.cpp

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ using namespace std::chrono_literals;
1313
// These are not main-local, as JS doesn't end when
1414
// C++ main ends
1515
namespace test_state {
16+
#ifndef __EMSCRIPTEN__
17+
opencmw::client::RestClient client(opencmw::client::VerifyServerCertificates(false));
18+
#else
1619
opencmw::client::RestClient client;
17-
20+
#endif
1821
std::string schema() {
1922
if (auto env = ::getenv("DISABLE_REST_HTTPS"); env != nullptr && std::string_view(env) == "1") {
2023
return "http";
@@ -39,10 +42,6 @@ auto run = rest_test_runner(
3942
} // namespace test_state
4043

4144
int main() {
42-
#ifndef __EMSCRIPTEN__
43-
opencmw::client::RestClient::CHECK_CERTIFICATES = false;
44-
#endif
45-
4645
using namespace test_state;
4746

4847
#ifndef __EMSCRIPTEN__

concepts/client/RestSubscription_example.cpp

-127
This file was deleted.

concepts/client/dns_example.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
#include <emscripten/trace.h>
1313
#endif // EMSCRIPTEN
1414

15+
#include <RestClient.hpp>
16+
1517
#include <string_view>
16-
#include <thread>
1718

1819
using namespace std::chrono_literals;
1920
using namespace opencmw;
@@ -32,12 +33,17 @@ using namespace opencmw::service::dns;
3233
void run_dns_server(std::string_view httpAddress, std::string_view mdpAddress) {
3334
majordomo::Broker<> broker{ "Broker", {} };
3435
std::string rootPath{ "./" };
35-
auto fs = cmrc::assets::get_filesystem();
36-
majordomo::RestBackend<majordomo::PLAIN_HTTP, decltype(fs)> rest_backend{ broker, fs, URI<>{ std::string{ httpAddress } } };
36+
majordomo::rest::Settings rest;
37+
rest.handlers = { majordomo::rest::cmrcHandler("/assets/*", "", std::make_shared<cmrc::embedded_filesystem>(cmrc::assets::get_filesystem()), "") };
38+
39+
if (const auto bound = broker.bindRest(rest); !bound) {
40+
fmt::println(std::cerr, "failed to bind REST: {}", bound.error());
41+
std::exit(1);
42+
return;
43+
}
3744
DnsWorkerType dnsWorker{ broker, DnsHandler{} };
3845
broker.bind(URI<>{ std::string{ mdpAddress } }, majordomo::BindOption::Router);
3946

40-
RunInThread restThread(rest_backend);
4147
RunInThread dnsThread(dnsWorker);
4248
RunInThread brokerThread(broker);
4349

concepts/majordomo/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ target_link_libraries(
3838
assets::rest
3939
assets::testImages)
4040

41+
add_executable(MajordomoRest_LoadTestServer MajordomoRest_LoadTestServer.cpp)
42+
target_include_directories(MajordomoRest_LoadTestServer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
43+
target_link_libraries(
44+
MajordomoRest_LoadTestServer
45+
PRIVATE majordomo
46+
opencmw_project_options
47+
opencmw_project_warnings
48+
assets::rest
49+
assets::testImages)
50+
4151
if(NOT
4252
CMAKE_CXX_COMPILER_ID
4353
MATCHES

0 commit comments

Comments
 (0)