aboutsummaryrefslogtreecommitdiff
path: root/zenhttp/httpserver.cpp
diff options
context:
space:
mode:
authorMartin Ridgers <[email protected]>2021-09-15 09:22:32 +0200
committerMartin Ridgers <[email protected]>2021-09-15 09:23:33 +0200
commit8f5e773529858223beeecf5d1b69c23991df644e (patch)
tree2c360c67e028f5ecd7368212b0adf8b23578ff9d /zenhttp/httpserver.cpp
parentUse zen::Sleep() in timer.cpp's tests (diff)
parentUpdated function service to new package management API (diff)
downloadzen-8f5e773529858223beeecf5d1b69c23991df644e.tar.xz
zen-8f5e773529858223beeecf5d1b69c23991df644e.zip
Merge main
Diffstat (limited to 'zenhttp/httpserver.cpp')
-rw-r--r--zenhttp/httpserver.cpp533
1 files changed, 533 insertions, 0 deletions
diff --git a/zenhttp/httpserver.cpp b/zenhttp/httpserver.cpp
new file mode 100644
index 000000000..f97ac0067
--- /dev/null
+++ b/zenhttp/httpserver.cpp
@@ -0,0 +1,533 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenhttp/httpserver.h>
+
+#include "httpnull.h"
+#include "httpshared.h"
+#include "httpsys.h"
+#include "httpuws.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 {
+
+using namespace std::literals;
+
+std::string_view
+MapContentTypeToString(HttpContentType ContentType)
+{
+ switch (ContentType)
+ {
+ default:
+ case HttpContentType::kUnknownContentType:
+ case HttpContentType::kBinary:
+ return "application/octet-stream"sv;
+
+ case HttpContentType::kText:
+ return "text/plain"sv;
+
+ case HttpContentType::kJSON:
+ return "application/json"sv;
+
+ case HttpContentType::kCbObject:
+ return "application/x-ue-cb"sv;
+
+ case HttpContentType::kCbPackage:
+ return "application/x-ue-cbpkg"sv;
+
+ case HttpContentType::kCbPackageOffer:
+ return "application/x-ue-offer"sv;
+
+ case HttpContentType::kYAML:
+ return "text/yaml"sv;
+ }
+}
+
+static const uint32_t HashBinary = HashStringDjb2("application/octet-stream"sv);
+static const uint32_t HashJson = HashStringDjb2("application/json"sv);
+static const uint32_t HashYaml = HashStringDjb2("text/yaml"sv);
+static const uint32_t HashText = HashStringDjb2("text/plain"sv);
+static const uint32_t HashCompactBinary = HashStringDjb2("application/x-ue-cb"sv);
+static const uint32_t HashCompactBinaryPackage = HashStringDjb2("application/x-ue-cbpkg"sv);
+static const uint32_t HashCompactBinaryPackageOffer = HashStringDjb2("application/x-ue-offer"sv);
+
+HttpContentType
+ParseContentType(const std::string_view& ContentTypeString)
+{
+ if (!ContentTypeString.empty())
+ {
+ const uint32_t CtHash = HashStringDjb2(ContentTypeString);
+
+ if (CtHash == HashBinary)
+ {
+ return HttpContentType::kBinary;
+ }
+ else if (CtHash == HashCompactBinary)
+ {
+ return HttpContentType::kCbObject;
+ }
+ else if (CtHash == HashCompactBinaryPackage)
+ {
+ return HttpContentType::kCbPackage;
+ }
+ else if (CtHash == HashCompactBinaryPackageOffer)
+ {
+ return HttpContentType::kCbPackageOffer;
+ }
+ else if (CtHash == HashJson)
+ {
+ return HttpContentType::kJSON;
+ }
+ else if (CtHash == HashYaml)
+ {
+ return HttpContentType::kYAML;
+ }
+ else if (CtHash == HashText)
+ {
+ return HttpContentType::kText;
+ }
+ }
+
+ return HttpContentType::kUnknownContentType;
+}
+
+const char*
+ReasonStringForHttpResultCode(int HttpCode)
+{
+ switch (HttpCode)
+ {
+ // 1xx Informational
+
+ case 100:
+ return "Continue";
+ case 101:
+ return "Switching Protocols";
+
+ // 2xx Success
+
+ case 200:
+ return "OK";
+ case 201:
+ return "Created";
+ case 202:
+ return "Accepted";
+ case 204:
+ return "No Content";
+ case 205:
+ return "Reset Content";
+ case 206:
+ return "Partial Content";
+
+ // 3xx Redirection
+
+ case 300:
+ return "Multiple Choices";
+ case 301:
+ return "Moved Permanently";
+ case 302:
+ return "Found";
+ case 303:
+ return "See Other";
+ case 304:
+ return "Not Modified";
+ case 305:
+ return "Use Proxy";
+ case 306:
+ return "Switch Proxy";
+ case 307:
+ return "Temporary Redirect";
+ case 308:
+ return "Permanent Redirect";
+
+ // 4xx Client errors
+
+ case 400:
+ return "Bad Request";
+ case 401:
+ return "Unauthorized";
+ case 402:
+ return "Payment Required";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 405:
+ return "Method Not Allowed";
+ case 406:
+ return "Not Acceptable";
+ case 407:
+ return "Proxy Authentication Required";
+ case 408:
+ return "Request Timeout";
+ case 409:
+ return "Conflict";
+ case 410:
+ return "Gone";
+ case 411:
+ return "Length Required";
+ case 412:
+ return "Precondition Failed";
+ case 413:
+ return "Payload Too Large";
+ case 414:
+ return "URI Too Long";
+ case 415:
+ return "Unsupported Media Type";
+ case 416:
+ return "Range Not Satisifiable";
+ case 417:
+ return "Expectation Failed";
+ case 418:
+ return "I'm a teapot";
+ case 421:
+ return "Misdirected Request";
+ case 422:
+ return "Unprocessable Entity";
+ case 423:
+ return "Locked";
+ case 424:
+ return "Failed Dependency";
+ case 425:
+ return "Too Early";
+ case 426:
+ return "Upgrade Required";
+ case 428:
+ return "Precondition Required";
+ case 429:
+ return "Too Many Requests";
+ case 431:
+ return "Request Header Fields Too Large";
+
+ // 5xx Server errors
+
+ case 500:
+ return "Internal Server Error";
+ case 501:
+ return "Not Implemented";
+ case 502:
+ return "Bad Gateway";
+ case 503:
+ return "Service Unavailable";
+ case 504:
+ return "Gateway Timeout";
+ case 505:
+ return "HTTP Version Not Supported";
+ case 506:
+ return "Variant Also Negotiates";
+ case 507:
+ return "Insufficient Storage";
+ case 508:
+ return "Loop Detected";
+ case 510:
+ return "Not Extended";
+ case 511:
+ return "Network Authentication Required";
+
+ default:
+ return "Unknown Result";
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+Ref<IHttpPackageHandler>
+HttpService::HandlePackageRequest(HttpServerRequest& HttpServiceRequest)
+{
+ ZEN_UNUSED(HttpServiceRequest);
+
+ return nullptr;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+HttpServerRequest::HttpServerRequest()
+{
+}
+
+HttpServerRequest::~HttpServerRequest()
+{
+}
+
+void
+HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, CbPackage Data)
+{
+ std::vector<IoBuffer> ResponseBuffers = FormatPackageMessage(Data);
+ return WriteResponse(ResponseCode, HttpContentType::kCbPackage, ResponseBuffers);
+}
+
+void
+HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, CbObject Data)
+{
+ SharedBuffer Buf = Data.GetBuffer();
+ std::array<IoBuffer, 1> Buffers{IoBufferBuilder::MakeCloneFromMemory(Buf.GetData(), Buf.GetSize())};
+ return WriteResponse(ResponseCode, HttpContentType::kCbObject, Buffers);
+}
+
+void
+HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::string_view ResponseString)
+{
+ return WriteResponse(ResponseCode, ContentType, std::u8string_view{(char8_t*)ResponseString.data(), ResponseString.size()});
+}
+
+void
+HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, IoBuffer Blob)
+{
+ std::array<IoBuffer, 1> Buffers{Blob};
+ return WriteResponse(ResponseCode, ContentType, Buffers);
+}
+
+void
+HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, CompositeBuffer& Payload)
+{
+ std::span<const SharedBuffer> Segments = Payload.GetSegments();
+
+ std::vector<IoBuffer> Buffers;
+
+ for (auto& Segment : Segments)
+ {
+ Buffers.push_back(Segment.AsIoBuffer());
+ }
+
+ WriteResponse(ResponseCode, ContentType, Payload);
+}
+
+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;
+}
+
+Oid
+HttpServerRequest::SessionId() const
+{
+ if (m_Flags & kHaveSessionId)
+ {
+ return m_SessionId;
+ }
+
+ m_SessionId = ParseSessionId();
+ m_Flags |= kHaveSessionId;
+ return m_SessionId;
+}
+
+uint32_t
+HttpServerRequest::RequestId() const
+{
+ if (m_Flags & kHaveRequestId)
+ {
+ return m_RequestId;
+ }
+
+ m_RequestId = ParseRequestId();
+ m_Flags |= kHaveRequestId;
+ return m_RequestId;
+}
+
+CbObject
+HttpServerRequest::ReadPayloadObject()
+{
+ if (IoBuffer Payload = ReadPayload())
+ {
+ return LoadCompactBinaryObject(std::move(Payload));
+ }
+
+ return {};
+}
+
+CbPackage
+HttpServerRequest::ReadPayloadPackage()
+{
+ if (IoBuffer Payload = ReadPayload())
+ {
+ return ParsePackageMessage(std::move(Payload));
+ }
+
+ return {};
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+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::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()
+{
+#if 0
+ return new HttpUwsServer;
+#elif ZEN_WITH_HTTPSYS
+ return new HttpSysServer{std::thread::hardware_concurrency()};
+#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