mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2025-05-10 09:43:51 +00:00
Merge pull request #85 from davidgfnet/streaming
Implementing streaming Responses
This commit is contained in:
commit
f2daaf1b85
35
httplib.h
35
httplib.h
@ -146,6 +146,7 @@ struct Response {
|
|||||||
int status;
|
int status;
|
||||||
Headers headers;
|
Headers headers;
|
||||||
std::string body;
|
std::string body;
|
||||||
|
std::function<std::string (uint64_t offset)> streamcb;
|
||||||
|
|
||||||
bool has_header(const char* key) const;
|
bool has_header(const char* key) const;
|
||||||
std::string get_header_value(const char* key) const;
|
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)
|
|||||||
return true;
|
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)
|
inline size_t to_utf8(int code, char* buff)
|
||||||
{
|
{
|
||||||
if (code < 0x0080) {
|
if (code < 0x0080) {
|
||||||
@ -1587,13 +1599,34 @@ inline void Server::write_response(Stream& strm, bool last_connection, const Req
|
|||||||
|
|
||||||
auto length = std::to_string(res.body.size());
|
auto length = std::to_string(res.body.size());
|
||||||
res.set_header("Content-Length", length.c_str());
|
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);
|
detail::write_headers(strm, res);
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
if (!res.body.empty() && req.method != "HEAD") {
|
if (req.method != "HEAD") {
|
||||||
|
if (!res.body.empty()) {
|
||||||
strm.write(res.body.c_str(), res.body.size());
|
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
|
// Log
|
||||||
|
36
test/test.cc
36
test/test.cc
@ -282,6 +282,25 @@ protected:
|
|||||||
res.status = 404;
|
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*/) {
|
.Post("/chunked", [&](const Request& req, Response& /*res*/) {
|
||||||
EXPECT_EQ(req.body, "dechunked post body");
|
EXPECT_EQ(req.body, "dechunked post body");
|
||||||
})
|
})
|
||||||
@ -712,6 +731,23 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding)
|
|||||||
EXPECT_EQ(200, res->status);
|
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) {
|
TEST_F(ServerTest, LargeChunkedPost) {
|
||||||
Request req;
|
Request req;
|
||||||
req.method = "POST";
|
req.method = "POST";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user