diff options
| author | Stefan Boberg <[email protected]> | 2021-09-09 15:14:08 +0200 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2021-09-09 15:14:08 +0200 |
| commit | 6ec26c46d694a1d5291790a9c70bec25dce4b513 (patch) | |
| tree | 4177d45f9703d11f00e495f0fde77f4445453d2d /zenhttp/httpserver.cpp | |
| parent | HttpServer::AddEndpoint -> HttpServer::RegisterService (diff) | |
| download | zen-6ec26c46d694a1d5291790a9c70bec25dce4b513.tar.xz zen-6ec26c46d694a1d5291790a9c70bec25dce4b513.zip | |
Factored out http server related code into zenhttp module since it feels out of place in zencore
Diffstat (limited to 'zenhttp/httpserver.cpp')
| -rw-r--r-- | zenhttp/httpserver.cpp | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/zenhttp/httpserver.cpp b/zenhttp/httpserver.cpp new file mode 100644 index 000000000..a0b17fe44 --- /dev/null +++ b/zenhttp/httpserver.cpp @@ -0,0 +1,389 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zenhttp/httpserver.h> + +#include "httpsys.h" + +#include <zencore/compactbinary.h> +#include <zencore/compactbinarypackage.h> +#include <zencore/iobuffer.h> +#include <zencore/logging.h> +#include <zencore/refcount.h> +#include <zencore/stream.h> +#include <zencore/string.h> +#include <zencore/thread.h> + +#include <conio.h> +#include <new.h> +#include <charconv> +#include <span> +#include <string_view> + +#include <doctest/doctest.h> + +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<const CbAttachment>& Attachments = Data.GetAttachments(); + + std::vector<IoBuffer> 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<uint32_t>(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<CbAttachmentEntry*>(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<IoBuffer, 1> 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<IoBuffer, 1> 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<CbAttachmentEntry[]> 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<HttpServer> +CreateHttpServer() +{ + return new HttpSysServer{32}; +} + +////////////////////////////////////////////////////////////////////////// + +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 |