Skip to content

Add zstd support #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }}
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
libzstd-dev${{ matrix.config.arch_suffix }}
- name: build and run tests
run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
- name: run fuzz test target
Expand Down Expand Up @@ -126,7 +127,7 @@ jobs:
- name: Setup msbuild on windows
uses: microsoft/setup-msbuild@v2
- name: Install vcpkg dependencies
run: vcpkg install gtest curl zlib brotli
run: vcpkg install gtest curl zlib brotli zstd
- name: Install OpenSSL
if: ${{ matrix.config.with_ssl }}
run: choco install openssl
Expand All @@ -139,6 +140,7 @@ jobs:
-DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
-DHTTPLIB_REQUIRE_ZLIB=ON
-DHTTPLIB_REQUIRE_BROTLI=ON
-DHTTPLIB_REQUIRE_ZSTD=ON
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
- name: Build ${{ matrix.config.name }}
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
* HTTPLIB_REQUIRE_OPENSSL (default off)
* HTTPLIB_REQUIRE_ZLIB (default off)
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_REQUIRE_ZSTD (default off)
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on)
Expand Down Expand Up @@ -45,6 +47,7 @@
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
* HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled.
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
Expand Down Expand Up @@ -101,6 +104,8 @@ option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
# Defaults to static library
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
Expand Down Expand Up @@ -153,6 +158,14 @@ elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
endif()

if(HTTPLIB_REQUIRE_ZSTD)
find_package(zstd REQUIRED)
set(HTTPLIB_IS_USING_ZSTD TRUE)
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
find_package(zstd QUIET)
set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
endif()

# Used for default, common dirs that the end-user can change (if needed)
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
include(GNUInstallDirs)
Expand Down Expand Up @@ -227,6 +240,7 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::decoder>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:ZLIB::ZLIB>
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:zstd::libzstd>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::SSL>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
)
Expand All @@ -236,6 +250,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
)
Expand Down
5 changes: 5 additions & 0 deletions cmake/httplibConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ if(@HTTPLIB_IS_USING_BROTLI@)
find_dependency(Brotli COMPONENTS common encoder decoder)
endif()

if(@HTTPLIB_IS_USING_ZSTD@)
find_dependency(zstd)
endif()

# Mildly useful for end-users
# Not really recommended to be used though
set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
Expand All @@ -46,6 +50,7 @@ set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httpl
set(httplib_OpenSSL_FOUND @HTTPLIB_IS_USING_OPENSSL@)
set(httplib_ZLIB_FOUND @HTTPLIB_IS_USING_ZLIB@)
set(httplib_Brotli_FOUND @HTTPLIB_IS_USING_BROTLI@)
set(httplib_zstd_FOUND @HTTPLIB_IS_USING_ZSTD@)

check_required_components(httplib)

Expand Down
119 changes: 117 additions & 2 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ using socket_t = int;
#include <brotli/encode.h>
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
#include <zstd.h>
#endif

/*
* Declaration
*/
Expand Down Expand Up @@ -2445,7 +2449,7 @@ ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);

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

enum class EncodingType { None = 0, Gzip, Brotli };
enum class EncodingType { None = 0, Gzip, Brotli, Zstd };

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

Expand Down Expand Up @@ -2558,6 +2562,34 @@ class brotli_decompressor final : public decompressor {
};
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
class zstd_compressor : public compressor {
public:
zstd_compressor();
~zstd_compressor();

bool compress(const char *data, size_t data_length, bool last,
Callback callback) override;

private:
ZSTD_CCtx *ctx_ = nullptr;
};

class zstd_decompressor : public decompressor {
public:
zstd_decompressor();
~zstd_decompressor();

bool is_valid() const override;

bool decompress(const char *data, size_t data_length,
Callback callback) override;

private:
ZSTD_DCtx *ctx_ = nullptr;
};
#endif

// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
// to store data. The call can set memory on stack for performance.
class stream_line_reader {
Expand Down Expand Up @@ -3949,6 +3981,12 @@ inline EncodingType encoding_type(const Request &req, const Response &res) {
if (ret) { return EncodingType::Gzip; }
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
// TODO: 'Accept-Encoding' has zstd, not zstd;q=0
ret = s.find("zstd") != std::string::npos;
if (ret) { return EncodingType::Zstd; }
#endif

return EncodingType::None;
}

Expand Down Expand Up @@ -4157,6 +4195,61 @@ inline bool brotli_decompressor::decompress(const char *data,
}
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
inline zstd_compressor::zstd_compressor() {
ctx_ = ZSTD_createCCtx();
ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
}

inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }

inline bool zstd_compressor::compress(const char *data, size_t data_length,
bool last, Callback callback) {
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};

ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
ZSTD_inBuffer input = {data, data_length, 0};

bool finished;
do {
ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);

if (ZSTD_isError(remaining)) { return false; }

if (!callback(buff.data(), output.pos)) { return false; }

finished = last ? (remaining == 0) : (input.pos == input.size);

} while (!finished);

return true;
}

inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }

inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }

inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }

inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
Callback callback) {
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
ZSTD_inBuffer input = {data, data_length, 0};

while (input.pos < input.size) {
ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);

if (ZSTD_isError(remaining)) { return false; }

if (!callback(buff.data(), output.pos)) { return false; }
}

return true;
}
#endif

inline bool has_header(const Headers &headers, const std::string &key) {
return headers.find(key) != headers.end();
}
Expand Down Expand Up @@ -4397,6 +4490,13 @@ bool prepare_content_receiver(T &x, int &status,
#else
status = StatusCode::UnsupportedMediaType_415;
return false;
#endif
} else if (encoding == "zstd") {
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
decompressor = detail::make_unique<zstd_decompressor>();
#else
status = StatusCode::UnsupportedMediaType_415;
return false;
#endif
}

Expand Down Expand Up @@ -6634,6 +6734,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
} else if (type == detail::EncodingType::Brotli) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
#endif
} else if (type == detail::EncodingType::Zstd) {
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
compressor = detail::make_unique<detail::zstd_compressor>();
#endif
} else {
compressor = detail::make_unique<detail::nocompressor>();
Expand Down Expand Up @@ -7049,6 +7153,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
res.set_header("Content-Encoding", "gzip");
} else if (type == detail::EncodingType::Brotli) {
res.set_header("Content-Encoding", "br");
} else if (type == detail::EncodingType::Zstd) {
res.set_header("Content-Encoding", "zstd");
}
}
}
Expand Down Expand Up @@ -7088,6 +7194,11 @@ inline void Server::apply_ranges(const Request &req, Response &res,
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
content_encoding = "br";
#endif
} else if (type == detail::EncodingType::Zstd) {
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
compressor = detail::make_unique<detail::zstd_compressor>();
content_encoding = "zstd";
#endif
}

Expand Down Expand Up @@ -7812,6 +7923,10 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (!accept_encoding.empty()) { accept_encoding += ", "; }
accept_encoding += "gzip, deflate";
#endif
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
if (!accept_encoding.empty()) { accept_encoding += ", "; }
accept_encoding += "zstd";
#endif
req.set_header("Accept-Encoding", accept_encoding);
}
Expand Down Expand Up @@ -10377,4 +10492,4 @@ inline SSL_CTX *Client::ssl_context() const {

} // namespace httplib

#endif // CPPHTTPLIB_HTTPLIB_H
#endif // CPPHTTPLIB_HTTPLIB_H
5 changes: 4 additions & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec

TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread -lcurl
ZSTD_DIR = $(PREFIX)/opt/zstd
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd

TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) -pthread -lcurl

# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
Expand Down
Loading
Loading