Skip to content

Commit eb12664

Browse files
committed
Add zstd support
1 parent 71ba7e7 commit eb12664

File tree

3 files changed

+397
-4
lines changed

3 files changed

+397
-4
lines changed

CMakeLists.txt

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
55
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
66
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
7+
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
78
* HTTPLIB_REQUIRE_OPENSSL (default off)
89
* HTTPLIB_REQUIRE_ZLIB (default off)
910
* HTTPLIB_REQUIRE_BROTLI (default off)
@@ -45,6 +46,7 @@
4546
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
4647
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
4748
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
49+
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
4850
* HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled.
4951
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
5052
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
@@ -101,6 +103,8 @@ option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
101103
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
102104
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
103105
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
106+
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
107+
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
104108
# Defaults to static library
105109
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
106110
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
@@ -153,6 +157,17 @@ elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
153157
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
154158
endif()
155159

160+
if(HTTPLIB_REQUIRE_ZSTD)
161+
find_package(ZSTD REQUIRED)
162+
set(HTTPLIB_IS_USING_ZSTD TRUE)
163+
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
164+
find_package(ZSTD QUIET)
165+
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
166+
if(TARGET ZSTD::ZSTD)
167+
set(HTTPLIB_IS_USING_ZSTD TRUE)
168+
endif()
169+
endif()
170+
156171
# Used for default, common dirs that the end-user can change (if needed)
157172
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
158173
include(GNUInstallDirs)
@@ -227,6 +242,7 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
227242
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
228243
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::decoder>
229244
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:ZLIB::ZLIB>
245+
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:ZSTD::ZSTD>
230246
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::SSL>
231247
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
232248
)
@@ -236,6 +252,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
236252
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
237253
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
238254
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
255+
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
239256
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
240257
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
241258
)

httplib.h

+130-1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ using socket_t = int;
313313
#include <brotli/encode.h>
314314
#endif
315315

316+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
317+
#include <zstd.h>
318+
#endif
319+
316320
/*
317321
* Declaration
318322
*/
@@ -2444,7 +2448,7 @@ ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
24442448

24452449
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
24462450

2447-
enum class EncodingType { None = 0, Gzip, Brotli };
2451+
enum class EncodingType { None = 0, Gzip, Brotli, Zstd};
24482452

24492453
EncodingType encoding_type(const Request &req, const Response &res);
24502454

@@ -2557,6 +2561,34 @@ class brotli_decompressor final : public decompressor {
25572561
};
25582562
#endif
25592563

2564+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
2565+
class zstd_compressor : public compressor {
2566+
public:
2567+
zstd_compressor();
2568+
~zstd_compressor();
2569+
2570+
bool compress(const char *data, size_t data_length, bool last,
2571+
Callback callback) override;
2572+
2573+
private:
2574+
ZSTD_CCtx *ctx_ = nullptr;
2575+
};
2576+
2577+
class zstd_decompressor : public decompressor {
2578+
public:
2579+
zstd_decompressor();
2580+
~zstd_decompressor();
2581+
2582+
bool is_valid() const override;
2583+
2584+
bool decompress(const char *data, size_t data_length,
2585+
Callback callback) override;
2586+
2587+
private:
2588+
ZSTD_DCtx *ctx_ = nullptr;
2589+
};
2590+
#endif
2591+
25602592
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
25612593
// to store data. The call can set memory on stack for performance.
25622594
class stream_line_reader {
@@ -3948,6 +3980,12 @@ inline EncodingType encoding_type(const Request &req, const Response &res) {
39483980
if (ret) { return EncodingType::Gzip; }
39493981
#endif
39503982

3983+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3984+
// TODO: 'Accept-Encoding' has zstd, not zstd;q=0
3985+
ret = s.find("zstd") != std::string::npos;
3986+
if (ret) { return EncodingType::Zstd; }
3987+
#endif
3988+
39513989
return EncodingType::None;
39523990
}
39533991

@@ -4156,6 +4194,75 @@ inline bool brotli_decompressor::decompress(const char *data,
41564194
}
41574195
#endif
41584196

4197+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4198+
inline zstd_compressor::zstd_compressor() {
4199+
ctx_ = ZSTD_createCCtx();
4200+
ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
4201+
}
4202+
4203+
inline zstd_compressor::~zstd_compressor() {
4204+
ZSTD_freeCCtx(ctx_);
4205+
}
4206+
4207+
inline bool zstd_compressor::compress(const char *data, size_t data_length,
4208+
bool last, Callback callback) {
4209+
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4210+
4211+
ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
4212+
ZSTD_inBuffer input = { data, data_length, 0 };
4213+
4214+
bool finished;
4215+
do {
4216+
ZSTD_outBuffer output = { buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0 };
4217+
size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
4218+
4219+
if (ZSTD_isError(remaining)) {
4220+
return false;
4221+
}
4222+
4223+
if (!callback(buff.data(), output.pos)) {
4224+
return false;
4225+
}
4226+
4227+
finished = last ? (remaining == 0) : (input.pos == input.size);
4228+
4229+
} while(!finished);
4230+
4231+
return true;
4232+
}
4233+
4234+
inline zstd_decompressor::zstd_decompressor() {
4235+
ctx_ = ZSTD_createDCtx();
4236+
}
4237+
4238+
inline zstd_decompressor::~zstd_decompressor() {
4239+
ZSTD_freeDCtx(ctx_);
4240+
}
4241+
4242+
inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
4243+
4244+
inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
4245+
Callback callback) {
4246+
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4247+
ZSTD_inBuffer input = { data, data_length, 0 };
4248+
4249+
while (input.pos < input.size) {
4250+
ZSTD_outBuffer output = { buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0 };
4251+
size_t const remaining = ZSTD_decompressStream(ctx_, &output , &input);
4252+
4253+
if (ZSTD_isError(remaining)) {
4254+
return false;
4255+
}
4256+
4257+
if (!callback(buff.data(), output.pos)) {
4258+
return false;
4259+
}
4260+
}
4261+
4262+
return true;
4263+
}
4264+
#endif
4265+
41594266
inline bool has_header(const Headers &headers, const std::string &key) {
41604267
return headers.find(key) != headers.end();
41614268
}
@@ -4393,6 +4500,13 @@ bool prepare_content_receiver(T &x, int &status,
43934500
#else
43944501
status = StatusCode::UnsupportedMediaType_415;
43954502
return false;
4503+
#endif
4504+
} else if (encoding == "zstd") {
4505+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4506+
decompressor = detail::make_unique<zstd_decompressor>();
4507+
#else
4508+
status = StatusCode::UnsupportedMediaType_415;
4509+
return false;
43964510
#endif
43974511
}
43984512

@@ -6633,6 +6747,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
66336747
} else if (type == detail::EncodingType::Brotli) {
66346748
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
66356749
compressor = detail::make_unique<detail::brotli_compressor>();
6750+
#endif
6751+
} else if (type == detail::EncodingType::Zstd) {
6752+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6753+
compressor = detail::make_unique<detail::zstd_compressor>();
66366754
#endif
66376755
} else {
66386756
compressor = detail::make_unique<detail::nocompressor>();
@@ -7048,6 +7166,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70487166
res.set_header("Content-Encoding", "gzip");
70497167
} else if (type == detail::EncodingType::Brotli) {
70507168
res.set_header("Content-Encoding", "br");
7169+
} else if (type == detail::EncodingType::Zstd) {
7170+
res.set_header("Content-Encoding", "zstd");
70517171
}
70527172
}
70537173
}
@@ -7087,6 +7207,11 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70877207
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
70887208
compressor = detail::make_unique<detail::brotli_compressor>();
70897209
content_encoding = "br";
7210+
#endif
7211+
} else if (type == detail::EncodingType::Zstd) {
7212+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7213+
compressor = detail::make_unique<detail::zstd_compressor>();
7214+
content_encoding = "zstd";
70907215
#endif
70917216
}
70927217

@@ -7811,6 +7936,10 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
78117936
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
78127937
if (!accept_encoding.empty()) { accept_encoding += ", "; }
78137938
accept_encoding += "gzip, deflate";
7939+
#endif
7940+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7941+
if (!accept_encoding.empty()) { accept_encoding += ", "; }
7942+
accept_encoding += "zstd";
78147943
#endif
78157944
req.set_header("Accept-Encoding", accept_encoding);
78167945
}

0 commit comments

Comments
 (0)