Skip to content

Commit 670d292

Browse files
committed
Add zstd support
1 parent 0bda3a7 commit 670d292

File tree

3 files changed

+397
-4
lines changed

3 files changed

+397
-4
lines changed

CMakeLists.txt

Lines changed: 17 additions & 0 deletions
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

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ using socket_t = int;
312312
#include <brotli/encode.h>
313313
#endif
314314

315+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
316+
#include <zstd.h>
317+
#endif
318+
315319
/*
316320
* Declaration
317321
*/
@@ -2445,7 +2449,7 @@ ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
24452449

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

2448-
enum class EncodingType { None = 0, Gzip, Brotli };
2452+
enum class EncodingType { None = 0, Gzip, Brotli, Zstd};
24492453

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

@@ -2558,6 +2562,34 @@ class brotli_decompressor final : public decompressor {
25582562
};
25592563
#endif
25602564

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

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

@@ -4157,6 +4195,75 @@ inline bool brotli_decompressor::decompress(const char *data,
41574195
}
41584196
#endif
41594197

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

@@ -6634,6 +6748,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
66346748
} else if (type == detail::EncodingType::Brotli) {
66356749
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
66366750
compressor = detail::make_unique<detail::brotli_compressor>();
6751+
#endif
6752+
} else if (type == detail::EncodingType::Zstd) {
6753+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6754+
compressor = detail::make_unique<detail::zstd_compressor>();
66376755
#endif
66386756
} else {
66396757
compressor = detail::make_unique<detail::nocompressor>();
@@ -7049,6 +7167,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70497167
res.set_header("Content-Encoding", "gzip");
70507168
} else if (type == detail::EncodingType::Brotli) {
70517169
res.set_header("Content-Encoding", "br");
7170+
} else if (type == detail::EncodingType::Zstd) {
7171+
res.set_header("Content-Encoding", "zstd");
70527172
}
70537173
}
70547174
}
@@ -7088,6 +7208,11 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70887208
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
70897209
compressor = detail::make_unique<detail::brotli_compressor>();
70907210
content_encoding = "br";
7211+
#endif
7212+
} else if (type == detail::EncodingType::Zstd) {
7213+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7214+
compressor = detail::make_unique<detail::zstd_compressor>();
7215+
content_encoding = "zstd";
70917216
#endif
70927217
}
70937218

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

0 commit comments

Comments
 (0)