diff options
Diffstat (limited to 'src/zenhttp/httpserver.cpp')
| -rw-r--r-- | src/zenhttp/httpserver.cpp | 138 |
1 files changed, 129 insertions, 9 deletions
diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp index e5cfbcbae..38021be16 100644 --- a/src/zenhttp/httpserver.cpp +++ b/src/zenhttp/httpserver.cpp @@ -329,6 +329,10 @@ ReasonStringForHttpResultCode(int HttpCode) return "Continue"sv; case 101: return "Switching Protocols"sv; + case 102: + return "Processing"sv; + case 103: + return "Early Hints"sv; // 2xx Success @@ -338,12 +342,20 @@ ReasonStringForHttpResultCode(int HttpCode) return "Created"sv; case 202: return "Accepted"sv; + case 203: + return "Non-Authoritative Information"sv; case 204: return "No Content"sv; case 205: return "Reset Content"sv; case 206: return "Partial Content"sv; + case 207: + return "Multi-Status"sv; + case 208: + return "Already Reported"sv; + case 226: + return "IM Used"sv; // 3xx Redirection @@ -424,6 +436,8 @@ ReasonStringForHttpResultCode(int HttpCode) return "Too Many Requests"sv; case 431: return "Request Header Fields Too Large"sv; + case 451: + return "Unavailable For Legal Reasons"sv; // 5xx Server errors @@ -465,6 +479,18 @@ HttpService::HandlePackageRequest(HttpServerRequest& HttpServiceRequest) return Ref<IHttpPackageHandler>(); } +bool +HttpService::AcceptsLocalFileReferences() const +{ + return false; +} + +const ILocalRefPolicy* +HttpService::GetLocalRefPolicy() const +{ + return nullptr; +} + ////////////////////////////////////////////////////////////////////////// HttpServerRequest::HttpServerRequest(HttpService& Service) : m_Service(Service) @@ -691,7 +717,10 @@ HttpServerRequest::ReadPayloadPackage() { if (IoBuffer Payload = ReadPayload()) { - return ParsePackageMessage(std::move(Payload)); + ParseFlags Flags = + (IsLocalMachineRequest() && m_Service.AcceptsLocalFileReferences()) ? ParseFlags::kAllowLocalReferences : ParseFlags::kDefault; + const ILocalRefPolicy* Policy = EnumHasAllFlags(Flags, ParseFlags::kAllowLocalReferences) ? m_Service.GetLocalRefPolicy() : nullptr; + return ParsePackageMessage(std::move(Payload), {}, Flags, Policy); } return {}; @@ -798,7 +827,18 @@ HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request) const HttpVerb Verb = Request.RequestVerb(); - std::string_view Uri = Request.RelativeUri(); + std::string_view Uri = Request.RelativeUri(); + + // Strip the separator slash left over after the service prefix is removed. + // When a service has BaseUri "/foo", the prefix length is set to len("/foo") = 4. + // Stripping 4 chars from "/foo/bar" yields "/bar" — the path separator becomes + // the first character of the relative URI. Remove it so patterns like "bar" or + // "{id}" match without needing to account for the leading slash. + if (!Uri.empty() && Uri.front() == '/') + { + Uri.remove_prefix(1); + } + HttpRouterRequest RouterRequest(Request); for (const MatcherEndpoint& Handler : m_MatcherEndpoints) @@ -974,6 +1014,12 @@ HttpServer::SetHttpRequestFilter(IHttpRequestFilter* RequestFilter) OnSetHttpRequestFilter(RequestFilter); } +void +HttpServer::HandleStatsRequest(HttpServerRequest& Request) +{ + Request.WriteResponse(HttpResponseCode::OK, CollectStats()); +} + CbObject HttpServer::CollectStats() { @@ -988,6 +1034,9 @@ HttpServer::CollectStats() } Cbo.EndObject(); + Cbo << "distinct_clients" << m_ClientAddresses.Count(); + Cbo << "distinct_sessions" << m_ClientSessions.Count(); + Cbo.BeginObject("websockets"); { Cbo << "active_connections" << GetActiveWebSocketConnectionCount(); @@ -1001,12 +1050,6 @@ HttpServer::CollectStats() return Cbo.Save(); } -void -HttpServer::HandleStatsRequest(HttpServerRequest& Request) -{ - Request.WriteResponse(HttpResponseCode::OK, CollectStats()); -} - ////////////////////////////////////////////////////////////////////////// HttpRpcHandler::HttpRpcHandler() @@ -1245,7 +1288,12 @@ HandlePackageOffers(HttpService& Service, HttpServerRequest& Request, Ref<IHttpP return PackageHandlerRef->CreateTarget(Cid, Size); }; - CbPackage Package = ParsePackageMessage(Request.ReadPayload(), CreateBuffer); + ParseFlags PkgFlags = (Request.IsLocalMachineRequest() && Service.AcceptsLocalFileReferences()) + ? ParseFlags::kAllowLocalReferences + : ParseFlags::kDefault; + const ILocalRefPolicy* PkgPolicy = + EnumHasAllFlags(PkgFlags, ParseFlags::kAllowLocalReferences) ? Service.GetLocalRefPolicy() : nullptr; + CbPackage Package = ParsePackageMessage(Request.ReadPayload(), CreateBuffer, PkgFlags, PkgPolicy); PackageHandlerRef->OnRequestComplete(); } @@ -1443,6 +1491,78 @@ TEST_CASE("http.common") } } + SUBCASE("router-leading-slash") + { + // Verify that HandleRequest strips the leading slash that server implementations + // leave in RelativeUri() when the service base URI has no trailing slash. + // e.g. BaseUri "/stats" + prefix-strip of "/stats/foo" yields "/foo", not "foo". + + bool HandledLiteral = false; + bool HandledPattern = false; + bool HandledTwoSeg = false; + std::vector<std::string> Captures; + auto Reset = [&] { + HandledLiteral = HandledPattern = HandledTwoSeg = false; + Captures.clear(); + }; + + TestHttpService Service; + HttpRequestRouter r; + + r.AddMatcher("seg", [](std::string_view In) -> bool { return !In.empty() && In.find('/') == std::string_view::npos; }); + + r.RegisterRoute( + "activity_counters", + [&](auto& /*Req*/) { HandledLiteral = true; }, + HttpVerb::kGet); + + r.RegisterRoute( + "{seg}", + [&](auto& Req) { + HandledPattern = true; + Captures = {std::string(Req.GetCapture(1))}; + }, + HttpVerb::kGet); + + r.RegisterRoute( + "prefix/{seg}", + [&](auto& Req) { + HandledTwoSeg = true; + Captures = {std::string(Req.GetCapture(1))}; + }, + HttpVerb::kGet); + + // Single-segment literal with leading slash — simulates real server RelativeUri + { + Reset(); + TestHttpServerRequest req{Service, "/activity_counters"sv}; + r.HandleRequest(req); + CHECK(HandledLiteral); + CHECK(!HandledPattern); + } + + // Single-segment pattern with leading slash + { + Reset(); + TestHttpServerRequest req{Service, "/hello"sv}; + r.HandleRequest(req); + CHECK(!HandledLiteral); + CHECK(HandledPattern); + REQUIRE_EQ(Captures.size(), 1); + CHECK_EQ(Captures[0], "hello"sv); + } + + // Two-segment route with leading slash — first literal segment + { + Reset(); + TestHttpServerRequest req{Service, "/prefix/world"sv}; + r.HandleRequest(req); + CHECK(HandledTwoSeg); + REQUIRE_EQ(Captures.size(), 1); + CHECK_EQ(Captures[0], "world"sv); + } + } + SUBCASE("content-type") { for (uint8_t i = 0; i < uint8_t(HttpContentType::kCOUNT); ++i) |