mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 342c3ab293 | |||
| 6cce7951fc | |||
| e9058e5639 | |||
| 2538a85486 | |||
| 8c501022b3 | |||
| 9f5db2d1aa | |||
| 12540fe8d3 | |||
| 29a06f852a | |||
| 0e9cfd9f49 | |||
| 90da199aba | |||
| 366d073490 | |||
| 9ca1fa8b18 | |||
| 15c4106a36 | |||
| 72ce293fed | |||
| b476b55771 | |||
| 0db9d21eb0 | |||
| 5ddaf949d0 | |||
| 457a5a7501 | |||
| 2ce080c2cb |
+1
-1
@@ -7,7 +7,7 @@ example/simplecli
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
example/sse
|
||||
example/sse*
|
||||
example/upload
|
||||
example/*.pem
|
||||
test/test
|
||||
|
||||
+57
-27
@@ -1,10 +1,15 @@
|
||||
#[[
|
||||
Build options:
|
||||
* BUILD_SHARED_LIBS (default off) builds as a static library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
@@ -36,10 +41,11 @@
|
||||
* HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h).
|
||||
* 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_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).
|
||||
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
|
||||
* HTTPLIB_VERSION - the project's version string.
|
||||
* httplib_VERSION or HTTPLIB_VERSION - the project's version string.
|
||||
* HTTPLIB_FOUND - a bool for if the target was found.
|
||||
|
||||
Want to use precompiled headers (Cmake feature since v3.16)?
|
||||
@@ -86,9 +92,16 @@ option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build.
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silenty continue if not found.
|
||||
# Make these options so their automatic use can be specifically disabled (as needed)
|
||||
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable compression support." ON)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
|
||||
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 compression 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)
|
||||
@@ -105,11 +118,33 @@ if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(Brotli_FOUND)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
|
||||
@@ -181,33 +216,25 @@ target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
# Always require threads
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
Threads::Threads
|
||||
# Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered.
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
$<$<PLATFORM_ID:Windows>:cryptui>
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
|
||||
$<$<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_OPENSSL}>:OpenSSL::SSL>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
|
||||
)
|
||||
|
||||
# We check for the target when using IF_AVAILABLE since it's possible we didn't find it.
|
||||
if(HTTPLIB_USE_OPENSSL_IF_AVAILABLE AND TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto OR HTTPLIB_REQUIRE_OPENSSL)
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
OpenSSL::SSL OpenSSL::Crypto
|
||||
)
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
|
||||
# We check for the target when using IF_AVAILABLE since it's possible we didn't find it.
|
||||
if(HTTPLIB_USE_ZLIB_IF_AVAILABLE AND TARGET ZLIB::ZLIB OR HTTPLIB_REQUIRE_ZLIB)
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
ZLIB::ZLIB
|
||||
)
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
CPPHTTPLIB_ZLIB_SUPPORT
|
||||
)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_ZLIB FALSE)
|
||||
endif()
|
||||
# Set the definitions to enable optional features
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:"CPPHTTPLIB_BROTLI_SUPPORT">
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:"CPPHTTPLIB_ZLIB_SUPPORT">
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:"CPPHTTPLIB_OPENSSL_SUPPORT">
|
||||
)
|
||||
|
||||
# Cmake's find_package search path is different based on the system
|
||||
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
|
||||
@@ -262,6 +289,9 @@ install(FILES "${_httplib_build_includedir}/httplib.h" DESTINATION ${CMAKE_INSTA
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Install it so it can be used later by the httplibConfig.cmake file.
|
||||
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
|
||||
|
||||
### Server-Sent Events
|
||||
|
||||
Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/sse.cc).
|
||||
Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc).
|
||||
|
||||
### Default thread pool support
|
||||
|
||||
@@ -306,20 +306,6 @@ httplib::Headers headers = {
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
|
||||
### GET with Content Receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert(res->body.empty());
|
||||
```
|
||||
|
||||
### POST
|
||||
|
||||
```c++
|
||||
@@ -390,8 +376,19 @@ cli.set_write_timeout(5, 0); // 5 seconds
|
||||
|
||||
### Receive content with Content receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get(
|
||||
"/stream", Headers(),
|
||||
[&](const Response &response) {
|
||||
@@ -408,6 +405,7 @@ auto res = cli.Get(
|
||||
|
||||
```cpp
|
||||
std::string body = ...;
|
||||
|
||||
auto res = cli_.Post(
|
||||
"/stream", body.size(),
|
||||
[](size_t offset, size_t length, DataSink &sink) {
|
||||
@@ -531,20 +529,27 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(true);
|
||||
```
|
||||
|
||||
Zlib Support
|
||||
------------
|
||||
Compression
|
||||
-----------
|
||||
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked.
|
||||
The server can applie compression to the following MIME type contents:
|
||||
|
||||
The server applies gzip compression to the following MIME type contents:
|
||||
|
||||
* all text types
|
||||
* all text types except text/event-stream
|
||||
* image/svg+xml
|
||||
* application/javascript
|
||||
* application/json
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
### Zlib Support
|
||||
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked.
|
||||
|
||||
### Brotli Support
|
||||
|
||||
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
|
||||
Please see https://github.com/google/brotli for more detail.
|
||||
|
||||
### Compress request body on client
|
||||
|
||||
```c++
|
||||
@@ -556,7 +561,7 @@ res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
|
||||
```c++
|
||||
cli.set_decompress(false);
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res->body; // Compressed data
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# A simple FindBrotli package for Cmake's find_package function.
|
||||
# Note: This find package doesn't have version support, as the version file doesn't seem to be installed on most systems.
|
||||
#
|
||||
# If you want to find the static packages instead of shared (the default), define BROTLI_USE_STATIC_LIBS as TRUE.
|
||||
# The targets will have the same names, but it will use the static libs.
|
||||
#
|
||||
# Valid find_package COMPONENTS names: "decoder", "encoder", and "common"
|
||||
#
|
||||
# Defines the libraries (if found): Brotli::decoder, Brotli::encoder, Brotli::common
|
||||
# and the includes path variable: Brotli_INCLUDE_DIR
|
||||
|
||||
function(brotli_err_msg _err_msg)
|
||||
# If the package is required, throw a fatal error
|
||||
# Otherwise, if not running quietly, we throw a warning
|
||||
if(Brotli_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "${_err_msg}")
|
||||
elseif(NOT Brotli_FIND_QUIETLY)
|
||||
message(WARNING "${_err_msg}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# If they asked for a specific version, warn/fail since we don't support it.
|
||||
if(Brotli_FIND_VERSION)
|
||||
brotli_err_msg("FindBrotli.cmake doesn't have version support!")
|
||||
endif()
|
||||
|
||||
# Since both decoder & encoder require the common lib (I think), force its requirement..
|
||||
# if the user is requiring either of those other libs.
|
||||
if(Brotli_FIND_REQUIRED_decoder OR Brotli_FIND_REQUIRED_encoder)
|
||||
set(Brotli_FIND_REQUIRED_common TRUE)
|
||||
endif()
|
||||
|
||||
# Make PkgConfig optional, since some users (mainly Windows) don't have it.
|
||||
# But it's a lot more clean than manually using find_library.
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
pkg_check_modules(Brotli_common_STATIC QUIET IMPORTED_TARGET libbrotlicommon)
|
||||
pkg_check_modules(Brotli_decoder_STATIC QUIET IMPORTED_TARGET libbrotlidec)
|
||||
pkg_check_modules(Brotli_encoder_STATIC QUIET IMPORTED_TARGET libbrotlienc)
|
||||
else()
|
||||
pkg_check_modules(Brotli_common QUIET IMPORTED_TARGET libbrotlicommon)
|
||||
pkg_check_modules(Brotli_decoder QUIET IMPORTED_TARGET libbrotlidec)
|
||||
pkg_check_modules(Brotli_encoder QUIET IMPORTED_TARGET libbrotlienc)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Only used if the PkgConfig libraries aren't used.
|
||||
find_path(Brotli_INCLUDE_DIR
|
||||
NAMES "brotli/decode.h" "brotli/encode.h"
|
||||
PATH_SUFFIXES "include" "includes"
|
||||
DOC "The path to Brotli's include directory."
|
||||
)
|
||||
|
||||
# Also check if Brotli_decoder was defined, as it can be passed by the end-user
|
||||
if(NOT TARGET PkgConfig::Brotli_decoder AND NOT Brotli_decoder)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_decoder_lib_names
|
||||
"brotlidec-static"
|
||||
"libbrotlidec-static"
|
||||
)
|
||||
else()
|
||||
list(APPEND _brotli_decoder_lib_names
|
||||
"brotlidec"
|
||||
"libbrotlidec"
|
||||
)
|
||||
endif()
|
||||
find_library(Brotli_decoder
|
||||
NAMES ${_brotli_decoder_lib_names}
|
||||
PATH_SUFFIXES
|
||||
"lib"
|
||||
"lib64"
|
||||
"libs"
|
||||
"libs64"
|
||||
"lib/x86_64-linux-gnu"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Also check if Brotli_encoder was defined, as it can be passed by the end-user
|
||||
if(NOT TARGET PkgConfig::Brotli_encoder AND NOT Brotli_encoder)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_encoder_lib_names
|
||||
"brotlienc-static"
|
||||
"libbrotlienc-static"
|
||||
)
|
||||
else()
|
||||
list(APPEND _brotli_encoder_lib_names
|
||||
"brotlienc"
|
||||
"libbrotlienc"
|
||||
)
|
||||
endif()
|
||||
find_library(Brotli_encoder
|
||||
NAMES ${_brotli_encoder_lib_names}
|
||||
PATH_SUFFIXES
|
||||
"lib"
|
||||
"lib64"
|
||||
"libs"
|
||||
"libs64"
|
||||
"lib/x86_64-linux-gnu"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Also check if Brotli_common was defined, as it can be passed by the end-user
|
||||
if(NOT TARGET PkgConfig::Brotli_common AND NOT Brotli_common)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_common_lib_names
|
||||
"brotlicommon-static"
|
||||
"libbrotlicommon-static"
|
||||
)
|
||||
else()
|
||||
list(APPEND _brotli_common_lib_names
|
||||
"brotlicommon"
|
||||
"libbrotlicommon"
|
||||
)
|
||||
endif()
|
||||
find_library(Brotli_common
|
||||
NAMES ${_brotli_common_lib_names}
|
||||
PATH_SUFFIXES
|
||||
"lib"
|
||||
"lib64"
|
||||
"libs"
|
||||
"libs64"
|
||||
"lib/x86_64-linux-gnu"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(_brotli_req_vars "")
|
||||
# Generic loop to either create all the aliases for the end-user, or throw errors/warnings.
|
||||
# Note that the case here needs to match the case we used elsewhere in this file.
|
||||
foreach(_target_name "common" "decoder" "encoder")
|
||||
# The PkgConfig IMPORTED_TARGET has PkgConfig:: prefixed to it.
|
||||
if(TARGET PkgConfig::Brotli_${_target_name})
|
||||
add_library(Brotli::${_target_name} ALIAS PkgConfig::Brotli_${_target_name})
|
||||
|
||||
if(Brotli_FIND_REQUIRED_${_target_name})
|
||||
# The PkgConfig version of the library has a slightly different path to its lib.
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_req_vars "Brotli_${_target_name}_STATIC_LINK_LIBRARIES")
|
||||
else()
|
||||
list(APPEND _brotli_req_vars "Brotli_${_target_name}_LINK_LIBRARIES")
|
||||
endif()
|
||||
endif()
|
||||
# This will only trigger for libraries we found using find_library
|
||||
elseif(Brotli_${_target_name})
|
||||
add_library("Brotli::${_target_name}" UNKNOWN IMPORTED)
|
||||
# Safety-check the includes dir
|
||||
if(NOT Brotli_INCLUDE_DIR)
|
||||
brotli_err_msg("Failed to find Brotli's includes directory. Try manually defining \"Brotli_INCLUDE_DIR\" to Brotli's header path on your system.")
|
||||
endif()
|
||||
# Attach the literal library and include dir to the IMPORTED target for the end-user
|
||||
set_target_properties("Brotli::${_target_name}" PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}"
|
||||
IMPORTED_LOCATION "${Brotli_${_target_name}}"
|
||||
)
|
||||
# Attach the library from find_library to our required vars (if it's required)
|
||||
if(Brotli_FIND_REQUIRED_${_target_name})
|
||||
list(APPEND _brotli_req_vars "Brotli_${_target_name}")
|
||||
endif()
|
||||
# This will only happen if it's a required library but we didn't find it.
|
||||
elseif(Brotli_FIND_REQUIRED_${_target_name})
|
||||
# Only bother with an error/failure if they actually required the lib.
|
||||
brotli_err_msg("Failed to find Brotli's ${_target_name} library. Try manually defining \"Brotli_${_target_name}\" to its path on your system.")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Brotli
|
||||
FOUND_VAR Brotli_FOUND
|
||||
REQUIRED_VARS ${_brotli_req_vars}
|
||||
)
|
||||
|
||||
if(Brotli_FOUND)
|
||||
include(FindPackageMessage)
|
||||
foreach(_lib_name ${_brotli_req_vars})
|
||||
# TODO: remove this if/when The Cmake PkgConfig file fixes the non-quiet message about libbrotlicommon being found.
|
||||
if(${_lib_name} MATCHES "common")
|
||||
# This avoids a duplicate "Found Brotli: /usr/lib/libbrotlicommon.so" type message.
|
||||
continue()
|
||||
endif()
|
||||
# Double-expand the var to get the actual path instead of the variable's name.
|
||||
find_package_message(Brotli "Found Brotli: ${${_lib_name}}"
|
||||
"[${${_lib_name}}][${Brotli_INCLUDE_DIR}]"
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
+20
-12
@@ -1,42 +1,50 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -std=c++14 -I.. -Wall -Wextra -pthread
|
||||
|
||||
OPENSSL_DIR = /usr/local/opt/openssl
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect sse benchmark
|
||||
BROTLI_DIR = /usr/local/opt/brotli
|
||||
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
client : client.cc ../httplib.h Makefile
|
||||
$(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
hello : hello.cc ../httplib.h Makefile
|
||||
$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
simplecli : simplecli.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
simplesvr : simplesvr.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
upload : upload.cc ../httplib.h Makefile
|
||||
$(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
redirect : redirect.cc ../httplib.h Makefile
|
||||
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
sse : sse.cc ../httplib.h Makefile
|
||||
$(CXX) -o sse $(CXXFLAGS) sse.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
ssesvr : ssesvr.cc ../httplib.h Makefile
|
||||
$(CXX) -o ssesvr $(CXXFLAGS) ssesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
ssecli : ssecli.cc ../httplib.h Makefile
|
||||
$(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplecli simplesvr upload redirect sse benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr sselci benchmark *.pem
|
||||
|
||||
@@ -46,7 +46,7 @@ string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||
snprintf(buf, sizeof(buf), "content type: %s\n", file.content_type.c_str());
|
||||
s += buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), "text length: %lu\n", file.content.size());
|
||||
snprintf(buf, sizeof(buf), "text length: %zu\n", file.content.size());
|
||||
s += buf;
|
||||
|
||||
s += "----------------\n";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// ssecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(void) {
|
||||
httplib::Client2("http://localhost:1234")
|
||||
.Get("/event1", [&](const char *data, size_t data_length) {
|
||||
std::cout << string(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -79,20 +79,20 @@ int main(void) {
|
||||
|
||||
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event1..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/event2", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event2..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
thread t([&] {
|
||||
@@ -87,24 +87,6 @@
|
||||
: 0))
|
||||
#endif
|
||||
|
||||
// Prefer gnu::deprecated, otherwise gcc complains if we use
|
||||
// [[deprecated]] together with pedantic.
|
||||
#ifndef CPPHTTPLIB_DEPRECATED
|
||||
#if defined(__has_cpp_attribute)
|
||||
#if __has_cpp_attribute(gnu::deprecated)
|
||||
#define CPPHTTPLIB_DEPRECATED [[gnu::deprecated]]
|
||||
#else
|
||||
#if __has_cpp_attribute(deprecated)
|
||||
#define CPPHTTPLIB_DEPRECATED [[deprecated]]
|
||||
#else
|
||||
#define CPPHTTPLIB_DEPRECATED
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#define CPPHTTPLIB_DEPRECATED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Headers
|
||||
*/
|
||||
@@ -144,6 +126,8 @@ using ssize_t = int;
|
||||
|
||||
#include <io.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <wincrypt.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
|
||||
@@ -212,6 +196,10 @@ using socket_t = int;
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <openssl/applink.c>
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -231,6 +219,11 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
#include <brotli/decode.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Declaration
|
||||
*/
|
||||
@@ -365,6 +358,8 @@ struct Request {
|
||||
|
||||
bool has_header(const char *key) const;
|
||||
std::string get_header_value(const char *key, size_t id = 0) const;
|
||||
template <typename T>
|
||||
T get_header_value(const char *key, size_t id = 0) const;
|
||||
size_t get_header_value_count(const char *key) const;
|
||||
void set_header(const char *key, const char *val);
|
||||
void set_header(const char *key, const std::string &val);
|
||||
@@ -390,6 +385,8 @@ struct Response {
|
||||
|
||||
bool has_header(const char *key) const;
|
||||
std::string get_header_value(const char *key, size_t id = 0) const;
|
||||
template <typename T>
|
||||
T get_header_value(const char *key, size_t id = 0) const;
|
||||
size_t get_header_value_count(const char *key) const;
|
||||
void set_header(const char *key, const char *val);
|
||||
void set_header(const char *key, const std::string &val);
|
||||
@@ -399,11 +396,11 @@ struct Response {
|
||||
void set_content(std::string s, const char *content_type);
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, ContentProvider provider,
|
||||
size_t length, const char *content_type, ContentProvider provider,
|
||||
std::function<void()> resource_releaser = [] {});
|
||||
|
||||
void set_chunked_content_provider(
|
||||
ChunkedContentProvider provider,
|
||||
const char *content_type, ChunkedContentProvider provider,
|
||||
std::function<void()> resource_releaser = [] {});
|
||||
|
||||
Response() = default;
|
||||
@@ -568,8 +565,7 @@ public:
|
||||
Server &Delete(const char *pattern, HandlerWithContentReader handler);
|
||||
Server &Options(const char *pattern, Handler handler);
|
||||
|
||||
CPPHTTPLIB_DEPRECATED bool set_base_dir(const char *dir,
|
||||
const char *mount_point = nullptr);
|
||||
bool set_base_dir(const char *dir, const char *mount_point = nullptr);
|
||||
bool set_mount_point(const char *mount_point, const char *dir);
|
||||
bool remove_mount_point(const char *mount_point);
|
||||
void set_file_extension_and_mimetype_mapping(const char *ext,
|
||||
@@ -815,7 +811,6 @@ public:
|
||||
void set_tcp_nodelay(bool on);
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
|
||||
CPPHTTPLIB_DEPRECATED void set_timeout_sec(time_t timeout_sec);
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
@@ -1598,6 +1593,74 @@ inline bool is_valid_path(const std::string &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string encode_url(const std::string &s) {
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
switch (s[i]) {
|
||||
case ' ': result += "%20"; break;
|
||||
case '+': result += "%2B"; break;
|
||||
case '\r': result += "%0D"; break;
|
||||
case '\n': result += "%0A"; break;
|
||||
case '\'': result += "%27"; break;
|
||||
case ',': result += "%2C"; break;
|
||||
// case ':': result += "%3A"; break; // ok? probably...
|
||||
case ';': result += "%3B"; break;
|
||||
default:
|
||||
auto c = static_cast<uint8_t>(s[i]);
|
||||
if (c >= 0x80) {
|
||||
result += '%';
|
||||
char hex[4];
|
||||
auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
|
||||
assert(len == 2);
|
||||
result.append(hex, static_cast<size_t>(len));
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string decode_url(const std::string &s,
|
||||
bool convert_plus_to_space) {
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
if (s[i] == '%' && i + 1 < s.size()) {
|
||||
if (s[i + 1] == 'u') {
|
||||
int val = 0;
|
||||
if (from_hex_to_i(s, i + 2, 4, val)) {
|
||||
// 4 digits Unicode codes
|
||||
char buff[4];
|
||||
size_t len = to_utf8(val, buff);
|
||||
if (len > 0) { result.append(buff, len); }
|
||||
i += 5; // 'u0000'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
} else {
|
||||
int val = 0;
|
||||
if (from_hex_to_i(s, i + 1, 2, val)) {
|
||||
// 2 digits hex codes
|
||||
result += static_cast<char>(val);
|
||||
i += 2; // '00'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
} else if (convert_plus_to_space && s[i] == '+') {
|
||||
result += ' ';
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void read_file(const std::string &path, std::string &out) {
|
||||
std::ifstream fs(path, std::ios_base::binary);
|
||||
fs.seekg(0, std::ios_base::end);
|
||||
@@ -1614,19 +1677,34 @@ inline std::string file_extension(const std::string &path) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
inline std::pair<int, int> trim(const char *b, const char *e, int left,
|
||||
int right) {
|
||||
while (b + left < e && b[left] == ' ') {
|
||||
left++;
|
||||
}
|
||||
while (right - 1 >= 0 && b[right - 1] == ' ') {
|
||||
right--;
|
||||
}
|
||||
return std::make_pair(left, right);
|
||||
}
|
||||
|
||||
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
|
||||
int i = 0;
|
||||
int beg = 0;
|
||||
|
||||
while (e ? (b + i != e) : (b[i] != '\0')) {
|
||||
if (b[i] == d) {
|
||||
fn(&b[beg], &b[i]);
|
||||
auto r = trim(b, e, beg, i);
|
||||
fn(&b[r.first], &b[r.second]);
|
||||
beg = i + 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i) { fn(&b[beg], &b[i]); }
|
||||
if (i) {
|
||||
auto r = trim(b, e, beg, i);
|
||||
fn(&b[r.first], &b[r.second]);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
|
||||
@@ -2261,99 +2339,194 @@ inline const char *status_message(int status) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
inline bool can_compress(const std::string &content_type) {
|
||||
return !content_type.find("text/") || content_type == "image/svg+xml" ||
|
||||
inline bool can_compress_content_type(const std::string &content_type) {
|
||||
return (!content_type.find("text/") && content_type != "text/event-stream") ||
|
||||
content_type == "image/svg+xml" ||
|
||||
content_type == "application/javascript" ||
|
||||
content_type == "application/json" ||
|
||||
content_type == "application/xml" ||
|
||||
content_type == "application/xhtml+xml";
|
||||
}
|
||||
|
||||
inline bool compress(std::string &content) {
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
enum class EncodingType { None = 0, Gzip, Brotli };
|
||||
|
||||
auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (ret != Z_OK) { return false; }
|
||||
inline EncodingType encoding_type(const Request &req, const Response &res) {
|
||||
auto ret =
|
||||
detail::can_compress_content_type(res.get_header_value("Content-Type"));
|
||||
if (!ret) { return EncodingType::None; }
|
||||
|
||||
strm.avail_in = static_cast<decltype(strm.avail_in)>(content.size());
|
||||
strm.next_in =
|
||||
const_cast<Bytef *>(reinterpret_cast<const Bytef *>(content.data()));
|
||||
const auto &s = req.get_header_value("Accept-Encoding");
|
||||
(void)(s);
|
||||
|
||||
std::string compressed;
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
// TODO: 'Accept-Encoding' has br, not br;q=0
|
||||
ret = s.find("br") != std::string::npos;
|
||||
if (ret) { return EncodingType::Brotli; }
|
||||
#endif
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
do {
|
||||
strm.avail_out = buff.size();
|
||||
strm.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
compressed.append(buff.data(), buff.size() - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
// TODO: 'Accept-Encoding' has gzip, not gzip;q=0
|
||||
ret = s.find("gzip") != std::string::npos;
|
||||
if (ret) { return EncodingType::Gzip; }
|
||||
#endif
|
||||
|
||||
assert(ret == Z_STREAM_END);
|
||||
assert(strm.avail_in == 0);
|
||||
|
||||
content.swap(compressed);
|
||||
|
||||
deflateEnd(&strm);
|
||||
return true;
|
||||
return EncodingType::None;
|
||||
}
|
||||
|
||||
class decompressor {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
class gzip_compressor {
|
||||
public:
|
||||
decompressor() {
|
||||
std::memset(&strm, 0, sizeof(strm));
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
gzip_compressor() {
|
||||
std::memset(&strm_, 0, sizeof(strm_));
|
||||
strm_.zalloc = Z_NULL;
|
||||
strm_.zfree = Z_NULL;
|
||||
strm_.opaque = Z_NULL;
|
||||
|
||||
is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
|
||||
Z_DEFAULT_STRATEGY) == Z_OK;
|
||||
}
|
||||
|
||||
~gzip_compressor() { deflateEnd(&strm_); }
|
||||
|
||||
template <typename T>
|
||||
bool compress(const char *data, size_t data_length, bool last, T callback) {
|
||||
assert(is_valid_);
|
||||
|
||||
auto flush = last ? Z_FINISH : Z_NO_FLUSH;
|
||||
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
int ret = Z_OK;
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
do {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = deflate(&strm_, flush);
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
|
||||
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
|
||||
return false;
|
||||
}
|
||||
} while (strm_.avail_out == 0);
|
||||
|
||||
assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
|
||||
assert(strm_.avail_in == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_valid_ = false;
|
||||
z_stream strm_;
|
||||
};
|
||||
|
||||
class gzip_decompressor {
|
||||
public:
|
||||
gzip_decompressor() {
|
||||
std::memset(&strm_, 0, sizeof(strm_));
|
||||
strm_.zalloc = Z_NULL;
|
||||
strm_.zfree = Z_NULL;
|
||||
strm_.opaque = Z_NULL;
|
||||
|
||||
// 15 is the value of wbits, which should be at the maximum possible value
|
||||
// to ensure that any gzip stream can be decoded. The offset of 32 specifies
|
||||
// that the stream type should be automatically detected either gzip or
|
||||
// deflate.
|
||||
is_valid_ = inflateInit2(&strm, 32 + 15) == Z_OK;
|
||||
is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
|
||||
}
|
||||
|
||||
~decompressor() { inflateEnd(&strm); }
|
||||
~gzip_decompressor() { inflateEnd(&strm_); }
|
||||
|
||||
bool is_valid() const { return is_valid_; }
|
||||
|
||||
template <typename T>
|
||||
bool decompress(const char *data, size_t data_length, T callback) {
|
||||
assert(is_valid_);
|
||||
|
||||
int ret = Z_OK;
|
||||
|
||||
strm.avail_in = static_cast<decltype(strm.avail_in)>(data_length);
|
||||
strm.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
do {
|
||||
strm.avail_out = buff.size();
|
||||
strm.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR: inflateEnd(&strm); return false;
|
||||
case Z_MEM_ERROR: inflateEnd(&strm_); return false;
|
||||
}
|
||||
|
||||
if (!callback(buff.data(), buff.size() - strm.avail_out)) {
|
||||
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
|
||||
return false;
|
||||
}
|
||||
} while (strm.avail_out == 0);
|
||||
} while (strm_.avail_out == 0);
|
||||
|
||||
return ret == Z_OK || ret == Z_STREAM_END;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_valid_;
|
||||
z_stream strm;
|
||||
bool is_valid_ = false;
|
||||
z_stream strm_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
class brotli_decompressor {
|
||||
public:
|
||||
brotli_decompressor() {
|
||||
decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
|
||||
decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
|
||||
: BROTLI_DECODER_RESULT_ERROR;
|
||||
}
|
||||
|
||||
~brotli_decompressor() {
|
||||
if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
|
||||
}
|
||||
|
||||
bool is_valid() const { return decoder_s; }
|
||||
|
||||
template <typename T>
|
||||
bool decompress(const char *data, size_t data_length, T callback) {
|
||||
if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
|
||||
decoder_r == BROTLI_DECODER_RESULT_ERROR)
|
||||
return 0;
|
||||
|
||||
const uint8_t *next_in = (const uint8_t *)data;
|
||||
size_t avail_in = data_length;
|
||||
size_t total_out;
|
||||
|
||||
decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
|
||||
|
||||
while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
|
||||
char output[1024];
|
||||
char *next_out = output;
|
||||
size_t avail_out = sizeof(output);
|
||||
|
||||
decoder_r = BrotliDecoderDecompressStream(
|
||||
decoder_s, &avail_in, &next_in, &avail_out,
|
||||
reinterpret_cast<unsigned char **>(&next_out), &total_out);
|
||||
|
||||
if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
|
||||
|
||||
if (!callback((const char *)output, sizeof(output) - avail_out)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
|
||||
decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
|
||||
}
|
||||
|
||||
private:
|
||||
BrotliDecoderResult decoder_r;
|
||||
BrotliDecoderState *decoder_s = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -2370,10 +2543,18 @@ inline const char *get_header_value(const Headers &headers, const char *key,
|
||||
return def;
|
||||
}
|
||||
|
||||
inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
|
||||
uint64_t def = 0) {
|
||||
auto it = headers.find(key);
|
||||
if (it != headers.end()) {
|
||||
template <typename T>
|
||||
inline T get_header_value(const Headers & /*headers*/, const char * /*key*/,
|
||||
size_t /*id*/ = 0, uint64_t /*def*/ = 0) {}
|
||||
|
||||
template <>
|
||||
inline uint64_t get_header_value<uint64_t>(const Headers &headers,
|
||||
const char *key, size_t id,
|
||||
uint64_t def) {
|
||||
auto rng = headers.equal_range(key);
|
||||
auto it = rng.first;
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
if (it != rng.second) {
|
||||
return std::strtoull(it->second.data(), nullptr, 10);
|
||||
}
|
||||
return def;
|
||||
@@ -2395,7 +2576,8 @@ inline void parse_header(const char *beg, const char *end, Headers &headers) {
|
||||
while (p < end) {
|
||||
p++;
|
||||
}
|
||||
headers.emplace(std::string(beg, key_end), std::string(val_begin, end));
|
||||
headers.emplace(std::string(beg, key_end),
|
||||
decode_url(std::string(val_begin, end), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2520,63 +2702,86 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
"chunked");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
Progress progress, ContentReceiver receiver,
|
||||
bool decompress) {
|
||||
template <typename T, typename U>
|
||||
bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver,
|
||||
bool decompress, U callback) {
|
||||
if (decompress) {
|
||||
std::string encoding = x.get_header_value("Content-Encoding");
|
||||
|
||||
if (encoding.find("gzip") != std::string::npos ||
|
||||
encoding.find("deflate") != std::string::npos) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
gzip_decompressor decompressor;
|
||||
if (decompressor.is_valid()) {
|
||||
ContentReceiver out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(
|
||||
buf, n,
|
||||
[&](const char *buf, size_t n) { return receiver(buf, n); });
|
||||
};
|
||||
return callback(out);
|
||||
} else {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
status = 415;
|
||||
return false;
|
||||
#endif
|
||||
} else if (encoding.find("br") != std::string::npos) {
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
brotli_decompressor decompressor;
|
||||
if (decompressor.is_valid()) {
|
||||
ContentReceiver out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(
|
||||
buf, n,
|
||||
[&](const char *buf, size_t n) { return receiver(buf, n); });
|
||||
};
|
||||
return callback(out);
|
||||
} else {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
status = 415;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
ContentReceiver out = [&](const char *buf, size_t n) {
|
||||
return receiver(buf, n);
|
||||
};
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
decompressor decompressor;
|
||||
#endif
|
||||
return callback(out);
|
||||
}
|
||||
|
||||
if (decompress) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
std::string content_encoding = x.get_header_value("Content-Encoding");
|
||||
if (content_encoding.find("gzip") != std::string::npos ||
|
||||
content_encoding.find("deflate") != std::string::npos) {
|
||||
if (!decompressor.is_valid()) {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
template <typename T>
|
||||
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
Progress progress, ContentReceiver receiver,
|
||||
bool decompress) {
|
||||
return prepare_content_receiver(
|
||||
x, status, receiver, decompress, [&](ContentReceiver &out) {
|
||||
auto ret = true;
|
||||
auto exceed_payload_max_length = false;
|
||||
|
||||
out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(buf, n, [&](const char *buf, size_t n) {
|
||||
return receiver(buf, n);
|
||||
});
|
||||
};
|
||||
}
|
||||
#else
|
||||
if (x.get_header_value("Content-Encoding") == "gzip") {
|
||||
status = 415;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (is_chunked_transfer_encoding(x.headers)) {
|
||||
ret = read_content_chunked(strm, out);
|
||||
} else if (!has_header(x.headers, "Content-Length")) {
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
|
||||
if (len > payload_max_length) {
|
||||
exceed_payload_max_length = true;
|
||||
skip_content_with_length(strm, len);
|
||||
ret = false;
|
||||
} else if (len > 0) {
|
||||
ret = read_content_with_length(strm, len, progress, out);
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = true;
|
||||
auto exceed_payload_max_length = false;
|
||||
|
||||
if (is_chunked_transfer_encoding(x.headers)) {
|
||||
ret = read_content_chunked(strm, out);
|
||||
} else if (!has_header(x.headers, "Content-Length")) {
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
|
||||
if (len > payload_max_length) {
|
||||
exceed_payload_max_length = true;
|
||||
skip_content_with_length(strm, len);
|
||||
ret = false;
|
||||
} else if (len > 0) {
|
||||
ret = read_content_with_length(strm, len, progress, out);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
||||
return ret;
|
||||
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -2642,39 +2847,96 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
||||
template <typename T>
|
||||
inline ssize_t write_content_chunked(Stream &strm,
|
||||
ContentProvider content_provider,
|
||||
T is_shutting_down) {
|
||||
T is_shutting_down, EncodingType type) {
|
||||
size_t offset = 0;
|
||||
auto data_available = true;
|
||||
ssize_t total_written_length = 0;
|
||||
|
||||
auto ok = true;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
if (ok) {
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
detail::gzip_compressor compressor;
|
||||
#endif
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
if (!ok) { return; }
|
||||
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
std::string payload;
|
||||
if (type == EncodingType::Gzip) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (!compressor.compress(d, l, false,
|
||||
[&](const char *data, size_t data_len) {
|
||||
payload.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
} else if (type == EncodingType::Brotli) {
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
#endif
|
||||
} else {
|
||||
payload = std::string(d, l);
|
||||
}
|
||||
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
|
||||
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (write_data(strm, chunk.data(), chunk.size())) {
|
||||
total_written_length += chunk.size();
|
||||
} else {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) {
|
||||
if (!ok) { return; }
|
||||
|
||||
data_available = false;
|
||||
if (ok) {
|
||||
static const std::string done_marker("0\r\n\r\n");
|
||||
if (write_data(strm, done_marker.data(), done_marker.size())) {
|
||||
total_written_length += done_marker.size();
|
||||
} else {
|
||||
|
||||
if (type == EncodingType::Gzip) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
std::string payload;
|
||||
if (!compressor.compress(nullptr, 0, true,
|
||||
[&](const char *data, size_t data_len) {
|
||||
payload.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (write_data(strm, chunk.data(), chunk.size())) {
|
||||
total_written_length += chunk.size();
|
||||
} else {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else if (type == EncodingType::Brotli) {
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
#endif
|
||||
}
|
||||
|
||||
static const std::string done_marker("0\r\n\r\n");
|
||||
if (write_data(strm, done_marker.data(), done_marker.size())) {
|
||||
total_written_length += done_marker.size();
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
@@ -2705,74 +2967,6 @@ inline bool redirect(T &cli, const Request &req, Response &res,
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string encode_url(const std::string &s) {
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
switch (s[i]) {
|
||||
case ' ': result += "%20"; break;
|
||||
case '+': result += "%2B"; break;
|
||||
case '\r': result += "%0D"; break;
|
||||
case '\n': result += "%0A"; break;
|
||||
case '\'': result += "%27"; break;
|
||||
case ',': result += "%2C"; break;
|
||||
// case ':': result += "%3A"; break; // ok? probably...
|
||||
case ';': result += "%3B"; break;
|
||||
default:
|
||||
auto c = static_cast<uint8_t>(s[i]);
|
||||
if (c >= 0x80) {
|
||||
result += '%';
|
||||
char hex[4];
|
||||
auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
|
||||
assert(len == 2);
|
||||
result.append(hex, static_cast<size_t>(len));
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string decode_url(const std::string &s,
|
||||
bool convert_plus_to_space) {
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
if (s[i] == '%' && i + 1 < s.size()) {
|
||||
if (s[i + 1] == 'u') {
|
||||
int val = 0;
|
||||
if (from_hex_to_i(s, i + 2, 4, val)) {
|
||||
// 4 digits Unicode codes
|
||||
char buff[4];
|
||||
size_t len = to_utf8(val, buff);
|
||||
if (len > 0) { result.append(buff, len); }
|
||||
i += 5; // 'u0000'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
} else {
|
||||
int val = 0;
|
||||
if (from_hex_to_i(s, i + 1, 2, val)) {
|
||||
// 2 digits hex codes
|
||||
result += static_cast<char>(val);
|
||||
i += 2; // '00'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
} else if (convert_plus_to_space && s[i] == '+') {
|
||||
result += ' ';
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string params_to_query_str(const Params ¶ms) {
|
||||
std::string query;
|
||||
|
||||
@@ -3398,6 +3592,11 @@ inline std::string Request::get_header_value(const char *key, size_t id) const {
|
||||
return detail::get_header_value(headers, key, id, "");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T Request::get_header_value(const char *key, size_t id) const {
|
||||
return detail::get_header_value<T>(headers, key, id, 0);
|
||||
}
|
||||
|
||||
inline size_t Request::get_header_value_count(const char *key) const {
|
||||
auto r = headers.equal_range(key);
|
||||
return static_cast<size_t>(std::distance(r.first, r.second));
|
||||
@@ -3457,6 +3656,11 @@ inline std::string Response::get_header_value(const char *key,
|
||||
return detail::get_header_value(headers, key, id, "");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T Response::get_header_value(const char *key, size_t id) const {
|
||||
return detail::get_header_value<T>(headers, key, id, 0);
|
||||
}
|
||||
|
||||
inline size_t Response::get_header_value_count(const char *key) const {
|
||||
auto r = headers.equal_range(key);
|
||||
return static_cast<size_t>(std::distance(r.first, r.second));
|
||||
@@ -3497,9 +3701,11 @@ inline void Response::set_content(std::string s, const char *content_type) {
|
||||
}
|
||||
|
||||
inline void
|
||||
Response::set_content_provider(size_t in_length, ContentProvider provider,
|
||||
Response::set_content_provider(size_t in_length, const char *content_type,
|
||||
ContentProvider provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
assert(in_length > 0);
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = in_length;
|
||||
content_provider_ = [provider](size_t offset, size_t length, DataSink &sink) {
|
||||
return provider(offset, length, sink);
|
||||
@@ -3508,7 +3714,9 @@ Response::set_content_provider(size_t in_length, ContentProvider provider,
|
||||
}
|
||||
|
||||
inline void Response::set_chunked_content_provider(
|
||||
ChunkedContentProvider provider, std::function<void()> resource_releaser) {
|
||||
const char *content_type, ChunkedContentProvider provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = [provider](size_t offset, size_t, DataSink &sink) {
|
||||
return provider(offset, sink);
|
||||
@@ -3876,6 +4084,8 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||
"multipart/byteranges; boundary=" + boundary);
|
||||
}
|
||||
|
||||
auto type = detail::encoding_type(req, res);
|
||||
|
||||
if (res.body.empty()) {
|
||||
if (res.content_length_ > 0) {
|
||||
size_t length = 0;
|
||||
@@ -3897,6 +4107,11 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||
} else {
|
||||
if (res.content_provider_) {
|
||||
res.set_header("Transfer-Encoding", "chunked");
|
||||
if (type == detail::EncodingType::Gzip) {
|
||||
res.set_header("Content-Encoding", "gzip");
|
||||
} else if (type == detail::EncodingType::Brotli) {
|
||||
res.set_header("Content-Encoding", "br");
|
||||
}
|
||||
} else {
|
||||
res.set_header("Content-Length", "0");
|
||||
}
|
||||
@@ -3918,16 +4133,27 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||
detail::make_multipart_ranges_data(req, res, boundary, content_type);
|
||||
}
|
||||
|
||||
if (type != detail::EncodingType::None) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
// TODO: 'Accept-Encoding' has gzip, not gzip;q=0
|
||||
const auto &encodings = req.get_header_value("Accept-Encoding");
|
||||
if (encodings.find("gzip") != std::string::npos &&
|
||||
detail::can_compress(res.get_header_value("Content-Type"))) {
|
||||
if (detail::compress(res.body)) {
|
||||
std::string compressed;
|
||||
|
||||
if (type == detail::EncodingType::Gzip) {
|
||||
detail::gzip_compressor compressor;
|
||||
if (!compressor.compress(res.body.data(), res.body.size(), true,
|
||||
[&](const char *data, size_t data_len) {
|
||||
compressed.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
res.set_header("Content-Encoding", "gzip");
|
||||
} else if (type == detail::EncodingType::Brotli) {
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
|
||||
res.body.swap(compressed);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto length = std::to_string(res.body.size());
|
||||
res.set_header("Content-Length", length);
|
||||
@@ -3988,8 +4214,9 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto type = detail::encoding_type(req, res);
|
||||
if (detail::write_content_chunked(strm, res.content_provider_,
|
||||
is_shutting_down) < 0) {
|
||||
is_shutting_down, type) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4729,26 +4956,47 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) {
|
||||
detail::gzip_compressor compressor;
|
||||
|
||||
if (content_provider) {
|
||||
auto ok = true;
|
||||
size_t offset = 0;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *data, size_t data_len) {
|
||||
req.body.append(data, data_len);
|
||||
offset += data_len;
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return true; };
|
||||
if (ok) {
|
||||
auto last = offset + data_len == content_length;
|
||||
|
||||
while (offset < content_length) {
|
||||
auto ret = compressor.compress(
|
||||
data, data_len, last, [&](const char *data, size_t data_len) {
|
||||
req.body.append(data, data_len);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
offset += data_len;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return ok && true; };
|
||||
|
||||
while (ok && offset < content_length) {
|
||||
if (!content_provider(offset, content_length - offset, data_sink)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
req.body = body;
|
||||
if (!compressor.compress(body.data(), body.size(), true,
|
||||
[&](const char *data, size_t data_len) {
|
||||
req.body.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!detail::compress(req.body)) { return nullptr; }
|
||||
req.headers.emplace("Content-Encoding", "gzip");
|
||||
} else
|
||||
#endif
|
||||
@@ -5126,10 +5374,6 @@ inline void Client::stop() {
|
||||
}
|
||||
}
|
||||
|
||||
inline void Client::set_timeout_sec(time_t timeout_sec) {
|
||||
set_connection_timeout(timeout_sec, 0);
|
||||
}
|
||||
|
||||
inline void Client::set_connection_timeout(time_t sec, time_t usec) {
|
||||
connection_timeout_sec_ = sec;
|
||||
connection_timeout_usec_ = usec;
|
||||
@@ -5820,4 +6064,3 @@ inline bool SSLClient::check_host_name(const char *pattern,
|
||||
} // namespace httplib
|
||||
|
||||
#endif // CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
|
||||
set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@)
|
||||
set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
@@ -26,6 +27,14 @@ if(@HTTPLIB_IS_USING_ZLIB@)
|
||||
find_dependency(ZLIB REQUIRED)
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
# Needed so we can use our own FindBrotli.cmake in this file.
|
||||
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder REQUIRED)
|
||||
endif()
|
||||
|
||||
# Mildly useful for end-users
|
||||
# Not really recommended to be used though
|
||||
set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
|
||||
+7
-2
@@ -1,10 +1,15 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
|
||||
|
||||
OPENSSL_DIR = /usr/local/opt/openssl
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = /usr/local/opt/brotli
|
||||
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
|
||||
|
||||
all : test
|
||||
./test
|
||||
|
||||
@@ -12,10 +17,10 @@ proxy : test_proxy
|
||||
./test_proxy
|
||||
|
||||
test : test.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
|
||||
$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
|
||||
|
||||
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
|
||||
$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
|
||||
|
||||
cert.pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
|
||||
+120
-9
@@ -100,7 +100,8 @@ TEST(GetHeaderValueTest, DefaultValue) {
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValueInt) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value_uint64(headers, "Content-Length", 100);
|
||||
auto val =
|
||||
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 100);
|
||||
EXPECT_EQ(100ull, val);
|
||||
}
|
||||
|
||||
@@ -112,7 +113,8 @@ TEST(GetHeaderValueTest, RegularValue) {
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValueInt) {
|
||||
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value_uint64(headers, "Content-Length", 0);
|
||||
auto val =
|
||||
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 0);
|
||||
EXPECT_EQ(100ull, val);
|
||||
}
|
||||
|
||||
@@ -214,6 +216,58 @@ TEST(ParseHeaderValueTest, Range) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ParseAcceptEncoding1, AcceptEncoding) {
|
||||
Request req;
|
||||
req.set_header("Accept-Encoding", "gzip");
|
||||
|
||||
Response res;
|
||||
res.set_header("Content-Type", "text/plain");
|
||||
|
||||
auto ret = detail::encoding_type(req, res);
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
EXPECT_TRUE(ret == detail::EncodingType::Gzip);
|
||||
#else
|
||||
EXPECT_TRUE(ret == detail::EncodingType::None);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ParseAcceptEncoding2, AcceptEncoding) {
|
||||
Request req;
|
||||
req.set_header("Accept-Encoding", "gzip, deflate, br");
|
||||
|
||||
Response res;
|
||||
res.set_header("Content-Type", "text/plain");
|
||||
|
||||
auto ret = detail::encoding_type(req, res);
|
||||
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
EXPECT_TRUE(ret == detail::EncodingType::Brotli);
|
||||
#elif CPPHTTPLIB_ZLIB_SUPPORT
|
||||
EXPECT_TRUE(ret == detail::EncodingType::Gzip);
|
||||
#else
|
||||
EXPECT_TRUE(ret == detail::EncodingType::None);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ParseAcceptEncoding3, AcceptEncoding) {
|
||||
Request req;
|
||||
req.set_header("Accept-Encoding", "br;q=1.0, gzip;q=0.8, *;q=0.1");
|
||||
|
||||
Response res;
|
||||
res.set_header("Content-Type", "text/plain");
|
||||
|
||||
auto ret = detail::encoding_type(req, res);
|
||||
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
EXPECT_TRUE(ret == detail::EncodingType::Brotli);
|
||||
#elif CPPHTTPLIB_ZLIB_SUPPORT
|
||||
EXPECT_TRUE(ret == detail::EncodingType::Gzip);
|
||||
#else
|
||||
EXPECT_TRUE(ret == detail::EncodingType::None);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BufferStreamTest, read) {
|
||||
detail::BufferStream strm1;
|
||||
Stream &strm = strm1;
|
||||
@@ -394,6 +448,20 @@ TEST(ConnectionErrorTest, InvalidHost) {
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidHost2) {
|
||||
auto host = "httpbin.org/";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
httplib::SSLClient cli(host);
|
||||
#else
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto host = "localhost";
|
||||
|
||||
@@ -702,6 +770,16 @@ TEST(RedirectToDifferentPort, Redirect) {
|
||||
ASSERT_FALSE(svr8080.is_running());
|
||||
ASSERT_FALSE(svr8081.is_running());
|
||||
}
|
||||
|
||||
TEST(UrlWithSpace, Redirect) {
|
||||
httplib::SSLClient cli("edge.forgecdn.net");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Server, BindDualStack) {
|
||||
@@ -885,7 +963,7 @@ protected:
|
||||
.Get("/streamed-chunked",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
"text/plain", [](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "123";
|
||||
sink.os << "456";
|
||||
@@ -898,6 +976,7 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto i = new int(0);
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
switch (*i) {
|
||||
@@ -914,7 +993,8 @@ protected:
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
6, [](size_t offset, size_t /*length*/, DataSink &sink) {
|
||||
6, "text/plain",
|
||||
[](size_t offset, size_t /*length*/, DataSink &sink) {
|
||||
sink.os << (offset < 3 ? "a" : "b");
|
||||
return true;
|
||||
});
|
||||
@@ -923,7 +1003,7 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
res.set_content_provider(
|
||||
data->size(),
|
||||
data->size(), "text/plain",
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
size_t DATA_CHUNK_SIZE = 4;
|
||||
@@ -938,7 +1018,7 @@ protected:
|
||||
.Get("/streamed-cancel",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
size_t(-1),
|
||||
size_t(-1), "text/plain",
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "data_chunk";
|
||||
@@ -1144,7 +1224,7 @@ protected:
|
||||
EXPECT_EQ(req.body, "content");
|
||||
})
|
||||
.Get("/last-request",
|
||||
[&](const Request & req, Response &/*res*/) {
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_EQ("close", req.get_header_value("Connection"));
|
||||
})
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
@@ -2022,6 +2102,25 @@ TEST_F(ServerTest, PutContentWithDeflate) {
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedChunkedWithGzip) {
|
||||
httplib::Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
auto res = cli_.Get("/streamed-chunked", headers);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedChunkedWithGzip2) {
|
||||
httplib::Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
auto res = cli_.Get("/streamed-chunked2", headers);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(ServerTest, Patch) {
|
||||
@@ -2513,9 +2612,9 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/events", [](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_header("Cache-Control", "no-cache");
|
||||
res.set_chunked_content_provider([](size_t offset, DataSink &sink) {
|
||||
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
|
||||
DataSink &sink) {
|
||||
char buffer[27];
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
@@ -3003,6 +3102,18 @@ TEST(YahooRedirectTest3, SimpleInterface) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
|
||||
httplib::Client2 cli("https://cdnjs.cloudflare.com");
|
||||
auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "brotli"}});
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(287630, res->body.size());
|
||||
EXPECT_EQ("application/javascript; charset=utf-8", res->get_header_value("Content-Type"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
|
||||
auto res =
|
||||
|
||||
Reference in New Issue
Block a user