// Copyright Epic Games, Inc. All Rights Reserved. #include "httphubservice.h" #include "hub.h" #include "storageserverinstance.h" #include #include #include namespace zen { namespace { bool HandleFailureResults(HttpServerRequest& Request, const Hub::Response& Resp) { if (Resp.ResponseCode == Hub::EResponseCode::Rejected) { if (Resp.Message.empty()) { Request.WriteResponse(HttpResponseCode::Conflict); } else { Request.WriteResponse(HttpResponseCode::Conflict, HttpContentType::kText, Resp.Message); } return true; } if (Resp.ResponseCode == Hub::EResponseCode::NotFound) { if (Resp.Message.empty()) { Request.WriteResponse(HttpResponseCode::NotFound); } else { Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, Resp.Message); } return true; } return false; } } // namespace HttpHubService::HttpHubService(Hub& Hub) : m_Hub(Hub) { using namespace std::literals; m_Router.AddMatcher("moduleid", [](std::string_view Str) -> bool { for (const auto C : Str) { if (std::isalnum(C) || C == '-') { // fine } else { // not fine return false; } } return true; }); m_Router.RegisterRoute( "status", [this](HttpRouterRequest& Req) { CbObjectWriter Obj; Obj.BeginArray("modules"); m_Hub.EnumerateModules([&Obj](std::string_view ModuleId, const Hub::InstanceInfo& Info) { Obj.BeginObject(); { Obj << "moduleId" << ModuleId; Obj << "state" << ToString(Info.State); Obj << "port" << Info.Port; Obj.BeginObject("process_metrics"); { Obj << "MemoryBytes" << Info.Metrics.MemoryBytes; Obj << "KernelTimeMs" << Info.Metrics.KernelTimeMs; Obj << "UserTimeMs" << Info.Metrics.UserTimeMs; Obj << "WorkingSetSize" << Info.Metrics.WorkingSetSize; Obj << "PeakWorkingSetSize" << Info.Metrics.PeakWorkingSetSize; Obj << "PagefileUsage" << Info.Metrics.PagefileUsage; Obj << "PeakPagefileUsage" << Info.Metrics.PeakPagefileUsage; } Obj.EndObject(); } Obj.EndObject(); }); Obj.EndArray(); Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); }, HttpVerb::kGet); m_Router.RegisterRoute( "modules/{moduleid}", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); if (Req.ServerRequest().RequestVerb() == HttpVerb::kDelete) { HandleModuleDelete(Req.ServerRequest(), ModuleId); } else { HandleModuleGet(Req.ServerRequest(), ModuleId); } }, HttpVerb::kGet | HttpVerb::kDelete); m_Router.RegisterRoute( "modules/{moduleid}/provision", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { HubProvisionedInstanceInfo Info; Hub::Response Resp = m_Hub.Provision(ModuleId, Info); if (HandleFailureResults(Req.ServerRequest(), Resp)) { return; } const HttpResponseCode HttpCode = (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; Obj << "baseUri" << Info.BaseUri; Obj << "port" << Info.Port; return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while provisioning module '{}': {}", ModuleId, Ex.what()); throw; } }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/deprovision", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { Hub::Response Resp = m_Hub.Deprovision(std::string(ModuleId)); if (HandleFailureResults(Req.ServerRequest(), Resp)) { return; } const HttpResponseCode HttpCode = (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while deprovisioning module '{}': {}", ModuleId, Ex.what()); throw; } }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/hibernate", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { Hub::Response Resp = m_Hub.Hibernate(std::string(ModuleId)); if (HandleFailureResults(Req.ServerRequest(), Resp)) { return; } const HttpResponseCode HttpCode = (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while hibernating module '{}': {}", ModuleId, Ex.what()); throw; } }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/wake", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { Hub::Response Resp = m_Hub.Wake(std::string(ModuleId)); if (HandleFailureResults(Req.ServerRequest(), Resp)) { return; } const HttpResponseCode HttpCode = (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while waking module '{}': {}", ModuleId, Ex.what()); throw; } }, HttpVerb::kPost); m_Router.RegisterRoute( "stats", [this](HttpRouterRequest& Req) { CbObjectWriter Obj; Obj << "currentInstanceCount" << m_Hub.GetInstanceCount(); Obj << "maxInstanceCount" << m_Hub.GetMaxInstanceCount(); Obj << "instanceLimit" << m_Hub.GetConfig().InstanceLimit; Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); }, HttpVerb::kGet); } HttpHubService::~HttpHubService() { } const char* HttpHubService::BaseUri() const { return "/hub/"; } void HttpHubService::SetNotificationEndpoint(std::string_view UpstreamNotificationEndpoint, std::string_view InstanceId) { ZEN_UNUSED(UpstreamNotificationEndpoint, InstanceId); // TODO: store these for use in notifications, on some interval/criteria which is currently TBD } void HttpHubService::HandleRequest(zen::HttpServerRequest& Request) { m_Router.HandleRequest(Request); } void HttpHubService::HandleModuleGet(HttpServerRequest& Request, std::string_view ModuleId) { Hub::InstanceInfo InstanceInfo; if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } CbObjectWriter Obj; Obj << "moduleId" << ModuleId; Obj << "state" << ToString(InstanceInfo.State); Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } void HttpHubService::HandleModuleDelete(HttpServerRequest& Request, std::string_view ModuleId) { Hub::InstanceInfo InstanceInfo; if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } if (InstanceInfo.State == HubInstanceState::Provisioned || InstanceInfo.State == HubInstanceState::Hibernated || InstanceInfo.State == HubInstanceState::Crashed) { try { Hub::Response Resp = m_Hub.Deprovision(std::string(ModuleId)); if (HandleFailureResults(Request, Resp)) { return; } // TODO: nuke all related storage const HttpResponseCode HttpCode = (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; return Request.WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while deprovisioning module '{}': {}", ModuleId, Ex.what()); throw; } } // TODO: nuke all related storage CbObjectWriter Obj; Obj << "moduleId" << ModuleId; Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } } // namespace zen