aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-08-29 09:59:07 +0200
committerGitHub <[email protected]>2023-08-29 09:59:07 +0200
commita343a7c4d3592d08137c56f65fd439875adbff3b (patch)
tree257d64fe06229d64d77dc35e04c065050ca006e6 /src
parent0.2.18 (diff)
downloadzen-a343a7c4d3592d08137c56f65fd439875adbff3b.tar.xz
zen-a343a7c4d3592d08137c56f65fd439875adbff3b.zip
add helper functions in HttpProjectService to help with readability (#378)
Diffstat (limited to 'src')
-rw-r--r--src/zenserver/projectstore/httpprojectstore.cpp2501
-rw-r--r--src/zenserver/projectstore/httpprojectstore.h20
2 files changed, 1326 insertions, 1195 deletions
diff --git a/src/zenserver/projectstore/httpprojectstore.cpp b/src/zenserver/projectstore/httpprojectstore.cpp
index 124f26692..9a5894c56 100644
--- a/src/zenserver/projectstore/httpprojectstore.cpp
+++ b/src/zenserver/projectstore/httpprojectstore.cpp
@@ -259,1462 +259,1573 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
m_Router.RegisterRoute(
"",
- [this](HttpRouterRequest& Req) { Req.ServerRequest().WriteResponse(HttpResponseCode::OK, m_ProjectStore->GetProjectsList()); },
+ [this](HttpRouterRequest& Req) { HandleProjectListRequest(Req); },
HttpVerb::kGet);
m_Router.RegisterRoute(
"list",
- [this](HttpRouterRequest& Req) { Req.ServerRequest().WriteResponse(HttpResponseCode::OK, m_ProjectStore->GetProjectsList()); },
+ [this](HttpRouterRequest& Req) { HandleProjectListRequest(Req); },
HttpVerb::kGet);
m_Router.RegisterRoute(
"{project}/oplog/{log}/batch",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
-
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchProject();
+ [this](HttpRouterRequest& Req) { HandleChunkBatchRequest(Req); },
+ HttpVerb::kPost);
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchOplog(OplogId);
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/files",
+ [this](HttpRouterRequest& Req) { HandleFilesRequest(Req); },
+ HttpVerb::kGet);
- // Parse Request
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/{chunk}/info",
+ [this](HttpRouterRequest& Req) { HandleChunkInfoRequest(Req); },
+ HttpVerb::kGet);
- IoBuffer Payload = HttpReq.ReadPayload();
- BinaryReader Reader(Payload);
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/{chunk}",
+ [this](HttpRouterRequest& Req) { HandleChunkByIdRequest(Req); },
+ HttpVerb::kGet | HttpVerb::kHead);
- struct RequestHeader
- {
- enum
- {
- kMagic = 0xAAAA'77AC
- };
- uint32_t Magic;
- uint32_t ChunkCount;
- uint32_t Reserved1;
- uint32_t Reserved2;
- };
-
- struct RequestChunkEntry
- {
- Oid ChunkId;
- uint32_t CorrelationId;
- uint64_t Offset;
- uint64_t RequestBytes;
- };
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/{hash}",
+ [this](HttpRouterRequest& Req) { HandleChunkByCidRequest(Req); },
+ HttpVerb::kGet | HttpVerb::kPost);
- if (Payload.Size() <= sizeof(RequestHeader))
- {
- m_ProjectStats.BadRequestCount++;
- HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/prep",
+ [this](HttpRouterRequest& Req) { HandleOplogOpPrepRequest(Req); },
+ HttpVerb::kPost);
- RequestHeader RequestHdr;
- Reader.Read(&RequestHdr, sizeof RequestHdr);
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/new",
+ [this](HttpRouterRequest& Req) { HandleOplogOpNewRequest(Req); },
+ HttpVerb::kPost);
- if (RequestHdr.Magic != RequestHeader::kMagic)
- {
- m_ProjectStats.BadRequestCount++;
- HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/{op}",
+ [this](HttpRouterRequest& Req) { HandleOpLogOpRequest(Req); },
+ HttpVerb::kGet);
- std::vector<RequestChunkEntry> RequestedChunks;
- RequestedChunks.resize(RequestHdr.ChunkCount);
- Reader.Read(RequestedChunks.data(), sizeof(RequestChunkEntry) * RequestHdr.ChunkCount);
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}",
+ [this](HttpRouterRequest& Req) { HandleOpLogRequest(Req); },
+ HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
- // Make Response
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/entries",
+ [this](HttpRouterRequest& Req) { HandleOpLogEntriesRequest(Req); },
+ HttpVerb::kGet);
- struct ResponseHeader
- {
- uint32_t Magic = 0xbada'b00f;
- uint32_t ChunkCount;
- uint32_t Reserved1 = 0;
- uint32_t Reserved2 = 0;
- };
+ m_Router.RegisterRoute(
+ "{project}",
+ [this](HttpRouterRequest& Req) { HandleProjectRequest(Req); },
+ HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
- struct ResponseChunkEntry
- {
- uint32_t CorrelationId;
- uint32_t Flags = 0;
- uint64_t ChunkSize;
- };
-
- std::vector<IoBuffer> OutBlobs;
- OutBlobs.emplace_back(sizeof(ResponseHeader) + RequestHdr.ChunkCount * sizeof(ResponseChunkEntry));
- for (uint32_t ChunkIndex = 0; ChunkIndex < RequestHdr.ChunkCount; ++ChunkIndex)
- {
- const RequestChunkEntry& RequestedChunk = RequestedChunks[ChunkIndex];
- IoBuffer FoundChunk = FoundLog->FindChunk(RequestedChunk.ChunkId);
- if (FoundChunk)
- {
- if (RequestedChunk.Offset > 0 || RequestedChunk.RequestBytes < uint64_t(-1))
- {
- uint64_t Offset = RequestedChunk.Offset;
- if (Offset > FoundChunk.Size())
- {
- Offset = FoundChunk.Size();
- }
- uint64_t Size = RequestedChunk.RequestBytes;
- if ((Offset + Size) > FoundChunk.Size())
- {
- Size = FoundChunk.Size() - Offset;
- }
- FoundChunk = IoBuffer(FoundChunk, Offset, Size);
- }
- }
- OutBlobs.emplace_back(std::move(FoundChunk));
- }
- uint8_t* ResponsePtr = reinterpret_cast<uint8_t*>(OutBlobs[0].MutableData());
- ResponseHeader ResponseHdr;
- ResponseHdr.ChunkCount = RequestHdr.ChunkCount;
- memcpy(ResponsePtr, &ResponseHdr, sizeof(ResponseHdr));
- ResponsePtr += sizeof(ResponseHdr);
- for (uint32_t ChunkIndex = 0; ChunkIndex < RequestHdr.ChunkCount; ++ChunkIndex)
- {
- // const RequestChunkEntry& RequestedChunk = RequestedChunks[ChunkIndex];
- const IoBuffer& FoundChunk(OutBlobs[ChunkIndex + 1]);
- ResponseChunkEntry ResponseChunk;
- ResponseChunk.CorrelationId = ChunkIndex;
- if (FoundChunk)
- {
- ResponseChunk.ChunkSize = FoundChunk.Size();
- }
- else
- {
- ResponseChunk.ChunkSize = uint64_t(-1);
- }
- memcpy(ResponsePtr, &ResponseChunk, sizeof(ResponseChunk));
- ResponsePtr += sizeof(ResponseChunk);
- }
- m_ProjectStats.ChunkHitCount += RequestHdr.ChunkCount;
- return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, OutBlobs);
- },
+ // Push a oplog container
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/save",
+ [this](HttpRouterRequest& Req) { HandleOplogSaveRequest(Req); },
HttpVerb::kPost);
+ // Pull a oplog container
m_Router.RegisterRoute(
- "{project}/oplog/{log}/files",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
-
- // File manifest fetch, returns the client file list
+ "{project}/oplog/{log}/load",
+ [this](HttpRouterRequest& Req) { HandleOplogLoadRequest(Req); },
+ HttpVerb::kGet);
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
+ // Do an rpc style operation on project/oplog
+ m_Router.RegisterRoute(
+ "{project}/oplog/{log}/rpc",
+ [this](HttpRouterRequest& Req) { HandleRpcRequest(Req); },
+ HttpVerb::kPost);
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ m_Router.RegisterRoute(
+ "details\\$",
+ [this](HttpRouterRequest& Req) { HandleDetailsRequest(Req); },
+ HttpVerb::kGet);
- const bool FilterClient = Params.GetValue("filter"sv) == "client"sv;
+ m_Router.RegisterRoute(
+ "details\\$/{project}",
+ [this](HttpRouterRequest& Req) { HandleProjectDetailsRequest(Req); },
+ HttpVerb::kGet);
- CbObject ResponsePayload;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetProjectFiles(ProjectId, OplogId, FilterClient, ResponsePayload);
- if (Result.first == HttpResponseCode::OK)
- {
- return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
- }
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- },
+ m_Router.RegisterRoute(
+ "details\\$/{project}/{log}",
+ [this](HttpRouterRequest& Req) { HandleOplogDetailsRequest(Req); },
HttpVerb::kGet);
m_Router.RegisterRoute(
- "{project}/oplog/{log}/{chunk}/info",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ "details\\$/{project}/{log}/{chunk}",
+ [this](HttpRouterRequest& Req) { HandleOplogOpDetailsRequest(Req); },
+ HttpVerb::kGet);
+}
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- const auto& ChunkId = Req.GetCapture(3);
+HttpProjectService::~HttpProjectService()
+{
+ m_StatsService.UnregisterHandler("prj", *this);
+}
- CbObject ResponsePayload;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetChunkInfo(ProjectId, OplogId, ChunkId, ResponsePayload);
- if (Result.first == HttpResponseCode::OK)
- {
- m_ProjectStats.ChunkHitCount++;
- return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
- }
- else if (Result.first == HttpResponseCode::NotFound)
- {
- m_ProjectStats.ChunkMissCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
- }
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- },
- HttpVerb::kGet);
+const char*
+HttpProjectService::BaseUri() const
+{
+ return "/prj/";
+}
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/{chunk}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+void
+HttpProjectService::HandleRequest(HttpServerRequest& Request)
+{
+ if (m_Router.HandleRequest(Request) == false)
+ {
+ ZEN_WARN("No route found for {0}", Request.RelativeUri());
+ }
+}
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- const auto& ChunkId = Req.GetCapture(3);
+void
+HttpProjectService::HandleStatsRequest(HttpServerRequest& HttpReq)
+{
+ ZEN_TRACE_CPU("ProjectService::Stats");
- uint64_t Offset = 0;
- uint64_t Size = ~(0ull);
+ const GcStorageSize StoreSize = m_ProjectStore->StorageSize();
+ const CidStoreSize CidSize = m_CidStore.TotalSize();
- auto QueryParms = HttpReq.GetQueryParams();
+ CbObjectWriter Cbo;
+ Cbo.BeginObject("store");
+ {
+ Cbo.BeginObject("size");
+ {
+ Cbo << "disk" << StoreSize.DiskSize;
+ Cbo << "memory" << StoreSize.MemorySize;
+ }
+ Cbo.EndObject();
- if (auto OffsetParm = QueryParms.GetValue("offset"); OffsetParm.empty() == false)
- {
- if (auto OffsetVal = ParseInt<uint64_t>(OffsetParm))
- {
- Offset = OffsetVal.value();
- }
- else
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
- }
+ Cbo.BeginObject("project");
+ {
+ Cbo << "readcount" << m_ProjectStats.ProjectReadCount << "writecount" << m_ProjectStats.ProjectWriteCount << "deletecount"
+ << m_ProjectStats.ProjectDeleteCount;
+ }
+ Cbo.EndObject();
- if (auto SizeParm = QueryParms.GetValue("size"); SizeParm.empty() == false)
- {
- if (auto SizeVal = ParseInt<uint64_t>(SizeParm))
- {
- Size = SizeVal.value();
- }
- else
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
- }
+ Cbo.BeginObject("oplog");
+ {
+ Cbo << "readcount" << m_ProjectStats.OpLogReadCount << "writecount" << m_ProjectStats.OpLogWriteCount << "deletecount"
+ << m_ProjectStats.OpLogDeleteCount;
+ }
+ Cbo.EndObject();
- HttpContentType AcceptType = HttpReq.AcceptContentType();
+ Cbo.BeginObject("op");
+ {
+ Cbo << "hitcount" << m_ProjectStats.OpHitCount << "misscount" << m_ProjectStats.OpMissCount << "writecount"
+ << m_ProjectStats.OpWriteCount;
+ }
+ Cbo.EndObject();
- IoBuffer Chunk;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetChunkRange(ProjectId, OplogId, ChunkId, Offset, Size, AcceptType, Chunk);
- if (Result.first == HttpResponseCode::OK)
- {
- m_ProjectStats.ChunkHitCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' '{}'", ProjectId, OplogId, ChunkId, ToString(Chunk.GetContentType()));
- return HttpReq.WriteResponse(HttpResponseCode::OK, Chunk.GetContentType(), Chunk);
- }
- else if (Result.first == HttpResponseCode::NotFound)
- {
- m_ProjectStats.ChunkMissCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
- }
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- },
- HttpVerb::kGet | HttpVerb::kHead);
+ Cbo.BeginObject("chunk");
+ {
+ Cbo << "hitcount" << m_ProjectStats.ChunkHitCount << "misscount" << m_ProjectStats.ChunkMissCount << "writecount"
+ << m_ProjectStats.ChunkWriteCount;
+ }
+ Cbo.EndObject();
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/{hash}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ Cbo << "badrequestcount" << m_ProjectStats.BadRequestCount;
+ }
+ Cbo.EndObject();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- const auto& Cid = Req.GetCapture(3);
- HttpContentType AcceptType = HttpReq.AcceptContentType();
- HttpContentType RequestType = HttpReq.RequestContentType();
+ Cbo.BeginObject("cid");
+ {
+ Cbo.BeginObject("size");
+ {
+ Cbo << "tiny" << CidSize.TinySize;
+ Cbo << "small" << CidSize.SmallSize;
+ Cbo << "large" << CidSize.LargeSize;
+ Cbo << "total" << CidSize.TotalSize;
+ }
+ Cbo.EndObject();
+ }
+ Cbo.EndObject();
- switch (HttpReq.RequestVerb())
- {
- case HttpVerb::kGet:
- {
- IoBuffer Value;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetChunk(ProjectId, OplogId, Cid, AcceptType, Value);
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+}
- if (Result.first == HttpResponseCode::OK)
- {
- m_ProjectStats.ChunkHitCount++;
- return HttpReq.WriteResponse(HttpResponseCode::OK, Value.GetContentType(), Value);
- }
- else if (Result.first == HttpResponseCode::NotFound)
- {
- m_ProjectStats.ChunkMissCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, Cid);
- }
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- }
- case HttpVerb::kPost:
- {
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->PutChunk(ProjectId, OplogId, Cid, RequestType, HttpReq.ReadPayload());
- if (Result.first == HttpResponseCode::OK || Result.first == HttpResponseCode::Created)
- {
- m_ProjectStats.ChunkWriteCount++;
- return HttpReq.WriteResponse(Result.first);
- }
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- }
- break;
- }
- },
- HttpVerb::kGet | HttpVerb::kPost);
+void
+HttpProjectService::HandleProjectListRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ProjectList");
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/prep",
- [this](HttpRouterRequest& Req) {
- ZEN_TRACE_CPU("ProjectService::OplogPrep");
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ CbArray ProjectsList = m_ProjectStore->GetProjectsList();
+ HttpReq.WriteResponse(HttpResponseCode::OK, ProjectsList);
+}
- HttpServerRequest& HttpReq = Req.ServerRequest();
+void
+HttpProjectService::HandleChunkBatchRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ChunkBatch");
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchProject();
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchOplog(OplogId);
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchOplog(OplogId);
- // This operation takes a list of referenced hashes and decides which
- // chunks are not present on this server. This list is then returned in
- // the "need" list in the response
+ // Parse Request
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject RequestObject = LoadCompactBinaryObject(Payload);
+ IoBuffer Payload = HttpReq.ReadPayload();
+ BinaryReader Reader(Payload);
- std::vector<IoHash> NeedList;
+ struct RequestHeader
+ {
+ enum
+ {
+ kMagic = 0xAAAA'77AC
+ };
+ uint32_t Magic;
+ uint32_t ChunkCount;
+ uint32_t Reserved1;
+ uint32_t Reserved2;
+ };
- for (auto Entry : RequestObject["have"sv])
- {
- const IoHash FileHash = Entry.AsHash();
+ struct RequestChunkEntry
+ {
+ Oid ChunkId;
+ uint32_t CorrelationId;
+ uint64_t Offset;
+ uint64_t RequestBytes;
+ };
- if (!m_CidStore.ContainsChunk(FileHash))
- {
- ZEN_DEBUG("prep - NEED: {}", FileHash);
+ if (Payload.Size() <= sizeof(RequestHeader))
+ {
+ m_ProjectStats.BadRequestCount++;
+ HttpReq.WriteResponse(HttpResponseCode::BadRequest);
+ }
- NeedList.push_back(FileHash);
- }
- }
+ RequestHeader RequestHdr;
+ Reader.Read(&RequestHdr, sizeof RequestHdr);
+
+ if (RequestHdr.Magic != RequestHeader::kMagic)
+ {
+ m_ProjectStats.BadRequestCount++;
+ HttpReq.WriteResponse(HttpResponseCode::BadRequest);
+ }
- CbObjectWriter Cbo;
- Cbo.BeginArray("need");
+ std::vector<RequestChunkEntry> RequestedChunks;
+ RequestedChunks.resize(RequestHdr.ChunkCount);
+ Reader.Read(RequestedChunks.data(), sizeof(RequestChunkEntry) * RequestHdr.ChunkCount);
- for (const IoHash& Hash : NeedList)
+ // Make Response
+
+ struct ResponseHeader
+ {
+ uint32_t Magic = 0xbada'b00f;
+ uint32_t ChunkCount;
+ uint32_t Reserved1 = 0;
+ uint32_t Reserved2 = 0;
+ };
+
+ struct ResponseChunkEntry
+ {
+ uint32_t CorrelationId;
+ uint32_t Flags = 0;
+ uint64_t ChunkSize;
+ };
+
+ std::vector<IoBuffer> OutBlobs;
+ OutBlobs.emplace_back(sizeof(ResponseHeader) + RequestHdr.ChunkCount * sizeof(ResponseChunkEntry));
+ for (uint32_t ChunkIndex = 0; ChunkIndex < RequestHdr.ChunkCount; ++ChunkIndex)
+ {
+ const RequestChunkEntry& RequestedChunk = RequestedChunks[ChunkIndex];
+ IoBuffer FoundChunk = FoundLog->FindChunk(RequestedChunk.ChunkId);
+ if (FoundChunk)
+ {
+ if (RequestedChunk.Offset > 0 || RequestedChunk.RequestBytes < uint64_t(-1))
{
- Cbo << Hash;
+ uint64_t Offset = RequestedChunk.Offset;
+ if (Offset > FoundChunk.Size())
+ {
+ Offset = FoundChunk.Size();
+ }
+ uint64_t Size = RequestedChunk.RequestBytes;
+ if ((Offset + Size) > FoundChunk.Size())
+ {
+ Size = FoundChunk.Size() - Offset;
+ }
+ FoundChunk = IoBuffer(FoundChunk, Offset, Size);
}
+ }
+ OutBlobs.emplace_back(std::move(FoundChunk));
+ }
+ uint8_t* ResponsePtr = reinterpret_cast<uint8_t*>(OutBlobs[0].MutableData());
+ ResponseHeader ResponseHdr;
+ ResponseHdr.ChunkCount = RequestHdr.ChunkCount;
+ memcpy(ResponsePtr, &ResponseHdr, sizeof(ResponseHdr));
+ ResponsePtr += sizeof(ResponseHdr);
+ for (uint32_t ChunkIndex = 0; ChunkIndex < RequestHdr.ChunkCount; ++ChunkIndex)
+ {
+ // const RequestChunkEntry& RequestedChunk = RequestedChunks[ChunkIndex];
+ const IoBuffer& FoundChunk(OutBlobs[ChunkIndex + 1]);
+ ResponseChunkEntry ResponseChunk;
+ ResponseChunk.CorrelationId = ChunkIndex;
+ if (FoundChunk)
+ {
+ ResponseChunk.ChunkSize = FoundChunk.Size();
+ }
+ else
+ {
+ ResponseChunk.ChunkSize = uint64_t(-1);
+ }
+ memcpy(ResponsePtr, &ResponseChunk, sizeof(ResponseChunk));
+ ResponsePtr += sizeof(ResponseChunk);
+ }
+ m_ProjectStats.ChunkHitCount += RequestHdr.ChunkCount;
+ return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, OutBlobs);
+}
+
+void
+HttpProjectService::HandleFilesRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::Files");
- Cbo.EndArray();
- CbObject Response = Cbo.Save();
+ using namespace std::literals;
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
- },
- HttpVerb::kPost);
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/new",
- [this](HttpRouterRequest& Req) {
- ZEN_TRACE_CPU("ProjectService::OplogNew");
+ // File manifest fetch, returns the client file list
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
+ const bool FilterClient = Params.GetValue("filter"sv) == "client"sv;
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ CbObject ResponsePayload;
+ std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetProjectFiles(ProjectId, OplogId, FilterClient, ResponsePayload);
+ if (Result.first == HttpResponseCode::OK)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
+ }
+ else
+ {
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
+ }
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
+}
- bool IsUsingSalt = false;
- IoHash SaltHash = IoHash::Zero;
+void
+HttpProjectService::HandleChunkInfoRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ChunkInfo");
- if (std::string_view SaltParam = Params.GetValue("salt"); SaltParam.empty() == false)
- {
- const uint32_t Salt = std::stoi(std::string(SaltParam));
- SaltHash = IoHash::HashBuffer(&Salt, sizeof Salt);
- IsUsingSalt = true;
- }
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchProject();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ const auto& ChunkId = Req.GetCapture(3);
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchOplog(OplogId);
+ CbObject ResponsePayload;
+ std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetChunkInfo(ProjectId, OplogId, ChunkId, ResponsePayload);
+ if (Result.first == HttpResponseCode::OK)
+ {
+ m_ProjectStats.ChunkHitCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
+ }
+ else if (Result.first == HttpResponseCode::NotFound)
+ {
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ }
+ else
+ {
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
+ }
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
+}
- ProjectStore::Oplog& Oplog = *FoundLog;
+void
+HttpProjectService::HandleChunkByIdRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ChunkById");
- IoBuffer Payload = HttpReq.ReadPayload();
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- // This will attempt to open files which may not exist for the case where
- // the prep step rejected the chunk. This should be fixed since there's
- // a performance cost associated with any file system activity
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ const auto& ChunkId = Req.GetCapture(3);
- bool IsValid = true;
- std::vector<IoHash> MissingChunks;
+ uint64_t Offset = 0;
+ uint64_t Size = ~(0ull);
- CbPackage::AttachmentResolver Resolver = [&](const IoHash& Hash) -> SharedBuffer {
- if (m_CidStore.ContainsChunk(Hash))
- {
- // Return null attachment as we already have it, no point in reading it and storing it again
- return {};
- }
+ auto QueryParms = HttpReq.GetQueryParams();
+
+ if (auto OffsetParm = QueryParms.GetValue("offset"); OffsetParm.empty() == false)
+ {
+ if (auto OffsetVal = ParseInt<uint64_t>(OffsetParm))
+ {
+ Offset = OffsetVal.value();
+ }
+ else
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
+ }
+ }
+
+ if (auto SizeParm = QueryParms.GetValue("size"); SizeParm.empty() == false)
+ {
+ if (auto SizeVal = ParseInt<uint64_t>(SizeParm))
+ {
+ Size = SizeVal.value();
+ }
+ else
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
+ }
+ }
- IoHash AttachmentId;
- if (IsUsingSalt)
+ HttpContentType AcceptType = HttpReq.AcceptContentType();
+
+ IoBuffer Chunk;
+ std::pair<HttpResponseCode, std::string> Result =
+ m_ProjectStore->GetChunkRange(ProjectId, OplogId, ChunkId, Offset, Size, AcceptType, Chunk);
+ if (Result.first == HttpResponseCode::OK)
+ {
+ m_ProjectStats.ChunkHitCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' '{}'", ProjectId, OplogId, ChunkId, ToString(Chunk.GetContentType()));
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Chunk.GetContentType(), Chunk);
+ }
+ else if (Result.first == HttpResponseCode::NotFound)
+ {
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ }
+ else
+ {
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
+ }
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
+}
+
+void
+HttpProjectService::HandleChunkByCidRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ChunkByCid");
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ const auto& Cid = Req.GetCapture(3);
+ HttpContentType AcceptType = HttpReq.AcceptContentType();
+ HttpContentType RequestType = HttpReq.RequestContentType();
+
+ switch (HttpReq.RequestVerb())
+ {
+ case HttpVerb::kGet:
+ {
+ IoBuffer Value;
+ std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetChunk(ProjectId, OplogId, Cid, AcceptType, Value);
+
+ if (Result.first == HttpResponseCode::OK)
{
- IoHash AttachmentSpec[]{SaltHash, Hash};
- AttachmentId = IoHash::HashBuffer(MakeMemoryView(AttachmentSpec));
+ m_ProjectStats.ChunkHitCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Value.GetContentType(), Value);
}
- else
+ else if (Result.first == HttpResponseCode::NotFound)
{
- AttachmentId = Hash;
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, Cid);
}
-
- std::filesystem::path AttachmentPath = Oplog.TempPath() / AttachmentId.ToHexString();
- if (IoBuffer Data = IoBufferBuilder::MakeFromTemporaryFile(AttachmentPath))
+ else
{
- return SharedBuffer(std::move(Data));
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
}
- else
+ if (Result.second.empty())
{
- IsValid = false;
- MissingChunks.push_back(Hash);
-
- return {};
+ return HttpReq.WriteResponse(Result.first);
}
- };
-
- CbPackage Package;
-
- if (!legacy::TryLoadCbPackage(Package, Payload, &UniqueBuffer::Alloc, &Resolver))
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
+ }
+ case HttpVerb::kPost:
{
- if (CbObject Core = LoadCompactBinaryObject(Payload))
+ if (!m_ProjectStore->AreDiskWritesAllowed())
{
- Package.SetObject(Core);
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- else
+ std::pair<HttpResponseCode, std::string> Result =
+ m_ProjectStore->PutChunk(ProjectId, OplogId, Cid, RequestType, HttpReq.ReadPayload());
+ if (Result.first == HttpResponseCode::OK || Result.first == HttpResponseCode::Created)
{
- std::filesystem::path BadPackagePath = Oplog.TempPath() / "bad_packages"sv /
- fmt::format("session{}_request{}"sv, HttpReq.SessionId(), HttpReq.RequestId());
-
- ZEN_WARN("Received malformed package! Saving payload to '{}'", BadPackagePath);
-
- WriteFile(BadPackagePath, Payload);
-
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
- HttpContentType::kText,
- u8"request body must be a compact binary object or package in legacy format");
+ m_ProjectStats.ChunkWriteCount++;
+ return HttpReq.WriteResponse(Result.first);
}
- }
-
- m_ProjectStats.ChunkMissCount += MissingChunks.size();
-
- if (!IsValid)
- {
- ExtendableStringBuilder<256> ResponseText;
- ResponseText.Append("Missing chunk references: ");
-
- bool IsFirst = true;
- for (const auto& Hash : MissingChunks)
+ else
{
- if (IsFirst)
+ if (Result.first == HttpResponseCode::BadRequest)
{
- IsFirst = false;
- }
- else
- {
- ResponseText.Append(", ");
+ m_ProjectStats.BadRequestCount++;
}
- Hash.ToHexString(ResponseText);
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
}
-
- return HttpReq.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, ResponseText);
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
+ break;
+ }
+}
- CbObject Core = Package.GetObject();
+void
+HttpProjectService::HandleOplogOpPrepRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogOpPrep");
- if (!Core["key"sv])
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "No oplog entry key specified");
- }
+ using namespace std::literals;
- // Write core to oplog
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- size_t AttachmentCount = Package.GetAttachments().size();
- const uint32_t OpLsn = Oplog.AppendNewOplogEntry(Package);
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
- if (OpLsn == ProjectStore::Oplog::kInvalidOp)
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
- m_ProjectStats.ChunkWriteCount += AttachmentCount;
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchProject();
- m_ProjectStats.OpWriteCount++;
- ZEN_DEBUG("'{}/{}' op #{} ({}) - '{}'", ProjectId, OplogId, OpLsn, NiceBytes(Payload.Size()), Core["key"sv].AsString());
- HttpReq.WriteResponse(HttpResponseCode::Created);
- },
- HttpVerb::kPost);
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchOplog(OplogId);
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/{op}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ // This operation takes a list of referenced hashes and decides which
+ // chunks are not present on this server. This list is then returned in
+ // the "need" list in the response
- const std::string& ProjectId = Req.GetCapture(1);
- const std::string& OplogId = Req.GetCapture(2);
- const std::string& OpIdString = Req.GetCapture(3);
+ IoBuffer Payload = HttpReq.ReadPayload();
+ CbObject RequestObject = LoadCompactBinaryObject(Payload);
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchProject();
+ std::vector<IoHash> NeedList;
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchOplog(OplogId);
+ for (auto Entry : RequestObject["have"sv])
+ {
+ const IoHash FileHash = Entry.AsHash();
- ProjectStore::Oplog& Oplog = *FoundLog;
+ if (!m_CidStore.ContainsChunk(FileHash))
+ {
+ ZEN_DEBUG("prep - NEED: {}", FileHash);
- if (const std::optional<int32_t> OpId = ParseInt<uint32_t>(OpIdString))
- {
- if (std::optional<CbObject> MaybeOp = Oplog.GetOpByIndex(OpId.value()))
- {
- CbObject& Op = MaybeOp.value();
- if (HttpReq.AcceptContentType() == ZenContentType::kCbPackage)
- {
- CbPackage Package;
- Package.SetObject(Op);
+ NeedList.push_back(FileHash);
+ }
+ }
- Op.IterateAttachments([&](CbFieldView FieldView) {
- const IoHash AttachmentHash = FieldView.AsAttachment();
- IoBuffer Payload = m_CidStore.FindChunkByCid(AttachmentHash);
+ CbObjectWriter Cbo;
+ Cbo.BeginArray("need");
- // We force this for now as content type is not consistently tracked (will
- // be fixed in CidStore refactor)
- Payload.SetContentType(ZenContentType::kCompressedBinary);
+ for (const IoHash& Hash : NeedList)
+ {
+ Cbo << Hash;
+ }
- if (Payload)
- {
- switch (Payload.GetContentType())
- {
- case ZenContentType::kCbObject:
- if (CbObject Object = LoadCompactBinaryObject(Payload))
- {
- Package.AddAttachment(CbAttachment(Object));
- }
- else
- {
- // Error - malformed object
-
- ZEN_WARN("malformed object returned for {}", AttachmentHash);
- }
- break;
-
- case ZenContentType::kCompressedBinary:
- if (CompressedBuffer Compressed = CompressedBuffer::FromCompressedNoValidate(std::move(Payload)))
- {
- Package.AddAttachment(CbAttachment(Compressed, AttachmentHash));
- }
- else
- {
- // Error - not compressed!
-
- ZEN_WARN("invalid compressed binary returned for {}", AttachmentHash);
- }
- break;
-
- default:
- Package.AddAttachment(CbAttachment(SharedBuffer(Payload)));
- break;
- }
- }
- });
- m_ProjectStats.OpHitCount++;
- return HttpReq.WriteResponse(HttpResponseCode::Accepted, Package);
- }
- else
- {
- // Client cannot accept a package, so we only send the core object
- m_ProjectStats.OpHitCount++;
- return HttpReq.WriteResponse(HttpResponseCode::Accepted, Op);
- }
- }
- }
- m_ProjectStats.OpMissCount++;
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- },
- HttpVerb::kGet);
+ Cbo.EndArray();
+ CbObject Response = Cbo.Save();
- m_Router.RegisterRoute(
- "{project}/oplog/{log}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+}
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
+void
+HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogOpNew");
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ using namespace std::literals;
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("project {} not found", ProjectId));
- }
- Project->TouchProject();
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- switch (HttpReq.RequestVerb())
- {
- case HttpVerb::kGet:
- {
- ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
- if (!OplogIt)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("oplog {} not found in project {}", OplogId, ProjectId));
- }
- Project->TouchOplog(OplogId);
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
- ProjectStore::Oplog& Log = *OplogIt;
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
- CbObjectWriter Cb;
- Cb << "id"sv << Log.OplogId() << "project"sv << Project->Identifier << "tempdir"sv << Log.TempPath().c_str()
- << "markerpath"sv << Log.MarkerPath().c_str() << "totalsize"sv << Log.TotalSize() << "opcount"
- << Log.OplogCount() << "expired"sv << Project->IsExpired(GcClock::TimePoint::min(), Log);
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- HttpReq.WriteResponse(HttpResponseCode::OK, Cb.Save());
+ bool IsUsingSalt = false;
+ IoHash SaltHash = IoHash::Zero;
- m_ProjectStats.OpLogReadCount++;
- }
- break;
+ if (std::string_view SaltParam = Params.GetValue("salt"sv); SaltParam.empty() == false)
+ {
+ const uint32_t Salt = std::stoi(std::string(SaltParam));
+ SaltHash = IoHash::HashBuffer(&Salt, sizeof Salt);
+ IsUsingSalt = true;
+ }
- case HttpVerb::kPost:
- {
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
- std::filesystem::path OplogMarkerPath;
- if (CbObject Params = HttpReq.ReadPayloadObject())
- {
- OplogMarkerPath = Params["gcpath"sv].AsString();
- }
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchProject();
- ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
- if (!OplogIt)
- {
- if (!Project->NewOplog(OplogId, OplogMarkerPath))
- {
- // TODO: indicate why the operation failed!
- return HttpReq.WriteResponse(HttpResponseCode::InternalServerError);
- }
- Project->TouchOplog(OplogId);
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchOplog(OplogId);
- m_ProjectStats.OpLogWriteCount++;
- ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+ ProjectStore::Oplog& Oplog = *FoundLog;
- return HttpReq.WriteResponse(HttpResponseCode::Created);
- }
+ IoBuffer Payload = HttpReq.ReadPayload();
- // I guess this should ultimately be used to execute RPCs but for now, it
- // does absolutely nothing
+ // This will attempt to open files which may not exist for the case where
+ // the prep step rejected the chunk. This should be fixed since there's
+ // a performance cost associated with any file system activity
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
- }
- break;
+ bool IsValid = true;
+ std::vector<IoHash> MissingChunks;
- case HttpVerb::kPut:
- {
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
+ CbPackage::AttachmentResolver Resolver = [&](const IoHash& Hash) -> SharedBuffer {
+ if (m_CidStore.ContainsChunk(Hash))
+ {
+ // Return null attachment as we already have it, no point in reading it and storing it again
+ return {};
+ }
- std::filesystem::path OplogMarkerPath;
- if (CbObject Params = HttpReq.ReadPayloadObject())
- {
- OplogMarkerPath = Params["gcpath"sv].AsString();
- }
+ IoHash AttachmentId;
+ if (IsUsingSalt)
+ {
+ IoHash AttachmentSpec[]{SaltHash, Hash};
+ AttachmentId = IoHash::HashBuffer(MakeMemoryView(AttachmentSpec));
+ }
+ else
+ {
+ AttachmentId = Hash;
+ }
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- if (!Project->NewOplog(OplogId, OplogMarkerPath))
- {
- // TODO: indicate why the operation failed!
- return HttpReq.WriteResponse(HttpResponseCode::InternalServerError);
- }
- Project->TouchOplog(OplogId);
+ std::filesystem::path AttachmentPath = Oplog.TempPath() / AttachmentId.ToHexString();
+ if (IoBuffer Data = IoBufferBuilder::MakeFromTemporaryFile(AttachmentPath))
+ {
+ return SharedBuffer(std::move(Data));
+ }
+ else
+ {
+ IsValid = false;
+ MissingChunks.push_back(Hash);
- m_ProjectStats.OpLogWriteCount++;
- ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+ return {};
+ }
+ };
- return HttpReq.WriteResponse(HttpResponseCode::Created);
- }
- Project->TouchOplog(OplogId);
+ CbPackage Package;
- FoundLog->Update(OplogMarkerPath);
+ if (!legacy::TryLoadCbPackage(Package, Payload, &UniqueBuffer::Alloc, &Resolver))
+ {
+ if (CbObject Core = LoadCompactBinaryObject(Payload))
+ {
+ Package.SetObject(Core);
+ }
+ else
+ {
+ std::filesystem::path BadPackagePath =
+ Oplog.TempPath() / "bad_packages"sv / fmt::format("session{}_request{}"sv, HttpReq.SessionId(), HttpReq.RequestId());
- m_ProjectStats.OpLogWriteCount++;
- ZEN_INFO("updated oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+ ZEN_WARN("Received malformed package! Saving payload to '{}'", BadPackagePath);
- return HttpReq.WriteResponse(HttpResponseCode::OK);
- }
- break;
+ WriteFile(BadPackagePath, Payload);
- case HttpVerb::kDelete:
- {
- ZEN_INFO("deleting oplog '{}/{}'", ProjectId, OplogId);
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ u8"request body must be a compact binary object or package in legacy format");
+ }
+ }
- Project->DeleteOplog(OplogId);
+ m_ProjectStats.ChunkMissCount += MissingChunks.size();
- m_ProjectStats.OpLogDeleteCount++;
- return HttpReq.WriteResponse(HttpResponseCode::OK);
- }
- break;
+ if (!IsValid)
+ {
+ ExtendableStringBuilder<256> ResponseText;
+ ResponseText.Append("Missing chunk references: ");
- default:
- break;
+ bool IsFirst = true;
+ for (const auto& Hash : MissingChunks)
+ {
+ if (IsFirst)
+ {
+ IsFirst = false;
}
- },
- HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
+ else
+ {
+ ResponseText.Append(", ");
+ }
+ Hash.ToHexString(ResponseText);
+ }
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/entries",
- [this](HttpRouterRequest& Req) {
- ZEN_TRACE_CPU("ProjectService::OplogEntries");
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, ResponseText);
+ }
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ CbObject Core = Package.GetObject();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
+ if (!Core["key"sv])
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "No oplog entry key specified");
+ }
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchProject();
+ // Write core to oplog
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- Project->TouchOplog(OplogId);
+ size_t AttachmentCount = Package.GetAttachments().size();
+ const uint32_t OpLsn = Oplog.AppendNewOplogEntry(Package);
- CbObjectWriter Response;
+ if (OpLsn == ProjectStore::Oplog::kInvalidOp)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
+ }
+ m_ProjectStats.ChunkWriteCount += AttachmentCount;
- if (FoundLog->OplogCount() > 0)
- {
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ m_ProjectStats.OpWriteCount++;
+ ZEN_DEBUG("'{}/{}' op #{} ({}) - '{}'", ProjectId, OplogId, OpLsn, NiceBytes(Payload.Size()), Core["key"sv].AsString());
+ HttpReq.WriteResponse(HttpResponseCode::Created);
+}
- if (auto OpKey = Params.GetValue("opkey"); !OpKey.empty())
- {
- Oid OpKeyId = OpKeyStringAsOId(OpKey);
- std::optional<CbObject> Op = FoundLog->GetOpByKey(OpKeyId);
+void
+HttpProjectService::HandleOpLogOpRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogOp");
- if (Op.has_value())
- {
- Response << "entry"sv << Op.value();
- }
- else
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- }
- else
- {
- Response.BeginArray("entries"sv);
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- FoundLog->IterateOplog([&Response](CbObject Op) { Response << Op; });
+ const std::string& ProjectId = Req.GetCapture(1);
+ const std::string& OplogId = Req.GetCapture(2);
+ const std::string& OpIdString = Req.GetCapture(3);
- Response.EndArray();
- }
- }
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchProject();
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response.Save());
- },
- HttpVerb::kGet);
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchOplog(OplogId);
- m_Router.RegisterRoute(
- "{project}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const std::string ProjectId = Req.GetCapture(1);
+ ProjectStore::Oplog& Oplog = *FoundLog;
- switch (HttpReq.RequestVerb())
+ if (const std::optional<int32_t> OpId = ParseInt<uint32_t>(OpIdString))
+ {
+ if (std::optional<CbObject> MaybeOp = Oplog.GetOpByIndex(OpId.value()))
+ {
+ CbObject& Op = MaybeOp.value();
+ if (HttpReq.AcceptContentType() == ZenContentType::kCbPackage)
{
- case HttpVerb::kPost:
- {
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
+ CbPackage Package;
+ Package.SetObject(Op);
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Params = LoadCompactBinaryObject(Payload);
- std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`)
- std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`)
- std::string_view ProjectRoot =
- Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
- std::string_view ProjectFilePath =
- Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
-
- const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
- m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
-
- ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
- ProjectId,
- Root,
- EngineRoot,
- ProjectRoot,
- ProjectFilePath,
- ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
-
- m_ProjectStats.ProjectWriteCount++;
- HttpReq.WriteResponse(HttpResponseCode::Created);
- }
- break;
+ Op.IterateAttachments([&](CbFieldView FieldView) {
+ const IoHash AttachmentHash = FieldView.AsAttachment();
+ IoBuffer Payload = m_CidStore.FindChunkByCid(AttachmentHash);
+
+ // We force this for now as content type is not consistently tracked (will
+ // be fixed in CidStore refactor)
+ Payload.SetContentType(ZenContentType::kCompressedBinary);
- case HttpVerb::kPut:
+ if (Payload)
{
- if (!m_ProjectStore->AreDiskWritesAllowed())
+ switch (Payload.GetContentType())
{
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
+ case ZenContentType::kCbObject:
+ if (CbObject Object = LoadCompactBinaryObject(Payload))
+ {
+ Package.AddAttachment(CbAttachment(Object));
+ }
+ else
+ {
+ // Error - malformed object
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Params = LoadCompactBinaryObject(Payload);
- std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`)
- std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`)
- std::string_view ProjectRoot =
- Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
- std::string_view ProjectFilePath =
- Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
+ ZEN_WARN("malformed object returned for {}", AttachmentHash);
+ }
+ break;
- if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath))
- {
- m_ProjectStats.ProjectWriteCount++;
- ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
- ProjectId,
- Root,
- EngineRoot,
- ProjectRoot,
- ProjectFilePath,
- ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
-
- HttpReq.WriteResponse(HttpResponseCode::OK);
- }
- else
- {
- const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
- m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
-
- m_ProjectStats.ProjectWriteCount++;
- ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
- ProjectId,
- Root,
- EngineRoot,
- ProjectRoot,
- ProjectFilePath,
- ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
-
- HttpReq.WriteResponse(HttpResponseCode::Created);
+ case ZenContentType::kCompressedBinary:
+ if (CompressedBuffer Compressed = CompressedBuffer::FromCompressedNoValidate(std::move(Payload)))
+ {
+ Package.AddAttachment(CbAttachment(Compressed, AttachmentHash));
+ }
+ else
+ {
+ // Error - not compressed!
+
+ ZEN_WARN("invalid compressed binary returned for {}", AttachmentHash);
+ }
+ break;
+
+ default:
+ Package.AddAttachment(CbAttachment(SharedBuffer(Payload)));
+ break;
}
}
- break;
+ });
+ m_ProjectStats.OpHitCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::Accepted, Package);
+ }
+ else
+ {
+ // Client cannot accept a package, so we only send the core object
+ m_ProjectStats.OpHitCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::Accepted, Op);
+ }
+ }
+ }
+ m_ProjectStats.OpMissCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+}
- case HttpVerb::kGet:
- {
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("project {} not found", ProjectId));
- }
- Project->TouchProject();
+void
+HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::Oplog");
- std::vector<std::string> OpLogs = Project->ScanForOplogs();
+ HttpServerRequest& HttpReq = Req.ServerRequest();
- CbObjectWriter Response;
- Response << "id"sv << Project->Identifier;
- Response << "root"sv << PathToUtf8(Project->RootDir);
- Response << "engine"sv << PathToUtf8(Project->EngineRootDir);
- Response << "project"sv << PathToUtf8(Project->ProjectRootDir);
- Response << "projectfile"sv << PathToUtf8(Project->ProjectFilePath);
+ using namespace std::literals;
- Response.BeginArray("oplogs"sv);
- for (const std::string& OplogId : OpLogs)
- {
- Response.BeginObject();
- Response << "id"sv << OplogId;
- Response.EndObject();
- }
- Response.EndArray(); // oplogs
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
- HttpReq.WriteResponse(HttpResponseCode::OK, Response.Save());
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- m_ProjectStats.ProjectReadCount++;
- }
- break;
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, fmt::format("project {} not found", ProjectId));
+ }
+ Project->TouchProject();
- case HttpVerb::kDelete:
- {
- Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
- if (!Project)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("project {} not found", ProjectId));
- }
+ switch (HttpReq.RequestVerb())
+ {
+ case HttpVerb::kGet:
+ {
+ ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
+ if (!OplogIt)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("oplog {} not found in project {}", OplogId, ProjectId));
+ }
+ Project->TouchOplog(OplogId);
- ZEN_INFO("deleting project '{}'", ProjectId);
- if (!m_ProjectStore->DeleteProject(ProjectId))
- {
- return HttpReq.WriteResponse(HttpResponseCode::Locked,
- HttpContentType::kText,
- fmt::format("project {} is in use", ProjectId));
- }
+ ProjectStore::Oplog& Log = *OplogIt;
- m_ProjectStats.ProjectDeleteCount++;
- return HttpReq.WriteResponse(HttpResponseCode::NoContent);
- }
- break;
+ CbObjectWriter Cb;
+ Cb << "id"sv << Log.OplogId() << "project"sv << Project->Identifier << "tempdir"sv << Log.TempPath().c_str()
+ << "markerpath"sv << Log.MarkerPath().c_str() << "totalsize"sv << Log.TotalSize() << "opcount" << Log.OplogCount()
+ << "expired"sv << Project->IsExpired(GcClock::TimePoint::min(), Log);
- default:
- break;
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cb.Save());
+
+ m_ProjectStats.OpLogReadCount++;
}
- },
- HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
+ break;
- // Push a oplog container
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/save",
- [this](HttpRouterRequest& Req) {
- ZEN_TRACE_CPU("ProjectService::OplogSave");
+ case HttpVerb::kPost:
+ {
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+ std::filesystem::path OplogMarkerPath;
+ if (CbObject Params = HttpReq.ReadPayloadObject())
+ {
+ OplogMarkerPath = Params["gcpath"sv].AsString();
+ }
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
+ if (!OplogIt)
+ {
+ if (!Project->NewOplog(OplogId, OplogMarkerPath))
+ {
+ // TODO: indicate why the operation failed!
+ return HttpReq.WriteResponse(HttpResponseCode::InternalServerError);
+ }
+ Project->TouchOplog(OplogId);
- if (!m_ProjectStore->AreDiskWritesAllowed())
- {
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
+ m_ProjectStats.OpLogWriteCount++;
+ ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+
+ return HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
+
+ // I guess this should ultimately be used to execute RPCs but for now, it
+ // does absolutely nothing
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- if (HttpReq.RequestContentType() != HttpContentType::kCbObject)
- {
m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid content type");
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
}
- IoBuffer Payload = HttpReq.ReadPayload();
+ break;
- CbObject Response;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->WriteOplog(ProjectId, OplogId, std::move(Payload), Response);
- if (Result.first == HttpResponseCode::OK)
- {
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
- }
- else
+ case HttpVerb::kPut:
{
- if (Result.first == HttpResponseCode::BadRequest)
+ if (!m_ProjectStore->AreDiskWritesAllowed())
{
- m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
+
+ std::filesystem::path OplogMarkerPath;
+ if (CbObject Params = HttpReq.ReadPayloadObject())
+ {
+ OplogMarkerPath = Params["gcpath"sv].AsString();
+ }
+
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ if (!Project->NewOplog(OplogId, OplogMarkerPath))
+ {
+ // TODO: indicate why the operation failed!
+ return HttpReq.WriteResponse(HttpResponseCode::InternalServerError);
+ }
+ Project->TouchOplog(OplogId);
+
+ m_ProjectStats.OpLogWriteCount++;
+ ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+
+ return HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
+ Project->TouchOplog(OplogId);
+
+ FoundLog->Update(OplogMarkerPath);
+
+ m_ProjectStats.OpLogWriteCount++;
+ ZEN_INFO("updated oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+
+ return HttpReq.WriteResponse(HttpResponseCode::OK);
}
- if (Result.second.empty())
+ break;
+
+ case HttpVerb::kDelete:
{
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- },
- HttpVerb::kPost);
+ ZEN_INFO("deleting oplog '{}/{}'", ProjectId, OplogId);
- // Pull a oplog container
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/load",
- [this](HttpRouterRequest& Req) {
- ZEN_TRACE_CPU("ProjectService::OplogLoad");
+ Project->DeleteOplog(OplogId);
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- if (HttpReq.AcceptContentType() != HttpContentType::kCbObject)
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid accept content type");
+ m_ProjectStats.OpLogDeleteCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::OK);
}
- IoBuffer Payload = HttpReq.ReadPayload();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogEntries");
+
+ using namespace std::literals;
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchProject();
+
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ Project->TouchOplog(OplogId);
+
+ CbObjectWriter Response;
- CbObject Response;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->ReadOplog(ProjectId, OplogId, HttpReq.GetQueryParams(), Response);
- if (Result.first == HttpResponseCode::OK)
+ if (FoundLog->OplogCount() > 0)
+ {
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+
+ if (auto OpKey = Params.GetValue("opkey"); !OpKey.empty())
+ {
+ Oid OpKeyId = OpKeyStringAsOId(OpKey);
+ std::optional<CbObject> Op = FoundLog->GetOpByKey(OpKeyId);
+
+ if (Op.has_value())
{
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+ Response << "entry"sv << Op.value();
}
else
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- },
- HttpVerb::kGet);
+ }
+ else
+ {
+ Response.BeginArray("entries"sv);
- // Do an rpc style operation on project/oplog
- m_Router.RegisterRoute(
- "{project}/oplog/{log}/rpc",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+ FoundLog->IterateOplog([&Response](CbObject Op) { Response << Op; });
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- IoBuffer Payload = HttpReq.ReadPayload();
+ Response.EndArray();
+ }
+ }
- bool OkRequest = m_ProjectStore->Rpc(HttpReq, ProjectId, OplogId, std::move(Payload), m_AuthMgr);
- if (!OkRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- },
- HttpVerb::kPost);
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Response.Save());
+}
- m_Router.RegisterRoute(
- "details\\$",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
+void
+HttpProjectService::HandleProjectRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::Project");
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- bool CSV = Params.GetValue("csv") == "true";
- bool Details = Params.GetValue("details") == "true";
- bool OpDetails = Params.GetValue("opdetails") == "true";
- bool AttachmentDetails = Params.GetValue("attachmentdetails") == "true";
+ using namespace std::literals;
- if (CSV)
- {
- ExtendableStringBuilder<4096> CSVWriter;
- CSVHeader(Details, AttachmentDetails, CSVWriter);
-
- m_ProjectStore->IterateProjects([&](ProjectStore::Project& Project) {
- Project.IterateOplogs([&](ProjectStore::Oplog& Oplog) {
- Oplog.IterateOplogWithKey(
- [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN, const Oid& Key, CbObject Op) {
- CSVWriteOp(m_CidStore,
- Project.Identifier,
- Oplog.OplogId(),
- Details,
- AttachmentDetails,
- LSN,
- Key,
- Op,
- CSVWriter);
- });
- });
- });
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const std::string ProjectId = Req.GetCapture(1);
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
- }
- else
+ switch (HttpReq.RequestVerb())
+ {
+ case HttpVerb::kPost:
{
- CbObjectWriter Cbo;
- Cbo.BeginArray("projects");
+ if (!m_ProjectStore->AreDiskWritesAllowed())
{
- m_ProjectStore->DiscoverProjects();
-
- m_ProjectStore->IterateProjects([&](ProjectStore::Project& Project) {
- std::vector<std::string> OpLogs = Project.ScanForOplogs();
- CbWriteProject(m_CidStore, Project, OpLogs, Details, OpDetails, AttachmentDetails, Cbo);
- });
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- Cbo.EndArray();
- HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
- }
- },
- HttpVerb::kGet);
- m_Router.RegisterRoute(
- "details\\$/{project}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const auto& ProjectId = Req.GetCapture(1);
-
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- bool CSV = Params.GetValue("csv") == "true";
- bool Details = Params.GetValue("details") == "true";
- bool OpDetails = Params.GetValue("opdetails") == "true";
- bool AttachmentDetails = Params.GetValue("attachmentdetails") == "true";
-
- Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
- if (!FoundProject)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- ProjectStore::Project& Project = *FoundProject.Get();
+ IoBuffer Payload = HttpReq.ReadPayload();
+ CbObject Params = LoadCompactBinaryObject(Payload);
+ std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`)
+ std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`)
+ std::string_view ProjectRoot = Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
+ std::string_view ProjectFilePath =
+ Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
- if (CSV)
- {
- ExtendableStringBuilder<4096> CSVWriter;
- CSVHeader(Details, AttachmentDetails, CSVWriter);
+ const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
+ m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
- FoundProject->IterateOplogs([&](ProjectStore::Oplog& Oplog) {
- Oplog.IterateOplogWithKey([this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN,
- const Oid& Key,
- CbObject Op) {
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
- });
- });
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
+ ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ m_ProjectStats.ProjectWriteCount++;
+ HttpReq.WriteResponse(HttpResponseCode::Created);
}
- else
+ break;
+
+ case HttpVerb::kPut:
{
- CbObjectWriter Cbo;
- std::vector<std::string> OpLogs = FoundProject->ScanForOplogs();
- Cbo.BeginArray("projects");
+ if (!m_ProjectStore->AreDiskWritesAllowed())
{
- CbWriteProject(m_CidStore, Project, OpLogs, Details, OpDetails, AttachmentDetails, Cbo);
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- Cbo.EndArray();
- HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
- }
- },
- HttpVerb::kGet);
- m_Router.RegisterRoute(
- "details\\$/{project}/{log}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
-
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- bool CSV = Params.GetValue("csv") == "true";
- bool Details = Params.GetValue("details") == "true";
- bool OpDetails = Params.GetValue("opdetails") == "true";
- bool AttachmentDetails = Params.GetValue("attachmentdetails") == "true";
-
- Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
- if (!FoundProject)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
+ IoBuffer Payload = HttpReq.ReadPayload();
+ CbObject Params = LoadCompactBinaryObject(Payload);
+ std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`)
+ std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`)
+ std::string_view ProjectRoot = Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
+ std::string_view ProjectFilePath =
+ Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath))
+ {
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ else
+ {
+ const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
+ m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
+
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
}
+ break;
- ProjectStore::Project& Project = *FoundProject.Get();
- ProjectStore::Oplog& Oplog = *FoundLog;
- if (CSV)
- {
- ExtendableStringBuilder<4096> CSVWriter;
- CSVHeader(Details, AttachmentDetails, CSVWriter);
-
- Oplog.IterateOplogWithKey(
- [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN, const Oid& Key, CbObject Op) {
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
- });
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
- }
- else
+ case HttpVerb::kGet:
{
- CbObjectWriter Cbo;
- Cbo.BeginArray("oplogs");
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- CbWriteOplog(m_CidStore, Oplog, Details, OpDetails, AttachmentDetails, Cbo);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("project {} not found", ProjectId));
}
- Cbo.EndArray();
- HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
- }
- },
- HttpVerb::kGet);
+ Project->TouchProject();
- m_Router.RegisterRoute(
- "details\\$/{project}/{log}/{chunk}",
- [this](HttpRouterRequest& Req) {
- HttpServerRequest& HttpReq = Req.ServerRequest();
- const auto& ProjectId = Req.GetCapture(1);
- const auto& OplogId = Req.GetCapture(2);
- const auto& ChunkId = Req.GetCapture(3);
-
- HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
- bool CSV = Params.GetValue("csv") == "true";
- bool Details = Params.GetValue("details") == "true";
- bool OpDetails = Params.GetValue("opdetails") == "true";
- bool AttachmentDetails = Params.GetValue("attachmentdetails") == "true";
-
- Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
- if (!FoundProject)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
+ std::vector<std::string> OpLogs = Project->ScanForOplogs();
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
- if (!FoundLog)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
+ CbObjectWriter Response;
+ Response << "id"sv << Project->Identifier;
+ Response << "root"sv << PathToUtf8(Project->RootDir);
+ Response << "engine"sv << PathToUtf8(Project->EngineRootDir);
+ Response << "project"sv << PathToUtf8(Project->ProjectRootDir);
+ Response << "projectfile"sv << PathToUtf8(Project->ProjectFilePath);
- if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
- {
- m_ProjectStats.BadRequestCount++;
- return HttpReq.WriteResponse(
- HttpResponseCode::BadRequest,
- HttpContentType::kText,
- fmt::format("Chunk info request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, ChunkId));
- }
+ Response.BeginArray("oplogs"sv);
+ for (const std::string& OplogId : OpLogs)
+ {
+ Response.BeginObject();
+ Response << "id"sv << OplogId;
+ Response.EndObject();
+ }
+ Response.EndArray(); // oplogs
- const Oid ObjId = Oid::FromHexString(ChunkId);
- ProjectStore::Project& Project = *FoundProject.Get();
- ProjectStore::Oplog& Oplog = *FoundLog;
+ HttpReq.WriteResponse(HttpResponseCode::OK, Response.Save());
- int LSN = Oplog.GetOpIndexByKey(ObjId);
- if (LSN == -1)
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
- }
- std::optional<CbObject> Op = Oplog.GetOpByIndex(LSN);
- if (!Op.has_value())
- {
- return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ m_ProjectStats.ProjectReadCount++;
}
+ break;
- if (CSV)
+ case HttpVerb::kDelete:
{
- ExtendableStringBuilder<4096> CSVWriter;
- CSVHeader(Details, AttachmentDetails, CSVWriter);
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("project {} not found", ProjectId));
+ }
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, ObjId, Op.value(), CSVWriter);
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
- }
- else
- {
- CbObjectWriter Cbo;
- Cbo.BeginArray("ops");
+ ZEN_INFO("deleting project '{}'", ProjectId);
+ if (!m_ProjectStore->DeleteProject(ProjectId))
{
- CbWriteOp(m_CidStore, Details, OpDetails, AttachmentDetails, LSN, ObjId, Op.value(), Cbo);
+ return HttpReq.WriteResponse(HttpResponseCode::Locked,
+ HttpContentType::kText,
+ fmt::format("project {} is in use", ProjectId));
}
- Cbo.EndArray();
- HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+
+ m_ProjectStats.ProjectDeleteCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::NoContent);
}
- },
- HttpVerb::kGet);
+ break;
+
+ default:
+ break;
+ }
}
-HttpProjectService::~HttpProjectService()
+void
+HttpProjectService::HandleOplogSaveRequest(HttpRouterRequest& Req)
{
- m_StatsService.UnregisterHandler("prj", *this);
+ ZEN_TRACE_CPU("ProjectService::OplogSave");
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ if (HttpReq.RequestContentType() != HttpContentType::kCbObject)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid content type");
+ }
+ IoBuffer Payload = HttpReq.ReadPayload();
+
+ CbObject Response;
+ std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->WriteOplog(ProjectId, OplogId, std::move(Payload), Response);
+ if (Result.first == HttpResponseCode::OK)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+ }
+ else
+ {
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
+ }
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
-const char*
-HttpProjectService::BaseUri() const
+void
+HttpProjectService::HandleOplogLoadRequest(HttpRouterRequest& Req)
{
- return "/prj/";
+ ZEN_TRACE_CPU("ProjectService::OplogLoad");
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ if (HttpReq.AcceptContentType() != HttpContentType::kCbObject)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid accept content type");
+ }
+ IoBuffer Payload = HttpReq.ReadPayload();
+
+ CbObject Response;
+ std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->ReadOplog(ProjectId, OplogId, HttpReq.GetQueryParams(), Response);
+ if (Result.first == HttpResponseCode::OK)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+ }
+ else
+ {
+ if (Result.first == HttpResponseCode::BadRequest)
+ {
+ m_ProjectStats.BadRequestCount++;
+ }
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ static_cast<int>(Result.first),
+ Result.second);
+ }
+ if (Result.second.empty())
+ {
+ return HttpReq.WriteResponse(Result.first);
+ }
+ return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
-HttpProjectService::HandleRequest(HttpServerRequest& Request)
+HttpProjectService::HandleRpcRequest(HttpRouterRequest& Req)
{
- if (m_Router.HandleRequest(Request) == false)
+ ZEN_TRACE_CPU("ProjectService::Rpc");
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ IoBuffer Payload = HttpReq.ReadPayload();
+
+ bool OkRequest = m_ProjectStore->Rpc(HttpReq, ProjectId, OplogId, std::move(Payload), m_AuthMgr);
+ if (!OkRequest)
{
- ZEN_WARN("No route found for {0}", Request.RelativeUri());
+ m_ProjectStats.BadRequestCount++;
}
}
-
void
-HttpProjectService::HandleStatsRequest(HttpServerRequest& HttpReq)
+HttpProjectService::HandleDetailsRequest(HttpRouterRequest& Req)
{
- const GcStorageSize StoreSize = m_ProjectStore->StorageSize();
- const CidStoreSize CidSize = m_CidStore.TotalSize();
+ ZEN_TRACE_CPU("ProjectService::Details");
- CbObjectWriter Cbo;
- Cbo.BeginObject("store");
+ using namespace std::literals;
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ bool CSV = Params.GetValue("csv"sv) == "true"sv;
+ bool Details = Params.GetValue("details"sv) == "true"sv;
+ bool OpDetails = Params.GetValue("opdetails"sv) == "true"sv;
+ bool AttachmentDetails = Params.GetValue("attachmentdetails"sv) == "true"sv;
+
+ if (CSV)
{
- Cbo.BeginObject("size");
- {
- Cbo << "disk" << StoreSize.DiskSize;
- Cbo << "memory" << StoreSize.MemorySize;
- }
- Cbo.EndObject();
+ ExtendableStringBuilder<4096> CSVWriter;
+ CSVHeader(Details, AttachmentDetails, CSVWriter);
- Cbo.BeginObject("project");
- {
- Cbo << "readcount" << m_ProjectStats.ProjectReadCount << "writecount" << m_ProjectStats.ProjectWriteCount << "deletecount"
- << m_ProjectStats.ProjectDeleteCount;
- }
- Cbo.EndObject();
+ m_ProjectStore->IterateProjects([&](ProjectStore::Project& Project) {
+ Project.IterateOplogs([&](ProjectStore::Oplog& Oplog) {
+ Oplog.IterateOplogWithKey(
+ [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN, const Oid& Key, CbObject Op) {
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
+ });
+ });
+ });
- Cbo.BeginObject("oplog");
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
+ }
+ else
+ {
+ CbObjectWriter Cbo;
+ Cbo.BeginArray("projects");
{
- Cbo << "readcount" << m_ProjectStats.OpLogReadCount << "writecount" << m_ProjectStats.OpLogWriteCount << "deletecount"
- << m_ProjectStats.OpLogDeleteCount;
+ m_ProjectStore->DiscoverProjects();
+
+ m_ProjectStore->IterateProjects([&](ProjectStore::Project& Project) {
+ std::vector<std::string> OpLogs = Project.ScanForOplogs();
+ CbWriteProject(m_CidStore, Project, OpLogs, Details, OpDetails, AttachmentDetails, Cbo);
+ });
}
- Cbo.EndObject();
+ Cbo.EndArray();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ }
+}
- Cbo.BeginObject("op");
+void
+HttpProjectService::HandleProjectDetailsRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::ProjectDetails");
+
+ using namespace std::literals;
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ bool CSV = Params.GetValue("csv"sv) == "true"sv;
+ bool Details = Params.GetValue("details"sv) == "true"sv;
+ bool OpDetails = Params.GetValue("opdetails"sv) == "true"sv;
+ bool AttachmentDetails = Params.GetValue("attachmentdetails"sv) == "true"sv;
+
+ Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
+ if (!FoundProject)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ ProjectStore::Project& Project = *FoundProject.Get();
+
+ if (CSV)
+ {
+ ExtendableStringBuilder<4096> CSVWriter;
+ CSVHeader(Details, AttachmentDetails, CSVWriter);
+
+ FoundProject->IterateOplogs([&](ProjectStore::Oplog& Oplog) {
+ Oplog.IterateOplogWithKey(
+ [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN, const Oid& Key, CbObject Op) {
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
+ });
+ });
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
+ }
+ else
+ {
+ CbObjectWriter Cbo;
+ std::vector<std::string> OpLogs = FoundProject->ScanForOplogs();
+ Cbo.BeginArray("projects");
{
- Cbo << "hitcount" << m_ProjectStats.OpHitCount << "misscount" << m_ProjectStats.OpMissCount << "writecount"
- << m_ProjectStats.OpWriteCount;
+ CbWriteProject(m_CidStore, Project, OpLogs, Details, OpDetails, AttachmentDetails, Cbo);
}
- Cbo.EndObject();
+ Cbo.EndArray();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ }
+}
- Cbo.BeginObject("chunk");
+void
+HttpProjectService::HandleOplogDetailsRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogDetails");
+
+ using namespace std::literals;
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ bool CSV = Params.GetValue("csv"sv) == "true"sv;
+ bool Details = Params.GetValue("details"sv) == "true"sv;
+ bool OpDetails = Params.GetValue("opdetails"sv) == "true"sv;
+ bool AttachmentDetails = Params.GetValue("attachmentdetails"sv) == "true"sv;
+
+ Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
+ if (!FoundProject)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+
+ ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+
+ ProjectStore::Project& Project = *FoundProject.Get();
+ ProjectStore::Oplog& Oplog = *FoundLog;
+ if (CSV)
+ {
+ ExtendableStringBuilder<4096> CSVWriter;
+ CSVHeader(Details, AttachmentDetails, CSVWriter);
+
+ Oplog.IterateOplogWithKey([this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](int LSN, const Oid& Key, CbObject Op) {
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
+ });
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
+ }
+ else
+ {
+ CbObjectWriter Cbo;
+ Cbo.BeginArray("oplogs");
{
- Cbo << "hitcount" << m_ProjectStats.ChunkHitCount << "misscount" << m_ProjectStats.ChunkMissCount << "writecount"
- << m_ProjectStats.ChunkWriteCount;
+ CbWriteOplog(m_CidStore, Oplog, Details, OpDetails, AttachmentDetails, Cbo);
}
- Cbo.EndObject();
+ Cbo.EndArray();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ }
+}
- Cbo << "badrequestcount" << m_ProjectStats.BadRequestCount;
+void
+HttpProjectService::HandleOplogOpDetailsRequest(HttpRouterRequest& Req)
+{
+ ZEN_TRACE_CPU("ProjectService::OplogOpDetails");
+
+ using namespace std::literals;
+
+ HttpServerRequest& HttpReq = Req.ServerRequest();
+ const auto& ProjectId = Req.GetCapture(1);
+ const auto& OplogId = Req.GetCapture(2);
+ const auto& OpId = Req.GetCapture(3);
+
+ HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+ bool CSV = Params.GetValue("csv"sv) == "true"sv;
+ bool Details = Params.GetValue("details"sv) == "true"sv;
+ bool OpDetails = Params.GetValue("opdetails"sv) == "true"sv;
+ bool AttachmentDetails = Params.GetValue("attachmentdetails"sv) == "true"sv;
+
+ Ref<ProjectStore::Project> FoundProject = m_ProjectStore->OpenProject(ProjectId);
+ if (!FoundProject)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- Cbo.EndObject();
- Cbo.BeginObject("cid");
+ ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
+ if (!FoundLog)
{
- Cbo.BeginObject("size");
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+
+ if (OpId.size() != 2 * sizeof(Oid::OidBits))
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Chunk info request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, OpId));
+ }
+
+ const Oid ObjId = Oid::FromHexString(OpId);
+ ProjectStore::Project& Project = *FoundProject.Get();
+ ProjectStore::Oplog& Oplog = *FoundLog;
+
+ int LSN = Oplog.GetOpIndexByKey(ObjId);
+ if (LSN == -1)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+ std::optional<CbObject> Op = Oplog.GetOpByIndex(LSN);
+ if (!Op.has_value())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
+ }
+
+ if (CSV)
+ {
+ ExtendableStringBuilder<4096> CSVWriter;
+ CSVHeader(Details, AttachmentDetails, CSVWriter);
+
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, ObjId, Op.value(), CSVWriter);
+ HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
+ }
+ else
+ {
+ CbObjectWriter Cbo;
+ Cbo.BeginArray("ops");
{
- Cbo << "tiny" << CidSize.TinySize;
- Cbo << "small" << CidSize.SmallSize;
- Cbo << "large" << CidSize.LargeSize;
- Cbo << "total" << CidSize.TotalSize;
+ CbWriteOp(m_CidStore, Details, OpDetails, AttachmentDetails, LSN, ObjId, Op.value(), Cbo);
}
- Cbo.EndObject();
+ Cbo.EndArray();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
}
- Cbo.EndObject();
-
- return HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
}
} // namespace zen
diff --git a/src/zenserver/projectstore/httpprojectstore.h b/src/zenserver/projectstore/httpprojectstore.h
index 5842eb3ec..5c558f8d2 100644
--- a/src/zenserver/projectstore/httpprojectstore.h
+++ b/src/zenserver/projectstore/httpprojectstore.h
@@ -59,6 +59,26 @@ private:
std::atomic_uint64_t BadRequestCount{};
};
+ void HandleProjectListRequest(HttpRouterRequest& Req);
+ void HandleChunkBatchRequest(HttpRouterRequest& Req);
+ void HandleFilesRequest(HttpRouterRequest& Req);
+ void HandleChunkInfoRequest(HttpRouterRequest& Req);
+ void HandleChunkByIdRequest(HttpRouterRequest& Req);
+ void HandleChunkByCidRequest(HttpRouterRequest& Req);
+ void HandleOplogOpPrepRequest(HttpRouterRequest& Req);
+ void HandleOplogOpNewRequest(HttpRouterRequest& Req);
+ void HandleOpLogOpRequest(HttpRouterRequest& Req);
+ void HandleOpLogRequest(HttpRouterRequest& Req);
+ void HandleOpLogEntriesRequest(HttpRouterRequest& Req);
+ void HandleProjectRequest(HttpRouterRequest& Req);
+ void HandleOplogSaveRequest(HttpRouterRequest& Req);
+ void HandleOplogLoadRequest(HttpRouterRequest& Req);
+ void HandleRpcRequest(HttpRouterRequest& Req);
+ void HandleDetailsRequest(HttpRouterRequest& Req);
+ void HandleProjectDetailsRequest(HttpRouterRequest& Req);
+ void HandleOplogDetailsRequest(HttpRouterRequest& Req);
+ void HandleOplogOpDetailsRequest(HttpRouterRequest& Req);
+
inline spdlog::logger& Log() { return m_Log; }
spdlog::logger& m_Log;