Skip to content
Merged
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
3 changes: 2 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ add_executable_coro(client_ws_coro ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_coro
# Unit tests
add_executable(tests
unit_tests/main.cpp
unit_tests/buffer.cpp
unit_tests/base64.cpp
unit_tests/sha1.cpp
unit_tests/buffer.cpp
unit_tests/headers.cpp
unit_tests/message.cpp
unit_tests/async.cpp)
target_compile_features(tests PUBLIC cxx_std_20)
Expand Down
13 changes: 8 additions & 5 deletions examples/client_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ void print_header(const http::response& resp)
{
printf("Status : %u - %s\n", resp.status, status_label(resp.status).data());
printf("Headers:\n");
for (const auto& [k,v] : resp.headers)
printf("\t%s : %s\n", field_label(k).data(), v.c_str());
for (size_t i{0} ; i < resp.headers.size() ; ++i)
{
const auto [k,v] = resp.headers[i];
printf("\t%s : %.*s\n", field_label(k).data(), (int)v.size(), v.data());
}
}

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

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

// Print response
print_header(resp);
if (auto it = resp.find(http::content_type); it != end(resp.headers) && it->contains_value("application/json"))
if (auto it = resp.headers.find(http::content_type); it && it->find("application/json") != size_t(-1))
print_json_body(resp);
}
catch(const boost::system::system_error& e)
Expand Down
20 changes: 10 additions & 10 deletions examples/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ struct api_options
void http_unauthorized (const http::request& req, http::response& resp, std::string_view msg)
{
resp.status = http::status_type::unauthorized;
resp.add_header(http::field::www_authenticate, "Basic realm=\"Access to the staging site\"");
resp.add_header(http::field::cache_control, "no-store");
resp.headers.add(http::field::www_authenticate, "Basic realm=\"Access to the staging site\"");
resp.headers.add(http::field::cache_control, "no-store");
resp.content_str = msg;
}

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

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

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

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

Expand Down
16 changes: 10 additions & 6 deletions examples/unit_tests/async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ TEST_SUITE("[ASYNC]")

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

resp_peer.status = http::ok;
resp_peer.content_str = "There is only passion";
Expand Down Expand Up @@ -150,17 +150,21 @@ TEST_SUITE("[ASYNC]")
REQUIRE(req_peer.headers.size() == req_client.headers.size());
for (size_t i = 0 ; i < req_peer.headers.size() ; ++i)
{
REQUIRE(req_peer.headers[i].key == req_client.headers[i].key);
REQUIRE(req_peer.headers[i].value == req_client.headers[i].value);
const auto [key0, val0] = req_peer.headers[i];
const auto [key1, val1] = req_client.headers[i];
REQUIRE(key0 == key1);
REQUIRE(val0 == val1);
}

REQUIRE(resp_client.status == http::ok);
REQUIRE(resp_client.content_str == "There is only passion");
REQUIRE(resp_peer.headers.size() == resp_client.headers.size());
for (size_t i = 0 ; i < resp_client.headers.size() ; ++i)
{
REQUIRE(resp_peer.headers[i].key == resp_client.headers[i].key);
REQUIRE(resp_peer.headers[i].value == resp_client.headers[i].value);
const auto [key0, val0] = resp_peer.headers[i];
const auto [key1, val1] = resp_client.headers[i];
REQUIRE(key0 == key1);
REQUIRE(val0 == val1);
}
}

Expand Down
103 changes: 103 additions & 0 deletions examples/unit_tests/headers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include <random>
#include <ostream>
#include <string_view>
#include <http.h>
#include "doctest.h"

const auto random_string = [](auto& eng, size_t len)
{
std::uniform_int_distribution<int> d(48, 126);
std::string str(len, '\0');
for (auto& c : str) c = static_cast<char>(d(eng));
return str;
};

TEST_SUITE("[HEADERS]")
{
TEST_CASE("headers")
{
std::mt19937 eng(std::random_device{}());
http::headers_container headers;

// Check empty
REQUIRE(headers.size() == 0);
for (unsigned int f = http::unknown_field ; f <= http::xref ; ++f)
REQUIRE(!headers.find(static_cast<http::field>(f)));

// Add headers
std::vector<std::string> strs;
std::vector<http::field> fields;

bool use_modify{false};

SUBCASE("using add_header()")
{
use_modify = false;
}

// Modifying a header that isn't present should just add it
SUBCASE("using modify()")
{
use_modify = true;
}

for (unsigned int f = http::unknown_field+1 ; f <= http::xref ; ++f)
{
const size_t len = std::uniform_int_distribution<size_t>{0, 128}(eng);
const std::string str = random_string(eng, len);
strs.push_back(str);
fields.push_back(static_cast<http::field>(f));
if (use_modify)
headers.modify(static_cast<http::field>(f), str);
else
headers.add(static_cast<http::field>(f), str);
REQUIRE(headers.size() == strs.size());

for (size_t i = 0 ; i < headers.size() ; ++i)
{
const auto [f2, str2] = headers[i];
REQUIRE(f2 == fields[i]);
REQUIRE(str2 == strs[i]);
}
}

// Randomly modify
for (size_t i = 0 ; i < headers.size() ; ++i)
{
const size_t idx = std::uniform_int_distribution<size_t>{0, headers.size()-1}(eng);
const size_t len = std::uniform_int_distribution<size_t>{0, 128}(eng);
const std::string str = random_string(eng, len);
strs[idx] = str;
headers.modify(fields[idx], str);

REQUIRE(headers.size() == strs.size());
for (size_t j = 0 ; j < headers.size() ; ++j)
{
const auto [f2, str2] = headers[j];
REQUIRE(f2 == fields[j]);
REQUIRE(str2 == strs[j]);
}
}

// Randomly remove fields
while (headers.size() > 0)
{
{
const size_t i = std::uniform_int_distribution<size_t>{0, fields.size()-1}(eng);
const auto f = fields[i];
const auto str = std::move(strs[i]);
fields.erase(begin(fields)+i);
strs.erase(begin(strs)+i);
headers.remove(f);
}

REQUIRE(headers.size() == strs.size());
for (size_t j = 0 ; j < headers.size() ; ++j)
{
const auto [f2, str2] = headers[j];
REQUIRE(f2 == fields[j]);
REQUIRE(str2 == strs[j]);
}
}
}
}
42 changes: 23 additions & 19 deletions examples/unit_tests/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,18 +419,18 @@ TEST_SUITE("[MESSAGE]")
http::request req0;
req0.verb = http::METHOD_GET;
req0.uri = "/path/to/resource/with+spaces";
req0.add_header(http::host, "www.example.com:8080");
req0.add_header(http::user_agent, "CustomTestAgent/7.4.2 (compatible; FancyBot/1.0; +https://example.com/bot)");
req0.add_header(http::accept, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
req0.add_header(http::accept_language, "en-US,en;q=0.5");
req0.add_header(http::accept_encoding, "gzip, deflate, br");
req0.add_header(http::connection, "keep-alive, Upgrade");
req0.add_header(http::upgrade, "websocket");
req0.add_header(http::sec_websocket_key, "x3JJHMbDL1EzLkh9GBhXDw==");
req0.add_header(http::sec_websocket_version, "13");
req0.add_header(http::cache_control, "no-cache, no-store, must-revalidate");
req0.add_header(http::pragma, "no-cache");
req0.add_header(http::content_type, "application/json; charset=\"utf-8\"");
req0.headers.add(http::host, "www.example.com:8080");
req0.headers.add(http::user_agent, "CustomTestAgent/7.4.2 (compatible; FancyBot/1.0; +https://example.com/bot)");
req0.headers.add(http::accept, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
req0.headers.add(http::accept_language, "en-US,en;q=0.5");
req0.headers.add(http::accept_encoding, "gzip, deflate, br");
req0.headers.add(http::connection, "keep-alive, Upgrade");
req0.headers.add(http::upgrade, "websocket");
req0.headers.add(http::sec_websocket_key, "x3JJHMbDL1EzLkh9GBhXDw==");
req0.headers.add(http::sec_websocket_version, "13");
req0.headers.add(http::cache_control, "no-cache, no-store, must-revalidate");
req0.headers.add(http::pragma, "no-cache");
req0.headers.add(http::content_type, "application/json; charset=\"utf-8\"");
req0.content = "{\"message\": \"This is a test body with some content.\"}";
req0.params.push_back({"q", "search term"});
req0.params.push_back({"empty", ""});
Expand Down Expand Up @@ -494,8 +494,10 @@ TEST_SUITE("[MESSAGE]")
REQUIRE(req0.headers.size() == req1.headers.size());
for (size_t i = 0 ; i < req0.headers.size() ; ++i)
{
REQUIRE(req0.headers[i].key == req1.headers[i].key);
REQUIRE(req0.headers[i].value == req1.headers[i].value);
const auto [key0, val0] = req0.headers[i];
const auto [key1, val1] = req1.headers[i];
REQUIRE(key0 == key1);
REQUIRE(val0 == val1);
}
REQUIRE(req0.content == req1.content);
}
Expand All @@ -504,9 +506,9 @@ TEST_SUITE("[MESSAGE]")
{
http::response resp0;
resp0.status = http::ok;
resp0.add_header(http::date, "Sat, 07 Jun 2025 11:34:29 GMT");
resp0.add_header(http::content_type, "application/json");
resp0.add_header(http::set_cookie, "sails.sid=s%3AzNjVxqbbKjdhW62QxWPrO9_s7iw6gFfj.YkpdH7mCTkx%2FC%2BgLXyBzXETRD7gKyFu%2BKWMS43uKq4Y; Path=/; HttpOnly");
resp0.headers.add(http::date, "Sat, 07 Jun 2025 11:34:29 GMT");
resp0.headers.add(http::content_type, "application/json");
resp0.headers.add(http::set_cookie, "sails.sid=s%3AzNjVxqbbKjdhW62QxWPrO9_s7iw6gFfj.YkpdH7mCTkx%2FC%2BgLXyBzXETRD7gKyFu%2BKWMS43uKq4Y; Path=/; HttpOnly");

// Serialize
std::error_code ec{};
Expand Down Expand Up @@ -558,8 +560,10 @@ TEST_SUITE("[MESSAGE]")
REQUIRE(resp0.headers.size() == resp1.headers.size());
for (size_t i = 0 ; i < resp0.headers.size() ; ++i)
{
REQUIRE(resp0.headers[i].key == resp1.headers[i].key);
REQUIRE(resp0.headers[i].value == resp1.headers[i].value);
const auto [key0, val0] = resp0.headers[i];
const auto [key1, val1] = resp1.headers[i];
REQUIRE(key0 == key1);
REQUIRE(val0 == val1);
}
REQUIRE(resp0.content_str == resp1.content_str);
}
Expand Down
Loading
Loading