From 4598f710da2d0e387c53eb97d983ff520c211a8f Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Thu, 9 Apr 2026 10:34:30 +0200 Subject: migrate from http_parser to llhttp (#929) --- src/zenhttp/include/zenhttp/httpserver.h | 5 +- src/zenhttp/servers/httpparser.cpp | 414 ++++++++++++++++++++++++--- src/zenhttp/servers/httpparser.h | 8 +- src/zenhttp/xmake.lua | 2 +- src/zenhttp/zenhttp.cpp | 1 + src/zenserver-test/xmake.lua | 2 +- src/zenserver/proxy/httptrafficinspector.cpp | 73 +++-- src/zenserver/proxy/httptrafficinspector.h | 10 +- src/zenserver/xmake.lua | 2 +- 9 files changed, 421 insertions(+), 96 deletions(-) (limited to 'src') diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h index 76f219f04..e8bfcfd4d 100644 --- a/src/zenhttp/include/zenhttp/httpserver.h +++ b/src/zenhttp/include/zenhttp/httpserver.h @@ -518,7 +518,8 @@ private: bool HandlePackageOffers(HttpService& Service, HttpServerRequest& Request, Ref& PackageHandlerRef); -void http_forcelink(); // internal -void websocket_forcelink(); // internal +void http_forcelink(); // internal +void httpparser_forcelink(); // internal +void websocket_forcelink(); // internal } // namespace zen diff --git a/src/zenhttp/servers/httpparser.cpp b/src/zenhttp/servers/httpparser.cpp index 918b55dc6..8b07c7905 100644 --- a/src/zenhttp/servers/httpparser.cpp +++ b/src/zenhttp/servers/httpparser.cpp @@ -8,6 +8,13 @@ #include +#if ZEN_WITH_TESTS +# include +# include +# include +# include +#endif + namespace zen { using namespace std::literals; @@ -29,25 +36,25 @@ static constexpr uint32_t HashSecWebSocketVersion = HashStringAsLowerDjb2("Sec-W // 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{}}; +// clang-format off +llhttp_settings_t HttpRequestParser::s_ParserSettings = []() { + llhttp_settings_t S; + llhttp_settings_init(&S); + S.on_message_begin = [](llhttp_t* p) { return GetThis(p)->OnMessageBegin(); }; + S.on_url = [](llhttp_t* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnUrl(Data, ByteCount); }; + S.on_status = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_header_field = [](llhttp_t* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeader(Data, ByteCount); }; + S.on_header_value = [](llhttp_t* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeaderValue(Data, ByteCount); }; + S.on_headers_complete = [](llhttp_t* p) { return GetThis(p)->OnHeadersComplete(); }; + S.on_body = [](llhttp_t* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnBody(Data, ByteCount); }; + S.on_message_complete = [](llhttp_t* p) { return GetThis(p)->OnMessageComplete(); }; + return S; +}(); +// clang-format on HttpRequestParser::HttpRequestParser(HttpRequestParserCallbacks& Connection) : m_Connection(Connection) { - http_parser_init(&m_Parser, HTTP_REQUEST); + llhttp_init(&m_Parser, HTTP_REQUEST, &s_ParserSettings); m_Parser.data = this; ResetState(); @@ -60,16 +67,17 @@ HttpRequestParser::~HttpRequestParser() 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) + llhttp_errno_t Err = llhttp_execute(&m_Parser, InputData, DataSize); + if (Err == HPE_OK) { - ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", http_errno_name(HttpErrno), http_errno_description(HttpErrno)); - return ~0ull; + return DataSize; } - return ConsumedBytes; + if (Err == HPE_PAUSED_UPGRADE) + { + return DataSize; + } + ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", llhttp_errno_name(Err), llhttp_get_error_reason(&m_Parser)); + return ~0ull; } int @@ -79,7 +87,7 @@ HttpRequestParser::OnUrl(const char* Data, size_t Bytes) if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; + return -1; } if (m_UrlRange.Length == 0) @@ -101,7 +109,7 @@ HttpRequestParser::OnHeader(const char* Data, size_t Bytes) if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; + return -1; } if (m_HeaderEntries.empty()) @@ -212,7 +220,7 @@ HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes) if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); - return 1; + return -1; } ZEN_ASSERT_SLOW(!m_HeaderEntries.empty()); @@ -269,9 +277,9 @@ HttpRequestParser::OnHeadersComplete() } } - m_KeepAlive = !!http_should_keep_alive(&m_Parser); + m_KeepAlive = !!llhttp_should_keep_alive(&m_Parser); - switch (m_Parser.method) + switch (llhttp_get_method(&m_Parser)) { case HTTP_GET: m_RequestVerb = HttpVerb::kGet; @@ -302,7 +310,7 @@ HttpRequestParser::OnHeadersComplete() break; default: - ZEN_WARN("invalid HTTP method: '{}'", http_method_str((http_method)m_Parser.method)); + ZEN_WARN("invalid HTTP method: '{}'", llhttp_method_name(static_cast(llhttp_get_method(&m_Parser)))); break; } @@ -349,20 +357,11 @@ HttpRequestParser::OnBody(const char* Data, size_t Bytes) { ZEN_WARN("HTTP parser incoming body is larger than content size, need {} more buffer bytes", (m_BodyPosition + Bytes) - m_BodyBuffer.Size()); - return 1; + return -1; } memcpy(reinterpret_cast(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 size mismatch! {} != {}", m_BodyPosition, m_BodyBuffer.Size()); - return 1; - } - } - return 0; } @@ -409,7 +408,7 @@ HttpRequestParser::OnMessageComplete() catch (const AssertException& AssertEx) { ZEN_WARN("Assert caught when processing http request: {}", AssertEx.FullDescription()); - return 1; + return -1; } catch (const std::system_error& SystemError) { @@ -426,19 +425,19 @@ HttpRequestParser::OnMessageComplete() ZEN_ERROR("failed processing http request: '{}' ({})", SystemError.what(), SystemError.code().value()); } ResetState(); - return 1; + return -1; } catch (const std::bad_alloc& BadAlloc) { ZEN_WARN("out of memory when processing http request: '{}'", BadAlloc.what()); ResetState(); - return 1; + return -1; } catch (const std::exception& Ex) { ZEN_ERROR("failed processing http request: '{}'", Ex.what()); ResetState(); - return 1; + return -1; } } @@ -459,4 +458,331 @@ HttpRequestParser::IsWebSocketUpgrade() const return StrCaseCompare(Upgrade.data(), "websocket", 9) == 0; } +////////////////////////////////////////////////////////////////////////// + +#if ZEN_WITH_TESTS + +namespace { + + struct MockCallbacks : HttpRequestParserCallbacks + { + int HandleRequestCount = 0; + int TerminateCount = 0; + + HttpRequestParser* Parser = nullptr; + + HttpVerb LastVerb{}; + std::string LastUrl; + std::string LastQueryString; + std::string LastBody; + bool LastKeepAlive = false; + bool LastIsWebSocketUpgrade = false; + std::string LastSecWebSocketKey; + std::string LastUpgradeHeader; + HttpContentType LastContentType{}; + + void HandleRequest() override + { + ++HandleRequestCount; + if (Parser) + { + LastVerb = Parser->RequestVerb(); + LastUrl = std::string(Parser->Url()); + LastQueryString = std::string(Parser->QueryString()); + LastKeepAlive = Parser->IsKeepAlive(); + LastIsWebSocketUpgrade = Parser->IsWebSocketUpgrade(); + LastSecWebSocketKey = std::string(Parser->SecWebSocketKey()); + LastUpgradeHeader = std::string(Parser->UpgradeHeader()); + LastContentType = Parser->ContentType(); + + IoBuffer Body = Parser->Body(); + if (Body.Size() > 0) + { + LastBody.assign(reinterpret_cast(Body.Data()), Body.Size()); + } + else + { + LastBody.clear(); + } + } + } + + void TerminateConnection() override { ++TerminateCount; } + }; + +} // anonymous namespace + +TEST_SUITE_BEGIN("http.httpparser"); + +TEST_CASE("httpparser.basic_get") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = "GET /path HTTP/1.1\r\nHost: localhost\r\n\r\n"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastVerb, HttpVerb::kGet); + CHECK_EQ(Mock.LastUrl, "/path"); + CHECK(Mock.LastKeepAlive); +} + +TEST_CASE("httpparser.post_with_body") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = + "POST /api HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Length: 13\r\n" + "Content-Type: application/json\r\n" + "\r\n" + "{\"key\":\"val\"}"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastVerb, HttpVerb::kPost); + CHECK_EQ(Mock.LastBody, "{\"key\":\"val\"}"); + CHECK_EQ(Mock.LastContentType, HttpContentType::kJSON); +} + +TEST_CASE("httpparser.pipelined_requests") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = + "GET /first HTTP/1.1\r\nHost: localhost\r\n\r\n" + "GET /second HTTP/1.1\r\nHost: localhost\r\n\r\n"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 2); + CHECK_EQ(Mock.LastUrl, "/second"); +} + +TEST_CASE("httpparser.partial_header") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Chunk1 = "GET /path HTTP/1.1\r\nHost: loc"; + std::string Chunk2 = "alhost\r\n\r\n"; + + size_t Consumed1 = Parser.ConsumeData(Chunk1.data(), Chunk1.size()); + CHECK_NE(Consumed1, ~0ull); + CHECK_EQ(Consumed1, Chunk1.size()); + CHECK_EQ(Mock.HandleRequestCount, 0); + + size_t Consumed2 = Parser.ConsumeData(Chunk2.data(), Chunk2.size()); + CHECK_NE(Consumed2, ~0ull); + CHECK_EQ(Consumed2, Chunk2.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastUrl, "/path"); +} + +TEST_CASE("httpparser.partial_body") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Headers = + "POST /api HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Length: 10\r\n" + "\r\n"; + std::string BodyPart1 = "hello"; + std::string BodyPart2 = "world"; + + std::string Chunk1 = Headers + BodyPart1; + + size_t Consumed1 = Parser.ConsumeData(Chunk1.data(), Chunk1.size()); + CHECK_NE(Consumed1, ~0ull); + CHECK_EQ(Consumed1, Chunk1.size()); + CHECK_EQ(Mock.HandleRequestCount, 0); + + size_t Consumed2 = Parser.ConsumeData(BodyPart2.data(), BodyPart2.size()); + CHECK_NE(Consumed2, ~0ull); + CHECK_EQ(Consumed2, BodyPart2.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastBody, "helloworld"); +} + +TEST_CASE("httpparser.invalid_request") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Garbage = "NOT_HTTP garbage data\r\n\r\n"; + + size_t Consumed = Parser.ConsumeData(Garbage.data(), Garbage.size()); + CHECK_EQ(Consumed, ~0ull); + CHECK_EQ(Mock.HandleRequestCount, 0); +} + +TEST_CASE("httpparser.body_overflow") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + // llhttp enforces Content-Length strictly: it delivers exactly 2 body bytes, + // fires on_message_complete, then tries to parse the remaining "O_LONG_BODY" + // as a new HTTP request which fails. + std::string Request = + "POST /api HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Length: 2\r\n" + "\r\n" + "TOO_LONG_BODY"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, ~0ull); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastBody, "TO"); +} + +TEST_CASE("httpparser.websocket_upgrade") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = + "GET /ws HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK(Mock.LastIsWebSocketUpgrade); + CHECK_EQ(Mock.LastSecWebSocketKey, "dGhlIHNhbXBsZSBub25jZQ=="); + CHECK_EQ(Mock.LastUpgradeHeader, "websocket"); +} + +TEST_CASE("httpparser.websocket_upgrade_with_trailing_bytes") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string HttpPart = + "GET /ws HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + + // Append fake WebSocket frame bytes after the HTTP message + std::string Request = HttpPart; + Request.push_back('\x81'); + Request.push_back('\x05'); + Request.append("hello"); + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_NE(Consumed, ~0ull); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK(Mock.LastIsWebSocketUpgrade); +} + +TEST_CASE("httpparser.keep_alive_detection") +{ + SUBCASE("HTTP/1.1 default keep-alive") + { + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = "GET /path HTTP/1.1\r\nHost: localhost\r\n\r\n"; + Parser.ConsumeData(Request.data(), Request.size()); + CHECK(Mock.LastKeepAlive); + } + + SUBCASE("Connection: close disables keep-alive") + { + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = "GET /path HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"; + Parser.ConsumeData(Request.data(), Request.size()); + CHECK_FALSE(Mock.LastKeepAlive); + } +} + +TEST_CASE("httpparser.all_verbs") +{ + struct VerbTest + { + const char* Method; + HttpVerb Expected; + }; + + VerbTest Tests[] = { + {"GET", HttpVerb::kGet}, + {"POST", HttpVerb::kPost}, + {"PUT", HttpVerb::kPut}, + {"DELETE", HttpVerb::kDelete}, + {"HEAD", HttpVerb::kHead}, + {"COPY", HttpVerb::kCopy}, + {"OPTIONS", HttpVerb::kOptions}, + }; + + for (const VerbTest& Test : Tests) + { + CAPTURE(Test.Method); + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = std::string(Test.Method) + " /path HTTP/1.1\r\nHost: localhost\r\n\r\n"; + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastVerb, Test.Expected); + } +} + +TEST_CASE("httpparser.query_string") +{ + MockCallbacks Mock; + HttpRequestParser Parser(Mock); + Mock.Parser = &Parser; + + std::string Request = "GET /path?key=val&other=123 HTTP/1.1\r\nHost: localhost\r\n\r\n"; + + size_t Consumed = Parser.ConsumeData(Request.data(), Request.size()); + CHECK_EQ(Consumed, Request.size()); + CHECK_EQ(Mock.HandleRequestCount, 1); + CHECK_EQ(Mock.LastUrl, "/path"); + CHECK_EQ(Mock.LastQueryString, "key=val&other=123"); +} + +TEST_SUITE_END(); + +void +httpparser_forcelink() +{ +} + +#endif // ZEN_WITH_TESTS + } // namespace zen diff --git a/src/zenhttp/servers/httpparser.h b/src/zenhttp/servers/httpparser.h index 23ad9d8fb..4ff216248 100644 --- a/src/zenhttp/servers/httpparser.h +++ b/src/zenhttp/servers/httpparser.h @@ -8,7 +8,7 @@ #include ZEN_THIRD_PARTY_INCLUDES_START -#include +#include ZEN_THIRD_PARTY_INCLUDES_END #include @@ -100,7 +100,7 @@ private: Oid m_SessionId{}; IoBuffer m_BodyBuffer; uint64_t m_BodyPosition = 0; - http_parser m_Parser; + llhttp_t m_Parser; eastl::fixed_vector m_HeaderData; std::string m_NormalizedUrl; @@ -114,8 +114,8 @@ private: int OnBody(const char* Data, size_t Bytes); int OnMessageComplete(); - static HttpRequestParser* GetThis(http_parser* Parser) { return reinterpret_cast(Parser->data); } - static http_parser_settings s_ParserSettings; + static HttpRequestParser* GetThis(llhttp_t* Parser) { return reinterpret_cast(Parser->data); } + static llhttp_settings_t s_ParserSettings; }; } // namespace zen diff --git a/src/zenhttp/xmake.lua b/src/zenhttp/xmake.lua index 7b050ae35..67a01403d 100644 --- a/src/zenhttp/xmake.lua +++ b/src/zenhttp/xmake.lua @@ -9,7 +9,7 @@ target('zenhttp') add_files("servers/wshttpsys.cpp", {unity_ignored=true}) add_includedirs("include", {public=true}) add_deps("zencore", "zentelemetry", "transport-sdk", "asio") - add_packages("http_parser", "json11", "libcurl") + add_packages("llhttp", "json11", "libcurl") add_options("httpsys") if is_plat("linux", "macosx") then diff --git a/src/zenhttp/zenhttp.cpp b/src/zenhttp/zenhttp.cpp index 3ac8eea8d..0b2a7ca7c 100644 --- a/src/zenhttp/zenhttp.cpp +++ b/src/zenhttp/zenhttp.cpp @@ -16,6 +16,7 @@ zenhttp_forcelinktests() { http_forcelink(); httpclient_forcelink(); + httpparser_forcelink(); httpclient_test_forcelink(); forcelink_packageformat(); passwordsecurity_forcelink(); diff --git a/src/zenserver-test/xmake.lua b/src/zenserver-test/xmake.lua index 7b208bbc7..c240712ea 100644 --- a/src/zenserver-test/xmake.lua +++ b/src/zenserver-test/xmake.lua @@ -9,7 +9,7 @@ target("zenserver-test") add_deps("zencore", "zenremotestore", "zenhttp", "zencompute", "zenstore") add_deps("zenserver", {inherit=false}) add_deps("zentest-appstub", {inherit=false}) - add_packages("http_parser") + add_packages("llhttp") if has_config("zennomad") then add_deps("zennomad") diff --git a/src/zenserver/proxy/httptrafficinspector.cpp b/src/zenserver/proxy/httptrafficinspector.cpp index 74ecbfd48..913bd2c28 100644 --- a/src/zenserver/proxy/httptrafficinspector.cpp +++ b/src/zenserver/proxy/httptrafficinspector.cpp @@ -10,29 +10,33 @@ namespace zen { // clang-format off -http_parser_settings HttpTrafficInspector::s_RequestSettings{ - .on_message_begin = [](http_parser*) { return 0; }, - .on_url = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnUrl(Data, Len); }, - .on_status = [](http_parser*, const char*, size_t) { return 0; }, - .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, - .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, - .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, - .on_body = [](http_parser*, const char*, size_t) { return 0; }, - .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, - .on_chunk_header{}, - .on_chunk_complete{}}; - -http_parser_settings HttpTrafficInspector::s_ResponseSettings{ - .on_message_begin = [](http_parser*) { return 0; }, - .on_url = [](http_parser*, const char*, size_t) { return 0; }, - .on_status = [](http_parser*, const char*, size_t) { return 0; }, - .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, - .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, - .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, - .on_body = [](http_parser*, const char*, size_t) { return 0; }, - .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, - .on_chunk_header{}, - .on_chunk_complete{}}; +llhttp_settings_t HttpTrafficInspector::s_RequestSettings = []() { + llhttp_settings_t S; + llhttp_settings_init(&S); + S.on_message_begin = [](llhttp_t*) { return 0; }; + S.on_url = [](llhttp_t* p, const char* Data, size_t Len) { return GetThis(p)->OnUrl(Data, Len); }; + S.on_status = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_header_field = [](llhttp_t* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }; + S.on_header_value = [](llhttp_t* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }; + S.on_headers_complete = [](llhttp_t* p) { return GetThis(p)->OnHeadersComplete(); }; + S.on_body = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_message_complete = [](llhttp_t* p) { return GetThis(p)->OnMessageComplete(); }; + return S; +}(); + +llhttp_settings_t HttpTrafficInspector::s_ResponseSettings = []() { + llhttp_settings_t S; + llhttp_settings_init(&S); + S.on_message_begin = [](llhttp_t*) { return 0; }; + S.on_url = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_status = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_header_field = [](llhttp_t* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }; + S.on_header_value = [](llhttp_t* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }; + S.on_headers_complete = [](llhttp_t* p) { return GetThis(p)->OnHeadersComplete(); }; + S.on_body = [](llhttp_t*, const char*, size_t) { return 0; }; + S.on_message_complete = [](llhttp_t* p) { return GetThis(p)->OnMessageComplete(); }; + return S; +}(); // clang-format on HttpTrafficInspector::HttpTrafficInspector(Direction Dir, std::string_view SessionLabel) @@ -40,7 +44,8 @@ HttpTrafficInspector::HttpTrafficInspector(Direction Dir, std::string_view Sessi , m_Direction(Dir) , m_SessionLabel(SessionLabel) { - http_parser_init(&m_Parser, Dir == Direction::Request ? HTTP_REQUEST : HTTP_RESPONSE); + llhttp_settings_t* Settings = (Dir == Direction::Request) ? &s_RequestSettings : &s_ResponseSettings; + llhttp_init(&m_Parser, Dir == Direction::Request ? HTTP_REQUEST : HTTP_RESPONSE, Settings); m_Parser.data = this; } @@ -52,11 +57,9 @@ HttpTrafficInspector::Inspect(const char* Data, size_t Length) return; } - http_parser_settings* Settings = (m_Direction == Direction::Request) ? &s_RequestSettings : &s_ResponseSettings; + llhttp_errno_t Err = llhttp_execute(&m_Parser, Data, Length); - size_t Parsed = http_parser_execute(&m_Parser, Settings, Data, Length); - - if (m_Parser.upgrade) + if (Err == HPE_PAUSED_UPGRADE) { if (m_Direction == Direction::Request) { @@ -72,15 +75,9 @@ HttpTrafficInspector::Inspect(const char* Data, size_t Length) return; } - http_errno Error = HTTP_PARSER_ERRNO(&m_Parser); - if (Error != HPE_OK) - { - ZEN_DEBUG("[{}] non-HTTP traffic detected ({}), disabling inspection", m_SessionLabel, http_errno_name(Error)); - m_Disabled = true; - } - else if (Parsed != Length) + if (Err != HPE_OK) { - ZEN_DEBUG("[{}] parser consumed {}/{} bytes, disabling inspection", m_SessionLabel, Parsed, Length); + ZEN_DEBUG("[{}] non-HTTP traffic detected ({}), disabling inspection", m_SessionLabel, llhttp_errno_name(Err)); m_Disabled = true; } } @@ -127,11 +124,11 @@ HttpTrafficInspector::OnHeadersComplete() { if (m_Direction == Direction::Request) { - m_Method = http_method_str(static_cast(m_Parser.method)); + m_Method = llhttp_method_name(static_cast(llhttp_get_method(&m_Parser))); } else { - m_StatusCode = m_Parser.status_code; + m_StatusCode = static_cast(llhttp_get_status_code(&m_Parser)); } return 0; } diff --git a/src/zenserver/proxy/httptrafficinspector.h b/src/zenserver/proxy/httptrafficinspector.h index f4af0e77e..8192632ba 100644 --- a/src/zenserver/proxy/httptrafficinspector.h +++ b/src/zenserver/proxy/httptrafficinspector.h @@ -6,7 +6,7 @@ #include ZEN_THIRD_PARTY_INCLUDES_START -#include +#include ZEN_THIRD_PARTY_INCLUDES_END #include @@ -45,15 +45,15 @@ private: void ResetMessageState(); - static HttpTrafficInspector* GetThis(http_parser* Parser) { return static_cast(Parser->data); } + static HttpTrafficInspector* GetThis(llhttp_t* Parser) { return static_cast(Parser->data); } - static http_parser_settings s_RequestSettings; - static http_parser_settings s_ResponseSettings; + static llhttp_settings_t s_RequestSettings; + static llhttp_settings_t s_ResponseSettings; LoggerRef Log() { return m_Log; } LoggerRef m_Log; - http_parser m_Parser; + llhttp_t m_Parser; Direction m_Direction; std::string m_SessionLabel; bool m_Disabled = false; diff --git a/src/zenserver/xmake.lua b/src/zenserver/xmake.lua index c2c81e7aa..b609d1050 100644 --- a/src/zenserver/xmake.lua +++ b/src/zenserver/xmake.lua @@ -32,7 +32,7 @@ target("zenserver") add_deps("protozero", "asio", "cxxopts") add_deps("sol2") - add_packages("http_parser") + add_packages("llhttp") add_packages("json11") add_packages("zlib") add_packages("lua") -- cgit v1.2.3