From 82fc7d5591237b49e3cb87e13ec4aec55c39a98b Mon Sep 17 00:00:00 2001
From: Thomas Tissot <thomas.tissot@zen.ly>
Date: Mon, 6 Aug 2018 11:54:52 +0200
Subject: [PATCH] Request cancelation feature

This commit modifies the signature of the `Progress` callback
so that its return value will indicate whether the request shall
continue to be processed by returning `true`, or if it shall
be aborted by returning `false`. Such modification will allow
one to cancel an ongoing request before it has completed.

When migrating, developers should modify there `Progress`
callbacks to always return `true` by default in case there
do not want to benefit from the cancelation feature.

A few unit tests use cases were provided, but anyone should feel
free to provide additional uses cases that they find relevant.
---
 httplib.h    |  6 ++++--
 test/test.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/httplib.h b/httplib.h
index bffdf91..dff8aad 100644
--- a/httplib.h
+++ b/httplib.h
@@ -107,7 +107,7 @@ std::pair<std::string, std::string> make_range_header(uint64_t value, Args... ar
 
 typedef std::multimap<std::string, std::string>                Params;
 typedef std::smatch                                            Match;
-typedef std::function<void (uint64_t current, uint64_t total)> Progress;
+typedef std::function<bool (uint64_t current, uint64_t total)> Progress;
 
 struct MultipartFile {
     std::string filename;
@@ -804,7 +804,9 @@ inline bool read_content_with_length(Stream& strm, std::string& out, size_t len,
         r += n;
 
         if (progress) {
-            progress(r, len);
+            if (!progress(r, len)) {
+                return false;
+            }
         }
     }
 
diff --git a/test/test.cc b/test/test.cc
index ca0c6e0..6169b60 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -230,6 +230,66 @@ TEST(ConnectionErrorTest, Timeout)
     ASSERT_TRUE(res == nullptr);
 }
 
+TEST(CancelTest, NoCancel) {
+    auto host = "httpbin.org";
+    auto sec = 5;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+    auto port = 443;
+    httplib::SSLClient cli(host, port, sec);
+#else
+    auto port = 80;
+    httplib::Client cli(host, port, sec);
+#endif
+
+    httplib::Headers headers;
+    auto res = cli.Get("/range/32", headers, [](uint64_t, uint64_t) {
+        return true;
+    });
+    ASSERT_TRUE(res != nullptr);
+    EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
+    EXPECT_EQ(200, res->status);
+}
+
+TEST(CancelTest, WithCancelSmallPayload) {
+    auto host = "httpbin.org";
+    auto sec = 5;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+    auto port = 443;
+    httplib::SSLClient cli(host, port, sec);
+#else
+    auto port = 80;
+    httplib::Client cli(host, port, sec);
+#endif
+
+    httplib::Headers headers;
+    auto res = cli.Get("/range/32", headers, [](uint64_t, uint64_t) {
+        return false;
+    });
+    ASSERT_TRUE(res == nullptr);
+}
+
+TEST(CancelTest, WithCancelLargePayload) {
+    auto host = "httpbin.org";
+    auto sec = 5;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+    auto port = 443;
+    httplib::SSLClient cli(host, port, sec);
+#else
+    auto port = 80;
+    httplib::Client cli(host, port, sec);
+#endif
+
+    uint32_t count = 0;
+    httplib::Headers headers;
+    auto res = cli.Get("/range/65536", headers, [&count](uint64_t, uint64_t) {
+        return (count++ == 0);
+    });
+    ASSERT_TRUE(res == nullptr);
+}
+
 TEST(Server, BindAndListenSeparately) {
     Server svr;
     int port = svr.bind_to_any_port("localhost");