// Copyright Epic Games, Inc. All Rights Reserved. #include "httphubservice.h" #include "hub.h" #include "storageserverinstance.h" #include #include #include namespace zen { 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](StorageServerInstance& Instance) { Obj.BeginObject(); Obj << "moduleId" << Instance.GetModuleId(); Obj << "provisioned" << Instance.IsProvisioned(); 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); std::string FailureReason = "unknown"; HttpResponseCode ResponseCode = HttpResponseCode::OK; try { HubProvisionedInstanceInfo Info; if (m_Hub.Provision(ModuleId, /* out */ Info, /* out */ FailureReason)) { CbObjectWriter Obj; Obj << "moduleId" << ModuleId; Obj << "baseUri" << Info.BaseUri; Obj << "port" << Info.Port; Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); return; } else { ResponseCode = HttpResponseCode::BadRequest; } } catch (const std::exception& Ex) { ZEN_ERROR("Exception while provisioning module '{}': {}", ModuleId, Ex.what()); FailureReason = Ex.what(); ResponseCode = HttpResponseCode::InternalServerError; } Req.ServerRequest().WriteResponse(ResponseCode, HttpContentType::kText, FailureReason); }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/deprovision", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); std::string FailureReason = "unknown"; try { if (!m_Hub.Deprovision(std::string(ModuleId), /* out */ FailureReason)) { if (FailureReason.empty()) { return Req.ServerRequest().WriteResponse(HttpResponseCode::NotFound); } else { return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, FailureReason); } } CbObjectWriter Obj; Obj << "moduleId" << ModuleId; return Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while deprovisioning module '{}': {}", ModuleId, Ex.what()); FailureReason = Ex.what(); } Req.ServerRequest().WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, FailureReason); }, 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) { StorageServerInstance* Instance = nullptr; if (!m_Hub.Find(ModuleId, &Instance)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } // TODO: A separate http request for the modules/{moduleid}/deprovision endpoint can be called and deprovision the instance leaving us // with a dangling pointer... CbObjectWriter Obj; Obj << "moduleId" << Instance->GetModuleId(); Obj << "provisioned" << Instance->IsProvisioned(); Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } void HttpHubService::HandleModuleDelete(HttpServerRequest& Request, std::string_view ModuleId) { StorageServerInstance* Instance = nullptr; if (!m_Hub.Find(ModuleId, &Instance)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } // TODO: deprovision and nuke all related storage CbObjectWriter Obj; Obj << "moduleId" << Instance->GetModuleId(); Obj << "provisioned" << Instance->IsProvisioned(); Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } } // namespace zen