aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/sessions/httpsessions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenserver/sessions/httpsessions.cpp')
-rw-r--r--src/zenserver/sessions/httpsessions.cpp161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/zenserver/sessions/httpsessions.cpp b/src/zenserver/sessions/httpsessions.cpp
index 6996ce5b5..f6eb77c15 100644
--- a/src/zenserver/sessions/httpsessions.cpp
+++ b/src/zenserver/sessions/httpsessions.cpp
@@ -111,6 +111,11 @@ HttpSessionsService::Initialize()
HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kPut | HttpVerb::kDelete);
m_Router.RegisterRoute(
+ "{session_id}/log",
+ [this](HttpRouterRequest& Req) { SessionLogRequest(Req); },
+ HttpVerb::kGet | HttpVerb::kPost);
+
+ m_Router.RegisterRoute(
"",
[this](HttpRouterRequest& Req) { ListSessionsRequest(Req); },
HttpVerb::kGet);
@@ -315,6 +320,162 @@ HttpSessionsService::SessionRequest(HttpRouterRequest& Req)
//////////////////////////////////////////////////////////////////////////
//
+// Session log
+//
+
+static void
+WriteLogEntry(CbWriter& Writer, const SessionsService::LogEntry& Entry)
+{
+ Writer << "timestamp" << Entry.Timestamp;
+ if (!Entry.Level.empty())
+ {
+ Writer << "level" << Entry.Level;
+ }
+ if (!Entry.Message.empty())
+ {
+ Writer << "message" << Entry.Message;
+ }
+ if (Entry.Data.GetSize() > 0)
+ {
+ Writer.AddObject("data"sv, Entry.Data);
+ }
+}
+
+void
+HttpSessionsService::SessionLogRequest(HttpRouterRequest& Req)
+{
+ HttpServerRequest& ServerRequest = Req.ServerRequest();
+
+ const Oid SessionId = Oid::TryFromHexString(Req.GetCapture(1));
+ if (SessionId == Oid::Zero)
+ {
+ m_SessionsStats.BadRequestCount++;
+ return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Invalid session id '{}'", Req.GetCapture(1)));
+ }
+
+ m_SessionsStats.RequestCount++;
+
+ Ref<SessionsService::Session> Session = m_Sessions.GetSession(SessionId);
+ if (!Session)
+ {
+ return ServerRequest.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Session '{}' not found", SessionId));
+ }
+
+ if (ServerRequest.RequestVerb() == HttpVerb::kPost)
+ {
+ m_SessionsStats.SessionWriteCount++;
+
+ if (ServerRequest.RequestContentType() == HttpContentType::kText)
+ {
+ // Raw text — split by newlines, one entry per line
+ IoBuffer Payload = ServerRequest.ReadPayload();
+ std::string_view Text(reinterpret_cast<const char*>(Payload.GetData()), Payload.GetSize());
+ const DateTime Now = DateTime::Now();
+
+ size_t Pos = 0;
+ while (Pos < Text.size())
+ {
+ size_t End = Text.find('\n', Pos);
+ if (End == std::string_view::npos)
+ {
+ End = Text.size();
+ }
+
+ std::string_view Line = Text.substr(Pos, End - Pos);
+ // Strip trailing \r
+ if (!Line.empty() && Line.back() == '\r')
+ {
+ Line.remove_suffix(1);
+ }
+
+ if (!Line.empty())
+ {
+ Session->AppendLog(SessionsService::LogEntry{
+ .Timestamp = Now,
+ .Message = std::string(Line),
+ });
+ }
+
+ Pos = End + 1;
+ }
+ }
+ else
+ {
+ // Structured log (JSON or CbObject)
+ // Accepts a single record or an "entries" array of records
+ CbObject RequestObject = ServerRequest.ReadPayloadObject();
+ const DateTime Now = DateTime::Now();
+
+ auto AppendFromObject = [&](CbObjectView Obj) {
+ std::string Level(Obj["level"sv].AsString());
+ std::string Message(Obj["message"sv].AsString());
+ CbObjectView DataView = Obj["data"sv].AsObjectView();
+
+ Session->AppendLog(SessionsService::LogEntry{
+ .Timestamp = Now,
+ .Level = std::move(Level),
+ .Message = std::move(Message),
+ .Data = CbObject::Clone(DataView),
+ });
+ };
+
+ CbFieldView EntriesField = RequestObject["entries"sv];
+ if (EntriesField.IsArray())
+ {
+ for (CbFieldView Entry : EntriesField)
+ {
+ AppendFromObject(Entry.AsObjectView());
+ }
+ }
+ else
+ {
+ AppendFromObject(RequestObject);
+ }
+ }
+
+ return ServerRequest.WriteResponse(HttpResponseCode::OK);
+ }
+ else
+ {
+ // GET - return log entries
+ m_SessionsStats.SessionReadCount++;
+
+ HttpServerRequest::QueryParams Params = ServerRequest.GetQueryParams();
+ uint32_t Limit = 0;
+ uint32_t Offset = 0;
+
+ if (std::string_view LimitStr = Params.GetValue("limit"sv); !LimitStr.empty())
+ {
+ Limit = uint32_t(std::strtoul(std::string(LimitStr).c_str(), nullptr, 10));
+ }
+ if (std::string_view OffsetStr = Params.GetValue("offset"sv); !OffsetStr.empty())
+ {
+ Offset = uint32_t(std::strtoul(std::string(OffsetStr).c_str(), nullptr, 10));
+ }
+
+ std::vector<SessionsService::LogEntry> Entries = Session->GetLogEntries(Limit, Offset);
+
+ CbObjectWriter Response;
+ Response << "total" << Session->GetLogCount();
+ Response.BeginArray("entries");
+ for (const SessionsService::LogEntry& Entry : Entries)
+ {
+ Response.BeginObject();
+ WriteLogEntry(Response, Entry);
+ Response.EndObject();
+ }
+ Response.EndArray();
+
+ return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+//
// WebSocket push
//