diff --git a/README.md b/README.md index b988791..a60ee22 100644 --- a/README.md +++ b/README.md @@ -989,7 +989,7 @@ httplib::UploadFormDataItems items = { auto res = cli.Post("/multipart", items); ``` -To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is read and sent in chunks with a correct `Content-Length` header. +To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is sent with chunked transfer encoding. ```cpp httplib::FormDataProviderItems providers = { @@ -1000,6 +1000,15 @@ httplib::FormDataProviderItems providers = { auto res = cli.Post("/upload", {}, {}, providers); ``` +### POST with a file body + +To POST a file as a raw binary body with `Content-Length`, use `make_file_body`. + +```cpp +auto [size, provider] = httplib::make_file_body("/path/to/data.bin"); +auto res = cli.Post("/upload", size, provider, "application/octet-stream"); +``` + ### PUT ```c++ diff --git a/httplib.h b/httplib.h index e4d461a..9f4ea3c 100644 --- a/httplib.h +++ b/httplib.h @@ -1042,6 +1042,32 @@ make_file_provider(const std::string &name, const std::string &filepath, return fdp; } +inline std::pair +make_file_body(const std::string &filepath) { + std::ifstream f(filepath, std::ios::binary | std::ios::ate); + if (!f) { return {0, ContentProvider{}}; } + auto size = static_cast(f.tellg()); + + ContentProvider provider = [filepath](size_t offset, size_t length, + DataSink &sink) -> bool { + std::ifstream f(filepath, std::ios::binary); + if (!f) { return false; } + f.seekg(static_cast(offset)); + if (!f.good()) { return false; } + char buf[8192]; + while (length > 0) { + auto to_read = (std::min)(sizeof(buf), length); + f.read(buf, static_cast(to_read)); + auto n = static_cast(f.gcount()); + if (n == 0) { break; } + if (!sink.write(buf, n)) { return false; } + length -= n; + } + return true; + }; + return {size, std::move(provider)}; +} + using ContentReceiverWithProgress = std::function; diff --git a/test/test.cc b/test/test.cc index 4e75f60..f25ca3d 100644 --- a/test/test.cc +++ b/test/test.cc @@ -11821,6 +11821,46 @@ TEST(MultipartFormDataTest, MakeFileProvider) { EXPECT_EQ(StatusCode::OK_200, res->status); } +TEST(MakeFileBodyTest, Basic) { + const std::string file_content(4096, 'Z'); + const std::string tmp_path = "/tmp/httplib_test_make_file_body.bin"; + { + std::ofstream ofs(tmp_path, std::ios::binary); + ofs.write(file_content.data(), + static_cast(file_content.size())); + } + + auto handled = false; + + Server svr; + svr.Post("/upload", [&](const Request &req, Response &res) { + EXPECT_EQ(file_content, req.body); + handled = true; + res.status = StatusCode::OK_200; + }); + + auto port = svr.bind_to_any_port(HOST); + auto t = thread([&] { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + ASSERT_TRUE(handled); + std::remove(tmp_path.c_str()); + }); + + svr.wait_until_ready(); + + auto fb = make_file_body(tmp_path); + ASSERT_GT(fb.first, 0u); + + Client cli(HOST, port); + auto res = + cli.Post("/upload", fb.first, fb.second, "application/octet-stream"); + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::OK_200, res->status); +} + TEST(TaskQueueTest, IncreaseAtomicInteger) { static constexpr unsigned int number_of_tasks{1000000}; std::atomic_uint count{0};