Skip to content

Commit f8bbdec

Browse files
author
pfeatherstone
committed
- added headers_container, a flat container for http headers
- don't duplicate the moethds in headers_container in request or response
1 parent 5115375 commit f8bbdec

File tree

9 files changed

+355
-204
lines changed

9 files changed

+355
-204
lines changed

examples/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ add_executable_coro(client_ws_coro ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_coro
4545
# Unit tests
4646
add_executable(tests
4747
unit_tests/main.cpp
48-
unit_tests/buffer.cpp
4948
unit_tests/base64.cpp
5049
unit_tests/sha1.cpp
50+
unit_tests/buffer.cpp
51+
unit_tests/headers.cpp
5152
unit_tests/message.cpp
5253
unit_tests/async.cpp)
5354
target_compile_features(tests PUBLIC cxx_std_20)

examples/client_http.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ void print_header(const http::response& resp)
3737
{
3838
printf("Status : %u - %s\n", resp.status, status_label(resp.status).data());
3939
printf("Headers:\n");
40-
for (const auto& [k,v] : resp.headers)
41-
printf("\t%s : %s\n", field_label(k).data(), v.c_str());
40+
for (size_t i{0} ; i < resp.headers.size() ; ++i)
41+
{
42+
const auto [k,v] = resp.headers[i];
43+
printf("\t%s : %.*s\n", field_label(k).data(), (int)v.size(), v.data());
44+
}
4245
}
4346

4447
void print_json_body(const http::response& resp)
@@ -65,8 +68,8 @@ awaitable_strand http_session(std::string_view host)
6568
// Prepare request
6669
req.verb = http::METHOD_GET;
6770
req.uri = "/get";
68-
req.add_header(http::host, host); // mandatory in HTTP/1.1 in request messages
69-
req.add_header(http::user_agent, "Boost::asio " + std::to_string(BOOST_ASIO_VERSION)); // optional header
71+
req.headers.add(http::host, host); // mandatory in HTTP/1.1 in request messages
72+
req.headers.add(http::user_agent, "Boost::asio " + std::to_string(BOOST_ASIO_VERSION)); // optional header
7073

7174
// Async IO
7275
co_await boost::asio::async_connect(sock.next_layer(), co_await resolver.async_resolve(host, "80"), boost::asio::cancel_after(5s, deferred));
@@ -75,7 +78,7 @@ awaitable_strand http_session(std::string_view host)
7578

7679
// Print response
7780
print_header(resp);
78-
if (auto it = resp.find(http::content_type); it != end(resp.headers) && it->contains_value("application/json"))
81+
if (auto it = resp.headers.find(http::content_type); it && it->find("application/json") != size_t(-1))
7982
print_json_body(resp);
8083
}
8184
catch(const boost::system::system_error& e)

examples/server.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ struct api_options
113113
void http_unauthorized (const http::request& req, http::response& resp, std::string_view msg)
114114
{
115115
resp.status = http::status_type::unauthorized;
116-
resp.add_header(http::field::www_authenticate, "Basic realm=\"Access to the staging site\"");
117-
resp.add_header(http::field::cache_control, "no-store");
116+
resp.headers.add(http::field::www_authenticate, "Basic realm=\"Access to the staging site\"");
117+
resp.headers.add(http::field::cache_control, "no-store");
118118
resp.content_str = msg;
119119
}
120120

@@ -139,20 +139,20 @@ void http_server_error (const http::request& req, http::response& resp, std::str
139139
void http_file_data (const http::request& req, http::response& resp, std::string_view path, http::file_ptr file)
140140
{
141141
resp.status = http::status_type::ok;
142-
resp.add_header(http::field::content_type, http::get_mime_type(path));
143-
resp.add_header(http::field::cache_control, "no-cache, no-store, must-revalidate, private, max-age=0");
144-
resp.add_header(http::field::pragma, "no-cache");
145-
resp.add_header(http::field::expires, "0");
142+
resp.headers.add(http::field::content_type, http::get_mime_type(path));
143+
resp.headers.add(http::field::cache_control, "no-cache, no-store, must-revalidate, private, max-age=0");
144+
resp.headers.add(http::field::pragma, "no-cache");
145+
resp.headers.add(http::field::expires, "0");
146146
resp.content_file = std::move(file);
147147
}
148148

149149
auto handle_authorization (const http::request& req, std::string_view username_exp, std::string_view passwd_exp)
150150
{
151-
auto field = req.find(http::field::authorization);
152-
if (field == end(req.headers))
151+
auto field = req.headers.find(http::field::authorization);
152+
if (!field)
153153
return std::make_pair(false, "Missing Authorization field");
154154

155-
std::string_view login_base64 = lskip(field->value, "Basic ");
155+
std::string_view login_base64 = lskip(*field, "Basic ");
156156
const auto login = http::base64_decode(login_base64);
157157

158158
const auto [user, passwd] = split_once(std::string_view((const char*)&login[0], login.size()), ':');
@@ -468,7 +468,7 @@ int main(int argc, char* argv[])
468468
{
469469
reply.status = http::status_type::ok;
470470
reply.content_str = read_next(fin0, 2000);
471-
reply.add_header(http::field::content_type, "text/plain");
471+
reply.headers.add(http::field::content_type, "text/plain");
472472
}}
473473
},
474474

examples/unit_tests/async.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ TEST_SUITE("[ASYNC]")
4242

4343
req_client.verb = http::METHOD_GET;
4444
req_client.uri = "/data?name=bane&code=Peace+is+a+lie.+There+is+only+Passion.";
45-
req_client.add_header(http::host, "hello there!");
46-
req_client.add_header(http::user_agent, "Boost::asio " + std::to_string(BOOST_ASIO_VERSION)); // optional header
45+
req_client.headers.add(http::host, "hello there!");
46+
req_client.headers.add(http::user_agent, "Boost::asio " + std::to_string(BOOST_ASIO_VERSION)); // optional header
4747

4848
resp_peer.status = http::ok;
4949
resp_peer.content_str = "There is only passion";
@@ -150,17 +150,21 @@ TEST_SUITE("[ASYNC]")
150150
REQUIRE(req_peer.headers.size() == req_client.headers.size());
151151
for (size_t i = 0 ; i < req_peer.headers.size() ; ++i)
152152
{
153-
REQUIRE(req_peer.headers[i].key == req_client.headers[i].key);
154-
REQUIRE(req_peer.headers[i].value == req_client.headers[i].value);
153+
const auto [key0, val0] = req_peer.headers[i];
154+
const auto [key1, val1] = req_client.headers[i];
155+
REQUIRE(key0 == key1);
156+
REQUIRE(val0 == val1);
155157
}
156158

157159
REQUIRE(resp_client.status == http::ok);
158160
REQUIRE(resp_client.content_str == "There is only passion");
159161
REQUIRE(resp_peer.headers.size() == resp_client.headers.size());
160162
for (size_t i = 0 ; i < resp_client.headers.size() ; ++i)
161163
{
162-
REQUIRE(resp_peer.headers[i].key == resp_client.headers[i].key);
163-
REQUIRE(resp_peer.headers[i].value == resp_client.headers[i].value);
164+
const auto [key0, val0] = resp_peer.headers[i];
165+
const auto [key1, val1] = resp_client.headers[i];
166+
REQUIRE(key0 == key1);
167+
REQUIRE(val0 == val1);
164168
}
165169
}
166170

examples/unit_tests/headers.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include <random>
2+
#include <http.h>
3+
#include "doctest.h"
4+
5+
const auto random_string = [](auto& eng, size_t len)
6+
{
7+
std::uniform_int_distribution<char> d(48, 126);
8+
std::string str(len, '\0');
9+
for (auto& c : str) c = d(eng);
10+
return str;
11+
};
12+
13+
TEST_SUITE("[HEADERS]")
14+
{
15+
TEST_CASE("headers")
16+
{
17+
std::mt19937 eng(std::random_device{}());
18+
http::headers_container headers;
19+
20+
// Check empty
21+
REQUIRE(headers.size() == 0);
22+
for (unsigned int f = http::unknown_field ; f <= http::xref ; ++f)
23+
REQUIRE(!headers.find(static_cast<http::field>(f)));
24+
25+
// Add headers
26+
std::vector<std::string> strs;
27+
std::vector<http::field> fields;
28+
29+
bool use_modify{false};
30+
31+
SUBCASE("using add_header()")
32+
{
33+
use_modify = false;
34+
}
35+
36+
// Modifying a header that isn't present should just add it
37+
SUBCASE("using modify()")
38+
{
39+
use_modify = true;
40+
}
41+
42+
for (unsigned int f = http::unknown_field+1 ; f <= http::xref ; ++f)
43+
{
44+
const size_t len = std::uniform_int_distribution<size_t>{0, 128}(eng);
45+
const std::string str = random_string(eng, len);
46+
strs.push_back(str);
47+
fields.push_back(static_cast<http::field>(f));
48+
if (use_modify)
49+
headers.modify(static_cast<http::field>(f), str);
50+
else
51+
headers.add(static_cast<http::field>(f), str);
52+
REQUIRE(headers.size() == strs.size());
53+
54+
for (size_t i = 0 ; i < headers.size() ; ++i)
55+
{
56+
const auto [f2, str2] = headers[i];
57+
REQUIRE(f2 == fields[i]);
58+
REQUIRE(str2 == strs[i]);
59+
}
60+
}
61+
62+
// Randomly modify
63+
for (size_t i = 0 ; i < headers.size() ; ++i)
64+
{
65+
const size_t idx = std::uniform_int_distribution<size_t>{0, headers.size()-1}(eng);
66+
const size_t len = std::uniform_int_distribution<size_t>{0, 128}(eng);
67+
const std::string str = random_string(eng, len);
68+
strs[idx] = str;
69+
headers.modify(fields[idx], str);
70+
71+
REQUIRE(headers.size() == strs.size());
72+
for (size_t j = 0 ; j < headers.size() ; ++j)
73+
{
74+
const auto [f2, str2] = headers[j];
75+
REQUIRE(f2 == fields[j]);
76+
REQUIRE(str2 == strs[j]);
77+
}
78+
}
79+
80+
// Randomly remove fields
81+
while (headers.size() > 0)
82+
{
83+
{
84+
const size_t i = std::uniform_int_distribution<size_t>{0, fields.size()-1}(eng);
85+
const auto f = fields[i];
86+
const auto str = std::move(strs[i]);
87+
fields.erase(begin(fields)+i);
88+
strs.erase(begin(strs)+i);
89+
headers.remove(f);
90+
}
91+
92+
REQUIRE(headers.size() == strs.size());
93+
for (size_t j = 0 ; j < headers.size() ; ++j)
94+
{
95+
const auto [f2, str2] = headers[j];
96+
REQUIRE(f2 == fields[j]);
97+
REQUIRE(str2 == strs[j]);
98+
}
99+
}
100+
}
101+
}

examples/unit_tests/message.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -419,18 +419,18 @@ TEST_SUITE("[MESSAGE]")
419419
http::request req0;
420420
req0.verb = http::METHOD_GET;
421421
req0.uri = "/path/to/resource/with+spaces";
422-
req0.add_header(http::host, "www.example.com:8080");
423-
req0.add_header(http::user_agent, "CustomTestAgent/7.4.2 (compatible; FancyBot/1.0; +https://example.com/bot)");
424-
req0.add_header(http::accept, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
425-
req0.add_header(http::accept_language, "en-US,en;q=0.5");
426-
req0.add_header(http::accept_encoding, "gzip, deflate, br");
427-
req0.add_header(http::connection, "keep-alive, Upgrade");
428-
req0.add_header(http::upgrade, "websocket");
429-
req0.add_header(http::sec_websocket_key, "x3JJHMbDL1EzLkh9GBhXDw==");
430-
req0.add_header(http::sec_websocket_version, "13");
431-
req0.add_header(http::cache_control, "no-cache, no-store, must-revalidate");
432-
req0.add_header(http::pragma, "no-cache");
433-
req0.add_header(http::content_type, "application/json; charset=\"utf-8\"");
422+
req0.headers.add(http::host, "www.example.com:8080");
423+
req0.headers.add(http::user_agent, "CustomTestAgent/7.4.2 (compatible; FancyBot/1.0; +https://example.com/bot)");
424+
req0.headers.add(http::accept, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
425+
req0.headers.add(http::accept_language, "en-US,en;q=0.5");
426+
req0.headers.add(http::accept_encoding, "gzip, deflate, br");
427+
req0.headers.add(http::connection, "keep-alive, Upgrade");
428+
req0.headers.add(http::upgrade, "websocket");
429+
req0.headers.add(http::sec_websocket_key, "x3JJHMbDL1EzLkh9GBhXDw==");
430+
req0.headers.add(http::sec_websocket_version, "13");
431+
req0.headers.add(http::cache_control, "no-cache, no-store, must-revalidate");
432+
req0.headers.add(http::pragma, "no-cache");
433+
req0.headers.add(http::content_type, "application/json; charset=\"utf-8\"");
434434
req0.content = "{\"message\": \"This is a test body with some content.\"}";
435435
req0.params.push_back({"q", "search term"});
436436
req0.params.push_back({"empty", ""});
@@ -494,8 +494,10 @@ TEST_SUITE("[MESSAGE]")
494494
REQUIRE(req0.headers.size() == req1.headers.size());
495495
for (size_t i = 0 ; i < req0.headers.size() ; ++i)
496496
{
497-
REQUIRE(req0.headers[i].key == req1.headers[i].key);
498-
REQUIRE(req0.headers[i].value == req1.headers[i].value);
497+
const auto [key0, val0] = req0.headers[i];
498+
const auto [key1, val1] = req1.headers[i];
499+
REQUIRE(key0 == key1);
500+
REQUIRE(val0 == val1);
499501
}
500502
REQUIRE(req0.content == req1.content);
501503
}
@@ -504,9 +506,9 @@ TEST_SUITE("[MESSAGE]")
504506
{
505507
http::response resp0;
506508
resp0.status = http::ok;
507-
resp0.add_header(http::date, "Sat, 07 Jun 2025 11:34:29 GMT");
508-
resp0.add_header(http::content_type, "application/json");
509-
resp0.add_header(http::set_cookie, "sails.sid=s%3AzNjVxqbbKjdhW62QxWPrO9_s7iw6gFfj.YkpdH7mCTkx%2FC%2BgLXyBzXETRD7gKyFu%2BKWMS43uKq4Y; Path=/; HttpOnly");
509+
resp0.headers.add(http::date, "Sat, 07 Jun 2025 11:34:29 GMT");
510+
resp0.headers.add(http::content_type, "application/json");
511+
resp0.headers.add(http::set_cookie, "sails.sid=s%3AzNjVxqbbKjdhW62QxWPrO9_s7iw6gFfj.YkpdH7mCTkx%2FC%2BgLXyBzXETRD7gKyFu%2BKWMS43uKq4Y; Path=/; HttpOnly");
510512

511513
// Serialize
512514
std::error_code ec{};
@@ -558,8 +560,10 @@ TEST_SUITE("[MESSAGE]")
558560
REQUIRE(resp0.headers.size() == resp1.headers.size());
559561
for (size_t i = 0 ; i < resp0.headers.size() ; ++i)
560562
{
561-
REQUIRE(resp0.headers[i].key == resp1.headers[i].key);
562-
REQUIRE(resp0.headers[i].value == resp1.headers[i].value);
563+
const auto [key0, val0] = resp0.headers[i];
564+
const auto [key1, val1] = resp1.headers[i];
565+
REQUIRE(key0 == key1);
566+
REQUIRE(val0 == val1);
563567
}
564568
REQUIRE(resp0.content_str == resp1.content_str);
565569
}

0 commit comments

Comments
 (0)