Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ endif()
# set g++ compiler flags
if ( DEFINED DEBUG_BUILD )
add_compile_definitions(DEBUG_BUILD)
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -std=c++20 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -fwrapv -rdynamic -fno-elide-constructors")
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -std=c++23 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -fwrapv -rdynamic -fno-elide-constructors")
else()
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -O3 -fstack-protector-strong -std=c++20 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -DNDEBUG -fwrapv -fno-elide-constructors")
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -O3 -fstack-protector-strong -std=c++23 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -DNDEBUG -fwrapv -fno-elide-constructors")
endif()

# set boost specs
Expand Down
64 changes: 30 additions & 34 deletions lib/http/httpparser.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "httpparser.hpp"
#include "httpconstants.hpp"
#include <spanstream>

using namespace std;

Expand Down Expand Up @@ -31,7 +32,7 @@ void HTTPParser::appendBuffer(const char* BufferRef, const uint16_t RecvBytes)
//- on incomplete (single) POST request
if (_POSTWaitContentLength == true && _HTTPRequestBuffer.length() >= _POSTContentLength) {
_RequestProperties.Payload = _HTTPRequestBuffer.substr(0, _POSTContentLength);
_HTTPRequestBuffer.replace(0, _POSTContentLength, "");
_HTTPRequestBuffer.erase(0, _POSTContentLength);

_Requests.push_back(_RequestProperties);

Expand All @@ -50,7 +51,7 @@ void HTTPParser::appendBuffer(const char* BufferRef, const uint16_t RecvBytes)
}
}

RequestsMap_t HTTPParser::getRequests()
const RequestsMap_t& HTTPParser::getRequests() const
{
return _Requests;
}
Expand Down Expand Up @@ -135,7 +136,7 @@ inline bool HTTPParser::_processRequestProperties(const size_t Index)
//- content length bytes already in buffer
if (_HTTPRequestBuffer.length() >= _POSTContentLength) {
_RequestProperties.Payload = _HTTPRequestBuffer.substr(0, _POSTContentLength);
_HTTPRequestBuffer.replace(0, _POSTContentLength, "");
_HTTPRequestBuffer.erase(0, _POSTContentLength);

//- add request to requests map
_Requests.push_back(_RequestProperties);
Expand All @@ -151,7 +152,7 @@ inline bool HTTPParser::_processRequestProperties(const size_t Index)
auto &NextRequest = _SplittedRequests.at(Index+1);
if (NextRequest.length() >= _POSTContentLength) {
_RequestProperties.Payload = NextRequest.substr(0, _POSTContentLength);
NextRequest.replace(0, _POSTContentLength, "");
NextRequest.erase(0, _POSTContentLength);
//- add request to requests map
_Requests.push_back(_RequestProperties);
}
Expand All @@ -163,13 +164,13 @@ inline bool HTTPParser::_processRequestProperties(const size_t Index)
return true;
}

inline bool HTTPParser::_parseRequestProperties(string& Request, const RequestPropertiesRef_t ResultBaseProps)
inline bool HTTPParser::_parseRequestProperties(string_view Request, const RequestPropertiesRef_t ResultBaseProps)
{
//- find first line endline
size_t StartPos = Request.find("\r\n");

//-> if no headers (no \r\n), set start pos to end of string
if (StartPos == string::npos) {
if (StartPos == string_view::npos) {
StartPos = Request.length();
}

Expand All @@ -196,53 +197,48 @@ inline bool HTTPParser::_parseRequestProperties(string& Request, const RequestPr
return true;
}

inline void HTTPParser::_parseRequestHeaders(string& Request, RequestHeaderRef_t ResultRef)
inline void HTTPParser::_parseRequestHeaders(string_view Request, RequestHeaderRef_t ResultRef)
{
//- split / reverse split header lines
vector<string> Lines;
StringHelper::split(Request, "\r\n", Lines);

Lines.push_back(Request);

//- loop over lines, split, put into result map
for (auto &Line:Lines) {

vector<string> HeaderPair;
if (Line.find(":") != string::npos) {

StringHelper::rsplit(Line, Line.length(), ": ", HeaderPair);

if (HeaderPair.size() == 2 && !HeaderPair.at(1).empty()) {
ResultRef.emplace(
HeaderPair.at(1), HeaderPair.at(0).substr(0, HeaderPair.at(0).length())
);
//- wrap the raw buffer in a spanstream to parse lines without intermediate copies
ispanstream ss(span<const char>(Request.data(), Request.size()));
string line;
while (getline(ss, line, '\n')) {
if (!line.empty() && line.back() == '\r') line.pop_back();

//- require ": " (colon + space) to match original strict parsing behaviour
const size_t colonSpace = line.find(": ");
if (colonSpace != string::npos && colonSpace > 0) {
string_view lv(line);
string_view key = lv.substr(0, colonSpace);
string_view value = lv.substr(colonSpace + 2);
if (!value.empty()) {
ResultRef.emplace(string(key), string(value));
}
}
}
}

inline void HTTPParser::_parseGETParameter(const string& RequestURL, URLParamMapRef_t ResultRef)
inline void HTTPParser::_parseGETParameter(string_view RequestURL, URLParamMapRef_t ResultRef)
{
//- only process on init character "?" found
const size_t URLParamsStartPos = RequestURL.find("?");

if (URLParamsStartPos != string::npos && RequestURL.length() > URLParamsStartPos) {
if (URLParamsStartPos != string_view::npos && RequestURL.length() > URLParamsStartPos) {

string URLParamsPart = RequestURL.substr(URLParamsStartPos+1, RequestURL.length());
string_view URLParamsPart = RequestURL.substr(URLParamsStartPos + 1);

vector<string> ParamValuePairs;
vector<string_view> ParamValuePairs;
StringHelper::split(URLParamsPart, "&", ParamValuePairs);

ParamValuePairs.push_back(URLParamsPart);

//- loop over param-value pairs
for (auto &ParamValuePair:ParamValuePairs) {
for (const auto& ParamValuePair : ParamValuePairs) {

const size_t PVPDelimiterPos = ParamValuePair.find("=");

if (PVPDelimiterPos != string::npos && PVPDelimiterPos != 0 && ParamValuePair.length() > PVPDelimiterPos) {
if (PVPDelimiterPos != string_view::npos && PVPDelimiterPos != 0 && ParamValuePair.length() > PVPDelimiterPos) {
ResultRef.emplace(
ParamValuePair.substr(0, PVPDelimiterPos), ParamValuePair.substr(PVPDelimiterPos+1, ParamValuePair.length())
string(ParamValuePair.substr(0, PVPDelimiterPos)),
string(ParamValuePair.substr(PVPDelimiterPos + 1))
);
}
}
Expand Down
71 changes: 43 additions & 28 deletions lib/http/httpparser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <string>
#include <string_view>
#include <span>
#include <vector>
#include <cstdint>
#include <utility>
Expand All @@ -9,11 +11,19 @@

using namespace std;

typedef unordered_map<string, string> RequestHeader_t;
typedef RequestHeader_t& RequestHeaderRef_t;
//- transparent hasher: enables heterogeneous lookup (string_view keys without string construction)
struct StringHash {
using is_transparent = void;
size_t operator()(string_view sv) const noexcept {
return hash<string_view>{}(sv);
}
};

typedef unordered_map<string, string> URLParamMap_t;
typedef URLParamMap_t& URLParamMapRef_t;
using RequestHeader_t = unordered_map<string, string, StringHash, equal_to<>>;
using RequestHeaderRef_t = RequestHeader_t&;

using URLParamMap_t = unordered_map<string, string, StringHash, equal_to<>>;
using URLParamMapRef_t = URLParamMap_t&;

struct RequestProperties_t
{
Expand All @@ -25,10 +35,10 @@ struct RequestProperties_t
URLParamMap_t URLParams;
};

typedef RequestProperties_t& RequestPropertiesRef_t;
using RequestPropertiesRef_t = RequestProperties_t&;

typedef vector<RequestProperties_t> RequestsMap_t;
typedef RequestsMap_t* RequestsMapPtr_t;
using RequestsMap_t = vector<RequestProperties_t>;
using RequestsMapPtr_t = RequestsMap_t*;


class HTTPParser
Expand All @@ -40,7 +50,7 @@ class HTTPParser
~HTTPParser();

void appendBuffer(const char*, const uint16_t);
RequestsMap_t getRequests();
const RequestsMap_t& getRequests() const;
RequestsMapPtr_t getRequestsPtr();

private:
Expand Down Expand Up @@ -68,61 +78,66 @@ class HTTPParser

protected:

bool _parseRequestProperties(string&, const RequestPropertiesRef_t);
void _parseRequestHeaders(string&, RequestHeaderRef_t);
void _parseGETParameter(const string&, URLParamMapRef_t);
bool _parseRequestProperties(string_view, const RequestPropertiesRef_t);
void _parseRequestHeaders(string_view, RequestHeaderRef_t);
void _parseGETParameter(string_view, URLParamMapRef_t);
};


class StringHelper {

public:

static void split(string& StringRef, const string Delimiter, vector<string>& ResultRef)
//- Non-destructive split: returns string_view slices into sv (zero heap allocation per token)
static void split(string_view sv, string_view delim, vector<string_view>& out)
{
string SplitElement;
auto pos = StringRef.find(Delimiter);
for (size_t pos; (pos = sv.find(delim)) != sv.npos; sv.remove_prefix(pos + delim.size()))
out.push_back(sv.substr(0, pos));
if (!sv.empty()) out.push_back(sv);
}

//- Destructive split: erases consumed tokens from StringRef in-place (used for buffer management)
static void split(string& StringRef, string_view Delimiter, vector<string>& ResultRef)
{
auto pos = StringRef.find(Delimiter);
while (pos != string::npos) {
SplitElement = StringRef.substr(0, pos);
ResultRef.push_back(SplitElement);
StringRef.erase(0, pos + Delimiter.length());
ResultRef.push_back(StringRef.substr(0, pos));
StringRef.erase(0, pos + Delimiter.size());
pos = StringRef.find(Delimiter);
}
}

static void rsplit(string& String, size_t StartPos, const string Delimiter, vector<string>& ResultRef)
static void rsplit(string_view String, size_t StartPos, string_view Delimiter, vector<string>& ResultRef)
{
size_t FindPos = 0;
size_t FindPosLast = 0;
string Token;

//- guard ensures StartPos >= Delimiter.length() before the subtraction below
if (StartPos < Delimiter.length()) {
ResultRef.push_back(String.substr(0, StartPos));
ResultRef.push_back(string(String.substr(0, StartPos)));
return;
}

StartPos -= Delimiter.length();
StartPos -= Delimiter.length(); //- safe: guarded by the check above

while ((FindPos = String.rfind(Delimiter, StartPos)) != String.npos) {
while ((FindPos = String.rfind(Delimiter, StartPos)) != string_view::npos) {

Token = String.substr(FindPos+Delimiter.length(), (StartPos-FindPos));
ResultRef.push_back(Token);
ResultRef.push_back(string(String.substr(FindPos + Delimiter.length(), StartPos - FindPos)));

//- guard ensures FindPos >= Delimiter.length() before the subtraction below
if (FindPos < Delimiter.length()) {
FindPosLast = FindPos;
break;
}

StartPos = FindPos - Delimiter.length();
StartPos = FindPos - Delimiter.length(); //- safe: guarded by the check above
FindPosLast = FindPos;
}

Token = String.substr(0, FindPosLast);
ResultRef.push_back(Token);
ResultRef.push_back(string(String.substr(0, FindPosLast)));
}

static bool is_digits(const string& checkdigits)
static bool is_digits(string_view checkdigits)
{
return all_of(checkdigits.begin(), checkdigits.end(), ::isdigit);
}
Expand Down
3 changes: 3 additions & 0 deletions test/performance/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ add_executable(test-performance ${SRC_LIST_TEST_PERFORMANCE})

# link libraries
target_link_libraries(test-performance ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})

# add http-parser benchmark subdirectory
add_subdirectory(http-parser)
9 changes: 9 additions & 0 deletions test/performance/http-parser/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# HTTP parser performance and memory benchmark tests

# Performance test: wall-clock timing comparison (new vs legacy parsing)
add_executable(test-parser-performance test-parser-performance.cpp)
target_link_libraries(test-parser-performance httpparser)

# Memory test: heap-allocation comparison (new vs legacy parsing)
add_executable(test-parser-memory test-parser-memory.cpp)
target_link_libraries(test-parser-memory httpparser)
Loading
Loading