Skip to content

Commit c269370

Browse files
committed
Add zstd support
1 parent 85b5cdd commit c269370

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
@@ -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
*/
@@ -2441,7 +2445,7 @@ ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
24412445

24422446
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
24432447

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

24462450
EncodingType encoding_type(const Request &req, const Response &res);
24472451

@@ -2554,6 +2558,34 @@ class brotli_decompressor final : public decompressor {
25542558
};
25552559
#endif
25562560

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

3971+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3972+
// TODO: 'Accept-Encoding' has zstd, not zstd;q=0
3973+
ret = s.find("zstd") != std::string::npos;
3974+
if (ret) { return EncodingType::Zstd; }
3975+
#endif
3976+
39393977
return EncodingType::None;
39403978
}
39413979

@@ -4144,6 +4182,75 @@ inline bool brotli_decompressor::decompress(const char *data,
41444182
}
41454183
#endif
41464184

4185+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4186+
inline zstd_compressor::zstd_compressor() {
4187+
ctx_ = ZSTD_createCCtx();
4188+
ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
4189+
}
4190+
4191+
inline zstd_compressor::~zstd_compressor() {
4192+
ZSTD_freeCCtx(ctx_);
4193+
}
4194+
4195+
inline bool zstd_compressor::compress(const char *data, size_t data_length,
4196+
bool last, Callback callback) {
4197+
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4198+
4199+
ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
4200+
ZSTD_inBuffer input = { data, data_length, 0 };
4201+
4202+
bool finished;
4203+
do {
4204+
ZSTD_outBuffer output = { buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0 };
4205+
size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
4206+
4207+
if (ZSTD_isError(remaining)) {
4208+
return false;
4209+
}
4210+
4211+
if (!callback(buff.data(), output.pos)) {
4212+
return false;
4213+
}
4214+
4215+
finished = last ? (remaining == 0) : (input.pos == input.size);
4216+
4217+
} while(!finished);
4218+
4219+
return true;
4220+
}
4221+
4222+
inline zstd_decompressor::zstd_decompressor() {
4223+
ctx_ = ZSTD_createDCtx();
4224+
}
4225+
4226+
inline zstd_decompressor::~zstd_decompressor() {
4227+
ZSTD_freeDCtx(ctx_);
4228+
}
4229+
4230+
inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
4231+
4232+
inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
4233+
Callback callback) {
4234+
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4235+
ZSTD_inBuffer input = { data, data_length, 0 };
4236+
4237+
while (input.pos < input.size) {
4238+
ZSTD_outBuffer output = { buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0 };
4239+
size_t const remaining = ZSTD_decompressStream(ctx_, &output , &input);
4240+
4241+
if (ZSTD_isError(remaining)) {
4242+
return false;
4243+
}
4244+
4245+
if (!callback(buff.data(), output.pos)) {
4246+
return false;
4247+
}
4248+
}
4249+
4250+
return true;
4251+
}
4252+
#endif
4253+
41474254
inline bool has_header(const Headers &headers, const std::string &key) {
41484255
return headers.find(key) != headers.end();
41494256
}
@@ -4381,6 +4488,13 @@ bool prepare_content_receiver(T &x, int &status,
43814488
#else
43824489
status = StatusCode::UnsupportedMediaType_415;
43834490
return false;
4491+
#endif
4492+
} else if (encoding == "zstd") {
4493+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4494+
decompressor = detail::make_unique<zstd_decompressor>();
4495+
#else
4496+
status = StatusCode::UnsupportedMediaType_415;
4497+
return false;
43844498
#endif
43854499
}
43864500

@@ -6621,6 +6735,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
66216735
} else if (type == detail::EncodingType::Brotli) {
66226736
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
66236737
compressor = detail::make_unique<detail::brotli_compressor>();
6738+
#endif
6739+
} else if (type == detail::EncodingType::Zstd) {
6740+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6741+
compressor = detail::make_unique<detail::zstd_compressor>();
66246742
#endif
66256743
} else {
66266744
compressor = detail::make_unique<detail::nocompressor>();
@@ -7036,6 +7154,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70367154
res.set_header("Content-Encoding", "gzip");
70377155
} else if (type == detail::EncodingType::Brotli) {
70387156
res.set_header("Content-Encoding", "br");
7157+
} else if (type == detail::EncodingType::Zstd) {
7158+
res.set_header("Content-Encoding", "zstd");
70397159
}
70407160
}
70417161
}
@@ -7075,6 +7195,11 @@ inline void Server::apply_ranges(const Request &req, Response &res,
70757195
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
70767196
compressor = detail::make_unique<detail::brotli_compressor>();
70777197
content_encoding = "br";
7198+
#endif
7199+
} else if (type == detail::EncodingType::Zstd) {
7200+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7201+
compressor = detail::make_unique<detail::zstd_compressor>();
7202+
content_encoding = "zstd";
70787203
#endif
70797204
}
70807205

@@ -7799,6 +7924,10 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
77997924
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
78007925
if (!accept_encoding.empty()) { accept_encoding += ", "; }
78017926
accept_encoding += "gzip, deflate";
7927+
#endif
7928+
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7929+
if (!accept_encoding.empty()) { accept_encoding += ", "; }
7930+
accept_encoding += "zstd";
78027931
#endif
78037932
req.set_header("Accept-Encoding", accept_encoding);
78047933
}

0 commit comments

Comments
 (0)