Browse Source

Content receiver support on server

yhirose 6 years ago
parent
commit
d03937e144
2 changed files with 301 additions and 83 deletions
  1. 203 74
      httplib.h
  2. 98 9
      test/test.cc

+ 203 - 74
httplib.h

@@ -206,6 +206,8 @@ typedef std::function<bool(const char *data, size_t data_length, size_t offset,
                            uint64_t content_length)>
                            uint64_t content_length)>
     ContentReceiver;
     ContentReceiver;
 
 
+typedef std::function<bool(ContentReceiver receiver)> ContentReader;
+
 typedef std::function<bool(uint64_t current, uint64_t total)> Progress;
 typedef std::function<bool(uint64_t current, uint64_t total)> Progress;
 
 
 struct Response;
 struct Response;
@@ -477,6 +479,9 @@ private:
 class Server {
 class Server {
 public:
 public:
   typedef std::function<void(const Request &, Response &)> Handler;
   typedef std::function<void(const Request &, Response &)> Handler;
+  typedef std::function<void(const Request &, Response &,
+                             const ContentReader &content_reader)>
+      HandlerWithContentReader;
   typedef std::function<void(const Request &, const Response &)> Logger;
   typedef std::function<void(const Request &, const Response &)> Logger;
 
 
   Server();
   Server();
@@ -487,9 +492,11 @@ public:
 
 
   Server &Get(const char *pattern, Handler handler);
   Server &Get(const char *pattern, Handler handler);
   Server &Post(const char *pattern, Handler handler);
   Server &Post(const char *pattern, Handler handler);
-
+  Server &Post(const char *pattern, HandlerWithContentReader handler);
   Server &Put(const char *pattern, Handler handler);
   Server &Put(const char *pattern, Handler handler);
+  Server &Put(const char *pattern, HandlerWithContentReader handler);
   Server &Patch(const char *pattern, Handler handler);
   Server &Patch(const char *pattern, Handler handler);
+  Server &Patch(const char *pattern, HandlerWithContentReader handler);
   Server &Delete(const char *pattern, Handler handler);
   Server &Delete(const char *pattern, Handler handler);
   Server &Options(const char *pattern, Handler handler);
   Server &Options(const char *pattern, Handler handler);
 
 
@@ -526,15 +533,20 @@ protected:
 
 
 private:
 private:
   typedef std::vector<std::pair<std::regex, Handler>> Handlers;
   typedef std::vector<std::pair<std::regex, Handler>> Handlers;
+  typedef std::vector<std::pair<std::regex, HandlerWithContentReader>>
+      HandersForContentReader;
 
 
   socket_t create_server_socket(const char *host, int port,
   socket_t create_server_socket(const char *host, int port,
                                 int socket_flags) const;
                                 int socket_flags) const;
   int bind_internal(const char *host, int port, int socket_flags);
   int bind_internal(const char *host, int port, int socket_flags);
   bool listen_internal();
   bool listen_internal();
 
 
-  bool routing(Request &req, Response &res);
+  bool routing(Request &req, Response &res, ContentReader content_reader);
   bool handle_file_request(Request &req, Response &res);
   bool handle_file_request(Request &req, Response &res);
   bool dispatch_request(Request &req, Response &res, Handlers &handlers);
   bool dispatch_request(Request &req, Response &res, Handlers &handlers);
+  bool dispatch_request_for_content_reader(Request &req, Response &res,
+                                           ContentReader content_reader,
+                                           HandersForContentReader &handlers);
 
 
   bool parse_request_line(const char *s, Request &req);
   bool parse_request_line(const char *s, Request &req);
   bool write_response(Stream &strm, bool last_connection, const Request &req,
   bool write_response(Stream &strm, bool last_connection, const Request &req,
@@ -542,6 +554,11 @@ private:
   bool write_content_with_provider(Stream &strm, const Request &req,
   bool write_content_with_provider(Stream &strm, const Request &req,
                                    Response &res, const std::string &boundary,
                                    Response &res, const std::string &boundary,
                                    const std::string &content_type);
                                    const std::string &content_type);
+  bool read_content(Stream &strm, bool last_connection, Request &req,
+                    Response &res);
+  bool read_content_with_content_receiver(Stream &strm, bool last_connection,
+                                          Request &req, Response &res,
+                                          ContentReceiver reveiver);
 
 
   virtual bool process_and_close_socket(socket_t sock);
   virtual bool process_and_close_socket(socket_t sock);
 
 
@@ -551,8 +568,11 @@ private:
   Handler file_request_handler_;
   Handler file_request_handler_;
   Handlers get_handlers_;
   Handlers get_handlers_;
   Handlers post_handlers_;
   Handlers post_handlers_;
+  HandersForContentReader post_handlers_for_content_reader;
   Handlers put_handlers_;
   Handlers put_handlers_;
+  HandersForContentReader put_handlers_for_content_reader;
   Handlers patch_handlers_;
   Handlers patch_handlers_;
+  HandersForContentReader patch_handlers_for_content_reader;
   Handlers delete_handlers_;
   Handlers delete_handlers_;
   Handlers options_handlers_;
   Handlers options_handlers_;
   Handler error_handler_;
   Handler error_handler_;
@@ -1487,13 +1507,13 @@ inline bool read_headers(Stream &strm, Headers &headers) {
   const auto bufsiz = 2048;
   const auto bufsiz = 2048;
   char buf[bufsiz];
   char buf[bufsiz];
 
 
-  stream_line_reader reader(strm, buf, bufsiz);
+  stream_line_reader line_reader(strm, buf, bufsiz);
 
 
   for (;;) {
   for (;;) {
-    if (!reader.getline()) { return false; }
-    if (!strcmp(reader.ptr(), "\r\n")) { break; }
+    if (!line_reader.getline()) { return false; }
+    if (!strcmp(line_reader.ptr(), "\r\n")) { break; }
     std::cmatch m;
     std::cmatch m;
-    if (std::regex_match(reader.ptr(), m, re)) {
+    if (std::regex_match(line_reader.ptr(), m, re)) {
       auto key = std::string(m[1]);
       auto key = std::string(m[1]);
       auto val = std::string(m[2]);
       auto val = std::string(m[2]);
       headers.emplace(key, val);
       headers.emplace(key, val);
@@ -1559,29 +1579,30 @@ inline bool read_content_chunked(Stream &strm, ContentReceiverCore out) {
   const auto bufsiz = 16;
   const auto bufsiz = 16;
   char buf[bufsiz];
   char buf[bufsiz];
 
 
-  stream_line_reader reader(strm, buf, bufsiz);
+  stream_line_reader line_reader(strm, buf, bufsiz);
 
 
-  if (!reader.getline()) { return false; }
+  if (!line_reader.getline()) { return false; }
 
 
-  auto chunk_len = std::stoi(reader.ptr(), 0, 16);
+  auto chunk_len = std::stoi(line_reader.ptr(), 0, 16);
 
 
   while (chunk_len > 0) {
   while (chunk_len > 0) {
     if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
     if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
       return false;
       return false;
     }
     }
 
 
-    if (!reader.getline()) { return false; }
+    if (!line_reader.getline()) { return false; }
 
 
-    if (strcmp(reader.ptr(), "\r\n")) { break; }
+    if (strcmp(line_reader.ptr(), "\r\n")) { break; }
 
 
-    if (!reader.getline()) { return false; }
+    if (!line_reader.getline()) { return false; }
 
 
-    chunk_len = std::stoi(reader.ptr(), 0, 16);
+    chunk_len = std::stoi(line_reader.ptr(), 0, 16);
   }
   }
 
 
   if (chunk_len == 0) {
   if (chunk_len == 0) {
     // Reader terminator after chunks
     // Reader terminator after chunks
-    if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) return false;
+    if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
+      return false;
   }
   }
 
 
   return true;
   return true;
@@ -1898,32 +1919,33 @@ inline bool parse_multipart_formdata(const std::string &boundary,
 
 
 inline bool parse_range_header(const std::string &s, Ranges &ranges) {
 inline bool parse_range_header(const std::string &s, Ranges &ranges) {
   try {
   try {
-    static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
+    static auto re_first_range =
+        std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
     std::smatch m;
     std::smatch m;
     if (std::regex_match(s, m, re_first_range)) {
     if (std::regex_match(s, m, re_first_range)) {
       auto pos = m.position(1);
       auto pos = m.position(1);
       auto len = m.length(1);
       auto len = m.length(1);
-      detail::split(&s[pos], &s[pos + len], ',',
-                    [&](const char *b, const char *e) {
-                      static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
-                      std::cmatch m;
-                      if (std::regex_match(b, e, m, re_another_range)) {
-                        ssize_t first = -1;
-                        if (!m.str(1).empty()) {
-                          first = static_cast<ssize_t>(std::stoll(m.str(1)));
-                        }
-
-                        ssize_t last = -1;
-                        if (!m.str(2).empty()) {
-                          last = static_cast<ssize_t>(std::stoll(m.str(2)));
-                        }
-
-                        if (first != -1 && last != -1 && first > last) {
-                          throw std::runtime_error("invalid range error");
-                        }
-                        ranges.emplace_back(std::make_pair(first, last));
-                      }
-                    });
+      detail::split(
+          &s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
+            static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
+            std::cmatch m;
+            if (std::regex_match(b, e, m, re_another_range)) {
+              ssize_t first = -1;
+              if (!m.str(1).empty()) {
+                first = static_cast<ssize_t>(std::stoll(m.str(1)));
+              }
+
+              ssize_t last = -1;
+              if (!m.str(2).empty()) {
+                last = static_cast<ssize_t>(std::stoll(m.str(2)));
+              }
+
+              if (first != -1 && last != -1 && first > last) {
+                throw std::runtime_error("invalid range error");
+              }
+              ranges.emplace_back(std::make_pair(first, last));
+            }
+          });
       return true;
       return true;
     }
     }
     return false;
     return false;
@@ -2344,16 +2366,37 @@ inline Server &Server::Post(const char *pattern, Handler handler) {
   return *this;
   return *this;
 }
 }
 
 
+inline Server &Server::Post(const char *pattern,
+                            HandlerWithContentReader handler) {
+  post_handlers_for_content_reader.push_back(
+      std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
 inline Server &Server::Put(const char *pattern, Handler handler) {
 inline Server &Server::Put(const char *pattern, Handler handler) {
   put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
 }
 }
 
 
+inline Server &Server::Put(const char *pattern,
+                           HandlerWithContentReader handler) {
+  put_handlers_for_content_reader.push_back(
+      std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
 inline Server &Server::Patch(const char *pattern, Handler handler) {
 inline Server &Server::Patch(const char *pattern, Handler handler) {
   patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
 }
 }
 
 
+inline Server &Server::Patch(const char *pattern,
+                             HandlerWithContentReader handler) {
+  patch_handlers_for_content_reader.push_back(
+      std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
 inline Server &Server::Delete(const char *pattern, Handler handler) {
 inline Server &Server::Delete(const char *pattern, Handler handler) {
   delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
@@ -2597,6 +2640,58 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
   return true;
   return true;
 }
 }
 
 
+inline bool Server::read_content(Stream &strm, bool last_connection,
+                                 Request &req, Response &res) {
+  if (!detail::read_content(strm, req, payload_max_length_, res.status,
+                            Progress(), [&](const char *buf, size_t n) {
+                              if (req.body.size() + n > req.body.max_size()) {
+                                return false;
+                              }
+                              req.body.append(buf, n);
+                              return true;
+                            })) {
+    return write_response(strm, last_connection, req, res);
+  }
+
+  const auto &content_type = req.get_header_value("Content-Type");
+
+  if (!content_type.find("application/x-www-form-urlencoded")) {
+    detail::parse_query_text(req.body, req.params);
+  } else if (!content_type.find("multipart/form-data")) {
+    std::string boundary;
+    if (!detail::parse_multipart_boundary(content_type, boundary) ||
+        !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
+      res.status = 400;
+      return write_response(strm, last_connection, req, res);
+    }
+  }
+
+  return true;
+}
+
+inline bool
+Server::read_content_with_content_receiver(Stream &strm, bool last_connection,
+                                           Request &req, Response &res,
+                                           ContentReceiver receiver) {
+  size_t offset = 0;
+
+  size_t length = 0;
+  if (req.get_header_value("Content-Encoding") != "gzip") {
+    length = get_header_value_uint64(req.headers, "Content-Length", 0);
+  }
+
+  if (!detail::read_content(strm, req, payload_max_length_, res.status,
+                            Progress(), [&](const char *buf, size_t n) {
+                              auto ret = receiver(buf, n, offset, length);
+                              offset += n;
+                              return ret;
+                            })) {
+    return write_response(strm, last_connection, req, res);
+  }
+
+  return true;
+}
+
 inline bool Server::handle_file_request(Request &req, Response &res) {
 inline bool Server::handle_file_request(Request &req, Response &res) {
   if (!base_dir_.empty() && detail::is_valid_path(req.path)) {
   if (!base_dir_.empty() && detail::is_valid_path(req.path)) {
     std::string path = base_dir_ + req.path;
     std::string path = base_dir_ + req.path;
@@ -2705,9 +2800,33 @@ inline bool Server::listen_internal() {
   return ret;
   return ret;
 }
 }
 
 
-inline bool Server::routing(Request &req, Response &res) {
+inline bool Server::routing(Request &req, Response &res,
+                            ContentReader content_reader) {
+  // File handler
   if (req.method == "GET" && handle_file_request(req, res)) { return true; }
   if (req.method == "GET" && handle_file_request(req, res)) { return true; }
 
 
+  // Content reader handler
+  if (req.method == "POST") {
+    if (dispatch_request_for_content_reader(req, res, content_reader,
+                                            post_handlers_for_content_reader)) {
+      return true;
+    }
+  } else if (req.method == "PUT") {
+    if (dispatch_request_for_content_reader(req, res, content_reader,
+                                            put_handlers_for_content_reader)) {
+      return true;
+    }
+  } else if (req.method == "PATCH") {
+    if (dispatch_request_for_content_reader(
+            req, res, content_reader, patch_handlers_for_content_reader)) {
+      return true;
+    }
+  }
+
+  // Read content into `req.body`
+  if (!content_reader(nullptr)) { return false; }
+
+  // Regular handler
   if (req.method == "GET" || req.method == "HEAD") {
   if (req.method == "GET" || req.method == "HEAD") {
     return dispatch_request(req, res, get_handlers_);
     return dispatch_request(req, res, get_handlers_);
   } else if (req.method == "POST") {
   } else if (req.method == "POST") {
@@ -2740,6 +2859,22 @@ inline bool Server::dispatch_request(Request &req, Response &res,
   return false;
   return false;
 }
 }
 
 
+inline bool
+Server::dispatch_request_for_content_reader(Request &req, Response &res,
+                                            ContentReader content_reader,
+                                            HandersForContentReader &handlers) {
+  for (const auto &x : handlers) {
+    const auto &pattern = x.first;
+    const auto &handler = x.second;
+
+    if (std::regex_match(req.path, req.matches, pattern)) {
+      handler(req, res, content_reader);
+      return true;
+    }
+  }
+  return false;
+}
+
 inline bool
 inline bool
 Server::process_request(Stream &strm, bool last_connection,
 Server::process_request(Stream &strm, bool last_connection,
                         bool &connection_close,
                         bool &connection_close,
@@ -2747,10 +2882,10 @@ Server::process_request(Stream &strm, bool last_connection,
   const auto bufsiz = 2048;
   const auto bufsiz = 2048;
   char buf[bufsiz];
   char buf[bufsiz];
 
 
-  detail::stream_line_reader reader(strm, buf, bufsiz);
+  detail::stream_line_reader line_reader(strm, buf, bufsiz);
 
 
   // Connection has been closed on client
   // Connection has been closed on client
-  if (!reader.getline()) { return false; }
+  if (!line_reader.getline()) { return false; }
 
 
   Request req;
   Request req;
   Response res;
   Response res;
@@ -2758,7 +2893,7 @@ Server::process_request(Stream &strm, bool last_connection,
   res.version = "HTTP/1.1";
   res.version = "HTTP/1.1";
 
 
   // Check if the request URI doesn't exceed the limit
   // Check if the request URI doesn't exceed the limit
-  if (reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+  if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
     Headers dummy;
     Headers dummy;
     detail::read_headers(strm, dummy);
     detail::read_headers(strm, dummy);
     res.status = 414;
     res.status = 414;
@@ -2766,7 +2901,7 @@ Server::process_request(Stream &strm, bool last_connection,
   }
   }
 
 
   // Request line and headers
   // Request line and headers
-  if (!parse_request_line(reader.ptr(), req) ||
+  if (!parse_request_line(line_reader.ptr(), req) ||
       !detail::read_headers(strm, req.headers)) {
       !detail::read_headers(strm, req.headers)) {
     res.status = 400;
     res.status = 400;
     return write_response(strm, last_connection, req, res);
     return write_response(strm, last_connection, req, res);
@@ -2783,34 +2918,6 @@ Server::process_request(Stream &strm, bool last_connection,
 
 
   req.set_header("REMOTE_ADDR", strm.get_remote_addr());
   req.set_header("REMOTE_ADDR", strm.get_remote_addr());
 
 
-  // Body
-  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
-      req.method == "PRI") {
-    if (!detail::read_content(strm, req, payload_max_length_, res.status,
-                              Progress(), [&](const char *buf, size_t n) {
-                                if (req.body.size() + n > req.body.max_size()) {
-                                  return false;
-                                }
-                                req.body.append(buf, n);
-                                return true;
-                              })) {
-      return write_response(strm, last_connection, req, res);
-    }
-
-    const auto &content_type = req.get_header_value("Content-Type");
-
-    if (!content_type.find("application/x-www-form-urlencoded")) {
-      detail::parse_query_text(req.body, req.params);
-    } else if (!content_type.find("multipart/form-data")) {
-      std::string boundary;
-      if (!detail::parse_multipart_boundary(content_type, boundary) ||
-          !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
-        res.status = 400;
-        return write_response(strm, last_connection, req, res);
-      }
-    }
-  }
-
   if (req.has_header("Range")) {
   if (req.has_header("Range")) {
     const auto &range_header_value = req.get_header_value("Range");
     const auto &range_header_value = req.get_header_value("Range");
     if (!detail::parse_range_header(range_header_value, req.ranges)) {
     if (!detail::parse_range_header(range_header_value, req.ranges)) {
@@ -2820,7 +2927,23 @@ Server::process_request(Stream &strm, bool last_connection,
 
 
   if (setup_request) { setup_request(req); }
   if (setup_request) { setup_request(req); }
 
 
-  if (routing(req, res)) {
+  // Body
+  ContentReader content_reader = [&](ContentReceiver receiver) {
+    if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
+      if (receiver) {
+        return read_content_with_content_receiver(strm, last_connection, req,
+                                                  res, receiver);
+      } else {
+        return read_content(strm, last_connection, req, res);
+      }
+    } else if (req.method == "PRI") {
+      return read_content(strm, last_connection, req, res);
+    }
+    return true;
+  };
+
+  // Rounting
+  if (routing(req, res, content_reader)) {
     if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
     if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
   } else {
   } else {
     if (res.status == -1) { res.status = 404; }
     if (res.status == -1) { res.status = 404; }
@@ -2876,14 +2999,14 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
   const auto bufsiz = 2048;
   const auto bufsiz = 2048;
   char buf[bufsiz];
   char buf[bufsiz];
 
 
-  detail::stream_line_reader reader(strm, buf, bufsiz);
+  detail::stream_line_reader line_reader(strm, buf, bufsiz);
 
 
-  if (!reader.getline()) { return false; }
+  if (!line_reader.getline()) { return false; }
 
 
   const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
   const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
 
 
   std::cmatch m;
   std::cmatch m;
-  if (std::regex_match(reader.ptr(), m, re)) {
+  if (std::regex_match(line_reader.ptr(), m, re)) {
     res.version = std::string(m[1]);
     res.version = std::string(m[1]);
     res.status = std::stoi(std::string(m[2]));
     res.status = std::stoi(std::string(m[2]));
   }
   }
@@ -3138,8 +3261,14 @@ inline bool Client::process_request(Stream &strm, const Request &req,
 
 
     if (req.content_receiver) {
     if (req.content_receiver) {
       auto offset = std::make_shared<size_t>();
       auto offset = std::make_shared<size_t>();
-      auto length = get_header_value_uint64(res.headers, "Content-Length", 0);
+
+      size_t length = 0;
+      if (res.get_header_value("Content-Encoding") != "gzip") {
+        length = get_header_value_uint64(res.headers, "Content-Length", 0);
+      }
+
       auto receiver = req.content_receiver;
       auto receiver = req.content_receiver;
+
       out = [offset, length, receiver](const char *buf, size_t n) {
       out = [offset, length, receiver](const char *buf, size_t n) {
         auto ret = receiver(buf, n, *offset, length);
         auto ret = receiver(buf, n, *offset, length);
         (*offset) += n;
         (*offset) += n;

+ 98 - 9
test/test.cc

@@ -744,6 +744,52 @@ protected:
                 EXPECT_EQ(1u, req.get_header_value_count("Content-Length"));
                 EXPECT_EQ(1u, req.get_header_value_count("Content-Length"));
                 EXPECT_EQ("5", req.get_header_value("Content-Length"));
                 EXPECT_EQ("5", req.get_header_value("Content-Length"));
               })
               })
+        .Post("/content_receiver",
+              [&](const Request & req, Response &res,
+                  const ContentReader &content_reader) {
+                std::string body;
+                content_reader([&](const char *data, size_t data_length,
+                                   size_t offset,
+                                   uint64_t content_length) {
+                  EXPECT_EQ(offset, 0);
+                  if (req.get_header_value("Content-Encoding") == "gzip") {
+                    EXPECT_EQ(content_length, 0);
+                  } else {
+                    EXPECT_EQ(content_length, 7);
+                  }
+                  EXPECT_EQ(data_length, 7);
+                  body.append(data, data_length);
+                  return true;
+                });
+                EXPECT_EQ(body, "content");
+                res.set_content(body, "text/plain");
+              })
+        .Put("/content_receiver",
+             [&](const Request & /*req*/, Response &res,
+                 const ContentReader &content_reader) {
+               std::string body;
+               content_reader([&](const char *data, size_t data_length,
+                                  size_t /*offset*/,
+                                  uint64_t /*content_length*/) {
+                 body.append(data, data_length);
+                 return true;
+               });
+               EXPECT_EQ(body, "content");
+               res.set_content(body, "text/plain");
+             })
+        .Patch("/content_receiver",
+               [&](const Request & /*req*/, Response &res,
+                   const ContentReader &content_reader) {
+                 std::string body;
+                 content_reader([&](const char *data, size_t data_length,
+                                    size_t /*offset*/,
+                                    uint64_t /*content_length*/) {
+                   body.append(data, data_length);
+                   return true;
+                 });
+                 EXPECT_EQ(body, "content");
+                 res.set_content(body, "text/plain");
+               })
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
         .Get("/gzip",
         .Get("/gzip",
              [&](const Request & /*req*/, Response &res) {
              [&](const Request & /*req*/, Response &res) {
@@ -1354,9 +1400,12 @@ TEST_F(ServerTest, Put) {
 }
 }
 
 
 TEST_F(ServerTest, PutWithContentProvider) {
 TEST_F(ServerTest, PutWithContentProvider) {
-  auto res = cli_.Put("/put", 3, [](size_t /*offset*/, size_t /*length*/, DataSink sink) {
-    sink("PUT", 3);
-  }, "text/plain");
+  auto res = cli_.Put(
+      "/put", 3,
+      [](size_t /*offset*/, size_t /*length*/, DataSink sink) {
+        sink("PUT", 3);
+      },
+      "text/plain");
 
 
   ASSERT_TRUE(res != nullptr);
   ASSERT_TRUE(res != nullptr);
   EXPECT_EQ(200, res->status);
   EXPECT_EQ(200, res->status);
@@ -1365,9 +1414,12 @@ TEST_F(ServerTest, PutWithContentProvider) {
 
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 TEST_F(ServerTest, PutWithContentProviderWithGzip) {
 TEST_F(ServerTest, PutWithContentProviderWithGzip) {
-  auto res = cli_.Put("/put", 3, [](size_t /*offset*/, size_t /*length*/, DataSink sink) {
-    sink("PUT", 3);
-  }, "text/plain", true);
+  auto res = cli_.Put(
+      "/put", 3,
+      [](size_t /*offset*/, size_t /*length*/, DataSink sink) {
+        sink("PUT", 3);
+      },
+      "text/plain", true);
 
 
   ASSERT_TRUE(res != nullptr);
   ASSERT_TRUE(res != nullptr);
   EXPECT_EQ(200, res->status);
   EXPECT_EQ(200, res->status);
@@ -1417,6 +1469,34 @@ TEST_F(ServerTest, NoMultipleHeaders) {
   EXPECT_EQ(200, res->status);
   EXPECT_EQ(200, res->status);
 }
 }
 
 
+TEST_F(ServerTest, PostContentReceiver) {
+  auto res = cli_.Post("/content_receiver", "content", "text/plain");
+  ASSERT_TRUE(res != nullptr);
+  ASSERT_EQ(200, res->status);
+  ASSERT_EQ("content", res->body);
+}
+
+TEST_F(ServerTest, PostContentReceiverGzip) {
+  auto res = cli_.Post("/content_receiver", "content", "text/plain", true);
+  ASSERT_TRUE(res != nullptr);
+  ASSERT_EQ(200, res->status);
+  ASSERT_EQ("content", res->body);
+}
+
+TEST_F(ServerTest, PutContentReceiver) {
+  auto res = cli_.Put("/content_receiver", "content", "text/plain");
+  ASSERT_TRUE(res != nullptr);
+  ASSERT_EQ(200, res->status);
+  ASSERT_EQ("content", res->body);
+}
+
+TEST_F(ServerTest, PatchContentReceiver) {
+  auto res = cli_.Patch("/content_receiver", "content", "text/plain");
+  ASSERT_TRUE(res != nullptr);
+  ASSERT_EQ(200, res->status);
+  ASSERT_EQ("content", res->body);
+}
+
 TEST_F(ServerTest, HTTP2Magic) {
 TEST_F(ServerTest, HTTP2Magic) {
   Request req;
   Request req;
   req.method = "PRI";
   req.method = "PRI";
@@ -1501,7 +1581,10 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
   std::string body;
   std::string body;
   auto res = cli_.Get("/gzip", headers,
   auto res = cli_.Get("/gzip", headers,
                       [&](const char *data, uint64_t data_length,
                       [&](const char *data, uint64_t data_length,
-                          uint64_t /*offset*/, uint64_t /*content_length*/) {
+                          uint64_t offset, uint64_t content_length) {
+                        EXPECT_EQ(data_length, 100);
+                        EXPECT_EQ(offset, 0);
+                        EXPECT_EQ(content_length, 0);
                         body.append(data, data_length);
                         body.append(data, data_length);
                         return true;
                         return true;
                       });
                       });
@@ -1521,7 +1604,10 @@ TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
   std::string body;
   std::string body;
   auto res = cli_.Get("/gzip", headers,
   auto res = cli_.Get("/gzip", headers,
                       [&](const char *data, uint64_t data_length,
                       [&](const char *data, uint64_t data_length,
-                          uint64_t /*offset*/, uint64_t /*content_length*/) {
+                          uint64_t offset, uint64_t content_length) {
+                        EXPECT_EQ(data_length, 100);
+                        EXPECT_EQ(offset, 0);
+                        EXPECT_EQ(content_length, 100);
                         body.append(data, data_length);
                         body.append(data, data_length);
                         return true;
                         return true;
                       });
                       });
@@ -1557,7 +1643,10 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
   std::string body;
   std::string body;
   auto res = cli_.Get("/nogzip", headers,
   auto res = cli_.Get("/nogzip", headers,
                       [&](const char *data, uint64_t data_length,
                       [&](const char *data, uint64_t data_length,
-                          uint64_t /*offset*/, uint64_t /*content_length*/) {
+                          uint64_t offset, uint64_t content_length) {
+                        EXPECT_EQ(data_length, 100);
+                        EXPECT_EQ(offset, 0);
+                        EXPECT_EQ(content_length, 100);
                         body.append(data, data_length);
                         body.append(data, data_length);
                         return true;
                         return true;
                       });
                       });