diff --git a/httplib.h b/httplib.h
index 909d9ec..bffdf91 100644
--- a/httplib.h
+++ b/httplib.h
@@ -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;
@@ -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) {
@@ -1587,13 +1599,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
diff --git a/test/test.cc b/test/test.cc
index d4e6791..ca0c6e0 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -282,6 +282,25 @@ protected:
                     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");
             })
@@ -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";