aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/httpserver.cpp
diff options
context:
space:
mode:
authorLiam Mitchell <[email protected]>2026-03-09 18:40:40 -0700
committerLiam Mitchell <[email protected]>2026-03-09 18:40:40 -0700
commit97aa4e5c48305647a5d8f09da5f24bc1ce5540f3 (patch)
tree11062e72f4342aeb2f16ac19d6af20ac0e4acd78 /src/zenhttp/httpserver.cpp
parentMerge branch 'main' into lm/oidctoken-exe-path (diff)
parentupdated chunk–block analyser (#818) (diff)
downloadzen-97aa4e5c48305647a5d8f09da5f24bc1ce5540f3.tar.xz
zen-97aa4e5c48305647a5d8f09da5f24bc1ce5540f3.zip
Merge branch 'main' into lm/oidctoken-exe-path
Diffstat (limited to 'src/zenhttp/httpserver.cpp')
-rw-r--r--src/zenhttp/httpserver.cpp167
1 files changed, 148 insertions, 19 deletions
diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp
index c4e67d4ed..9bae95690 100644
--- a/src/zenhttp/httpserver.cpp
+++ b/src/zenhttp/httpserver.cpp
@@ -23,10 +23,12 @@
#include <zencore/logging.h>
#include <zencore/stream.h>
#include <zencore/string.h>
+#include <zencore/system.h>
#include <zencore/testing.h>
#include <zencore/thread.h>
#include <zenhttp/packageformat.h>
#include <zentelemetry/otlptrace.h>
+#include <zentelemetry/stats.h>
#include <charconv>
#include <mutex>
@@ -463,7 +465,7 @@ HttpService::HandlePackageRequest(HttpServerRequest& HttpServiceRequest)
//////////////////////////////////////////////////////////////////////////
-HttpServerRequest::HttpServerRequest(HttpService& Service) : m_BaseUri(Service.BaseUri())
+HttpServerRequest::HttpServerRequest(HttpService& Service) : m_Service(Service)
{
}
@@ -745,6 +747,10 @@ HttpRequestRouter::RegisterRoute(const char* UriPattern, HttpRequestRouter::Hand
{
if (UriPattern[i] == '}')
{
+ if (i == PatternStart)
+ {
+ throw std::runtime_error(fmt::format("matcher pattern is empty in URI pattern '{}'", UriPattern));
+ }
std::string_view Pattern(&UriPattern[PatternStart], i - PatternStart);
if (auto it = m_MatcherNameMap.find(std::string(Pattern)); it != m_MatcherNameMap.end())
{
@@ -910,8 +916,9 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
CapturedSegments.emplace_back(Uri);
- for (int MatcherIndex : Matchers)
+ for (size_t MatcherOffset = 0; MatcherOffset < Matchers.size(); MatcherOffset++)
{
+ int MatcherIndex = Matchers[MatcherOffset];
if (UriPos >= UriLen)
{
IsMatch = false;
@@ -921,9 +928,9 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
if (MatcherIndex < 0)
{
// Literal match
- int LitIndex = -MatcherIndex - 1;
- const std::string& LitStr = m_Literals[LitIndex];
- size_t LitLen = LitStr.length();
+ int LitIndex = -MatcherIndex - 1;
+ std::string_view LitStr = m_Literals[LitIndex];
+ size_t LitLen = LitStr.length();
if (Uri.substr(UriPos, LitLen) == LitStr)
{
@@ -939,9 +946,18 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
{
// Matcher function
size_t SegmentStart = UriPos;
- while (UriPos < UriLen && Uri[UriPos] != '/')
+
+ if (MatcherOffset == (Matchers.size() - 1))
+ {
+ // Last matcher, use the remaining part of the uri
+ UriPos = UriLen;
+ }
+ else
{
- ++UriPos;
+ while (UriPos < UriLen && Uri[UriPos] != '/')
+ {
+ ++UriPos;
+ }
}
std::string_view Segment = Uri.substr(SegmentStart, UriPos - SegmentStart);
@@ -970,7 +986,7 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
if (otel::Span* ActiveSpan = otel::Span::GetCurrentSpan())
{
ExtendableStringBuilder<128> RoutePath;
- RoutePath.Append(Request.BaseUri());
+ RoutePath.Append(Request.Service().BaseUri());
RoutePath.Append(Handler.Pattern);
ActiveSpan->AddAttribute("http.route"sv, RoutePath.ToView());
}
@@ -994,7 +1010,7 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
if (otel::Span* ActiveSpan = otel::Span::GetCurrentSpan())
{
ExtendableStringBuilder<128> RoutePath;
- RoutePath.Append(Request.BaseUri());
+ RoutePath.Append(Request.Service().BaseUri());
RoutePath.Append(Handler.Pattern);
ActiveSpan->AddAttribute("http.route"sv, RoutePath.ToView());
}
@@ -1014,7 +1030,28 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request)
int
HttpServer::Initialize(int BasePort, std::filesystem::path DataDir)
{
- return OnInitialize(BasePort, std::move(DataDir));
+ m_EffectivePort = OnInitialize(BasePort, std::move(DataDir));
+ m_ExternalHost = OnGetExternalHost();
+ return m_EffectivePort;
+}
+
+std::string
+HttpServer::OnGetExternalHost() const
+{
+ return GetMachineName();
+}
+
+std::string
+HttpServer::GetServiceUri(const HttpService* Service) const
+{
+ if (Service)
+ {
+ return fmt::format("http://{}:{}{}", m_ExternalHost, m_EffectivePort, Service->BaseUri());
+ }
+ else
+ {
+ return fmt::format("http://{}:{}", m_ExternalHost, m_EffectivePort);
+ }
}
void
@@ -1052,6 +1089,45 @@ HttpServer::EnumerateServices(std::function<void(HttpService& Service)>&& Callba
}
}
+void
+HttpServer::SetHttpRequestFilter(IHttpRequestFilter* RequestFilter)
+{
+ OnSetHttpRequestFilter(RequestFilter);
+}
+
+CbObject
+HttpServer::CollectStats()
+{
+ CbObjectWriter Cbo;
+
+ metrics::EmitSnapshot("requests", m_RequestMeter, Cbo);
+
+ Cbo.BeginObject("bytes");
+ {
+ Cbo << "received" << GetTotalBytesReceived();
+ Cbo << "sent" << GetTotalBytesSent();
+ }
+ Cbo.EndObject();
+
+ Cbo.BeginObject("websockets");
+ {
+ Cbo << "active_connections" << GetActiveWebSocketConnectionCount();
+ Cbo << "frames_received" << m_WsFramesReceived.load(std::memory_order_relaxed);
+ Cbo << "frames_sent" << m_WsFramesSent.load(std::memory_order_relaxed);
+ Cbo << "bytes_received" << m_WsBytesReceived.load(std::memory_order_relaxed);
+ Cbo << "bytes_sent" << m_WsBytesSent.load(std::memory_order_relaxed);
+ }
+ Cbo.EndObject();
+
+ return Cbo.Save();
+}
+
+void
+HttpServer::HandleStatsRequest(HttpServerRequest& Request)
+{
+ Request.WriteResponse(HttpResponseCode::OK, CollectStats());
+}
+
//////////////////////////////////////////////////////////////////////////
HttpRpcHandler::HttpRpcHandler()
@@ -1294,6 +1370,8 @@ HandlePackageOffers(HttpService& Service, HttpServerRequest& Request, Ref<IHttpP
#if ZEN_WITH_TESTS
+TEST_SUITE_BEGIN("http.httpserver");
+
TEST_CASE("http.common")
{
using namespace std::literals;
@@ -1310,7 +1388,11 @@ TEST_CASE("http.common")
{
TestHttpServerRequest(HttpService& Service, std::string_view Uri) : HttpServerRequest(Service) { m_Uri = Uri; }
virtual IoBuffer ReadPayload() override { return IoBuffer(); }
- virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span<IoBuffer> Blobs) override
+
+ virtual bool IsLocalMachineRequest() const override { return false; }
+ virtual std::string_view GetAuthorizationHeader() const override { return {}; }
+
+ virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span<IoBuffer> Blobs) override
{
ZEN_UNUSED(ResponseCode, ContentType, Blobs);
}
@@ -1395,20 +1477,33 @@ TEST_CASE("http.common")
SUBCASE("router-matcher")
{
- bool HandledA = false;
- bool HandledAA = false;
- bool HandledAB = false;
- bool HandledAandB = false;
+ bool HandledA = false;
+ bool HandledAA = false;
+ bool HandledAB = false;
+ bool HandledAandB = false;
+ bool HandledAandPath = false;
std::vector<std::string> Captures;
auto Reset = [&] {
- HandledA = HandledAA = HandledAB = HandledAandB = false;
+ HandledA = HandledAA = HandledAB = HandledAandB = HandledAandPath = false;
Captures.clear();
};
TestHttpService Service;
HttpRequestRouter r;
- r.AddMatcher("a", [](std::string_view In) -> bool { return In.length() % 2 == 0; });
- r.AddMatcher("b", [](std::string_view In) -> bool { return In.length() % 3 == 0; });
+
+ r.AddMatcher("a", [](std::string_view In) -> bool { return In.length() % 2 == 0 && In.find('/') == std::string_view::npos; });
+ r.AddMatcher("b", [](std::string_view In) -> bool { return In.length() % 3 == 0 && In.find('/') == std::string_view::npos; });
+ static constexpr AsciiSet ValidPathCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789/_.,;$~{}+-[]%()]ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
+ r.AddMatcher("path", [](std::string_view Str) -> bool { return !Str.empty() && AsciiSet::HasOnly(Str, ValidPathCharactersSet); });
+
+ r.RegisterRoute(
+ "path/{a}/{path}",
+ [&](auto& Req) {
+ HandledAandPath = true;
+ Captures = {std::string(Req.GetCapture(1)), std::string(Req.GetCapture(2))};
+ },
+ HttpVerb::kGet);
+
r.RegisterRoute(
"{a}",
[&](auto& Req) {
@@ -1437,7 +1532,6 @@ TEST_CASE("http.common")
Captures = {std::string(Req.GetCapture(1)), std::string(Req.GetCapture(2))};
},
HttpVerb::kGet);
-
{
Reset();
TestHttpServerRequest req{Service, "ab"sv};
@@ -1445,6 +1539,7 @@ TEST_CASE("http.common")
CHECK(HandledA);
CHECK(!HandledAA);
CHECK(!HandledAB);
+ CHECK(!HandledAandPath);
REQUIRE_EQ(Captures.size(), 1);
CHECK_EQ(Captures[0], "ab"sv);
@@ -1457,6 +1552,7 @@ TEST_CASE("http.common")
CHECK(!HandledA);
CHECK(!HandledAA);
CHECK(HandledAB);
+ CHECK(!HandledAandPath);
REQUIRE_EQ(Captures.size(), 2);
CHECK_EQ(Captures[0], "ab"sv);
CHECK_EQ(Captures[1], "def"sv);
@@ -1470,6 +1566,7 @@ TEST_CASE("http.common")
CHECK(!HandledAA);
CHECK(!HandledAB);
CHECK(HandledAandB);
+ CHECK(!HandledAandPath);
REQUIRE_EQ(Captures.size(), 2);
CHECK_EQ(Captures[0], "ab"sv);
CHECK_EQ(Captures[1], "def"sv);
@@ -1482,6 +1579,7 @@ TEST_CASE("http.common")
CHECK(!HandledA);
CHECK(!HandledAA);
CHECK(!HandledAB);
+ CHECK(!HandledAandPath);
}
{
@@ -1491,6 +1589,35 @@ TEST_CASE("http.common")
CHECK(HandledA);
CHECK(!HandledAA);
CHECK(!HandledAB);
+ CHECK(!HandledAandPath);
+ REQUIRE_EQ(Captures.size(), 1);
+ CHECK_EQ(Captures[0], "a123"sv);
+ }
+
+ {
+ Reset();
+ TestHttpServerRequest req{Service, "path/ab/simple_path.txt"sv};
+ r.HandleRequest(req);
+ CHECK(!HandledA);
+ CHECK(!HandledAA);
+ CHECK(!HandledAB);
+ CHECK(HandledAandPath);
+ REQUIRE_EQ(Captures.size(), 2);
+ CHECK_EQ(Captures[0], "ab"sv);
+ CHECK_EQ(Captures[1], "simple_path.txt"sv);
+ }
+
+ {
+ Reset();
+ TestHttpServerRequest req{Service, "path/ab/directory/and/path.txt"sv};
+ r.HandleRequest(req);
+ CHECK(!HandledA);
+ CHECK(!HandledAA);
+ CHECK(!HandledAB);
+ CHECK(HandledAandPath);
+ REQUIRE_EQ(Captures.size(), 2);
+ CHECK_EQ(Captures[0], "ab"sv);
+ CHECK_EQ(Captures[1], "directory/and/path.txt"sv);
}
}
@@ -1508,6 +1635,8 @@ TEST_CASE("http.common")
}
}
+TEST_SUITE_END();
+
void
http_forcelink()
{