Compare commits

...

19 Commits

Author SHA1 Message Date
KTGH 342c3ab293 Add Brotli Cmake support (#584)
Had to create a custom FindBrotli package, as not all users have
PkgConfig installed (which Brotli uses). This file gets installed
alongside httplibConfig.cmake for the end-users convenience.

Set BROTLI_USE_STATIC_LIBS to ON if you want to find the static libs
instead of default shared.

Adds the HTTPLIB_REQUIRE_BROTLI (default off) and HTTPLIB_USE_BROTLI_IF_AVAILABLE
(default on) options, which work in the same manner as the other optional/required
dependency options.

Moved the scattered linking and definitions to a single call.

Updated some documentation about the new options.

Improved the in-tree support by setting the HTTPLIB_IS_USING_XYZ
variables in the main CMakeLists (as well as having them in the
httplibConfig.cmake file).

Fixes #582
2020-07-28 17:04:29 -04:00
yhirose 6cce7951fc Fixed build error on non Windows environments with OpenSSL 2020-07-27 22:34:35 -04:00
yhirose e9058e5639 Fixed build error on Windows with OpenSSL 2020-07-27 22:32:31 -04:00
yhirose 2538a85486 Fix #581 2020-07-27 22:07:04 -04:00
KTGH 8c501022b3 Fix Cmake build for MinGW (#580)
Seems certain targets/hosts failed without these, as "_MSC_VER" is
undefined on MinGW, which caused the 'pragma comment(lib "libname")' to
fail.

Fixes #575
2020-07-26 12:27:03 -04:00
yhirose 9f5db2d1aa Updated README 2020-07-25 20:53:38 -04:00
yhirose 12540fe8d3 Brotli support on client 2020-07-25 20:44:02 -04:00
yhirose 29a06f852a Update README 2020-07-25 11:24:06 -04:00
yhirose 0e9cfd9f49 SSE client example 2020-07-25 11:20:57 -04:00
yhirose 90da199aba Disable compression when content-type is text/event-stream 2020-07-25 10:46:52 -04:00
yhirose 366d073490 Fixed build errors 2020-07-25 09:40:35 -04:00
yhirose 9ca1fa8b18 Fix #576 2020-07-25 09:37:57 -04:00
yhirose 15c4106a36 Added a unit test 2020-07-22 08:07:59 -04:00
yhirose 72ce293fed Removed set_timeout_sec and left set_base_dir 2020-07-20 17:15:16 -04:00
yhirose b476b55771 Fix #557 2020-07-20 17:04:50 -04:00
yhirose 0db9d21eb0 Fix #571 2020-07-19 18:40:55 -04:00
yhirose 5ddaf949d0 Fixed build error on Windows 2020-07-19 18:32:28 -04:00
yhirose 457a5a7501 Added compressor class 2020-07-19 17:44:45 -04:00
Daniel Ottiger 2ce080c2cb include <wincrypt.h> as otherwise CertOpenSystemStoreW can not be found (#568)
- visual studio 2019, version 16.6.3
- 64 bit target
2020-07-15 10:17:18 -04:00
12 changed files with 929 additions and 312 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ example/simplecli
example/simplesvr
example/benchmark
example/redirect
example/sse
example/sse*
example/upload
example/*.pem
test/test
+57 -27
View File
@@ -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}
)
+27 -22
View File
@@ -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
```
+185
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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";
+21
View File
@@ -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;
}
+10 -10
View File
@@ -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([&] {
+471 -228
View File
@@ -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 &params) {
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
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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 =