Skip to content

Commit

Permalink
Implementing streaming Responses
Browse files Browse the repository at this point in the history
This enables a much easier handling of big queries after all.
  • Loading branch information
davidgfnet committed Aug 1, 2018
1 parent ca343ae commit dae4124
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 2 deletions.
37 changes: 35 additions & 2 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ struct Response {
int status;
Headers headers;
std::string body;
std::function<std::string (uint64_t offset)> streamcb;

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

inline std::string from_i_to_hex(uint64_t n)
{
const char *charset = "0123456789abcdef";
std::string ret;
do {
ret = charset[n & 15] + ret;
n >>= 4;
} while (n > 0);
return ret;
}

inline size_t to_utf8(int code, char* buff)
{
if (code < 0x0080) {
Expand Down Expand Up @@ -1586,13 +1598,34 @@ inline void Server::write_response(Stream& strm, bool last_connection, const Req

auto length = std::to_string(res.body.size());
res.set_header("Content-Length", length.c_str());
} else if (res.streamcb) {
// Streamed response
bool chunked_response = !res.has_header("Content-Length");
if (chunked_response)
res.set_header("Transfer-Encoding", "chunked");
}

detail::write_headers(strm, res);

// Body
if (!res.body.empty() && req.method != "HEAD") {
strm.write(res.body.c_str(), res.body.size());
if (req.method != "HEAD") {
if (!res.body.empty()) {
strm.write(res.body.c_str(), res.body.size());
} else if (res.streamcb) {
bool chunked_response = !res.has_header("Content-Length");
uint64_t offset = 0;
bool data_available = true;
while (data_available) {
std::string chunk = res.streamcb(offset);
offset += chunk.size();
data_available = !chunk.empty();
// Emit chunked response header and footer for each chunk
if (chunked_response)
chunk = detail::from_i_to_hex(chunk.size()) + "\r\n" + chunk + "\r\n";
if (strm.write(chunk.c_str(), chunk.size()) < 0)
break; // Stop on error
}
}
}

// Log
Expand Down
36 changes: 36 additions & 0 deletions test/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,25 @@ class ServerTest : public ::testing::Test {
res.status = 404;
}
})
.Get("/streamedchunked", [&](const Request& /*req*/, Response& res) {
res.streamcb = [] (uint64_t offset) {
if (offset < 3)
return "a";
if (offset < 6)
return "b";
return "";
};
})
.Get("/streamed", [&](const Request& /*req*/, Response& res) {
res.set_header("Content-Length", "6");
res.streamcb = [] (uint64_t offset) {
if (offset < 3)
return "a";
if (offset < 6)
return "b";
return "";
};
})
.Post("/chunked", [&](const Request& req, Response& /*res*/) {
EXPECT_EQ(req.body, "dechunked post body");
})
Expand Down Expand Up @@ -712,6 +731,23 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding)
EXPECT_EQ(200, res->status);
}

TEST_F(ServerTest, GetStreamed)
{
auto res = cli_.Get("/streamed");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("6", res->get_header_value("Content-Length"));
EXPECT_TRUE(res->body == "aaabbb");
}

TEST_F(ServerTest, GetStreamedChunked)
{
auto res = cli_.Get("/streamedchunked");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_TRUE(res->body == "aaabbb");
}

TEST_F(ServerTest, LargeChunkedPost) {
Request req;
req.method = "POST";
Expand Down

0 comments on commit dae4124

Please sign in to comment.