Add Brotli compression support and corresponding tests

This commit is contained in:
yhirose committed 2026-03-14 21:42:48 -04:00
1 parent 4978f26f86
commit ec1ffbc27d
2 files changed
+80 -24

No files matched your search

+33 -24
View File
@@ -6862,6 +6862,21 @@ create_decompressor(const std::string &encoding) {
return decompressor; return decompressor;
} }
// Returns the best available compressor and its Content-Encoding name.
// Priority: Brotli > Gzip > Zstd (matches server-side preference).
inline std::pair<std::unique_ptr<compressor>, const char *>
create_compressor() {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
return {detail::make_unique<brotli_compressor>(), "br"};
#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
return {detail::make_unique<gzip_compressor>(), "gzip"};
#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
return {detail::make_unique<zstd_compressor>(), "zstd"};
#else
return {nullptr, nullptr};
#endif
}
inline bool is_prohibited_header_name(const std::string &name) { inline bool is_prohibited_header_name(const std::string &name) {
using udl::operator""_t; using udl::operator""_t;
@@ -13015,14 +13030,9 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
auto is_shutting_down = []() { return false; }; auto is_shutting_down = []() { return false; };
if (req.is_chunked_content_provider_) { if (req.is_chunked_content_provider_) {
// TODO: Brotli support auto compressor = compress_ ? detail::create_compressor().first
std::unique_ptr<detail::compressor> compressor; : std::unique_ptr<detail::compressor>();
#ifdef CPPHTTPLIB_ZLIB_SUPPORT if (!compressor) {
if (compress_) {
compressor = detail::make_unique<detail::gzip_compressor>();
} else
#endif
{
compressor = detail::make_unique<detail::nocompressor>(); compressor = detail::make_unique<detail::nocompressor>();
} }
@@ -13253,14 +13263,15 @@ ClientImpl::send_with_content_provider_and_receiver(
Error &error) { Error &error) {
if (!content_type.empty()) { req.set_header("Content-Type", content_type); } if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
#ifdef CPPHTTPLIB_ZLIB_SUPPORT auto enc = compress_
if (compress_) { req.set_header("Content-Encoding", "gzip"); } ? detail::create_compressor()
#endif : std::pair<std::unique_ptr<detail::compressor>, const char *>(
nullptr, nullptr);
#ifdef CPPHTTPLIB_ZLIB_SUPPORT if (enc.second) { req.set_header("Content-Encoding", enc.second); }
if (compress_ && !content_provider_without_length) {
// TODO: Brotli support if (enc.first && !content_provider_without_length) {
detail::gzip_compressor compressor; auto &compressor = enc.first;
if (content_provider) { if (content_provider) {
auto ok = true; auto ok = true;
@@ -13271,7 +13282,7 @@ ClientImpl::send_with_content_provider_and_receiver(
if (ok) { if (ok) {
auto last = offset + data_len == content_length; auto last = offset + data_len == content_length;
auto ret = compressor.compress( auto ret = compressor->compress(
data, data_len, last, data, data_len, last,
[&](const char *compressed_data, size_t compressed_data_len) { [&](const char *compressed_data, size_t compressed_data_len) {
req.body.append(compressed_data, compressed_data_len); req.body.append(compressed_data, compressed_data_len);
@@ -13295,19 +13306,17 @@ ClientImpl::send_with_content_provider_and_receiver(
} }
} }
} else { } else {
if (!compressor.compress(body, content_length, true, if (!compressor->compress(body, content_length, true,
[&](const char *data, size_t data_len) { [&](const char *data, size_t data_len) {
req.body.append(data, data_len); req.body.append(data, data_len);
return true; return true;
})) { })) {
error = Error::Compression; error = Error::Compression;
output_error_log(error, &req); output_error_log(error, &req);
return nullptr; return nullptr;
} }
} }
} else } else {
#endif
{
if (content_provider) { if (content_provider) {
req.content_length_ = content_length; req.content_length_ = content_length;
req.content_provider_ = std::move(content_provider); req.content_provider_ = std::move(content_provider);
+47
View File
@@ -5745,7 +5745,13 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
// depending on the zlib library. // depending on the zlib library.
EXPECT_LT(res.get_request_header_value_u64("Content-Length"), EXPECT_LT(res.get_request_header_value_u64("Content-Length"),
static_cast<uint64_t>(10 * 1024 * 1024)); static_cast<uint64_t>(10 * 1024 * 1024));
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
EXPECT_EQ("br", res.get_request_header_value("Content-Encoding"));
#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding")); EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
EXPECT_EQ("zstd", res.get_request_header_value("Content-Encoding"));
#endif
} }
TEST_F(ServerTest, PutContentWithDeflate) { TEST_F(ServerTest, PutContentWithDeflate) {
@@ -5955,6 +5961,47 @@ TEST_F(ServerTest, GetStreamedChunkedWithBrotli2) {
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(std::string("123456789"), res->body); EXPECT_EQ(std::string("123456789"), res->body);
} }
TEST_F(ServerTest, PutWithContentProviderWithBrotli) {
cli_.set_compress(true);
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.os << "PUT";
return true;
},
"text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithBrotli) {
cli_.set_compress(true);
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
sink.os << "PUT";
sink.done();
return true;
},
"text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PutLargeFileWithBrotli) {
cli_.set_compress(true);
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
EXPECT_EQ("br", res.get_request_header_value("Content-Encoding"));
}
#endif #endif
TEST_F(ServerTest, Patch) { TEST_F(ServerTest, Patch) {