Skip to content

Commit 1701e5f

Browse files
author
pfeatherstone
committed
- make http version an enum
- more unit tests
1 parent b55e6ae commit 1701e5f

File tree

6 files changed

+129
-68
lines changed

6 files changed

+129
-68
lines changed

examples/client_http.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ awaitable_strand http_session(std::string_view host)
6565
// Prepare request
6666
req.verb = http::GET;
6767
req.uri = "/get";
68-
req.http_version_minor = 1;
6968
req.add_header(http::host, host); // mandatory in HTTP/1.1 in request messages
7069
req.add_header(http::user_agent, "Boost::asio " + std::to_string(BOOST_ASIO_VERSION)); // optional header
7170

examples/server.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ awaitable_strand http_session (
357357
// Reset response, set http version and keep alive status of response
358358
const bool keep_alive = req.keep_alive();
359359
resp.clear();
360-
resp.http_version_minor = req.http_version_minor;
360+
resp.version = req.version;
361361
resp.keep_alive(keep_alive);
362362

363363
// Handle request and set response

examples/unit_tests/message.cpp

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ TEST_SUITE("[MESSAGE]")
8484
}
8585
}
8686

87-
TEST_CASE("serialise & parse bad requests")
87+
TEST_CASE("serialise bad requests")
8888
{
8989
http::request req;
9090

@@ -97,25 +97,10 @@ TEST_SUITE("[MESSAGE]")
9797
req.verb = http::GET;
9898
}
9999

100-
SUBCASE("missing http version")
101-
{
102-
req.verb = http::GET;
103-
req.uri = "/index";
104-
}
105-
106100
SUBCASE("missing host")
107101
{
108102
req.verb = http::GET;
109103
req.uri = "/index";
110-
req.http_version_minor = 1;
111-
}
112-
113-
SUBCASE("bad http version")
114-
{
115-
req.verb = http::GET;
116-
req.uri = "/index";
117-
req.http_version_minor = 100;
118-
req.add_header(http::host, "www.example.com");
119104
}
120105

121106
std::error_code ec{};
@@ -124,12 +109,21 @@ TEST_SUITE("[MESSAGE]")
124109
REQUIRE(bool(ec));
125110
}
126111

112+
TEST_CASE("serialise bad response")
113+
{
114+
http::response resp;
115+
SUBCASE("empty"){}
116+
std::error_code ec{};
117+
std::string buf;
118+
http::serialize_header(resp, buf, ec);
119+
REQUIRE(bool(ec));
120+
}
121+
127122
TEST_CASE("serialize & parse good request")
128123
{
129124
http::request req0;
130125
req0.verb = http::GET;
131126
req0.uri = "/path/to/resource/with+spaces";
132-
req0.http_version_minor = 1;
133127
req0.add_header(http::host, "www.example.com:8080");
134128
req0.add_header(http::user_agent, "CustomTestAgent/7.4.2 (compatible; FancyBot/1.0; +https://example.com/bot)");
135129
req0.add_header(http::accept, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
@@ -194,7 +188,7 @@ TEST_SUITE("[MESSAGE]")
194188

195189
REQUIRE(buf.empty());
196190
REQUIRE(req0.verb == req1.verb);
197-
REQUIRE(req0.http_version_minor == req1.http_version_minor);
191+
REQUIRE(req0.version == req1.version);
198192
REQUIRE(req0.uri == req1.uri);
199193
REQUIRE(req0.params.size() == req1.params.size());
200194
for (size_t i = 0 ; i < req0.params.size() ; ++i)
@@ -210,4 +204,68 @@ TEST_SUITE("[MESSAGE]")
210204
}
211205
REQUIRE(req0.content == req1.content);
212206
}
207+
208+
TEST_CASE("serialize & parse good response")
209+
{
210+
http::response resp0;
211+
resp0.status = http::ok;
212+
resp0.add_header(http::date, "Sat, 07 Jun 2025 11:34:29 GMT");
213+
resp0.add_header(http::content_type, "application/json");
214+
resp0.add_header(http::set_cookie, "sails.sid=s%3AzNjVxqbbKjdhW62QxWPrO9_s7iw6gFfj.YkpdH7mCTkx%2FC%2BgLXyBzXETRD7gKyFu%2BKWMS43uKq4Y; Path=/; HttpOnly");
215+
216+
// Serialize
217+
std::error_code ec{};
218+
std::string buf;
219+
http::serialize_header(resp0, buf, ec);
220+
buf.append(resp0.content_str);
221+
REQUIRE(!bool(ec));
222+
223+
// Parse
224+
http::response resp1;
225+
226+
SUBCASE("parse entire message")
227+
{
228+
const bool finished = http::parser<http::response>{}.parse(resp1, buf, ec);
229+
REQUIRE(!bool(ec));
230+
REQUIRE(finished);
231+
}
232+
233+
SUBCASE("parse block by block")
234+
{
235+
http::parser<http::response> parser;
236+
237+
size_t blocksize{};
238+
239+
SUBCASE("blocksize == 1") { blocksize = 1;}
240+
SUBCASE("blocksize == 10") { blocksize = 10;}
241+
SUBCASE("blocksize == 99") { blocksize = 99;}
242+
SUBCASE("blocksize == 128") { blocksize = 128;}
243+
SUBCASE("blocksize == 1024") { blocksize = 1024;}
244+
245+
size_t nblocks = (buf.size() + blocksize - 1) / blocksize;
246+
std::string block;
247+
248+
for (size_t i = 0 ; i < nblocks ; ++i)
249+
{
250+
const size_t len = std::min(blocksize, buf.size());
251+
block.append(&buf[0], len);
252+
buf.erase(begin(buf), begin(buf) + len);
253+
const bool finished = parser.parse(resp1, block, ec);
254+
REQUIRE_MESSAGE(!bool(ec), ec.message());
255+
REQUIRE(finished == (i == (nblocks-1)));
256+
}
257+
REQUIRE(block.empty());
258+
}
259+
260+
REQUIRE(buf.empty());
261+
REQUIRE(resp0.status == resp1.status);
262+
REQUIRE(resp0.version == resp1.version);
263+
REQUIRE(resp0.headers.size() == resp1.headers.size());
264+
for (size_t i = 0 ; i < resp0.headers.size() ; ++i)
265+
{
266+
REQUIRE(resp0.headers[i].key == resp1.headers[i].key);
267+
REQUIRE(resp0.headers[i].value == resp1.headers[i].value);
268+
}
269+
REQUIRE(resp0.content_str == resp1.content_str);
270+
}
213271
}

src/http.cpp

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ namespace http
906906
uri.clear();
907907
headers.clear();
908908
content.clear();
909-
http_version_minor = -1;
909+
version = {};
910910
verb = UNKNOWN_VERB;
911911
}
912912

@@ -934,7 +934,7 @@ namespace http
934934
}
935935

936936
// HTTP 1.1 - default is to keep open otherwise default is to close
937-
return http_version_minor == 1;
937+
return version == HTTP_1_1;
938938
}
939939

940940
bool request::is_websocket_req() const
@@ -946,8 +946,8 @@ namespace http
946946

947947
void response::clear()
948948
{
949-
status = unknown;
950-
http_version_minor = -1;
949+
status = unknown;
950+
version = {};
951951
headers.clear();
952952
content_str.clear();
953953
content_file.reset();
@@ -987,7 +987,7 @@ namespace http
987987
if constexpr (std::is_same_v<Message, request>)
988988
state = method;
989989
else
990-
state = http_version;
990+
state = version;
991991
body_read = 0;
992992
}
993993

@@ -1045,7 +1045,7 @@ namespace http
10451045
if constexpr (std::is_same_v<Message, request>)
10461046
parse_url(buf.substr(0, end), msg.uri, msg.params, ec);
10471047

1048-
state = http_version;
1048+
state = version;
10491049
buf.erase(begin(buf), begin(buf) + end + 1);
10501050
}
10511051

@@ -1055,7 +1055,7 @@ namespace http
10551055
}
10561056

10571057
// HTTP version
1058-
else if (state == http_version)
1058+
else if (state == version)
10591059
{
10601060
constexpr std::size_t http_size{8};
10611061

@@ -1082,7 +1082,7 @@ namespace http
10821082
buf.erase(begin(buf), begin(buf) + http_size + 1);
10831083
}
10841084

1085-
msg.http_version_minor = minor;
1085+
msg.version = (http_version)minor;
10861086
}
10871087

10881088
// Not found
@@ -1131,45 +1131,39 @@ namespace http
11311131
{
11321132
const auto end = buf.find("\r\n");
11331133

1134-
// Found
1134+
// Sufficient
11351135
if (end != std::string::npos)
11361136
{
11371137
state = header_line;
11381138
buf.erase(begin(buf), begin(buf) + end + 2);
11391139
}
11401140

1141-
// Not Found
1141+
// Insufficient
11421142
else
1143-
ec = make_error_code(http_read_bad_status);
1143+
break;
11441144
}
11451145

11461146
// Header line
11471147
else if (state == header_line)
11481148
{
1149-
// Find EOL
1150-
char* end = strstr(&buf[0], "\r\n");
1149+
const auto end = buf.find("\r\n");
11511150

1152-
// Not found
1153-
if (end == nullptr)
1154-
break;
1155-
1156-
// Found
1157-
else
1151+
// Sufficient
1152+
if (end != std::string::npos)
11581153
{
1159-
*end = '\0';
1160-
1161-
// Header line
1162-
if (std::distance(&buf[0], end) > 0)
1154+
// Found header
1155+
if (end > 0)
11631156
{
1164-
char* kend = strstr(&buf[0], ": ");
1157+
std::string_view line(&buf[0], end);
1158+
const auto kend = line.find(": ");
11651159

1166-
if (kend == nullptr)
1160+
if (kend == std::string_view::npos)
11671161
ec = make_error_code(http_read_header_kv_delimiter_not_found);
1168-
1162+
11691163
else
11701164
{
1171-
auto field = field_enum(std::string_view(&buf[0], std::distance(&buf[0], kend)));
1172-
auto value = std::string_view(kend+2, std::distance(kend+2, end));
1165+
auto field = field_enum(line.substr(0, kend));
1166+
auto value = line.substr(kend+2);
11731167

11741168
if (field == unknown_field)
11751169
ec = make_error_code(http_read_header_unsupported_field);
@@ -1196,8 +1190,12 @@ namespace http
11961190
state = done;
11971191
}
11981192

1199-
buf.erase(begin(buf), begin(buf) + std::distance(&buf[0], end + 2));
1193+
buf.erase(begin(buf), begin(buf) + end + 2);
12001194
}
1195+
1196+
// Insufficient
1197+
else
1198+
break;
12011199
}
12021200

12031201
// Body
@@ -1289,12 +1287,6 @@ namespace http
12891287
return;
12901288
}
12911289

1292-
if (!(req.http_version_minor == 0 || req.http_version_minor == 1))
1293-
{
1294-
ec = make_error_code(http::http_write_unsupported_http_version);
1295-
return;
1296-
}
1297-
12981290
// HTTP requests require "host" field
12991291
if (!contains(req.headers, field::host))
13001292
{
@@ -1320,7 +1312,7 @@ namespace http
13201312
}
13211313

13221314
// Set request line
1323-
const std::string status_str = format("%s %s HTTP/1.%i\r\n", verb_label(req.verb).data(), uri_encoded.c_str(), req.http_version_minor);
1315+
const std::string status_str = format("%s %s HTTP/1.%i\r\n", verb_label(req.verb).data(), uri_encoded.c_str(), (int)req.version);
13241316

13251317
// Add default connection string if empty
13261318
if (!contains(req.headers, field::connection))
@@ -1340,9 +1332,15 @@ namespace http
13401332

13411333
void serialize_header(response& resp, std::string& buf, std::error_code& ec)
13421334
{
1335+
if (resp.status == unknown)
1336+
{
1337+
ec = make_error_code(http_write_response_missing_status);
1338+
return;
1339+
}
1340+
13431341
// Set status string
13441342
char status_str[64] = {0};
1345-
snprintf(status_str, sizeof(status_str), "HTTP/1.%i %i %s\r\n", resp.http_version_minor, resp.status, status_label(resp.status).data());
1343+
snprintf(status_str, sizeof(status_str), "HTTP/1.%i %i %s\r\n", (int)resp.version, resp.status, status_label(resp.status).data());
13461344

13471345
// Add default server string if empty
13481346
if (!contains(resp.headers, field::server))
@@ -1381,17 +1379,17 @@ namespace http
13811379
{
13821380
switch(static_cast<error>(ev))
13831381
{
1384-
case http_read_header_line_too_big: return "HTTP header line is too big";
1385-
case http_read_bad_method: return "HTTP request method bad";
1382+
case http_read_header_line_too_big: return "Header line is too big";
1383+
case http_read_bad_method: return "Request method bad";
13861384
case http_read_unsupported_http_version: return "HTTP version either bad or unsupported";
13871385
case http_read_bad_status: return "HTTP status code bad";
13881386
case http_read_bad_query_string: return "Bad query string formatting";
13891387
case http_read_header_kv_delimiter_not_found: return "Missing delimiter in HTTP header line";
13901388
case http_read_header_unsupported_field: return "HTTP header field unsupported";
1391-
case http_write_unsupported_http_version: return "HTTP message contains bad or unsupported http minor version";
13921389
case http_write_request_bad_verb: return "HTTP request contains bad verb";
13931390
case http_write_request_missing_uri: return "HTTP request missing URI";
13941391
case http_write_request_missing_host: return "HTTP request missing 'host' filed";
1392+
case http_write_response_missing_status: return "Missing status code";
13951393
case ws_handshake_bad_status: return "Status code not 101 (Switching Protocol) in websocket upgrade response";
13961394
case ws_handshake_bad_headers: return "Missing connection: upgrade or upgrade: websocket in HTTP headers";
13971395
case ws_handshake_missing_seq_accept: return "Missing seq-websocket-accept in HTTP websocket switching response message";

0 commit comments

Comments
 (0)