aboutsummaryrefslogtreecommitdiff
path: root/zenhttp/httpserver.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2021-09-09 15:14:08 +0200
committerStefan Boberg <[email protected]>2021-09-09 15:14:08 +0200
commit6ec26c46d694a1d5291790a9c70bec25dce4b513 (patch)
tree4177d45f9703d11f00e495f0fde77f4445453d2d /zenhttp/httpserver.cpp
parentHttpServer::AddEndpoint -> HttpServer::RegisterService (diff)
downloadzen-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.cpp389
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