Skip to content

Commit 9925d38

Browse files
committed
[Network/HttpClient] Added proper support for chunked responses
1 parent a26ba3c commit 9925d38

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

include/RaZ/Network/HttpClient.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class HttpClient {
2323
void disconnect() { m_tcpClient.disconnect(); }
2424

2525
private:
26+
std::string receiveChunked();
27+
2628
std::string m_host;
2729
TcpClient m_tcpClient;
2830
};

src/RaZ/Network/HttpClient.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,39 @@ std::string HttpClient::get(std::string_view resource) {
5858
if (contentLength > 0)
5959
return m_tcpClient.receiveExactly(contentLength);
6060

61-
if (isChunked) // TODO: properly handle chunked data
62-
return m_tcpClient.receiveUntil("\r\n\r\n");
61+
if (isChunked)
62+
return receiveChunked();
6363

6464
Logger::error("[HttpClient] Failed to get resource '{}': unsupported response method", resource);
6565
return {};
6666
}
6767

68+
std::string HttpClient::receiveChunked() {
69+
const auto receiveCrlf = [this] () {
70+
if (const std::string chunkEnd = m_tcpClient.receiveExactly(2); chunkEnd != "\r\n")
71+
throw std::runtime_error(std::format("[HttpClient] Ill-formed end of chunk: expected CRLF ('\\r\\n'), got '{}'", chunkEnd));
72+
};
73+
74+
std::string data;
75+
76+
while (true) {
77+
const std::string chunkSizeStr = m_tcpClient.receiveUntil("\r\n");
78+
79+
if (chunkSizeStr == "0\r\n") {
80+
receiveCrlf();
81+
break;
82+
}
83+
84+
std::size_t chunkSize {};
85+
86+
if (std::from_chars(chunkSizeStr.data(), chunkSizeStr.data() + chunkSizeStr.size() - 2, chunkSize, 16).ec != std::errc())
87+
throw std::runtime_error(std::format("[HttpClient] Failed to parse chunk size: '{}'", chunkSizeStr));
88+
89+
data.append(m_tcpClient.receiveExactly(chunkSize));
90+
receiveCrlf();
91+
}
92+
93+
return data;
94+
}
95+
6896
}

tests/src/RaZ/Network/HttpClient.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,18 @@ TEST_CASE("HttpClient connection", "[network]") {
1616
CHECK_NOTHROW(client.connect("localhost")); // Local TCP server listening on port 80 (HTTP)
1717
CHECK_NOTHROW(client.disconnect());
1818
}
19+
20+
TEST_CASE("HttpClient get", "[network][!mayfail]") {
21+
Raz::HttpClient client;
22+
23+
REQUIRE_NOTHROW(client.connect("example.com"));
24+
25+
std::string result;
26+
REQUIRE_NOTHROW(result = client.get("/"));
27+
28+
CHECK(result.size() == 528);
29+
CHECK(result.starts_with("<!doctype html>"));
30+
CHECK(result.ends_with("</html>\n"));
31+
32+
CHECK_NOTHROW(client.disconnect());
33+
}

0 commit comments

Comments
 (0)