aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-02 13:23:42 +0200
committerGitHub <[email protected]>2023-05-02 13:23:42 +0200
commitfc53dd4bd6737f4e1c406f24cd66b4255f383e60 (patch)
tree56bf06028ddae6ed2ff445a78db6a781538949f4 /src/zenhttp
parentmove auth code from zenserver into zenhttp (#265) (diff)
downloadzen-fc53dd4bd6737f4e1c406f24cd66b4255f383e60.tar.xz
zen-fc53dd4bd6737f4e1c406f24cd66b4255f383e60.zip
move testing and observability code to zenhttp (#266)
Diffstat (limited to 'src/zenhttp')
-rw-r--r--src/zenhttp/diagsvcs.cpp127
-rw-r--r--src/zenhttp/include/zenhttp/diagsvcs.h111
-rw-r--r--src/zenhttp/include/zenhttp/formatters.h71
-rw-r--r--src/zenhttp/include/zenhttp/httpstats.h38
-rw-r--r--src/zenhttp/include/zenhttp/httpstatus.h38
-rw-r--r--src/zenhttp/include/zenhttp/httptest.h55
-rw-r--r--src/zenhttp/monitoring/httpstats.cpp62
-rw-r--r--src/zenhttp/monitoring/httpstatus.cpp62
-rw-r--r--src/zenhttp/testing/httptest.cpp207
9 files changed, 771 insertions, 0 deletions
diff --git a/src/zenhttp/diagsvcs.cpp b/src/zenhttp/diagsvcs.cpp
new file mode 100644
index 000000000..8fa71b375
--- /dev/null
+++ b/src/zenhttp/diagsvcs.cpp
@@ -0,0 +1,127 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenhttp/diagsvcs.h"
+
+#include <zencore/compactbinary.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/config.h>
+#include <zencore/filesystem.h>
+#include <zencore/logging.h>
+#include <zencore/string.h>
+#include <fstream>
+#include <sstream>
+
+#include <json11.hpp>
+
+namespace zen {
+
+using namespace std::literals;
+
+bool
+ReadFile(const std::string& Path, StringBuilderBase& Out)
+{
+ try
+ {
+ constexpr auto ReadSize = std::size_t{4096};
+ auto FileStream = std::ifstream{Path};
+
+ std::string Buf(ReadSize, '\0');
+ while (FileStream.read(&Buf[0], ReadSize))
+ {
+ Out.Append(std::string_view(&Buf[0], FileStream.gcount()));
+ }
+ Out.Append(std::string_view(&Buf[0], FileStream.gcount()));
+
+ return true;
+ }
+ catch (std::exception&)
+ {
+ Out.Reset();
+ return false;
+ }
+}
+
+HttpHealthService::HttpHealthService()
+{
+ m_Router.RegisterRoute(
+ "",
+ [](HttpRouterRequest& RoutedReq) {
+ HttpServerRequest& HttpReq = RoutedReq.ServerRequest();
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, u8"OK!"sv);
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "info",
+ [this](HttpRouterRequest& RoutedReq) {
+ HttpServerRequest& HttpReq = RoutedReq.ServerRequest();
+
+ CbObjectWriter Writer;
+ Writer << "DataRoot"sv << m_HealthInfo.DataRoot.string();
+ Writer << "AbsLogPath"sv << m_HealthInfo.AbsLogPath.string();
+ Writer << "BuildVersion"sv << m_HealthInfo.BuildVersion;
+ Writer << "HttpServerClass"sv << m_HealthInfo.HttpServerClass;
+
+ HttpReq.WriteResponse(HttpResponseCode::OK, Writer.Save());
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "log",
+ [this](HttpRouterRequest& RoutedReq) {
+ HttpServerRequest& HttpReq = RoutedReq.ServerRequest();
+
+ zen::Log().flush();
+
+ std::filesystem::path Path =
+ m_HealthInfo.AbsLogPath.empty() ? m_HealthInfo.DataRoot / "logs/zenserver.log" : m_HealthInfo.AbsLogPath;
+
+ ExtendableStringBuilder<4096> Sb;
+ if (ReadFile(Path.string(), Sb) && Sb.Size() > 0)
+ {
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, Sb.ToView());
+ }
+ else
+ {
+ HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ },
+ HttpVerb::kGet);
+ m_Router.RegisterRoute(
+ "version",
+ [this](HttpRouterRequest& RoutedReq) {
+ HttpServerRequest& HttpReq = RoutedReq.ServerRequest();
+ if (HttpReq.GetQueryParams().GetValue("detailed") == "true")
+ {
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, ZEN_CFG_VERSION_BUILD_STRING_FULL);
+ }
+ else
+ {
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, ZEN_CFG_VERSION);
+ }
+ },
+ HttpVerb::kGet);
+}
+
+void
+HttpHealthService::SetHealthInfo(HealthServiceInfo&& Info)
+{
+ m_HealthInfo = std::move(Info);
+}
+
+const char*
+HttpHealthService::BaseUri() const
+{
+ return "/health/";
+}
+
+void
+HttpHealthService::HandleRequest(HttpServerRequest& Request)
+{
+ if (!m_Router.HandleRequest(Request))
+ {
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, u8"OK!"sv);
+ }
+}
+
+} // namespace zen
diff --git a/src/zenhttp/include/zenhttp/diagsvcs.h b/src/zenhttp/include/zenhttp/diagsvcs.h
new file mode 100644
index 000000000..bd03f8023
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/diagsvcs.h
@@ -0,0 +1,111 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/iobuffer.h>
+#include <zenhttp/httpserver.h>
+
+#include <filesystem>
+
+//////////////////////////////////////////////////////////////////////////
+
+namespace zen {
+
+class HttpTestService : public HttpService
+{
+ uint32_t LogPoint = 0;
+
+public:
+ HttpTestService() {}
+ ~HttpTestService() = default;
+
+ virtual const char* BaseUri() const override { return "/test/"; }
+
+ virtual void HandleRequest(HttpServerRequest& Request) override
+ {
+ using namespace std::literals;
+
+ auto Uri = Request.RelativeUri();
+
+ if (Uri == "hello"sv)
+ {
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, u8"hello world!"sv);
+
+ // OutputLogMessageInternal(&LogPoint, 0, 0);
+ }
+ else if (Uri == "1K"sv)
+ {
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, m_1k);
+ }
+ else if (Uri == "1M"sv)
+ {
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, m_1m);
+ }
+ else if (Uri == "1M_1k"sv)
+ {
+ std::vector<IoBuffer> Buffers;
+ Buffers.reserve(1024);
+
+ for (int i = 0; i < 1024; ++i)
+ {
+ Buffers.push_back(m_1k);
+ }
+
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, Buffers);
+ }
+ else if (Uri == "1G"sv)
+ {
+ std::vector<IoBuffer> Buffers;
+ Buffers.reserve(1024);
+
+ for (int i = 0; i < 1024; ++i)
+ {
+ Buffers.push_back(m_1m);
+ }
+
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, Buffers);
+ }
+ else if (Uri == "1G_1k"sv)
+ {
+ std::vector<IoBuffer> Buffers;
+ Buffers.reserve(1024 * 1024);
+
+ for (int i = 0; i < 1024 * 1024; ++i)
+ {
+ Buffers.push_back(m_1k);
+ }
+
+ Request.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, Buffers);
+ }
+ }
+
+private:
+ IoBuffer m_1m{1024 * 1024};
+ IoBuffer m_1k{m_1m, 0u, 1024};
+};
+
+struct HealthServiceInfo
+{
+ std::filesystem::path DataRoot;
+ std::filesystem::path AbsLogPath;
+ std::string HttpServerClass;
+ std::string BuildVersion;
+};
+
+class HttpHealthService : public HttpService
+{
+public:
+ HttpHealthService();
+ ~HttpHealthService() = default;
+
+ void SetHealthInfo(HealthServiceInfo&& Info);
+
+ virtual const char* BaseUri() const override;
+ virtual void HandleRequest(HttpServerRequest& Request) override final;
+
+private:
+ HttpRequestRouter m_Router;
+ HealthServiceInfo m_HealthInfo;
+};
+
+} // namespace zen
diff --git a/src/zenhttp/include/zenhttp/formatters.h b/src/zenhttp/include/zenhttp/formatters.h
new file mode 100644
index 000000000..759df58d3
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/formatters.h
@@ -0,0 +1,71 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/compactbinary.h>
+#include <zencore/compactbinaryvalidation.h>
+#include <zencore/iobuffer.h>
+#include <zencore/string.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cpr/cpr.h>
+#include <fmt/format.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+template<>
+struct fmt::formatter<cpr::Response>
+{
+ constexpr auto parse(format_parse_context& Ctx) -> decltype(Ctx.begin()) { return Ctx.end(); }
+
+ template<typename FormatContext>
+ auto format(const cpr::Response& Response, FormatContext& Ctx) -> decltype(Ctx.out())
+ {
+ using namespace std::literals;
+
+ if (Response.status_code == 200 || Response.status_code == 201)
+ {
+ return fmt::format_to(Ctx.out(),
+ "Url: {}, Status: {}, Bytes: {}/{} (Up/Down), Elapsed: {}s",
+ Response.url.str(),
+ Response.status_code,
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ Response.elapsed);
+ }
+ else
+ {
+ const auto It = Response.header.find("Content-Type");
+ const std::string_view ContentType = It != Response.header.end() ? It->second : "<None>"sv;
+
+ if (ContentType == "application/x-ue-cb"sv)
+ {
+ zen::IoBuffer Body(zen::IoBuffer::Wrap, Response.text.data(), Response.text.size());
+ zen::CbObjectView Obj(Body.Data());
+ zen::ExtendableStringBuilder<256> Sb;
+ std::string_view Json = Obj.ToJson(Sb).ToView();
+
+ return fmt::format_to(Ctx.out(),
+ "Url: {}, Status: {}, Bytes: {}/{} (Up/Down), Elapsed: {}s, Response: '{}', Reason: '{}'",
+ Response.url.str(),
+ Response.status_code,
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ Response.elapsed,
+ Json,
+ Response.reason);
+ }
+ else
+ {
+ return fmt::format_to(Ctx.out(),
+ "Url: {}, Status: {}, Bytes: {}/{} (Up/Down), Elapsed: {}s, Reponse: '{}', Reason: '{}'",
+ Response.url.str(),
+ Response.status_code,
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ Response.elapsed,
+ Response.text,
+ Response.reason);
+ }
+ }
+ }
+};
diff --git a/src/zenhttp/include/zenhttp/httpstats.h b/src/zenhttp/include/zenhttp/httpstats.h
new file mode 100644
index 000000000..732815a9a
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/httpstats.h
@@ -0,0 +1,38 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging.h>
+#include <zenhttp/httpserver.h>
+
+#include <map>
+
+namespace zen {
+
+struct IHttpStatsProvider
+{
+ virtual void HandleStatsRequest(HttpServerRequest& Request) = 0;
+};
+
+class HttpStatsService : public HttpService
+{
+public:
+ HttpStatsService();
+ ~HttpStatsService();
+
+ virtual const char* BaseUri() const override;
+ virtual void HandleRequest(HttpServerRequest& Request) override;
+ void RegisterHandler(std::string_view Id, IHttpStatsProvider& Provider);
+ void UnregisterHandler(std::string_view Id, IHttpStatsProvider& Provider);
+
+private:
+ spdlog::logger& m_Log;
+ HttpRequestRouter m_Router;
+
+ inline spdlog::logger& Log() { return m_Log; }
+
+ RwLock m_Lock;
+ std::map<std::string, IHttpStatsProvider*> m_Providers;
+};
+
+} // namespace zen \ No newline at end of file
diff --git a/src/zenhttp/include/zenhttp/httpstatus.h b/src/zenhttp/include/zenhttp/httpstatus.h
new file mode 100644
index 000000000..b04e45324
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/httpstatus.h
@@ -0,0 +1,38 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging.h>
+#include <zenhttp/httpserver.h>
+
+#include <map>
+
+namespace zen {
+
+struct IHttpStatusProvider
+{
+ virtual void HandleStatusRequest(HttpServerRequest& Request) = 0;
+};
+
+class HttpStatusService : public HttpService
+{
+public:
+ HttpStatusService();
+ ~HttpStatusService();
+
+ virtual const char* BaseUri() const override;
+ virtual void HandleRequest(HttpServerRequest& Request) override;
+ void RegisterHandler(std::string_view Id, IHttpStatusProvider& Provider);
+ void UnregisterHandler(std::string_view Id, IHttpStatusProvider& Provider);
+
+private:
+ spdlog::logger& m_Log;
+ HttpRequestRouter m_Router;
+
+ RwLock m_Lock;
+ std::map<std::string, IHttpStatusProvider*> m_Providers;
+
+ inline spdlog::logger& Log() { return m_Log; }
+};
+
+} // namespace zen \ No newline at end of file
diff --git a/src/zenhttp/include/zenhttp/httptest.h b/src/zenhttp/include/zenhttp/httptest.h
new file mode 100644
index 000000000..57d2d63f3
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/httptest.h
@@ -0,0 +1,55 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging.h>
+#include <zencore/stats.h>
+#include <zenhttp/httpserver.h>
+#include <zenhttp/websocket.h>
+
+#include <atomic>
+
+namespace zen {
+
+/**
+ * Test service to facilitate testing the HTTP framework and client interactions
+ */
+class HttpTestingService : public HttpService, public WebSocketService
+{
+public:
+ HttpTestingService();
+ ~HttpTestingService();
+
+ virtual const char* BaseUri() const override;
+ virtual void HandleRequest(HttpServerRequest& Request) override;
+ virtual Ref<IHttpPackageHandler> HandlePackageRequest(HttpServerRequest& HttpServiceRequest) override;
+
+ class PackageHandler : public IHttpPackageHandler
+ {
+ public:
+ PackageHandler(HttpTestingService& Svc, uint32_t RequestId);
+ ~PackageHandler();
+
+ virtual void FilterOffer(std::vector<IoHash>& OfferCids) override;
+ virtual void OnRequestBegin() override;
+ virtual IoBuffer CreateTarget(const IoHash& Cid, uint64_t StorageSize) override;
+ virtual void OnRequestComplete() override;
+
+ private:
+ HttpTestingService& m_Svc;
+ uint32_t m_RequestId;
+ };
+
+private:
+ virtual void RegisterHandlers(WebSocketServer& Server) override;
+ virtual bool HandleRequest(const WebSocketMessage& Request) override;
+
+ HttpRequestRouter m_Router;
+ std::atomic<uint32_t> m_Counter{0};
+ metrics::OperationTiming m_TimingStats;
+
+ RwLock m_RwLock;
+ std::unordered_map<uint32_t, Ref<PackageHandler>> m_HandlerMap;
+};
+
+} // namespace zen
diff --git a/src/zenhttp/monitoring/httpstats.cpp b/src/zenhttp/monitoring/httpstats.cpp
new file mode 100644
index 000000000..a873b4977
--- /dev/null
+++ b/src/zenhttp/monitoring/httpstats.cpp
@@ -0,0 +1,62 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenhttp/httpstats.h"
+
+namespace zen {
+
+HttpStatsService::HttpStatsService() : m_Log(logging::Get("stats"))
+{
+}
+
+HttpStatsService::~HttpStatsService()
+{
+}
+
+const char*
+HttpStatsService::BaseUri() const
+{
+ return "/stats/";
+}
+
+void
+HttpStatsService::RegisterHandler(std::string_view Id, IHttpStatsProvider& Provider)
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_Providers.insert_or_assign(std::string(Id), &Provider);
+}
+
+void
+HttpStatsService::UnregisterHandler(std::string_view Id, IHttpStatsProvider& Provider)
+{
+ ZEN_UNUSED(Provider);
+
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_Providers.erase(std::string(Id));
+}
+
+void
+HttpStatsService::HandleRequest(HttpServerRequest& Request)
+{
+ using namespace std::literals;
+
+ std::string_view Key = Request.RelativeUri();
+
+ switch (Request.RequestVerb())
+ {
+ case HttpVerb::kHead:
+ case HttpVerb::kGet:
+ {
+ RwLock::SharedLockScope _(m_Lock);
+ if (auto It = m_Providers.find(std::string{Key}); It != end(m_Providers))
+ {
+ return It->second->HandleStatsRequest(Request);
+ }
+ }
+
+ [[fallthrough]];
+ default:
+ return;
+ }
+}
+
+} // namespace zen
diff --git a/src/zenhttp/monitoring/httpstatus.cpp b/src/zenhttp/monitoring/httpstatus.cpp
new file mode 100644
index 000000000..9fecedb06
--- /dev/null
+++ b/src/zenhttp/monitoring/httpstatus.cpp
@@ -0,0 +1,62 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenhttp/httpstatus.h"
+
+namespace zen {
+
+HttpStatusService::HttpStatusService() : m_Log(logging::Get("status"))
+{
+}
+
+HttpStatusService::~HttpStatusService()
+{
+}
+
+const char*
+HttpStatusService::BaseUri() const
+{
+ return "/status/";
+}
+
+void
+HttpStatusService::RegisterHandler(std::string_view Id, IHttpStatusProvider& Provider)
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_Providers.insert_or_assign(std::string(Id), &Provider);
+}
+
+void
+HttpStatusService::UnregisterHandler(std::string_view Id, IHttpStatusProvider& Provider)
+{
+ ZEN_UNUSED(Provider);
+
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_Providers.erase(std::string(Id));
+}
+
+void
+HttpStatusService::HandleRequest(HttpServerRequest& Request)
+{
+ using namespace std::literals;
+
+ std::string_view Key = Request.RelativeUri();
+
+ switch (Request.RequestVerb())
+ {
+ case HttpVerb::kHead:
+ case HttpVerb::kGet:
+ {
+ RwLock::SharedLockScope _(m_Lock);
+ if (auto It = m_Providers.find(std::string{Key}); It != end(m_Providers))
+ {
+ return It->second->HandleStatusRequest(Request);
+ }
+ }
+
+ [[fallthrough]];
+ default:
+ return;
+ }
+}
+
+} // namespace zen
diff --git a/src/zenhttp/testing/httptest.cpp b/src/zenhttp/testing/httptest.cpp
new file mode 100644
index 000000000..3e77e9c8c
--- /dev/null
+++ b/src/zenhttp/testing/httptest.cpp
@@ -0,0 +1,207 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenhttp/httptest.h"
+
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinarypackage.h>
+#include <zencore/timer.h>
+
+namespace zen {
+
+using namespace std::literals;
+
+HttpTestingService::HttpTestingService()
+{
+ m_Router.RegisterRoute(
+ "hello",
+ [](HttpRouterRequest& Req) { Req.ServerRequest().WriteResponse(HttpResponseCode::OK); },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "hello_slow",
+ [](HttpRouterRequest& Req) {
+ Req.ServerRequest().WriteResponseAsync([](HttpServerRequest& Request) {
+ Stopwatch Timer;
+ Sleep(1000);
+ Request.WriteResponse(HttpResponseCode::OK,
+ HttpContentType::kText,
+ fmt::format("hello, took me {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())));
+ });
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "hello_veryslow",
+ [](HttpRouterRequest& Req) {
+ Req.ServerRequest().WriteResponseAsync([](HttpServerRequest& Request) {
+ Stopwatch Timer;
+ Sleep(60000);
+ Request.WriteResponse(HttpResponseCode::OK,
+ HttpContentType::kText,
+ fmt::format("hello, took me {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs())));
+ });
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "hello_throw",
+ [](HttpRouterRequest& Req) {
+ Req.ServerRequest().WriteResponseAsync([](HttpServerRequest&) { throw std::runtime_error("intentional error"); });
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "hello_noresponse",
+ [](HttpRouterRequest& Req) { Req.ServerRequest().WriteResponseAsync([](HttpServerRequest&) {}); },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "metrics",
+ [this](HttpRouterRequest& Req) {
+ metrics::OperationTiming::Scope _(m_TimingStats);
+ Req.ServerRequest().WriteResponse(HttpResponseCode::OK);
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "get_metrics",
+ [this](HttpRouterRequest& Req) {
+ CbObjectWriter Cbo;
+ EmitSnapshot("requests", m_TimingStats, Cbo);
+ Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "json",
+ [this](HttpRouterRequest& Req) {
+ CbObjectWriter Obj;
+ Obj.AddBool("ok", true);
+ Obj.AddInteger("counter", ++m_Counter);
+ Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save());
+ },
+ HttpVerb::kGet);
+
+ m_Router.RegisterRoute(
+ "echo",
+ [](HttpRouterRequest& Req) {
+ IoBuffer Body = Req.ServerRequest().ReadPayload();
+ Req.ServerRequest().WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, Body);
+ },
+ HttpVerb::kPost);
+
+ m_Router.RegisterRoute(
+ "package",
+ [](HttpRouterRequest& Req) {
+ CbPackage Pkg = Req.ServerRequest().ReadPayloadPackage();
+ Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Pkg);
+ },
+ HttpVerb::kPost);
+}
+
+HttpTestingService::~HttpTestingService()
+{
+}
+
+const char*
+HttpTestingService::BaseUri() const
+{
+ return "/testing/";
+}
+
+void
+HttpTestingService::HandleRequest(HttpServerRequest& Request)
+{
+ m_Router.HandleRequest(Request);
+}
+
+Ref<IHttpPackageHandler>
+HttpTestingService::HandlePackageRequest(HttpServerRequest& HttpServiceRequest)
+{
+ RwLock::ExclusiveLockScope _(m_RwLock);
+
+ const uint32_t RequestId = HttpServiceRequest.RequestId();
+
+ if (auto It = m_HandlerMap.find(RequestId); It != m_HandlerMap.end())
+ {
+ Ref<HttpTestingService::PackageHandler> Handler = std::move(It->second);
+
+ m_HandlerMap.erase(It);
+
+ return Handler;
+ }
+
+ auto InsertResult = m_HandlerMap.insert({RequestId, Ref<PackageHandler>()});
+
+ _.ReleaseNow();
+
+ return (InsertResult.first->second = Ref<PackageHandler>(new PackageHandler(*this, RequestId)));
+}
+
+void
+HttpTestingService::RegisterHandlers(WebSocketServer& Server)
+{
+ Server.RegisterRequestHandler("SayHello"sv, *this);
+}
+
+bool
+HttpTestingService::HandleRequest(const WebSocketMessage& RequestMsg)
+{
+ CbObjectView Request = RequestMsg.Body().GetObject();
+
+ std::string_view Method = Request["Method"].AsString();
+
+ if (Method != "SayHello"sv)
+ {
+ return false;
+ }
+
+ CbObjectWriter Response;
+ Response.AddString("Result"sv, "Hello Friend!!");
+
+ WebSocketMessage ResponseMsg;
+ ResponseMsg.SetMessageType(WebSocketMessageType::kResponse);
+ ResponseMsg.SetCorrelationId(RequestMsg.CorrelationId());
+ ResponseMsg.SetSocketId(RequestMsg.SocketId());
+ ResponseMsg.SetBody(Response.Save());
+
+ SocketServer().SendResponse(std::move(ResponseMsg));
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+HttpTestingService::PackageHandler::PackageHandler(HttpTestingService& Svc, uint32_t RequestId) : m_Svc(Svc), m_RequestId(RequestId)
+{
+}
+
+HttpTestingService::PackageHandler::~PackageHandler()
+{
+}
+
+void
+HttpTestingService::PackageHandler::FilterOffer(std::vector<IoHash>& OfferCids)
+{
+ ZEN_UNUSED(OfferCids);
+ // No-op
+ return;
+}
+void
+HttpTestingService::PackageHandler::OnRequestBegin()
+{
+}
+
+void
+HttpTestingService::PackageHandler::OnRequestComplete()
+{
+}
+
+IoBuffer
+HttpTestingService::PackageHandler::CreateTarget(const IoHash& Cid, uint64_t StorageSize)
+{
+ ZEN_UNUSED(Cid);
+ return IoBuffer{StorageSize};
+}
+
+} // namespace zen