Skip to content

Commit dae4124

Browse files
committed
Implementing streaming Responses
This enables a much easier handling of big queries after all.
1 parent ca343ae commit dae4124

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

httplib.h

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ struct Response {
146146
int status;
147147
Headers headers;
148148
std::string body;
149+
std::function<std::string (uint64_t offset)> streamcb;
149150

150151
bool has_header(const char* key) const;
151152
std::string get_header_value(const char* key) const;
@@ -964,6 +965,17 @@ inline bool from_hex_to_i(const std::string& s, size_t i, size_t cnt, int& val)
964965
return true;
965966
}
966967

968+
inline std::string from_i_to_hex(uint64_t n)
969+
{
970+
const char *charset = "0123456789abcdef";
971+
std::string ret;
972+
do {
973+
ret = charset[n & 15] + ret;
974+
n >>= 4;
975+
} while (n > 0);
976+
return ret;
977+
}
978+
967979
inline size_t to_utf8(int code, char* buff)
968980
{
969981
if (code < 0x0080) {
@@ -1586,13 +1598,34 @@ inline void Server::write_response(Stream& strm, bool last_connection, const Req
15861598

15871599
auto length = std::to_string(res.body.size());
15881600
res.set_header("Content-Length", length.c_str());
1601+
} else if (res.streamcb) {
1602+
// Streamed response
1603+
bool chunked_response = !res.has_header("Content-Length");
1604+
if (chunked_response)
1605+
res.set_header("Transfer-Encoding", "chunked");
15891606
}
15901607

15911608
detail::write_headers(strm, res);
15921609

15931610
// Body
1594-
if (!res.body.empty() && req.method != "HEAD") {
1595-
strm.write(res.body.c_str(), res.body.size());
1611+
if (req.method != "HEAD") {
1612+
if (!res.body.empty()) {
1613+
strm.write(res.body.c_str(), res.body.size());
1614+
} else if (res.streamcb) {
1615+
bool chunked_response = !res.has_header("Content-Length");
1616+
uint64_t offset = 0;
1617+
bool data_available = true;
1618+
while (data_available) {
1619+
std::string chunk = res.streamcb(offset);
1620+
offset += chunk.size();
1621+
data_available = !chunk.empty();
1622+
// Emit chunked response header and footer for each chunk
1623+
if (chunked_response)
1624+
chunk = detail::from_i_to_hex(chunk.size()) + "\r\n" + chunk + "\r\n";
1625+
if (strm.write(chunk.c_str(), chunk.size()) < 0)
1626+
break; // Stop on error
1627+
}
1628+
}
15961629
}
15971630

15981631
// Log

test/test.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,25 @@ class ServerTest : public ::testing::Test {
282282
res.status = 404;
283283
}
284284
})
285+
.Get("/streamedchunked", [&](const Request& /*req*/, Response& res) {
286+
res.streamcb = [] (uint64_t offset) {
287+
if (offset < 3)
288+
return "a";
289+
if (offset < 6)
290+
return "b";
291+
return "";
292+
};
293+
})
294+
.Get("/streamed", [&](const Request& /*req*/, Response& res) {
295+
res.set_header("Content-Length", "6");
296+
res.streamcb = [] (uint64_t offset) {
297+
if (offset < 3)
298+
return "a";
299+
if (offset < 6)
300+
return "b";
301+
return "";
302+
};
303+
})
285304
.Post("/chunked", [&](const Request& req, Response& /*res*/) {
286305
EXPECT_EQ(req.body, "dechunked post body");
287306
})
@@ -712,6 +731,23 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding)
712731
EXPECT_EQ(200, res->status);
713732
}
714733

734+
TEST_F(ServerTest, GetStreamed)
735+
{
736+
auto res = cli_.Get("/streamed");
737+
ASSERT_TRUE(res != nullptr);
738+
EXPECT_EQ(200, res->status);
739+
EXPECT_EQ("6", res->get_header_value("Content-Length"));
740+
EXPECT_TRUE(res->body == "aaabbb");
741+
}
742+
743+
TEST_F(ServerTest, GetStreamedChunked)
744+
{
745+
auto res = cli_.Get("/streamedchunked");
746+
ASSERT_TRUE(res != nullptr);
747+
EXPECT_EQ(200, res->status);
748+
EXPECT_TRUE(res->body == "aaabbb");
749+
}
750+
715751
TEST_F(ServerTest, LargeChunkedPost) {
716752
Request req;
717753
req.method = "POST";

0 commit comments

Comments
 (0)