diff --git a/lib/http/request.cpp b/lib/http/request.cpp index 65dc095..75d90df 100644 --- a/lib/http/request.cpp +++ b/lib/http/request.cpp @@ -1,27 +1,30 @@ #include "request.hpp" #include "../log/log.hpp" #include "constants.hpp" +#include #include #include namespace anthracite::http { -void request::parse_header(char* raw_line) { - uint32_t delim_pos = strstr(raw_line, ":") - raw_line + 1; +void request::parse_header(std::string& raw_line) { + auto delim_pos = raw_line.find_first_of(':'); + auto value_pos = raw_line.find_first_not_of(' ', delim_pos+1); - std::string query_val((char*)(raw_line + delim_pos)); - std::string query_name(raw_line, delim_pos - 1); + std::string header_name = raw_line.substr(0,delim_pos); + std::string header_val = raw_line.substr(value_pos); - _headers[query_name] = header(query_name, query_val); + _headers[header_name] = header_val; } -void request::parse_query_param(char* raw_param) { - uint32_t delim_pos = strstr(raw_param, "=") - raw_param + 1; +void request::parse_query_param(std::string& raw_param) { + auto delim_pos = raw_param.find_first_of('='); + auto value_pos = delim_pos+1; - std::string query_val((char*)(raw_param + delim_pos)); - std::string query_name(raw_param, delim_pos - 1); + std::string query_name = raw_param.substr(0,delim_pos); + std::string query_val = raw_param.substr(value_pos); - _query_params[query_name] = query_param(query_name, query_val); + _query_params[query_name] = query_val; } void request::parse_path(char* raw_path) { @@ -32,19 +35,19 @@ void request::parse_path(char* raw_path) { _path = tok; } - tok = strtok_r(nullptr, "&", &saveptr); + tok = strtok_r(nullptr, "?", &saveptr); while(tok) { - parse_query_param(tok); - tok = strtok_r(nullptr, "&", &saveptr); + std::string rtok(tok); + parse_query_param(rtok); + tok = strtok_r(nullptr, "?", &saveptr); } } -void request::parse_request_line(std::string& raw_line) { +void request::parse_request_line(char* raw_line) { request_line_parser_state state = METHOD; - std::stringstream ss(raw_line); char* saveptr = nullptr; - char* tok = strtok_r(raw_line.data(), " ", &saveptr); + char* tok = strtok_r(raw_line, " \r", &saveptr); while(tok){ switch(state) { @@ -61,6 +64,7 @@ void request::parse_request_line(std::string& raw_line) { }; case PATH: { + std::string str_tok(tok); parse_path(tok); state = VERSION; break; @@ -76,7 +80,7 @@ void request::parse_request_line(std::string& raw_line) { return; }; } - tok = strtok_r(nullptr, " ", &saveptr); + tok = strtok_r(nullptr, " \r", &saveptr); } } @@ -87,22 +91,25 @@ request::request(std::string& raw_data, const std::string& client_ip) parser_state state = REQUEST_LINE; - std::stringstream line_stream(raw_data); - std::string line; + char* saveptr = nullptr; + char* tok = strtok_r(raw_data.data(), "\r\n", &saveptr); - while(getline(line_stream, line, '\n') && state != BODY_CONTENT){ - line.pop_back(); // HTTP requests do newline as \r\n, this removes the \r + while(tok && state != BODY_CONTENT){ switch(state) { case REQUEST_LINE: { - parse_request_line(line); + parse_request_line(tok); state = HEADERS; + tok = strtok_r(nullptr, "\n", &saveptr); break; }; case HEADERS: { - if (line.length() == 0) { + if (tok[0] == '\r') { state = BODY_CONTENT; } else { - parse_header(line.data()); + std::string rtok(tok); + rtok.pop_back(); + parse_header(rtok); + tok = strtok_r(nullptr, "\n", &saveptr); } break; }; @@ -110,9 +117,13 @@ request::request(std::string& raw_data, const std::string& client_ip) } } - if (getline(line_stream, line, '\0')) { - _body_content = line; + tok = strtok_r(nullptr, "", &saveptr); + if (tok) { + _body_content = std::string(tok); } + //if (getline(line_stream, line, '\0')) { + // _body_content = line; + //} } std::string request::path() { return _path; } @@ -136,7 +147,7 @@ bool request::close_connection() const auto& header = _headers.find("Connection"); const bool found = header != _headers.end(); - if (found && header->second.value() == "keep-alive") { + if (found && header->second == "keep-alive") { return false; } @@ -146,19 +157,36 @@ bool request::close_connection() std::string request::to_string() { std::string response = ""; - response += reverse_method_map.find(_method)->second + " " + _path + "?"; + response += reverse_method_map.find(_method)->second + " " + _path; - for (auto qp : _query_params) { - response += qp.second.to_string() + "&"; + if (_query_params.size() > 0) { + response += "?"; + } + + auto qp_map = std::map(_query_params.begin(), _query_params.end()); + auto qp = qp_map.begin(); + while (qp != qp_map.end()) { + response += qp->first + "=" + qp->second; + if (++qp != qp_map.end()) { + response += "&"; + } } response += " " + reverse_version_map.find(_http_version)->second + "\r\n"; - for (auto header : _headers) { - response += header.second.to_string(); + if (_headers.size() == 0) { + response += "\r\n"; + } + + auto hd_map = std::map(_headers.begin(), _headers.end()); + auto hd = hd_map.begin(); + while (hd != hd_map.end()) { + response += hd->first + ": " + hd->second + "\r\n"; + if (++hd == hd_map.end()) { + response += "\r\n"; + } } - response += "\r\n"; response += _body_content; return response; diff --git a/lib/http/request.hpp b/lib/http/request.hpp index a898b21..1d425ba 100644 --- a/lib/http/request.hpp +++ b/lib/http/request.hpp @@ -24,13 +24,13 @@ private: std::string _path; std::string _client_ipaddr; std::string _body_content; - std::unordered_map _headers; // kinda goofy, whatever - std::unordered_map _query_params; // kinda goofy, whatever - // - void parse_request_line(std::string& raw_line); - void parse_header(char* raw_line); + std::unordered_map _headers; + std::unordered_map _query_params; + + void parse_request_line(char* raw_line); + void parse_header(std::string& raw_line); void parse_path(char* raw_path); - void parse_query_param(char* raw_param); + void parse_query_param(std::string& raw_param); public: request(std::string& raw_data, const std::string& client_ip); diff --git a/tests/speed_tests.cpp b/tests/speed_tests.cpp index f3eebea..9f6835f 100644 --- a/tests/speed_tests.cpp +++ b/tests/speed_tests.cpp @@ -2,63 +2,65 @@ #include #include #include "../lib/http/request.hpp" -#include +#ifdef SPEEDTEST_COMPARE_BOOST +#include +#endif + + +using std::chrono::high_resolution_clock; +using std::chrono::duration_cast; +using std::chrono::duration; +using std::chrono::milliseconds; + +constexpr uint32_t num_requests = 10000000; TEST(speed_tests, request_parse) { - using std::chrono::high_resolution_clock; - using std::chrono::duration_cast; - using std::chrono::duration; - using std::chrono::milliseconds; - std::ifstream t("./test_files/test_request.http"); std::stringstream buffer; buffer << t.rdbuf(); std::string raw_req = buffer.str(); - auto t1 = high_resolution_clock::now(); - for(int i = 0; i < 10000000; ++i) { + auto start = high_resolution_clock::now(); + + for(int i = 0; i < num_requests; ++i) { volatile anthracite::http::request req (raw_req, "0.0.0.0"); } - auto t2 = high_resolution_clock::now(); - auto ms_int = duration_cast(t2 - t1); + auto end = high_resolution_clock::now(); - std::cout << "Parsed 1 Million requests in " << ms_int << "ms" << std::endl; + auto ms_int = duration_cast(end-start); + + double m_rps = ((1000.0 / ms_int.count()) * num_requests) / 1000000; + + std::cout << "Parsed " << (num_requests/1000000) << " Million requests in " << ms_int << " ms"; + std::cout << " at " << m_rps << " Million RPS " << std::endl; + + ASSERT_LT(ms_int.count(), 2000); } +#ifdef SPEEDTEST_COMPARE_BOOST TEST(speed_tests, boost) { - using std::chrono::high_resolution_clock; - using std::chrono::duration_cast; - using std::chrono::duration; - using std::chrono::milliseconds; - std::ifstream t("./test_files/test_request.http"); std::stringstream buffer; buffer << t.rdbuf(); std::string raw_req = buffer.str(); - auto t1 = high_resolution_clock::now(); - for(int i = 0; i < 10000000; ++i) { + auto start = high_resolution_clock::now(); + + for(int i = 0; i < num_requests; ++i) { boost::system::error_code ec; boost::beast::http::request_parser p; p.put(boost::asio::buffer(raw_req), ec); boost::beast::http::request r = p.get(); } - auto t2 = high_resolution_clock::now(); - auto ms_int = duration_cast(t2 - t1); + auto end = high_resolution_clock::now(); + auto ms_int = duration_cast(end-start); - std::cout << "Parsed 1 Million requests in " << ms_int << "ms" << std::endl; -} - -TEST(speed_tests, single_request_parse) { - std::ifstream t("./test_files/test_request.http"); - std::stringstream buffer; - buffer << t.rdbuf(); - std::string raw_req = buffer.str(); - - anthracite::http::request req (raw_req, "0.0.0.0"); - - std::cout << req.to_string() << std::endl; + double m_rps = ((1000.0 / ms_int.count()) * num_requests) / 1000000; + + std::cout << "Parsed " << (num_requests/1000000) << " Million requests in " << ms_int << " ms"; + std::cout << " at " << m_rps << " Million RPS " << std::endl; } +#endif diff --git a/tests/test_files/test_request.http b/tests/test_files/test_request.http index 4f79472..b7c6957 100644 --- a/tests/test_files/test_request.http +++ b/tests/test_files/test_request.http @@ -1,13 +1,15 @@ GET /foo/bar?test=a&test2=b HTTP/1.1 -Host: example.org -User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 Accept: */* -Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 -Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 -Keep-Alive: 115 +Accept-Encoding: gzip,deflate +Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Connection: keep-alive Content-Type: application/x-www-form-urlencoded -X-Requested-With: XMLHttpRequest -Referer: http://example.org/test Cookie: foo=bar; lorem=ipsum; +Host: example.org +Keep-Alive: 115 +Referer: http://example.org/test +User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 +X-Requested-With: XMLHttpRequest + +{ "test": "content" } diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp new file mode 100644 index 0000000..c7e3117 --- /dev/null +++ b/tests/unit_tests.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "../lib/http/request.hpp" +#include + +TEST(unit_tests, single_request_parse) { + std::ifstream t("./test_files/test_request.http"); + std::stringstream buffer; + buffer << t.rdbuf(); + + std::string raw_req = buffer.str(); + std::string expected = buffer.str(); + + anthracite::http::request req (raw_req, "0.0.0.0"); + + ASSERT_EQ(expected, req.to_string()); +}