aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-04-09 10:34:30 +0200
committerGitHub Enterprise <[email protected]>2026-04-09 10:34:30 +0200
commit4598f710da2d0e387c53eb97d983ff520c211a8f (patch)
treeb731277ec552a5ad978e1caa104a082ce25ecf2b /src
parent5.8.3 (diff)
downloadzen-4598f710da2d0e387c53eb97d983ff520c211a8f.tar.xz
zen-4598f710da2d0e387c53eb97d983ff520c211a8f.zip
migrate from http_parser to llhttp (#929)
Diffstat (limited to 'src')
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h5
-rw-r--r--src/zenhttp/servers/httpparser.cpp414
-rw-r--r--src/zenhttp/servers/httpparser.h8
-rw-r--r--src/zenhttp/xmake.lua2
-rw-r--r--src/zenhttp/zenhttp.cpp1
-rw-r--r--src/zenserver-test/xmake.lua2
-rw-r--r--src/zenserver/proxy/httptrafficinspector.cpp73
-rw-r--r--src/zenserver/proxy/httptrafficinspector.h10
-rw-r--r--src/zenserver/xmake.lua2
9 files changed, 421 insertions, 96 deletions
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<IHttpPackageHandler>& 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 <limits>
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+# include <cstring>
+# include <string>
+# include <string_view>
+#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_method_t>(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<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 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<const char*>(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 <EASTL/fixed_vector.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <http_parser.h>
+#include <llhttp.h>
ZEN_THIRD_PARTY_INCLUDES_END
#include <atomic>
@@ -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<char, 512> 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<HttpRequestParser*>(Parser->data); }
- static http_parser_settings s_ParserSettings;
+ static HttpRequestParser* GetThis(llhttp_t* Parser) { return reinterpret_cast<HttpRequestParser*>(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<http_method>(m_Parser.method));
+ m_Method = llhttp_method_name(static_cast<llhttp_method_t>(llhttp_get_method(&m_Parser)));
}
else
{
- m_StatusCode = m_Parser.status_code;
+ m_StatusCode = static_cast<uint16_t>(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 <zencore/uid.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <http_parser.h>
+#include <llhttp.h>
ZEN_THIRD_PARTY_INCLUDES_END
#include <atomic>
@@ -45,15 +45,15 @@ private:
void ResetMessageState();
- static HttpTrafficInspector* GetThis(http_parser* Parser) { return static_cast<HttpTrafficInspector*>(Parser->data); }
+ static HttpTrafficInspector* GetThis(llhttp_t* Parser) { return static_cast<HttpTrafficInspector*>(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")