diff options
| author | Stefan Boberg <[email protected]> | 2023-10-03 18:15:35 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-03 18:15:35 +0200 |
| commit | c10f6ce83c4460fa162641a22243faed27abe541 (patch) | |
| tree | eb32786f786e8ec50ba7c6f86f231128ef5df10e /src | |
| parent | faster accesstime save restore (#439) (diff) | |
| download | zen-c10f6ce83c4460fa162641a22243faed27abe541.tar.xz zen-c10f6ce83c4460fa162641a22243faed27abe541.zip | |
factored out http parser from asio into separate files (#444)
factored out http request parsing from httpasio into separate files to enable code to be reused for different transports
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenhttp/httpasio.cpp | 465 | ||||
| -rw-r--r-- | src/zenhttp/httpparser.cpp | 367 | ||||
| -rw-r--r-- | src/zenhttp/httpparser.h | 113 |
3 files changed, 495 insertions, 450 deletions
diff --git a/src/zenhttp/httpasio.cpp b/src/zenhttp/httpasio.cpp index f29b3132e..702ca11fd 100644 --- a/src/zenhttp/httpasio.cpp +++ b/src/zenhttp/httpasio.cpp @@ -7,6 +7,8 @@ #include <zencore/trace.h> #include <zenhttp/httpserver.h> +#include "httpparser.h" + #include <deque> #include <memory> #include <string_view> @@ -17,7 +19,6 @@ ZEN_THIRD_PARTY_INCLUDES_START # include <conio.h> # include <mstcpip.h> #endif -#include <http_parser.h> #include <asio.hpp> ZEN_THIRD_PARTY_INCLUDES_END @@ -34,7 +35,6 @@ namespace zen::asio_http { using namespace std::literals; struct HttpAcceptor; -struct HttpRequest; struct HttpResponse; struct HttpServerConnection; @@ -96,7 +96,7 @@ public: class HttpAsioServerRequest : public HttpServerRequest { public: - HttpAsioServerRequest(asio_http::HttpRequest& Request, HttpService& Service, IoBuffer PayloadBuffer); + HttpAsioServerRequest(HttpRequestParser& Request, HttpService& Service, IoBuffer PayloadBuffer); ~HttpAsioServerRequest(); virtual Oid ParseSessionId() const override; @@ -114,101 +114,11 @@ public: HttpAsioServerRequest(const HttpAsioServerRequest&) = delete; HttpAsioServerRequest& operator=(const HttpAsioServerRequest&) = delete; - asio_http::HttpRequest& m_Request; + HttpRequestParser& m_Request; IoBuffer m_PayloadBuffer; std::unique_ptr<HttpResponse> m_Response; }; -struct HttpRequest -{ - explicit HttpRequest(HttpServerConnection& Connection) : m_Connection(Connection) {} - - void Initialize(); - size_t ConsumeData(const char* InputData, size_t DataSize); - void ResetState(); - - HttpVerb RequestVerb() const { return m_RequestVerb; } - bool IsKeepAlive() const { return m_KeepAlive; } - std::string_view Url() const { return m_NormalizedUrl.empty() ? std::string_view(m_Url, m_UrlLength) : m_NormalizedUrl; } - std::string_view QueryString() const { return std::string_view(m_QueryString, m_QueryLength); } - IoBuffer Body() { return m_BodyBuffer; } - - inline HttpContentType ContentType() - { - if (m_ContentTypeHeaderIndex < 0) - { - return HttpContentType::kUnknownContentType; - } - - return ParseContentType(m_Headers[m_ContentTypeHeaderIndex].Value); - } - - inline HttpContentType AcceptType() - { - if (m_AcceptHeaderIndex < 0) - { - return HttpContentType::kUnknownContentType; - } - - return ParseContentType(m_Headers[m_AcceptHeaderIndex].Value); - } - - Oid SessionId() const { return m_SessionId; } - int RequestId() const { return m_RequestId; } - - std::string_view RangeHeader() const { return m_RangeHeaderIndex != -1 ? m_Headers[m_RangeHeaderIndex].Value : std::string_view(); } - -private: - struct HeaderEntry - { - HeaderEntry() = default; - - HeaderEntry(std::string_view InName, std::string_view InValue) : Name(InName), Value(InValue) {} - - std::string_view Name; - std::string_view Value; - }; - - HttpServerConnection& m_Connection; - char* m_HeaderCursor = m_HeaderBuffer; - char* m_Url = nullptr; - size_t m_UrlLength = 0; - char* m_QueryString = nullptr; - size_t m_QueryLength = 0; - char* m_CurrentHeaderName = nullptr; // Used while parsing headers - size_t m_CurrentHeaderNameLength = 0; - char* m_CurrentHeaderValue = nullptr; // Used while parsing headers - size_t m_CurrentHeaderValueLength = 0; - std::vector<HeaderEntry> m_Headers; - int8_t m_ContentLengthHeaderIndex; - int8_t m_AcceptHeaderIndex; - int8_t m_ContentTypeHeaderIndex; - int8_t m_RangeHeaderIndex; - HttpVerb m_RequestVerb; - bool m_KeepAlive = false; - bool m_Expect100Continue = false; - int m_RequestId = -1; - Oid m_SessionId{}; - IoBuffer m_BodyBuffer; - uint64_t m_BodyPosition = 0; - http_parser m_Parser; - char m_HeaderBuffer[1024]; - std::string m_NormalizedUrl; - - void AppendCurrentHeader(); - - int OnMessageBegin(); - int OnUrl(const char* Data, size_t Bytes); - int OnHeader(const char* Data, size_t Bytes); - int OnHeaderValue(const char* Data, size_t Bytes); - int OnHeadersComplete(); - int OnBody(const char* Data, size_t Bytes); - int OnMessageComplete(); - - static HttpRequest* GetThis(http_parser* Parser) { return reinterpret_cast<HttpRequest*>(Parser->data); } - static http_parser_settings s_ParserSettings; -}; - struct HttpResponse { public: @@ -304,17 +214,19 @@ private: ////////////////////////////////////////////////////////////////////////// -struct HttpServerConnection : std::enable_shared_from_this<HttpServerConnection> +struct HttpServerConnection : public HttpConnectionBase, std::enable_shared_from_this<HttpServerConnection> { HttpServerConnection(HttpAsioServerImpl& Server, std::unique_ptr<asio::ip::tcp::socket>&& Socket); ~HttpServerConnection(); - void HandleNewRequest(); - void TerminateConnection(); - void HandleRequest(); - std::shared_ptr<HttpServerConnection> AsSharedPtr() { return shared_from_this(); } + // HttpConnectionBase implementation + + virtual void HandleNewRequest() override; + virtual void TerminateConnection() override; + virtual void HandleRequest() override; + private: enum class RequestState { @@ -327,8 +239,8 @@ private: kTerminated }; - RequestState m_RequestState = RequestState::kInitialState; - HttpRequest m_RequestData{*this}; + RequestState m_RequestState = RequestState::kInitialState; + HttpRequestParser m_RequestData{*this}; void EnqueueRead(); void OnDataReceived(const asio::error_code& Ec, std::size_t ByteCount); @@ -662,353 +574,6 @@ HttpServerConnection::HandleRequest() } ////////////////////////////////////////////////////////////////////////// -// -// HttpRequest -// - -http_parser_settings HttpRequest::s_ParserSettings{ - .on_message_begin = [](http_parser* p) { return GetThis(p)->OnMessageBegin(); }, - .on_url = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnUrl(Data, ByteCount); }, - .on_status = - [](http_parser* p, const char* Data, size_t ByteCount) { - ZEN_UNUSED(p, Data, ByteCount); - return 0; - }, - .on_header_field = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeader(Data, ByteCount); }, - .on_header_value = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeaderValue(Data, ByteCount); }, - .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, - .on_body = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnBody(Data, ByteCount); }, - .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, - .on_chunk_header{}, - .on_chunk_complete{}}; - -void -HttpRequest::Initialize() -{ - http_parser_init(&m_Parser, HTTP_REQUEST); - m_Parser.data = this; - - ResetState(); -} - -size_t -HttpRequest::ConsumeData(const char* InputData, size_t DataSize) -{ - const size_t ConsumedBytes = http_parser_execute(&m_Parser, &s_ParserSettings, InputData, DataSize); - - http_errno HttpErrno = HTTP_PARSER_ERRNO((&m_Parser)); - - if (HttpErrno && HttpErrno != HPE_INVALID_EOF_STATE) - { - ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", http_errno_name(HttpErrno), http_errno_description(HttpErrno)); - return ~0ull; - } - - return ConsumedBytes; -} - -int -HttpRequest::OnUrl(const char* Data, size_t Bytes) -{ - if (!m_Url) - { - ZEN_ASSERT_SLOW(m_UrlLength == 0); - m_Url = m_HeaderCursor; - } - - const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; - - if (RemainingBufferSpace < Bytes) - { - ZEN_WARN("HTTP parser does not have enough space for incoming request, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; - } - - memcpy(m_HeaderCursor, Data, Bytes); - m_HeaderCursor += Bytes; - m_UrlLength += Bytes; - - return 0; -} - -int -HttpRequest::OnHeader(const char* Data, size_t Bytes) -{ - if (m_CurrentHeaderValueLength) - { - AppendCurrentHeader(); - - m_CurrentHeaderNameLength = 0; - m_CurrentHeaderValueLength = 0; - m_CurrentHeaderName = m_HeaderCursor; - } - else if (m_CurrentHeaderName == nullptr) - { - m_CurrentHeaderName = m_HeaderCursor; - } - - const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; - if (RemainingBufferSpace < Bytes) - { - ZEN_WARN("HTTP parser does not have enough space for incoming header name, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; - } - - memcpy(m_HeaderCursor, Data, Bytes); - m_HeaderCursor += Bytes; - m_CurrentHeaderNameLength += Bytes; - - return 0; -} - -void -HttpRequest::AppendCurrentHeader() -{ - std::string_view HeaderName(m_CurrentHeaderName, m_CurrentHeaderNameLength); - std::string_view HeaderValue(m_CurrentHeaderValue, m_CurrentHeaderValueLength); - - const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName); - - if (HeaderHash == HashContentLength) - { - m_ContentLengthHeaderIndex = (int8_t)m_Headers.size(); - } - else if (HeaderHash == HashAccept) - { - m_AcceptHeaderIndex = (int8_t)m_Headers.size(); - } - else if (HeaderHash == HashContentType) - { - m_ContentTypeHeaderIndex = (int8_t)m_Headers.size(); - } - else if (HeaderHash == HashSession) - { - m_SessionId = Oid::FromHexString(HeaderValue); - } - else if (HeaderHash == HashRequest) - { - std::from_chars(HeaderValue.data(), HeaderValue.data() + HeaderValue.size(), m_RequestId); - } - else if (HeaderHash == HashExpect) - { - if (HeaderValue == "100-continue"sv) - { - // We don't currently do anything with this - m_Expect100Continue = true; - } - else - { - ZEN_INFO("Unexpected expect - Expect: {}", HeaderValue); - } - } - else if (HeaderHash == HashRange) - { - m_RangeHeaderIndex = (int8_t)m_Headers.size(); - } - - m_Headers.emplace_back(HeaderName, HeaderValue); -} - -int -HttpRequest::OnHeaderValue(const char* Data, size_t Bytes) -{ - if (m_CurrentHeaderValueLength == 0) - { - m_CurrentHeaderValue = m_HeaderCursor; - } - - const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; - if (RemainingBufferSpace < Bytes) - { - ZEN_WARN("HTTP parser does not have enough space for incoming header value, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; - } - - memcpy(m_HeaderCursor, Data, Bytes); - m_HeaderCursor += Bytes; - m_CurrentHeaderValueLength += Bytes; - - return 0; -} - -static void -NormalizeUrlPath(const char* Url, size_t UrlLength, std::string& NormalizedUrl) -{ - bool LastCharWasSeparator = false; - for (std::string_view::size_type UrlIndex = 0; UrlIndex < UrlLength; ++UrlIndex) - { - const char UrlChar = Url[UrlIndex]; - const bool IsSeparator = (UrlChar == '/'); - - if (IsSeparator && LastCharWasSeparator) - { - if (NormalizedUrl.empty()) - { - NormalizedUrl.reserve(UrlLength); - NormalizedUrl.append(Url, UrlIndex); - } - - if (!LastCharWasSeparator) - { - NormalizedUrl.push_back('/'); - } - } - else if (!NormalizedUrl.empty()) - { - NormalizedUrl.push_back(UrlChar); - } - - LastCharWasSeparator = IsSeparator; - } -} - -int -HttpRequest::OnHeadersComplete() -{ - try - { - if (m_CurrentHeaderValueLength) - { - AppendCurrentHeader(); - } - - m_KeepAlive = !!http_should_keep_alive(&m_Parser); - - switch (m_Parser.method) - { - case HTTP_GET: - m_RequestVerb = HttpVerb::kGet; - break; - - case HTTP_POST: - m_RequestVerb = HttpVerb::kPost; - break; - - case HTTP_PUT: - m_RequestVerb = HttpVerb::kPut; - break; - - case HTTP_DELETE: - m_RequestVerb = HttpVerb::kDelete; - break; - - case HTTP_HEAD: - m_RequestVerb = HttpVerb::kHead; - break; - - case HTTP_COPY: - m_RequestVerb = HttpVerb::kCopy; - break; - - case HTTP_OPTIONS: - m_RequestVerb = HttpVerb::kOptions; - break; - - default: - ZEN_WARN("invalid HTTP method: '{}'", http_method_str((http_method)m_Parser.method)); - break; - } - - std::string_view Url(m_Url, m_UrlLength); - - if (auto QuerySplit = Url.find_first_of('?'); QuerySplit != std::string_view::npos) - { - m_UrlLength = QuerySplit; - m_QueryString = m_Url + QuerySplit + 1; - m_QueryLength = Url.size() - QuerySplit - 1; - } - - NormalizeUrlPath(m_Url, m_UrlLength, m_NormalizedUrl); - - if (m_ContentLengthHeaderIndex >= 0) - { - std::string_view& Value = m_Headers[m_ContentLengthHeaderIndex].Value; - uint64_t ContentLength = 0; - std::from_chars(Value.data(), Value.data() + Value.size(), ContentLength); - - if (ContentLength) - { - m_BodyBuffer = IoBuffer(ContentLength); - } - - m_BodyBuffer.SetContentType(ContentType()); - - m_BodyPosition = 0; - } - } - catch (const std::exception& Ex) - { - ZEN_WARN("HttpRequest::OnHeadersComplete failed. Reason '{}'", Ex.what()); - return -1; - } - return 0; -} - -int -HttpRequest::OnBody(const char* Data, size_t Bytes) -{ - if (m_BodyPosition + Bytes > m_BodyBuffer.Size()) - { - ZEN_WARN("HTTP parser incoming body is larger than content size, need {} more bytes", - (m_BodyPosition + Bytes) - m_BodyBuffer.Size()); - return 1; - } - memcpy(reinterpret_cast<uint8_t*>(m_BodyBuffer.MutableData()) + m_BodyPosition, Data, Bytes); - m_BodyPosition += Bytes; - - if (http_body_is_final(&m_Parser)) - { - if (m_BodyPosition != m_BodyBuffer.Size()) - { - ZEN_WARN("Body mismatch! {} != {}", m_BodyPosition, m_BodyBuffer.Size()); - return 1; - } - } - - return 0; -} - -void -HttpRequest::ResetState() -{ - m_HeaderCursor = m_HeaderBuffer; - m_CurrentHeaderName = nullptr; - m_CurrentHeaderNameLength = 0; - m_CurrentHeaderValue = nullptr; - m_CurrentHeaderValueLength = 0; - m_CurrentHeaderName = nullptr; - m_Url = nullptr; - m_UrlLength = 0; - m_QueryString = nullptr; - m_QueryLength = 0; - m_ContentLengthHeaderIndex = -1; - m_AcceptHeaderIndex = -1; - m_ContentTypeHeaderIndex = -1; - m_RangeHeaderIndex = -1; - m_Expect100Continue = false; - m_BodyBuffer = {}; - m_BodyPosition = 0; - m_Headers.clear(); - m_NormalizedUrl.clear(); -} - -int -HttpRequest::OnMessageBegin() -{ - return 0; -} - -int -HttpRequest::OnMessageComplete() -{ - m_Connection.HandleRequest(); - - ResetState(); - - return 0; -} - -////////////////////////////////////////////////////////////////////////// struct HttpAcceptor { @@ -1136,7 +701,7 @@ private: ////////////////////////////////////////////////////////////////////////// -HttpAsioServerRequest::HttpAsioServerRequest(asio_http::HttpRequest& Request, HttpService& Service, IoBuffer PayloadBuffer) +HttpAsioServerRequest::HttpAsioServerRequest(HttpRequestParser& Request, HttpService& Service, IoBuffer PayloadBuffer) : m_Request(Request) , m_PayloadBuffer(std::move(PayloadBuffer)) { @@ -1369,7 +934,7 @@ HttpAsioServer::HttpAsioServer(unsigned int ThreadCount) : m_ThreadCount(ThreadCount != 0 ? ThreadCount : Max(std::thread::hardware_concurrency(), 8u)) , m_Impl(std::make_unique<asio_http::HttpAsioServerImpl>()) { - ZEN_DEBUG("Request object size: {} ({:#x})", sizeof(asio_http::HttpRequest), sizeof(asio_http::HttpRequest)); + ZEN_DEBUG("Request object size: {} ({:#x})", sizeof(HttpRequestParser), sizeof(HttpRequestParser)); } HttpAsioServer::~HttpAsioServer() diff --git a/src/zenhttp/httpparser.cpp b/src/zenhttp/httpparser.cpp new file mode 100644 index 000000000..ebfe36227 --- /dev/null +++ b/src/zenhttp/httpparser.cpp @@ -0,0 +1,367 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "httpparser.h" + +#include <zencore/logging.h> +#include <zencore/string.h> + +namespace zen { + +using namespace std::literals; + +static constinit uint32_t HashContentLength = HashStringAsLowerDjb2("Content-Length"sv); +static constinit uint32_t HashContentType = HashStringAsLowerDjb2("Content-Type"sv); +static constinit uint32_t HashAccept = HashStringAsLowerDjb2("Accept"sv); +static constinit uint32_t HashExpect = HashStringAsLowerDjb2("Expect"sv); +static constinit uint32_t HashSession = HashStringAsLowerDjb2("UE-Session"sv); +static constinit uint32_t HashRequest = HashStringAsLowerDjb2("UE-Request"sv); +static constinit uint32_t HashRange = HashStringAsLowerDjb2("Range"sv); + +////////////////////////////////////////////////////////////////////////// +// +// HttpRequestParser +// + +http_parser_settings HttpRequestParser::s_ParserSettings{ + .on_message_begin = [](http_parser* p) { return GetThis(p)->OnMessageBegin(); }, + .on_url = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnUrl(Data, ByteCount); }, + .on_status = + [](http_parser* p, const char* Data, size_t ByteCount) { + ZEN_UNUSED(p, Data, ByteCount); + return 0; + }, + .on_header_field = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeader(Data, ByteCount); }, + .on_header_value = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeaderValue(Data, ByteCount); }, + .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, + .on_body = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnBody(Data, ByteCount); }, + .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, + .on_chunk_header{}, + .on_chunk_complete{}}; + +void +HttpRequestParser::Initialize() +{ + http_parser_init(&m_Parser, HTTP_REQUEST); + m_Parser.data = this; + + ResetState(); +} + +size_t +HttpRequestParser::ConsumeData(const char* InputData, size_t DataSize) +{ + const size_t ConsumedBytes = http_parser_execute(&m_Parser, &s_ParserSettings, InputData, DataSize); + + http_errno HttpErrno = HTTP_PARSER_ERRNO((&m_Parser)); + + if (HttpErrno && HttpErrno != HPE_INVALID_EOF_STATE) + { + ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", http_errno_name(HttpErrno), http_errno_description(HttpErrno)); + return ~0ull; + } + + return ConsumedBytes; +} + +int +HttpRequestParser::OnUrl(const char* Data, size_t Bytes) +{ + if (!m_Url) + { + ZEN_ASSERT_SLOW(m_UrlLength == 0); + m_Url = m_HeaderCursor; + } + + const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; + + if (RemainingBufferSpace < Bytes) + { + ZEN_WARN("HTTP parser does not have enough space for incoming request, need {} more bytes", Bytes - RemainingBufferSpace); + return 1; + } + + memcpy(m_HeaderCursor, Data, Bytes); + m_HeaderCursor += Bytes; + m_UrlLength += Bytes; + + return 0; +} + +int +HttpRequestParser::OnHeader(const char* Data, size_t Bytes) +{ + if (m_CurrentHeaderValueLength) + { + AppendCurrentHeader(); + + m_CurrentHeaderNameLength = 0; + m_CurrentHeaderValueLength = 0; + m_CurrentHeaderName = m_HeaderCursor; + } + else if (m_CurrentHeaderName == nullptr) + { + m_CurrentHeaderName = m_HeaderCursor; + } + + const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; + if (RemainingBufferSpace < Bytes) + { + ZEN_WARN("HTTP parser does not have enough space for incoming header name, need {} more bytes", Bytes - RemainingBufferSpace); + return 1; + } + + memcpy(m_HeaderCursor, Data, Bytes); + m_HeaderCursor += Bytes; + m_CurrentHeaderNameLength += Bytes; + + return 0; +} + +void +HttpRequestParser::AppendCurrentHeader() +{ + std::string_view HeaderName(m_CurrentHeaderName, m_CurrentHeaderNameLength); + std::string_view HeaderValue(m_CurrentHeaderValue, m_CurrentHeaderValueLength); + + const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName); + + if (HeaderHash == HashContentLength) + { + m_ContentLengthHeaderIndex = (int8_t)m_Headers.size(); + } + else if (HeaderHash == HashAccept) + { + m_AcceptHeaderIndex = (int8_t)m_Headers.size(); + } + else if (HeaderHash == HashContentType) + { + m_ContentTypeHeaderIndex = (int8_t)m_Headers.size(); + } + else if (HeaderHash == HashSession) + { + m_SessionId = Oid::FromHexString(HeaderValue); + } + else if (HeaderHash == HashRequest) + { + std::from_chars(HeaderValue.data(), HeaderValue.data() + HeaderValue.size(), m_RequestId); + } + else if (HeaderHash == HashExpect) + { + if (HeaderValue == "100-continue"sv) + { + // We don't currently do anything with this + m_Expect100Continue = true; + } + else + { + ZEN_INFO("Unexpected expect - Expect: {}", HeaderValue); + } + } + else if (HeaderHash == HashRange) + { + m_RangeHeaderIndex = (int8_t)m_Headers.size(); + } + + m_Headers.emplace_back(HeaderName, HeaderValue); +} + +int +HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes) +{ + if (m_CurrentHeaderValueLength == 0) + { + m_CurrentHeaderValue = m_HeaderCursor; + } + + const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; + if (RemainingBufferSpace < Bytes) + { + ZEN_WARN("HTTP parser does not have enough space for incoming header value, need {} more bytes", Bytes - RemainingBufferSpace); + return 1; + } + + memcpy(m_HeaderCursor, Data, Bytes); + m_HeaderCursor += Bytes; + m_CurrentHeaderValueLength += Bytes; + + return 0; +} + +static void +NormalizeUrlPath(const char* Url, size_t UrlLength, std::string& NormalizedUrl) +{ + bool LastCharWasSeparator = false; + for (std::string_view::size_type UrlIndex = 0; UrlIndex < UrlLength; ++UrlIndex) + { + const char UrlChar = Url[UrlIndex]; + const bool IsSeparator = (UrlChar == '/'); + + if (IsSeparator && LastCharWasSeparator) + { + if (NormalizedUrl.empty()) + { + NormalizedUrl.reserve(UrlLength); + NormalizedUrl.append(Url, UrlIndex); + } + + if (!LastCharWasSeparator) + { + NormalizedUrl.push_back('/'); + } + } + else if (!NormalizedUrl.empty()) + { + NormalizedUrl.push_back(UrlChar); + } + + LastCharWasSeparator = IsSeparator; + } +} + +int +HttpRequestParser::OnHeadersComplete() +{ + try + { + if (m_CurrentHeaderValueLength) + { + AppendCurrentHeader(); + } + + m_KeepAlive = !!http_should_keep_alive(&m_Parser); + + switch (m_Parser.method) + { + case HTTP_GET: + m_RequestVerb = HttpVerb::kGet; + break; + + case HTTP_POST: + m_RequestVerb = HttpVerb::kPost; + break; + + case HTTP_PUT: + m_RequestVerb = HttpVerb::kPut; + break; + + case HTTP_DELETE: + m_RequestVerb = HttpVerb::kDelete; + break; + + case HTTP_HEAD: + m_RequestVerb = HttpVerb::kHead; + break; + + case HTTP_COPY: + m_RequestVerb = HttpVerb::kCopy; + break; + + case HTTP_OPTIONS: + m_RequestVerb = HttpVerb::kOptions; + break; + + default: + ZEN_WARN("invalid HTTP method: '{}'", http_method_str((http_method)m_Parser.method)); + break; + } + + std::string_view Url(m_Url, m_UrlLength); + + if (auto QuerySplit = Url.find_first_of('?'); QuerySplit != std::string_view::npos) + { + m_UrlLength = QuerySplit; + m_QueryString = m_Url + QuerySplit + 1; + m_QueryLength = Url.size() - QuerySplit - 1; + } + + NormalizeUrlPath(m_Url, m_UrlLength, m_NormalizedUrl); + + if (m_ContentLengthHeaderIndex >= 0) + { + std::string_view& Value = m_Headers[m_ContentLengthHeaderIndex].Value; + uint64_t ContentLength = 0; + std::from_chars(Value.data(), Value.data() + Value.size(), ContentLength); + + if (ContentLength) + { + m_BodyBuffer = IoBuffer(ContentLength); + } + + m_BodyBuffer.SetContentType(ContentType()); + + m_BodyPosition = 0; + } + } + catch (const std::exception& Ex) + { + ZEN_WARN("HttpRequestParser::OnHeadersComplete failed. Reason '{}'", Ex.what()); + return -1; + } + return 0; +} + +int +HttpRequestParser::OnBody(const char* Data, size_t Bytes) +{ + if (m_BodyPosition + Bytes > m_BodyBuffer.Size()) + { + ZEN_WARN("HTTP parser incoming body is larger than content size, need {} more bytes", + (m_BodyPosition + Bytes) - m_BodyBuffer.Size()); + return 1; + } + memcpy(reinterpret_cast<uint8_t*>(m_BodyBuffer.MutableData()) + m_BodyPosition, Data, Bytes); + m_BodyPosition += Bytes; + + if (http_body_is_final(&m_Parser)) + { + if (m_BodyPosition != m_BodyBuffer.Size()) + { + ZEN_WARN("Body mismatch! {} != {}", m_BodyPosition, m_BodyBuffer.Size()); + return 1; + } + } + + return 0; +} + +void +HttpRequestParser::ResetState() +{ + m_HeaderCursor = m_HeaderBuffer; + m_CurrentHeaderName = nullptr; + m_CurrentHeaderNameLength = 0; + m_CurrentHeaderValue = nullptr; + m_CurrentHeaderValueLength = 0; + m_CurrentHeaderName = nullptr; + m_Url = nullptr; + m_UrlLength = 0; + m_QueryString = nullptr; + m_QueryLength = 0; + m_ContentLengthHeaderIndex = -1; + m_AcceptHeaderIndex = -1; + m_ContentTypeHeaderIndex = -1; + m_RangeHeaderIndex = -1; + m_Expect100Continue = false; + m_BodyBuffer = {}; + m_BodyPosition = 0; + m_Headers.clear(); + m_NormalizedUrl.clear(); +} + +int +HttpRequestParser::OnMessageBegin() +{ + return 0; +} + +int +HttpRequestParser::OnMessageComplete() +{ + m_Connection.HandleRequest(); + + ResetState(); + + return 0; +} + +} // namespace zen diff --git a/src/zenhttp/httpparser.h b/src/zenhttp/httpparser.h new file mode 100644 index 000000000..cce51fcca --- /dev/null +++ b/src/zenhttp/httpparser.h @@ -0,0 +1,113 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/uid.h> +#include <zenhttp/httpcommon.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <http_parser.h> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace zen { + +class HttpConnectionBase +{ +public: + virtual ~HttpConnectionBase() = default; + virtual void HandleNewRequest() = 0; + virtual void TerminateConnection() = 0; + virtual void HandleRequest() = 0; +}; + +struct HttpRequestParser +{ + explicit HttpRequestParser(HttpConnectionBase& Connection) : m_Connection(Connection) {} + + void Initialize(); + size_t ConsumeData(const char* InputData, size_t DataSize); + void ResetState(); + + HttpVerb RequestVerb() const { return m_RequestVerb; } + bool IsKeepAlive() const { return m_KeepAlive; } + std::string_view Url() const { return m_NormalizedUrl.empty() ? std::string_view(m_Url, m_UrlLength) : m_NormalizedUrl; } + std::string_view QueryString() const { return std::string_view(m_QueryString, m_QueryLength); } + IoBuffer Body() { return m_BodyBuffer; } + + inline HttpContentType ContentType() + { + if (m_ContentTypeHeaderIndex < 0) + { + return HttpContentType::kUnknownContentType; + } + + return ParseContentType(m_Headers[m_ContentTypeHeaderIndex].Value); + } + + inline HttpContentType AcceptType() + { + if (m_AcceptHeaderIndex < 0) + { + return HttpContentType::kUnknownContentType; + } + + return ParseContentType(m_Headers[m_AcceptHeaderIndex].Value); + } + + Oid SessionId() const { return m_SessionId; } + int RequestId() const { return m_RequestId; } + + std::string_view RangeHeader() const { return m_RangeHeaderIndex != -1 ? m_Headers[m_RangeHeaderIndex].Value : std::string_view(); } + +private: + struct HeaderEntry + { + HeaderEntry() = default; + + HeaderEntry(std::string_view InName, std::string_view InValue) : Name(InName), Value(InValue) {} + + std::string_view Name; + std::string_view Value; + }; + + HttpConnectionBase& m_Connection; + char* m_HeaderCursor = m_HeaderBuffer; + char* m_Url = nullptr; + size_t m_UrlLength = 0; + char* m_QueryString = nullptr; + size_t m_QueryLength = 0; + char* m_CurrentHeaderName = nullptr; // Used while parsing headers + size_t m_CurrentHeaderNameLength = 0; + char* m_CurrentHeaderValue = nullptr; // Used while parsing headers + size_t m_CurrentHeaderValueLength = 0; + std::vector<HeaderEntry> m_Headers; + int8_t m_ContentLengthHeaderIndex; + int8_t m_AcceptHeaderIndex; + int8_t m_ContentTypeHeaderIndex; + int8_t m_RangeHeaderIndex; + HttpVerb m_RequestVerb; + bool m_KeepAlive = false; + bool m_Expect100Continue = false; + int m_RequestId = -1; + Oid m_SessionId{}; + IoBuffer m_BodyBuffer; + uint64_t m_BodyPosition = 0; + http_parser m_Parser; + char m_HeaderBuffer[1024]; + std::string m_NormalizedUrl; + + void AppendCurrentHeader(); + + int OnMessageBegin(); + int OnUrl(const char* Data, size_t Bytes); + int OnHeader(const char* Data, size_t Bytes); + int OnHeaderValue(const char* Data, size_t Bytes); + int OnHeadersComplete(); + int OnBody(const char* Data, size_t Bytes); + int OnMessageComplete(); + + static HttpRequestParser* GetThis(http_parser* Parser) { return reinterpret_cast<HttpRequestParser*>(Parser->data); } + static http_parser_settings s_ParserSettings; +}; + +} // namespace zen |