mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51dee793fe | |||
| 457fc4306e | |||
| 4f5b003e76 | |||
| 5421e27106 | |||
| fe07660f40 | |||
| da2f9e476e | |||
| 1a7a7ed1c3 | |||
| 413994912d | |||
| 01dcf1d0ad | |||
| 8e378779c2 | |||
| 970b52897c | |||
| 412ba04d19 | |||
| bfef4b3e9b | |||
| 7bd316f3d0 | |||
| 26208363ee | |||
| b1b4bb8850 | |||
| 9dd565b6e3 | |||
| 924f214303 | |||
| 5c1a34e766 | |||
| fa90d06dd5 | |||
| d869054318 | |||
| 0cc1ca9a8d | |||
| 3701195033 | |||
| f884a56258 | |||
| d79633ff52 | |||
| e0ebc431dc | |||
| 131bc6c674 | |||
| 10d68cff50 | |||
| 996acc5253 | |||
| 7c4799d0cf | |||
| c239087332 | |||
| 7018e9263d | |||
| 4990b4b4b7 | |||
| 5064373c23 | |||
| 6c93aea59a | |||
| 6553cdedab | |||
| a61b2427b0 | |||
| af4ece3d5f |
+32
-10
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: install libraries
|
||||
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
|
||||
- name: build and run tests
|
||||
run: cd test && make -j4
|
||||
run: cd test && make
|
||||
- name: run fuzz test target
|
||||
run: cd test && make fuzz_test
|
||||
|
||||
@@ -21,23 +21,45 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: build and run tests
|
||||
run: |
|
||||
cd test && make -j2
|
||||
run: cd test && make
|
||||
- name: run fuzz test target
|
||||
run: cd test && make fuzz_test
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: prepare git for checkout on windows
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup msbuild on windows
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Setup msbuild on windows
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: make-windows
|
||||
- name: Install libraries
|
||||
run: |
|
||||
cd test
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
|
||||
x64\Release\test.exe
|
||||
vcpkg install gtest curl zlib brotli
|
||||
choco install openssl
|
||||
|
||||
- name: Configure CMake with SSL
|
||||
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build with with SSL
|
||||
run: cmake --build build --config Release
|
||||
- name: Run tests with SSL
|
||||
run: ctest --output-on-failure --test-dir build -C Release
|
||||
|
||||
- name: Configure CMake without SSL
|
||||
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build without SSL
|
||||
run: cmake --build build-no-ssl --config Release
|
||||
- name: Run tests without SSL
|
||||
run: ctest --output-on-failure --test-dir build-no-ssl -C Release
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
@@ -299,6 +299,8 @@ if(HTTPLIB_INSTALL)
|
||||
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
|
||||
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
|
||||
@@ -79,7 +79,7 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
// Disable host verification
|
||||
cli.enable_server_host_verification(false);
|
||||
cli.enable_server_hostname_verification(false);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -557,18 +557,18 @@ enum Error {
|
||||
|
||||
```c++
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
{ "Hello", "World!" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
or
|
||||
```c++
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
auto res = cli.Get("/hi", {{"Hello", "World!"}});
|
||||
```
|
||||
or
|
||||
```c++
|
||||
cli.set_default_headers({
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
{ "Hello", "World!" }
|
||||
});
|
||||
auto res = cli.Get("/hi");
|
||||
```
|
||||
@@ -823,6 +823,21 @@ The server can apply compression to the following MIME type contents:
|
||||
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
|
||||
Please see https://github.com/google/brotli for more detail.
|
||||
|
||||
### Default `Accept-Encoding` value
|
||||
|
||||
The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo");
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
```
|
||||
|
||||
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}});
|
||||
```
|
||||
|
||||
### Compress request body on client
|
||||
|
||||
```c++
|
||||
@@ -834,8 +849,9 @@ res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
|
||||
```c++
|
||||
cli.set_decompress(false);
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res = cli.Get("/resource/foo");
|
||||
res->body; // Compressed data
|
||||
|
||||
```
|
||||
|
||||
Use `poll` instead of `select`
|
||||
@@ -843,6 +859,23 @@ Use `poll` instead of `select`
|
||||
|
||||
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
|
||||
|
||||
Unix Domain Socket Support
|
||||
--------------------------
|
||||
|
||||
Unix Domain Socket support is available on Linux and macOS.
|
||||
|
||||
```c++
|
||||
// Server
|
||||
httplib::Server svr("./my-socket.sock");
|
||||
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
|
||||
|
||||
// Client
|
||||
httplib::Client cli("./my-socket.sock");
|
||||
cli.set_address_family(AF_UNIX);
|
||||
```
|
||||
|
||||
"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
|
||||
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
+7
-5
@@ -1,6 +1,6 @@
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
|
||||
THEAD_POOL_COUNT = 16
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||
MONITOR = ali http://localhost:8080
|
||||
@@ -18,11 +18,11 @@ run : server
|
||||
@./server
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
|
||||
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# cpp-httplib
|
||||
bench-base: server-base
|
||||
@echo "---------------------\n cpp-httplib v0.17.0\n---------------------\n"
|
||||
@echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n"
|
||||
@./server-base & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
@@ -33,7 +33,7 @@ run-base : server-base
|
||||
@./server-base
|
||||
|
||||
server-base : cpp-httplib-base/main.cpp cpp-httplib-base/httplib.h
|
||||
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib-base/main.cpp
|
||||
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-base/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@@ -51,10 +51,12 @@ server-crow : crow/main.cpp
|
||||
g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-base server-crow
|
||||
|
||||
bench-all: bench-crow bench bench-base
|
||||
|
||||
issue:
|
||||
$(BENCH)
|
||||
bombardier -c 10 -d 30s localhost:8080
|
||||
|
||||
clean:
|
||||
rm -rf server*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,34 +22,34 @@
|
||||
<ProjectGuid>{6DB1FC63-B153-4279-92B7-D8A11AF285D6}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>client</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
|
||||
@@ -18,38 +18,41 @@
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="simplesvr.cc" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{864CD288-050A-4C8B-9BEF-3048BD876C5B}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>sample</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
@@ -151,9 +154,6 @@
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="server.cc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.18.0"
|
||||
#define CPPHTTPLIB_VERSION "0.18.2"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -18,6 +18,10 @@
|
||||
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
|
||||
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
|
||||
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
|
||||
#endif
|
||||
@@ -365,8 +369,9 @@ inline unsigned char to_lower(int c) {
|
||||
|
||||
inline bool equal(const std::string &a, const std::string &b) {
|
||||
return a.size() == b.size() &&
|
||||
std::equal(a.begin(), a.end(), b.begin(),
|
||||
[](char ca, char cb) { return to_lower(ca) == to_lower(cb); });
|
||||
std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
|
||||
return to_lower(ca) == to_lower(cb);
|
||||
});
|
||||
}
|
||||
|
||||
struct equal_to {
|
||||
@@ -391,7 +396,7 @@ struct hash {
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace case_ignore
|
||||
} // namespace case_ignore
|
||||
|
||||
// This is based on
|
||||
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
|
||||
@@ -607,6 +612,7 @@ using Ranges = std::vector<Range>;
|
||||
struct Request {
|
||||
std::string method;
|
||||
std::string path;
|
||||
Params params;
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
@@ -618,7 +624,6 @@ struct Request {
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
Params params;
|
||||
MultipartFormDataMap files;
|
||||
Ranges ranges;
|
||||
Match matches;
|
||||
@@ -1577,6 +1582,9 @@ private:
|
||||
bool send_(Request &req, Response &res, Error &error);
|
||||
Result send_(Request &&req);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool is_ssl_peer_could_be_closed(SSL *ssl) const;
|
||||
#endif
|
||||
socket_t create_client_socket(Error &error) const;
|
||||
bool read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) const;
|
||||
@@ -2253,13 +2261,33 @@ make_basic_authentication_header(const std::string &username,
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_WIN32)
|
||||
inline std::wstring u8string_to_wstring(const char *s) {
|
||||
std::wstring ws;
|
||||
auto len = static_cast<int>(strlen(s));
|
||||
auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
|
||||
if (wlen > 0) {
|
||||
ws.resize(wlen);
|
||||
wlen = ::MultiByteToWideChar(
|
||||
CP_UTF8, 0, s, len,
|
||||
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
|
||||
if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
|
||||
}
|
||||
return ws;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct FileStat {
|
||||
FileStat(const std::string &path);
|
||||
bool is_file() const;
|
||||
bool is_dir() const;
|
||||
|
||||
private:
|
||||
#if defined(_WIN32)
|
||||
struct _stat st_;
|
||||
#else
|
||||
struct stat st_;
|
||||
#endif
|
||||
int ret_ = -1;
|
||||
};
|
||||
|
||||
@@ -2634,7 +2662,12 @@ inline bool is_valid_path(const std::string &path) {
|
||||
}
|
||||
|
||||
inline FileStat::FileStat(const std::string &path) {
|
||||
#if defined(_WIN32)
|
||||
auto wpath = u8string_to_wstring(path.c_str());
|
||||
ret_ = _wstat(wpath.c_str(), &st_);
|
||||
#else
|
||||
ret_ = stat(path.c_str(), &st_);
|
||||
#endif
|
||||
}
|
||||
inline bool FileStat::is_file() const {
|
||||
return ret_ >= 0 && S_ISREG(st_.st_mode);
|
||||
@@ -2896,9 +2929,7 @@ inline void stream_line_reader::append(char c) {
|
||||
}
|
||||
}
|
||||
|
||||
inline mmap::mmap(const char *path) {
|
||||
open(path);
|
||||
}
|
||||
inline mmap::mmap(const char *path) { open(path); }
|
||||
|
||||
inline mmap::~mmap() { close(); }
|
||||
|
||||
@@ -2906,10 +2937,8 @@ inline bool mmap::open(const char *path) {
|
||||
close();
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::wstring wpath;
|
||||
for (size_t i = 0; i < strlen(path); i++) {
|
||||
wpath += path[i];
|
||||
}
|
||||
auto wpath = u8string_to_wstring(path);
|
||||
if (wpath.empty()) { return false; }
|
||||
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
|
||||
@@ -3251,6 +3280,39 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
time_t keep_alive_timeout_sec) {
|
||||
using namespace std::chrono;
|
||||
|
||||
const auto interval_usec =
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
|
||||
|
||||
// Avoid expensive `steady_clock::now()` call for the first time
|
||||
if (select_read(sock, 0, interval_usec) > 0) { return true; }
|
||||
|
||||
const auto start = steady_clock::now() - microseconds{interval_usec};
|
||||
const auto timeout = seconds{keep_alive_timeout_sec};
|
||||
|
||||
while (true) {
|
||||
if (svr_sock == INVALID_SOCKET) {
|
||||
break; // Server socket is closed
|
||||
}
|
||||
|
||||
auto val = select_read(sock, 0, interval_usec);
|
||||
if (val < 0) {
|
||||
break; // Ssocket error
|
||||
} else if (val == 0) {
|
||||
if (steady_clock::now() - start > timeout) {
|
||||
break; // Timeout
|
||||
}
|
||||
} else {
|
||||
return true; // Ready for read
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
@@ -3259,8 +3321,7 @@ process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
assert(keep_alive_max_count > 0);
|
||||
auto ret = false;
|
||||
auto count = keep_alive_max_count;
|
||||
while (svr_sock != INVALID_SOCKET && count > 0 &&
|
||||
select_read(sock, keep_alive_timeout_sec, 0) > 0) {
|
||||
while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
|
||||
auto close_connection = count == 1;
|
||||
auto connection_closed = false;
|
||||
ret = callback(close_connection, connection_closed);
|
||||
@@ -3722,7 +3783,7 @@ inline unsigned int str2tag(const std::string &s) {
|
||||
|
||||
namespace udl {
|
||||
|
||||
inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
|
||||
inline constexpr unsigned int operator""_t(const char *s, size_t l) {
|
||||
return str2tag_core(s, l, 0);
|
||||
}
|
||||
|
||||
@@ -4077,7 +4138,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
if (p <= end) {
|
||||
auto key_len = key_end - beg;
|
||||
if (!key_len) { return false; }
|
||||
|
||||
@@ -7355,6 +7416,14 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const {
|
||||
char buf[1];
|
||||
return !SSL_peek(ssl, buf, 1) &&
|
||||
SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
@@ -7366,6 +7435,13 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
auto is_alive = false;
|
||||
if (socket_.is_open()) {
|
||||
is_alive = detail::is_socket_alive(socket_.sock);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_alive && is_ssl()) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!is_alive) {
|
||||
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
|
||||
// like the other side has already closed the connection Also, there
|
||||
@@ -7642,12 +7718,26 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
|
||||
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.set_header("User-Agent", agent);
|
||||
}
|
||||
if (!req.content_receiver) {
|
||||
if (!req.has_header("Accept-Encoding")) {
|
||||
std::string accept_encoding;
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
accept_encoding = "br";
|
||||
#endif
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (!accept_encoding.empty()) { accept_encoding += ", "; }
|
||||
accept_encoding += "gzip, deflate";
|
||||
#endif
|
||||
req.set_header("Accept-Encoding", accept_encoding);
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.set_header("User-Agent", agent);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
if (req.body.empty()) {
|
||||
if (req.content_provider_) {
|
||||
@@ -7707,7 +7797,13 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
||||
const auto &path_with_query =
|
||||
req.params.empty() ? req.path
|
||||
: append_query_params(req.path, req.params);
|
||||
|
||||
const auto &path =
|
||||
url_encode_ ? detail::encode_url(path_with_query) : path_with_query;
|
||||
|
||||
detail::write_request_line(bstrm, req.method, path);
|
||||
|
||||
header_writer_(bstrm, req.headers);
|
||||
@@ -7848,9 +7944,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
if (is_ssl()) {
|
||||
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
|
||||
if (!is_proxy_enabled) {
|
||||
char buf[1];
|
||||
if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
|
||||
SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) {
|
||||
error = Error::SSLPeerCouldBeClosed_;
|
||||
return false;
|
||||
}
|
||||
@@ -7889,9 +7983,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
: static_cast<ContentReceiverWithProgress>(
|
||||
[&](const char *buf, size_t n, uint64_t /*off*/,
|
||||
uint64_t /*len*/) {
|
||||
if (res.body.size() + n > res.body.max_size()) {
|
||||
return false;
|
||||
}
|
||||
assert(res.body.size() + n <= res.body.max_size());
|
||||
res.body.append(buf, n);
|
||||
return true;
|
||||
});
|
||||
@@ -7905,9 +7997,12 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
|
||||
if (res.has_header("Content-Length")) {
|
||||
if (!req.content_receiver) {
|
||||
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
|
||||
res.body.max_size());
|
||||
if (len > 0) { res.body.reserve(len); }
|
||||
auto len = res.get_header_value_u64("Content-Length");
|
||||
if (len > res.body.max_size()) {
|
||||
error = Error::Read;
|
||||
return false;
|
||||
}
|
||||
res.body.reserve(len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9454,8 +9549,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
|
||||
auto type = GEN_DNS;
|
||||
|
||||
struct in6_addr addr6 {};
|
||||
struct in_addr addr {};
|
||||
struct in6_addr addr6{};
|
||||
struct in_addr addr{};
|
||||
size_t addr_len = 0;
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
+4
-6
@@ -13,7 +13,7 @@ project(
|
||||
'b_lto=true',
|
||||
'warning_level=3'
|
||||
],
|
||||
meson_version: '>=0.47.0'
|
||||
meson_version: '>=0.62.0'
|
||||
)
|
||||
|
||||
# Check just in case downstream decides to edit the source
|
||||
@@ -98,20 +98,18 @@ if get_option('cpp-httplib_compile')
|
||||
)
|
||||
else
|
||||
install_headers('httplib.h')
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.')
|
||||
|
||||
import('pkgconfig').generate(
|
||||
name: 'cpp-httplib',
|
||||
description: 'A C++ HTTP/HTTPS server and client library',
|
||||
install_dir: join_paths(get_option('datadir'), 'pkgconfig'),
|
||||
install_dir: get_option('datadir')/'pkgconfig',
|
||||
url: 'https://github.com/yhirose/cpp-httplib',
|
||||
version: version
|
||||
)
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
endif
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
|
||||
if get_option('cpp-httplib_test')
|
||||
subdir('test')
|
||||
|
||||
+7
-2
@@ -37,6 +37,11 @@ file(
|
||||
)
|
||||
|
||||
if(HTTPLIB_IS_USING_OPENSSL)
|
||||
if (OPENSSL_VERSION VERSION_LESS "3.2.0")
|
||||
set(OPENSSL_X509_FLAG "-x509")
|
||||
else()
|
||||
set(OPENSSL_X509_FLAG "-x509v1")
|
||||
endif()
|
||||
find_program(OPENSSL_COMMAND
|
||||
NAMES openssl
|
||||
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
|
||||
@@ -56,7 +61,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
@@ -67,7 +72,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
OUTPUT_FILE rootCA.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
|
||||
+2
-12
@@ -63,18 +63,8 @@ httplib.cc : ../httplib.h
|
||||
python3 ../split.py -o .
|
||||
|
||||
cert.pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
openssl genrsa 2048 > rootCA.key.pem
|
||||
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
|
||||
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
|
||||
#c_rehash .
|
||||
./gen-certs.sh
|
||||
|
||||
clean:
|
||||
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
|
||||
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
if [[ $(openssl version) =~ 3\.[2-9]\.[0-9]+ ]]; then
|
||||
OPENSSL_X509_FLAG='-x509v1'
|
||||
else
|
||||
OPENSSL_X509_FLAG='-x509'
|
||||
fi
|
||||
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
openssl genrsa 2048 > rootCA.key.pem
|
||||
openssl req $OPENSSL_X509_FLAG -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
|
||||
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
|
||||
+7
-6
@@ -6,6 +6,7 @@ gtest_dep = dependency('gtest', main: true)
|
||||
libcurl_dep = dependency('libcurl')
|
||||
openssl = find_program('openssl')
|
||||
test_conf = files('test.conf')
|
||||
req_x509_flag = openssl.version().version_compare('>=3.2.0') ? '-x509v1' : '-x509'
|
||||
|
||||
key_pem = custom_target(
|
||||
'key_pem',
|
||||
@@ -31,7 +32,7 @@ cert2_pem = custom_target(
|
||||
'cert2_pem',
|
||||
input: key_pem,
|
||||
output: 'cert2.pem',
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
key_encrypted_pem = custom_target(
|
||||
@@ -44,7 +45,7 @@ cert_encrypted_pem = custom_target(
|
||||
'cert_encrypted_pem',
|
||||
input: key_encrypted_pem,
|
||||
output: 'cert_encrypted.pem',
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
rootca_key_pem = custom_target(
|
||||
@@ -57,7 +58,7 @@ rootca_cert_pem = custom_target(
|
||||
'rootca_cert_pem',
|
||||
input: rootca_key_pem,
|
||||
output: 'rootCA.cert.pem',
|
||||
command: [openssl, 'req', '-x509', '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
|
||||
command: [openssl, 'req', req_x509_flag, '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
client_key_pem = custom_target(
|
||||
@@ -103,9 +104,9 @@ client_encrypted_cert_pem = custom_target(
|
||||
# Copy test files to the build directory
|
||||
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
|
||||
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
|
||||
subdir(join_paths('www', 'dir'))
|
||||
subdir(join_paths('www2', 'dir'))
|
||||
subdir(join_paths('www3', 'dir'))
|
||||
subdir('www')
|
||||
subdir('www2'/'dir')
|
||||
subdir('www3'/'dir')
|
||||
|
||||
# GoogleTest 1.13.0 requires C++14
|
||||
test_options = []
|
||||
|
||||
+241
-46
@@ -1,3 +1,4 @@
|
||||
// NOTE: This file should be saved as UTF-8 w/ BOM
|
||||
#include <httplib.h>
|
||||
#include <signal.h>
|
||||
|
||||
@@ -241,7 +242,7 @@ TEST(DecodeURLTest, PercentCharacter) {
|
||||
detail::decode_url(
|
||||
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)",
|
||||
false),
|
||||
R"(descrip=Gastos áéíóúñÑ 6)");
|
||||
u8"descrip=Gastos áéíóúñÑ 6");
|
||||
}
|
||||
|
||||
TEST(DecodeURLTest, PercentCharacterNUL) {
|
||||
@@ -267,9 +268,9 @@ TEST(EncodeQueryParamTest, ParseReservedCharactersTest) {
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, TestUTF8Characters) {
|
||||
string chineseCharacters = "中国語";
|
||||
string russianCharacters = "дом";
|
||||
string brazilianCharacters = "óculos";
|
||||
string chineseCharacters = u8"中国語";
|
||||
string russianCharacters = u8"дом";
|
||||
string brazilianCharacters = u8"óculos";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(chineseCharacters),
|
||||
"%E4%B8%AD%E5%9B%BD%E8%AA%9E");
|
||||
@@ -2003,7 +2004,7 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2013,7 +2014,46 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionHandlerTest, ContentLength) {
|
||||
TEST(ExceptionTest, WithoutExceptionHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, WithExceptionHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||
@@ -2021,7 +2061,9 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
EXPECT_FALSE(ep == nullptr);
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) { EXPECT_EQ("abc", std::string(e.what())); }
|
||||
} catch (std::exception &e) {
|
||||
EXPECT_EQ("abc", std::string(e.what()));
|
||||
} catch (...) {}
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13 at this point
|
||||
@@ -2045,7 +2087,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2056,7 +2098,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2065,6 +2107,66 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, AndErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
if (res.body.empty()) { res.set_content("NOT_FOUND", "text/html"); }
|
||||
});
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const Request & /*req*/, Response &res, std::exception_ptr ep) {
|
||||
EXPECT_FALSE(ep == nullptr);
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) {
|
||||
res.set_content(e.what(), "text/html");
|
||||
} catch (...) {}
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
});
|
||||
|
||||
svr.Get("/exception", [](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("EXCEPTION");
|
||||
});
|
||||
|
||||
svr.Get("/error", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ERROR", "text/html");
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("EXCEPTION", res->body);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/error");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("ERROR", res->body);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/invalid");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
EXPECT_EQ("NOT_FOUND", res->body);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(NoContentTest, ContentLength) {
|
||||
@@ -3700,6 +3802,19 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
|
||||
auto res = cli_.Get("/with-range", {
|
||||
{"Range", "bytes=0-"},
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
||||
auto res =
|
||||
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
|
||||
@@ -3787,7 +3902,10 @@ TEST_F(ServerTest, ClientStop) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange1) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{3, 5}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
@@ -3797,7 +3915,10 @@ TEST_F(ServerTest, GetWithRange1) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange2) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{1, -1}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{1, -1}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
@@ -3807,7 +3928,10 @@ TEST_F(ServerTest, GetWithRange2) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange3) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{0, 0}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{0, 0}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("1", res->get_header_value("Content-Length"));
|
||||
@@ -3817,7 +3941,10 @@ TEST_F(ServerTest, GetWithRange3) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange4) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{-1, 2}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{-1, 2}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
@@ -4131,7 +4258,10 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
|
||||
// The compressed size should be less than a 10th of the original. May vary
|
||||
// depending on the zlib library.
|
||||
EXPECT_LT(res.get_request_header_value_u64("Content-Length"),
|
||||
static_cast<uint64_t>(10 * 1024 * 1024));
|
||||
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
|
||||
}
|
||||
|
||||
@@ -4562,7 +4692,9 @@ TEST_F(ServerTest, Gzip) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
auto res = cli_.Get("/compress");
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "");
|
||||
auto res = cli_.Get("/compress", headers);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
@@ -4611,12 +4743,16 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "");
|
||||
|
||||
std::string body;
|
||||
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
|
||||
EXPECT_EQ(100U, data_length);
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli_.Get("/compress", headers,
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
EXPECT_EQ(100U, data_length);
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
@@ -4912,6 +5048,15 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, EmptyFieldValue) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("GET /header_field_value_check HTTP/1.1\r\n"
|
||||
"Test: \r\n\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -5151,18 +5296,9 @@ TEST(MountTest, Redicect) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
TEST(MountTest, MultibytesPathName) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
@@ -5170,27 +5306,16 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.set_mount_point("/", "./www");
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
auto res = cli.Get(u8"/日本語Dir/日本語File.txt");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(u8"日本語コンテンツ", res->body);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(KeepAliveTest, ReadTimeout) {
|
||||
Server svr;
|
||||
@@ -5258,6 +5383,41 @@ TEST(KeepAliveTest, Issue1041) {
|
||||
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, Issue1959) {
|
||||
Server svr;
|
||||
svr.set_keep_alive_timeout(5);
|
||||
|
||||
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("a", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
if (!svr.is_running()) return;
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
using namespace std::chrono;
|
||||
auto start = steady_clock::now();
|
||||
|
||||
cli.Get("/a");
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
|
||||
auto end = steady_clock::now();
|
||||
auto elapsed = duration_cast<milliseconds>(end - start).count();
|
||||
|
||||
EXPECT_LT(elapsed, 5000);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, SSLClientReconnection) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
@@ -5452,6 +5612,7 @@ TEST(LongPollingTest, ClientCloseDetection) {
|
||||
auto count = 10;
|
||||
while (count > 0 && sink.is_writable()) {
|
||||
this_thread::sleep_for(chrono::milliseconds(10));
|
||||
count--;
|
||||
}
|
||||
EXPECT_FALSE(sink.is_writable()); // the socket is closed
|
||||
return true;
|
||||
@@ -6382,6 +6543,40 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(StatusCode::MovedPermanently_301, res->status);
|
||||
}
|
||||
|
||||
TEST(SendAPI, WithParamsInRequest) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_TRUE(req.has_param("test"));
|
||||
EXPECT_EQ("test_value", req.get_param_value("test"));
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
{
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
req.path = "/";
|
||||
req.params.emplace("test", "test_value");
|
||||
auto res = cli.send(req);
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
{
|
||||
auto res = cli.Get("/", {{"test", "test_value"}}, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClientImplMethods, GetSocketTest) {
|
||||
httplib::Server svr;
|
||||
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||
|
||||
+5
-5
@@ -28,26 +28,26 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -177,4 +177,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2024 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
configure_file(input: 'empty_file', output: 'empty_file', copy: true)
|
||||
configure_file(input: 'file', output: 'file', copy: true)
|
||||
subdir('dir')
|
||||
@@ -0,0 +1 @@
|
||||
日本語コンテンツ
|
||||
Reference in New Issue
Block a user