yhirose 1 year ago
parent
commit
8415bf0823
3 changed files with 103 additions and 10 deletions
  1. 12 0
      README.md
  2. 42 0
      httplib.h
  3. 49 10
      test/test.cc

+ 12 - 0
README.md

@@ -384,6 +384,18 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
 });
 });
 ```
 ```
 
 
+### Send file content
+
+```cpp
+svr.Get("/content", [&](const Request &req, Response &res) {
+  res.set_file_content("./path/to/conent.html");
+});
+
+svr.Get("/content", [&](const Request &req, Response &res) {
+  res.set_file_content("./path/to/conent", "text/html");
+});
+```
+
 ### 'Expect: 100-continue' handler
 ### 'Expect: 100-continue' handler
 
 
 By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
 By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.

+ 42 - 0
httplib.h

@@ -675,6 +675,10 @@ struct Response {
       const std::string &content_type, ContentProviderWithoutLength provider,
       const std::string &content_type, ContentProviderWithoutLength provider,
       ContentProviderResourceReleaser resource_releaser = nullptr);
       ContentProviderResourceReleaser resource_releaser = nullptr);
 
 
+  void set_file_content(const std::string &path,
+                        const std::string &content_type);
+  void set_file_content(const std::string &path);
+
   Response() = default;
   Response() = default;
   Response(const Response &) = default;
   Response(const Response &) = default;
   Response &operator=(const Response &) = default;
   Response &operator=(const Response &) = default;
@@ -692,6 +696,8 @@ struct Response {
   ContentProviderResourceReleaser content_provider_resource_releaser_;
   ContentProviderResourceReleaser content_provider_resource_releaser_;
   bool is_chunked_content_provider_ = false;
   bool is_chunked_content_provider_ = false;
   bool content_provider_success_ = false;
   bool content_provider_success_ = false;
+  std::string file_content_path_;
+  std::string file_content_content_type_;
 };
 };
 
 
 class Stream {
 class Stream {
@@ -5703,6 +5709,16 @@ inline void Response::set_chunked_content_provider(
   is_chunked_content_provider_ = true;
   is_chunked_content_provider_ = true;
 }
 }
 
 
+inline void Response::set_file_content(const std::string &path,
+                                       const std::string &content_type) {
+  file_content_path_ = path;
+  file_content_content_type_ = content_type;
+}
+
+inline void Response::set_file_content(const std::string &path) {
+  file_content_path_ = path;
+}
+
 // Result implementation
 // Result implementation
 inline bool Result::has_request_header(const std::string &key) const {
 inline bool Result::has_request_header(const std::string &key) const {
   return request_headers_.find(key) != request_headers_.end();
   return request_headers_.find(key) != request_headers_.end();
@@ -7043,6 +7059,32 @@ Server::process_request(Stream &strm, bool close_connection,
       return write_response(strm, close_connection, req, res);
       return write_response(strm, close_connection, req, res);
     }
     }
 
 
+    // Serve file content by using a content provider
+    if (!res.file_content_path_.empty()) {
+      const auto &path = res.file_content_path_;
+      auto mm = std::make_shared<detail::mmap>(path.c_str());
+      if (!mm->is_open()) {
+        res.body.clear();
+        res.content_length_ = 0;
+        res.content_provider_ = nullptr;
+        res.status = StatusCode::NotFound_404;
+        return write_response(strm, close_connection, req, res);
+      }
+
+      auto content_type = res.file_content_content_type_;
+      if (content_type.empty()) {
+        content_type = detail::find_content_type(
+            path, file_extension_and_mimetype_map_, default_file_mimetype_);
+      }
+
+      res.set_content_provider(
+          mm->size(), content_type,
+          [mm](size_t offset, size_t length, DataSink &sink) -> bool {
+            sink.write(mm->data() + offset, length);
+            return true;
+          });
+    }
+
     return write_response_with_content(strm, close_connection, req, res);
     return write_response_with_content(strm, close_connection, req, res);
   } else {
   } else {
     if (res.status == -1) { res.status = StatusCode::NotFound_404; }
     if (res.status == -1) { res.status = StatusCode::NotFound_404; }

+ 49 - 10
test/test.cc

@@ -2300,6 +2300,18 @@ protected:
              [&](const Request & /*req*/, Response &res) {
              [&](const Request & /*req*/, Response &res) {
                res.set_content("Hello World!", "text/plain");
                res.set_content("Hello World!", "text/plain");
              })
              })
+        .Get("/file_content",
+             [&](const Request & /*req*/, Response &res) {
+               res.set_file_content("./www/dir/test.html");
+             })
+        .Get("/file_content_with_content_type",
+             [&](const Request & /*req*/, Response &res) {
+               res.set_file_content("./www/file", "text/plain");
+             })
+        .Get("/invalid_file_content",
+             [&](const Request & /*req*/, Response &res) {
+               res.set_file_content("./www/dir/invalid_file_path");
+             })
         .Get("/http_response_splitting",
         .Get("/http_response_splitting",
              [&](const Request & /*req*/, Response &res) {
              [&](const Request & /*req*/, Response &res) {
                res.set_header("a", "1\r\nSet-Cookie: a=1");
                res.set_header("a", "1\r\nSet-Cookie: a=1");
@@ -2904,6 +2916,30 @@ TEST_F(ServerTest, GetMethod200) {
   EXPECT_EQ("Hello World!", res->body);
   EXPECT_EQ("Hello World!", res->body);
 }
 }
 
 
+TEST_F(ServerTest, GetFileContent) {
+  auto res = cli_.Get("/file_content");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::OK_200, res->status);
+  EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
+  EXPECT_EQ(9, std::stoi(res->get_header_value("Content-Length")));
+  EXPECT_EQ("test.html", res->body);
+}
+
+TEST_F(ServerTest, GetFileContentWithContentType) {
+  auto res = cli_.Get("/file_content_with_content_type");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::OK_200, res->status);
+  EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
+  EXPECT_EQ(5, std::stoi(res->get_header_value("Content-Length")));
+  EXPECT_EQ("file\n", res->body);
+}
+
+TEST_F(ServerTest, GetInvalidFileContent) {
+  auto res = cli_.Get("/invalid_file_content");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::NotFound_404, res->status);
+}
+
 TEST_F(ServerTest, GetMethod200withPercentEncoding) {
 TEST_F(ServerTest, GetMethod200withPercentEncoding) {
   auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
   auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
   ASSERT_TRUE(res);
   ASSERT_TRUE(res);
@@ -4722,9 +4758,10 @@ static void test_raw_request(const std::string &req,
   svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
   svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
     res.set_content("ok", "text/plain");
     res.set_content("ok", "text/plain");
   });
   });
-  svr.Get("/header_field_value_check", [&](const Request &/*req*/, Response &res) {
-    res.set_content("ok", "text/plain");
-  });
+  svr.Get("/header_field_value_check",
+          [&](const Request & /*req*/, Response &res) {
+            res.set_content("ok", "text/plain");
+          });
 
 
   // Server read timeout must be longer than the client read timeout for the
   // Server read timeout must be longer than the client read timeout for the
   // bug to reproduce, probably to force the server to process a request
   // bug to reproduce, probably to force the server to process a request
@@ -7640,7 +7677,7 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
 TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
 TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
   Server svr;
   Server svr;
 
 
-  svr.Get("/test", [&](const Request &/*req*/, Response &res) {
+  svr.Get("/test", [&](const Request & /*req*/, Response &res) {
     EXPECT_EQ(res.status, 400);
     EXPECT_EQ(res.status, 400);
   });
   });
 
 
@@ -7666,11 +7703,12 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
 
 
   Server svr;
   Server svr;
 
 
-  svr.set_expect_100_continue_handler([](const Request &/*req*/, Response &res) {
-    res.status = StatusCode::Unauthorized_401;
-    res.set_content(reject, "text/plain");
-    return res.status;
-  });
+  svr.set_expect_100_continue_handler(
+      [](const Request & /*req*/, Response &res) {
+        res.status = StatusCode::Unauthorized_401;
+        res.set_content(reject, "text/plain");
+        return res.status;
+      });
   svr.Post("/", [&](const Request & /*req*/, Response &res) {
   svr.Post("/", [&](const Request & /*req*/, Response &res) {
     res.set_content(accept, "text/plain");
     res.set_content(accept, "text/plain");
   });
   });
@@ -7745,7 +7783,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
 
 
     {
     {
       auto dl = curl_off_t{};
       auto dl = curl_off_t{};
-      const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
+      const auto res =
+          curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
       ASSERT_EQ(res, CURLE_OK);
       ASSERT_EQ(res, CURLE_OK);
       ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
       ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
     }
     }