// Copyright Epic Games, Inc. All Rights Reserved. #include "httpsessions.h" #include #include #include #include #include #include "sessions.h" namespace zen { using namespace std::literals; HttpSessionsService::HttpSessionsService(HttpStatusService& StatusService, HttpStatsService& StatsService, SessionsService& Sessions) : m_Log(logging::Get("sessions")) , m_StatusService(StatusService) , m_StatsService(StatsService) , m_Sessions(Sessions) { Initialize(); } HttpSessionsService::~HttpSessionsService() { m_StatsService.UnregisterHandler("sessions", *this); m_StatusService.UnregisterHandler("sessions", *this); } const char* HttpSessionsService::BaseUri() const { return "/sessions/"; } void HttpSessionsService::HandleRequest(HttpServerRequest& Request) { metrics::OperationTiming::Scope $(m_HttpRequests); if (m_Router.HandleRequest(Request) == false) { ZEN_WARN("No route found for {0}", Request.RelativeUri()); return Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv); } } CbObject HttpSessionsService::CollectStats() { ZEN_TRACE_CPU("SessionsService::Stats"); CbObjectWriter Cbo; EmitSnapshot("requests", m_HttpRequests, Cbo); Cbo.BeginObject("sessions"); { Cbo << "readcount" << m_SessionsStats.SessionReadCount; Cbo << "writecount" << m_SessionsStats.SessionWriteCount; Cbo << "deletecount" << m_SessionsStats.SessionDeleteCount; Cbo << "listcount" << m_SessionsStats.SessionListCount; Cbo << "requestcount" << m_SessionsStats.RequestCount; Cbo << "badrequestcount" << m_SessionsStats.BadRequestCount; Cbo << "count" << m_Sessions.GetSessionCount(); } Cbo.EndObject(); return Cbo.Save(); } void HttpSessionsService::HandleStatsRequest(HttpServerRequest& HttpReq) { HttpReq.WriteResponse(HttpResponseCode::OK, CollectStats()); } void HttpSessionsService::HandleStatusRequest(HttpServerRequest& Request) { ZEN_TRACE_CPU("HttpSessionsService::Status"); CbObjectWriter Cbo; Cbo << "ok" << true; Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); } void HttpSessionsService::Initialize() { using namespace std::literals; ZEN_INFO("Initializing Sessions Service"); static constexpr AsciiSet ValidHexCharactersSet{"0123456789abcdefABCDEF"}; m_Router.AddMatcher("session_id", [](std::string_view Str) -> bool { return Str.length() == Oid::StringLength && AsciiSet::HasOnly(Str, ValidHexCharactersSet); }); m_Router.RegisterRoute( "list", [this](HttpRouterRequest& Req) { ListSessionsRequest(Req); }, HttpVerb::kGet); m_Router.RegisterRoute( "{session_id}", [this](HttpRouterRequest& Req) { SessionRequest(Req); }, HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kPut | HttpVerb::kDelete); m_Router.RegisterRoute( "", [this](HttpRouterRequest& Req) { ListSessionsRequest(Req); }, HttpVerb::kGet); m_StatsService.RegisterHandler("sessions", *this); m_StatusService.RegisterHandler("sessions", *this); } static void WriteSessionInfo(CbWriter& Writer, const SessionsService::SessionInfo& Info) { Writer << "id" << Info.Id; if (!Info.AppName.empty()) { Writer << "appname" << Info.AppName; } if (Info.JobId != Oid::Zero) { Writer << "jobid" << Info.JobId; } Writer << "created_at" << Info.CreatedAt; Writer << "updated_at" << Info.UpdatedAt; if (Info.Metadata.GetSize() > 0) { Writer.BeginObject("metadata"); for (const CbField& Field : Info.Metadata) { Writer.AddField(Field); } Writer.EndObject(); } } void HttpSessionsService::ListSessionsRequest(HttpRouterRequest& Req) { HttpServerRequest& ServerRequest = Req.ServerRequest(); m_SessionsStats.SessionListCount++; m_SessionsStats.RequestCount++; std::vector> Sessions = m_Sessions.GetSessions(); CbObjectWriter Response; Response.BeginArray("sessions"); for (const Ref& Session : Sessions) { Response.BeginObject(); { WriteSessionInfo(Response, Session->Info()); } Response.EndObject(); } Response.EndArray(); return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save()); } void HttpSessionsService::SessionRequest(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++; switch (ServerRequest.RequestVerb()) { case HttpVerb::kPost: case HttpVerb::kPut: { IoBuffer Payload = ServerRequest.ReadPayload(); CbObject RequestObject; if (Payload.GetSize() > 0) { if (CbValidateError ValidationResult = ValidateCompactBinary(Payload.GetView(), CbValidateMode::All); ValidationResult != CbValidateError::None) { m_SessionsStats.BadRequestCount++; return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, fmt::format("Invalid payload: {}", zen::ToString(ValidationResult))); } RequestObject = LoadCompactBinaryObject(Payload); } if (ServerRequest.RequestVerb() == HttpVerb::kPost) { std::string AppName(RequestObject["appname"sv].AsString()); Oid JobId = RequestObject["jobid"sv].AsObjectId(); CbObjectView MetadataView = RequestObject["metadata"sv].AsObjectView(); m_SessionsStats.SessionWriteCount++; if (m_Sessions.RegisterSession(SessionId, std::move(AppName), JobId, MetadataView)) { return ServerRequest.WriteResponse(HttpResponseCode::Created, HttpContentType::kText, fmt::format("{}", SessionId)); } else { // Already exists - try update instead if (m_Sessions.UpdateSession(SessionId, MetadataView)) { return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", SessionId)); } return ServerRequest.WriteResponse(HttpResponseCode::InternalServerError); } } else { // PUT - update only m_SessionsStats.SessionWriteCount++; if (m_Sessions.UpdateSession(SessionId, RequestObject["metadata"sv].AsObjectView())) { return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", SessionId)); } return ServerRequest.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, fmt::format("Session '{}' not found", SessionId)); } } case HttpVerb::kGet: { m_SessionsStats.SessionReadCount++; Ref Session = m_Sessions.GetSession(SessionId); if (Session) { CbObjectWriter Response; WriteSessionInfo(Response, Session->Info()); return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save()); } return ServerRequest.WriteResponse(HttpResponseCode::NotFound); } case HttpVerb::kDelete: { m_SessionsStats.SessionDeleteCount++; if (m_Sessions.RemoveSession(SessionId)) { return ServerRequest.WriteResponse(HttpResponseCode::OK); } return ServerRequest.WriteResponse(HttpResponseCode::NotFound); } default: { return ServerRequest.WriteResponse(HttpResponseCode::MethodNotAllowed); } } } } // namespace zen