// Copyright Epic Games, Inc. All Rights Reserved. #include #include "httpsys.h" #include "httpnull.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zen { HttpServerRequest::HttpServerRequest() { } HttpServerRequest::~HttpServerRequest() { } struct CbPackageHeader { uint32_t HeaderMagic; uint32_t AttachmentCount; uint32_t Reserved1; uint32_t Reserved2; }; static constinit uint32_t kCbPkgMagic = 0xaa77aacc; struct CbAttachmentEntry { uint64_t AttachmentSize; uint32_t Reserved1; IoHash AttachmentHash; }; void HttpServerRequest::WriteResponse(HttpResponse HttpResponseCode, CbPackage Data) { const std::span& Attachments = Data.GetAttachments(); std::vector ResponseBuffers; ResponseBuffers.reserve(3 + Attachments.size()); // TODO: may want to use an additional fudge factor here to avoid growing since each // attachment is likely to consist of several buffers uint64_t TotalAttachmentsSize = 0; // Fixed size header CbPackageHeader Hdr{.HeaderMagic = kCbPkgMagic, .AttachmentCount = gsl::narrow(Attachments.size())}; ResponseBuffers.push_back(IoBufferBuilder::MakeCloneFromMemory(&Hdr, sizeof Hdr)); // Attachment metadata array IoBuffer AttachmentMetadataBuffer = IoBuffer{sizeof(CbAttachmentEntry) * (Attachments.size() + /* root */ 1)}; CbAttachmentEntry* AttachmentInfo = reinterpret_cast(AttachmentMetadataBuffer.MutableData()); ResponseBuffers.push_back(AttachmentMetadataBuffer); // Attachment metadata // Root object IoBuffer RootIoBuffer = Data.GetObject().GetBuffer().AsIoBuffer(); ResponseBuffers.push_back(RootIoBuffer); // Root object *AttachmentInfo++ = {.AttachmentSize = RootIoBuffer.Size(), .AttachmentHash = Data.GetObjectHash()}; // Attachment payloads for (const CbAttachment& Attachment : Attachments) { CompressedBuffer AttachmentBuffer = Attachment.AsCompressedBinary(); CompositeBuffer Compressed = AttachmentBuffer.GetCompressed(); *AttachmentInfo++ = {.AttachmentSize = AttachmentBuffer.GetCompressedSize(), .AttachmentHash = IoHash::FromBLAKE3(AttachmentBuffer.GetRawHash())}; for (const SharedBuffer& Segment : Compressed.GetSegments()) { ResponseBuffers.push_back(Segment.AsIoBuffer()); TotalAttachmentsSize += Segment.GetSize(); } } return WriteResponse(HttpResponseCode, HttpContentType::kCbPackage, ResponseBuffers); } void HttpServerRequest::WriteResponse(HttpResponse HttpResponseCode, CbObject Data) { SharedBuffer Buf = Data.GetBuffer(); std::array Buffers{IoBufferBuilder::MakeCloneFromMemory(Buf.GetData(), Buf.GetSize())}; return WriteResponse(HttpResponseCode, HttpContentType::kCbObject, Buffers); } void HttpServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::string_view ResponseString) { return WriteResponse(HttpResponseCode, ContentType, std::u8string_view{(char8_t*)ResponseString.data(), ResponseString.size()}); } void HttpServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, IoBuffer Blob) { std::array Buffers{Blob}; return WriteResponse(HttpResponseCode, ContentType, Buffers); } HttpServerRequest::QueryParams HttpServerRequest::GetQueryParams() { QueryParams Params; const std::string_view QStr = QueryString(); const char* QueryIt = QStr.data(); const char* QueryEnd = QueryIt + QStr.size(); while (QueryIt != QueryEnd) { if (*QueryIt == '&') { ++QueryIt; continue; } const std::string_view Query{QueryIt, QueryEnd}; size_t DelimIndex = Query.find('&', 0); if (DelimIndex == std::string_view::npos) { DelimIndex = Query.size(); } std::string_view ThisQuery{QueryIt, DelimIndex}; size_t EqIndex = ThisQuery.find('=', 0); if (EqIndex != std::string_view::npos) { std::string_view Parm{ThisQuery.data(), EqIndex}; ThisQuery.remove_prefix(EqIndex + 1); Params.KvPairs.emplace_back(Parm, ThisQuery); } QueryIt += DelimIndex; } return Params; } CbObject HttpServerRequest::ReadPayloadObject() { IoBuffer Payload = ReadPayload(); if (Payload) { return LoadCompactBinaryObject(std::move(Payload)); } else { return {}; } } CbPackage HttpServerRequest::ReadPayloadPackage() { // TODO: this should not read into a contiguous buffer! IoBuffer Payload = ReadPayload(); MemoryInStream InStream(Payload); BinaryReader Reader(InStream); if (!Payload) { return {}; } CbPackage Package; CbPackageHeader Hdr; Reader.Read(&Hdr, sizeof Hdr); if (Hdr.HeaderMagic != kCbPkgMagic) { // report error return {}; } uint32_t ChunkCount = Hdr.AttachmentCount + 1; std::unique_ptr AttachmentEntries{new CbAttachmentEntry[ChunkCount]}; Reader.Read(AttachmentEntries.get(), sizeof(CbAttachmentEntry) * ChunkCount); for (uint32_t i = 0; i < ChunkCount; ++i) { const uint64_t AttachmentSize = AttachmentEntries[i].AttachmentSize; IoBuffer AttachmentBuffer{AttachmentSize}; Reader.Read(AttachmentBuffer.MutableData(), AttachmentSize); CompressedBuffer CompBuf(CompressedBuffer::FromCompressed(SharedBuffer(AttachmentBuffer))); if (i == 0) { Package.SetObject(LoadCompactBinaryObject(CompBuf)); } else { CbAttachment Attachment(CompBuf); Package.AddAttachment(Attachment); } } return Package; } ////////////////////////////////////////////////////////////////////////// HttpServerException::HttpServerException(const char* Message, uint32_t Error) : m_ErrorCode(Error) { using namespace fmt::literals; m_Message = "{} (HTTP error {})"_format(Message, m_ErrorCode); } const char* HttpServerException::what() const { return m_Message.c_str(); } ////////////////////////////////////////////////////////////////////////// void HttpRequestRouter::AddPattern(const char* Id, const char* Regex) { ZEN_ASSERT(m_PatternMap.find(Id) == m_PatternMap.end()); m_PatternMap.insert({Id, Regex}); } void HttpRequestRouter::RegisterRoute(const char* Regex, HttpRequestRouter::HandlerFunc_t&& HandlerFunc, HttpVerb SupportedVerbs) { ExtendableStringBuilder<128> ExpandedRegex; ProcessRegexSubstitutions(Regex, ExpandedRegex); m_Handlers.emplace_back(ExpandedRegex.c_str(), SupportedVerbs, std::move(HandlerFunc), Regex); } void HttpRequestRouter::RegisterRoute(const char* Regex, PackageEndpointHandler& Handler) { ExtendableStringBuilder<128> ExpandedRegex; ProcessRegexSubstitutions(Regex, ExpandedRegex); m_Handlers.emplace_back( ExpandedRegex.c_str(), HttpVerb::kPost, [&Handler](HttpRouterRequest& Request) { Handler.HandleRequest(Request); }, Regex); } void HttpRequestRouter::ProcessRegexSubstitutions(const char* Regex, StringBuilderBase& OutExpandedRegex) { size_t RegexLen = strlen(Regex); for (size_t i = 0; i < RegexLen;) { bool matched = false; if (Regex[i] == '{' && ((i == 0) || (Regex[i - 1] != '\\'))) { // Might have a pattern reference - find closing brace for (size_t j = i + 1; j < RegexLen; ++j) { if (Regex[j] == '}') { std::string Pattern(&Regex[i + 1], j - i - 1); if (auto it = m_PatternMap.find(Pattern); it != m_PatternMap.end()) { OutExpandedRegex.Append(it->second.c_str()); } else { // Default to anything goes (or should this just be an error?) OutExpandedRegex.Append("(.+?)"); } // skip ahead i = j + 1; matched = true; break; } } } if (!matched) { OutExpandedRegex.Append(Regex[i++]); } } } bool HttpRequestRouter::HandleRequest(zen::HttpServerRequest& Request) { const HttpVerb Verb = Request.RequestVerb(); std::string_view Uri = Request.RelativeUri(); HttpRouterRequest RouterRequest(Request); for (const auto& Handler : m_Handlers) { if ((Handler.Verbs & Verb) == Verb && regex_match(begin(Uri), end(Uri), RouterRequest.m_Match, Handler.RegEx)) { Handler.Handler(RouterRequest); return true; // Route matched } } return false; // No route matched } ////////////////////////////////////////////////////////////////////////// Ref CreateHttpServer() { #if ZEN_WITH_HTTPSYS return new HttpSysServer{32}; #else return new HttpNullServer; #endif } ////////////////////////////////////////////////////////////////////////// TEST_CASE("http") { using namespace std::literals; SUBCASE("router") { HttpRequestRouter r; r.AddPattern("a", "[[:alpha:]]+"); r.RegisterRoute( "{a}", [&](auto) {}, HttpVerb::kGet); // struct TestHttpServerRequest : public HttpServerRequest //{ // TestHttpServerRequest(std::string_view Uri) : m_uri{Uri} {} //}; // TestHttpServerRequest req{}; // r.HandleRequest(req); } } void http_forcelink() { } } // namespace zen