mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7bc00e330 | |||
| 11a40584e9 | |||
| 3e86bdb4d8 | |||
| c817d65695 | |||
| 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 | |||
| e64379c3d7 | |||
| 5053912534 | |||
| 932b1cbc32 | |||
| de36ea7755 | |||
| 9f8db2c230 | |||
| 3f00e1b321 | |||
| 7ab9c119ef | |||
| 3f2922b3fa | |||
| 509f583dca | |||
| 2d01e71286 | |||
| e612154694 | |||
| 82fcbe3901 | |||
| dbd2465b56 | |||
| ea79494b29 | |||
| f35aff84c2 | |||
| 7b18ae6f16 | |||
| a79c56d06b | |||
| 3d6e315a4c | |||
| 4c27f9c6ef | |||
| d173a37d17 | |||
| 7fd346a2ca | |||
| c673d502b9 | |||
| c43c51362a | |||
| 3e86d93d13 | |||
| f6e4e2d0f3 | |||
| 01a52aa8bd | |||
| 8415bf0823 | |||
| 327ff263f5 |
@@ -19,7 +19,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
+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"
|
||||
|
||||
+3
-2
@@ -9,6 +9,8 @@ example/benchmark
|
||||
example/redirect
|
||||
example/sse*
|
||||
example/upload
|
||||
example/one_time_request
|
||||
example/server_and_client
|
||||
example/*.pem
|
||||
test/httplib.cc
|
||||
test/httplib.h
|
||||
@@ -22,8 +24,7 @@ test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
work/
|
||||
benchmark/server
|
||||
benchmark/server-crow
|
||||
benchmark/server*
|
||||
|
||||
*.swp
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -77,6 +77,9 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
// Disable host verification
|
||||
cli.enable_server_hostname_verification(false);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -384,6 +387,18 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Send file content
|
||||
|
||||
```cpp
|
||||
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||
res.set_file_content("./path/to/conent.html");
|
||||
});
|
||||
|
||||
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||
res.set_file_content("./path/to/conent", "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
@@ -542,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");
|
||||
```
|
||||
@@ -808,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++
|
||||
@@ -819,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`
|
||||
@@ -828,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
|
||||
-------------------------------
|
||||
|
||||
+37
-14
@@ -1,23 +1,48 @@
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
|
||||
THEAD_POOL_COUNT = 16
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
BENCH_CMD = bombardier -c 8 -d 5s localhost:8080
|
||||
# BENCH_CMD = wrk -d 5s http://localhost:8080
|
||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||
MONITOR = ali http://localhost:8080
|
||||
|
||||
# cpp-httplib
|
||||
bench: server
|
||||
@./server & export PID=$$!; $(BENCH_CMD); kill $${PID}
|
||||
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||
@./server & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor: server
|
||||
@./server & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
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.18.0\n---------------------\n"
|
||||
@./server-base & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-base: server-base
|
||||
@./server-base & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-base : server-base
|
||||
@./server-base
|
||||
|
||||
server-base : cpp-httplib-base/main.cpp cpp-httplib-base/httplib.h
|
||||
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-base/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@./server-crow & export PID=$$!; $(BENCH_CMD); kill $${PID}
|
||||
@echo "-------------\n Crow v1.2.0\n-------------\n"
|
||||
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-crow: server-crow
|
||||
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
@@ -25,15 +50,13 @@ run-crow : server-crow
|
||||
server-crow : crow/main.cpp
|
||||
g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
|
||||
# flask
|
||||
bench-flask:
|
||||
@FLASK_APP=flask/main.py flask run --port=8080 & export PID=$$!; $(BENCH_CMD); kill $${PID}
|
||||
|
||||
run-flask:
|
||||
@FLASK_APP=flask/main.py flask run --port=8080
|
||||
|
||||
# misc
|
||||
bench-all: bench bench-crow bench-flask
|
||||
build: server server-base server-crow
|
||||
|
||||
bench-all: bench-crow bench bench-base
|
||||
|
||||
issue:
|
||||
bombardier -c 10 -d 30s localhost:8080
|
||||
|
||||
clean:
|
||||
rm -rf server*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
#include "./httplib.h"
|
||||
using namespace httplib;
|
||||
|
||||
int main() {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request &, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
rm -f httplib.h
|
||||
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
|
||||
@@ -1,9 +0,0 @@
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
import logging
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello, World!'
|
||||
+4
-1
@@ -53,9 +53,12 @@ benchmark : benchmark.cc ../httplib.h Makefile
|
||||
one_time_request : one_time_request.cc ../httplib.h Makefile
|
||||
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
server_and_client : server_and_client.cc ../httplib.h Makefile
|
||||
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.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 ssesvr ssecli benchmark one_time_request *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client *.pem
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// server_and_client.cc
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
std::string dump_headers(const Headers &headers) {
|
||||
std::string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
for (auto it = headers.begin(); it != headers.end(); ++it) {
|
||||
const auto &x = *it;
|
||||
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
|
||||
s += buf;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void logger(const Request &req, const Response &res) {
|
||||
std::string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
s += "================================\n";
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
|
||||
req.version.c_str(), req.path.c_str());
|
||||
s += buf;
|
||||
|
||||
std::string query;
|
||||
for (auto it = req.params.begin(); it != req.params.end(); ++it) {
|
||||
const auto &x = *it;
|
||||
snprintf(buf, sizeof(buf), "%c%s=%s",
|
||||
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
|
||||
x.second.c_str());
|
||||
query += buf;
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
|
||||
s += buf;
|
||||
|
||||
s += dump_headers(req.headers);
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
|
||||
s += buf;
|
||||
s += dump_headers(res.headers);
|
||||
s += "\n";
|
||||
|
||||
if (!res.body.empty()) { s += res.body; }
|
||||
|
||||
s += "\n";
|
||||
|
||||
std::cout << s;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Server
|
||||
Server svr;
|
||||
svr.set_logger(logger);
|
||||
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("POST", "text/plain");
|
||||
});
|
||||
|
||||
auto th = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
th.join();
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Client
|
||||
Client cli{"localhost", 8080};
|
||||
|
||||
std::string body = R"({"hello": "world"})";
|
||||
|
||||
auto res = cli.Post("/post", body, "application/json");
|
||||
std::cout << "--------------------------------" << std::endl;
|
||||
std::cout << to_string(res.error()) << std::endl;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.17.3"
|
||||
#define CPPHTTPLIB_VERSION "0.18.3"
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -30,20 +34,36 @@
|
||||
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
|
||||
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
|
||||
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
|
||||
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
|
||||
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
|
||||
@@ -273,7 +293,7 @@ using socket_t = int;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL)
|
||||
#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010107f
|
||||
#error Please use OpenSSL or a current version of BoringSSL
|
||||
#endif
|
||||
@@ -349,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 a, char b) { return to_lower(a) == to_lower(b); });
|
||||
std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
|
||||
return to_lower(ca) == to_lower(cb);
|
||||
});
|
||||
}
|
||||
|
||||
struct equal_to {
|
||||
@@ -375,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".
|
||||
@@ -591,6 +612,7 @@ using Ranges = std::vector<Range>;
|
||||
struct Request {
|
||||
std::string method;
|
||||
std::string path;
|
||||
Params params;
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
@@ -602,7 +624,6 @@ struct Request {
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
Params params;
|
||||
MultipartFormDataMap files;
|
||||
Ranges ranges;
|
||||
Match matches;
|
||||
@@ -675,6 +696,10 @@ struct Response {
|
||||
const std::string &content_type, ContentProviderWithoutLength provider,
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
void set_file_content(const std::string &path,
|
||||
const std::string &content_type);
|
||||
void set_file_content(const std::string &path);
|
||||
|
||||
Response() = default;
|
||||
Response(const Response &) = default;
|
||||
Response &operator=(const Response &) = default;
|
||||
@@ -692,6 +717,8 @@ struct Response {
|
||||
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
||||
bool is_chunked_content_provider_ = false;
|
||||
bool content_provider_success_ = false;
|
||||
std::string file_content_path_;
|
||||
std::string file_content_content_type_;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
@@ -786,7 +813,8 @@ private:
|
||||
fn();
|
||||
}
|
||||
|
||||
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL)
|
||||
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
|
||||
!defined(LIBRESSL_VERSION_NUMBER)
|
||||
OPENSSL_thread_stop();
|
||||
#endif
|
||||
}
|
||||
@@ -987,17 +1015,19 @@ public:
|
||||
std::function<TaskQueue *(void)> new_task_queue;
|
||||
|
||||
protected:
|
||||
bool process_request(Stream &strm, bool close_connection,
|
||||
bool process_request(Stream &strm, const std::string &remote_addr,
|
||||
int remote_port, const std::string &local_addr,
|
||||
int local_port, bool close_connection,
|
||||
bool &connection_closed,
|
||||
const std::function<void(Request &)> &setup_request);
|
||||
|
||||
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
|
||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
|
||||
time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
|
||||
time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
|
||||
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
||||
@@ -1110,6 +1140,7 @@ enum class Error {
|
||||
SSLConnection,
|
||||
SSLLoadingCerts,
|
||||
SSLServerVerification,
|
||||
SSLServerHostnameVerification,
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
@@ -1425,6 +1456,8 @@ public:
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
void enable_server_hostname_verification(bool enabled);
|
||||
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
@@ -1491,10 +1524,10 @@ protected:
|
||||
|
||||
time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
|
||||
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
|
||||
|
||||
std::string basic_auth_username_;
|
||||
std::string basic_auth_password_;
|
||||
@@ -1539,6 +1572,8 @@ protected:
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool server_certificate_verification_ = true;
|
||||
bool server_hostname_verification_ = true;
|
||||
std::function<bool(SSL *ssl)> server_certificate_verifier_;
|
||||
#endif
|
||||
|
||||
Logger logger_;
|
||||
@@ -1547,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;
|
||||
@@ -1844,6 +1882,8 @@ public:
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
void enable_server_hostname_verification(bool enabled);
|
||||
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
@@ -2135,6 +2175,8 @@ inline std::string to_string(const Error error) {
|
||||
case Error::SSLConnection: return "SSL connection failed";
|
||||
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
|
||||
case Error::SSLServerVerification: return "SSL server verification failed";
|
||||
case Error::SSLServerHostnameVerification:
|
||||
return "SSL server hostname verification failed";
|
||||
case Error::UnsupportedMultipartBoundaryChars:
|
||||
return "Unsupported HTTP multipart boundary characters";
|
||||
case Error::Compression: return "Compression failed";
|
||||
@@ -2219,9 +2261,35 @@ make_basic_authentication_header(const std::string &username,
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool is_file(const std::string &path);
|
||||
#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
|
||||
|
||||
bool is_dir(const std::string &path);
|
||||
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;
|
||||
};
|
||||
|
||||
std::string encode_query_param(const std::string &value);
|
||||
|
||||
@@ -2428,13 +2496,14 @@ public:
|
||||
|
||||
private:
|
||||
#if defined(_WIN32)
|
||||
HANDLE hFile_;
|
||||
HANDLE hMapping_;
|
||||
HANDLE hFile_ = NULL;
|
||||
HANDLE hMapping_ = NULL;
|
||||
#else
|
||||
int fd_;
|
||||
int fd_ = -1;
|
||||
#endif
|
||||
size_t size_;
|
||||
void *addr_;
|
||||
size_t size_ = 0;
|
||||
void *addr_ = nullptr;
|
||||
bool is_open_empty_file = false;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@@ -2592,14 +2661,19 @@ inline bool is_valid_path(const std::string &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool is_file(const std::string &path) {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
|
||||
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 is_dir(const std::string &path) {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
|
||||
inline bool FileStat::is_file() const {
|
||||
return ret_ >= 0 && S_ISREG(st_.st_mode);
|
||||
}
|
||||
inline bool FileStat::is_dir() const {
|
||||
return ret_ >= 0 && S_ISDIR(st_.st_mode);
|
||||
}
|
||||
|
||||
inline std::string encode_query_param(const std::string &value) {
|
||||
@@ -2855,16 +2929,7 @@ inline void stream_line_reader::append(char c) {
|
||||
}
|
||||
}
|
||||
|
||||
inline mmap::mmap(const char *path)
|
||||
#if defined(_WIN32)
|
||||
: hFile_(NULL), hMapping_(NULL)
|
||||
#else
|
||||
: fd_(-1)
|
||||
#endif
|
||||
,
|
||||
size_(0), addr_(nullptr) {
|
||||
open(path);
|
||||
}
|
||||
inline mmap::mmap(const char *path) { open(path); }
|
||||
|
||||
inline mmap::~mmap() { close(); }
|
||||
|
||||
@@ -2872,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,
|
||||
@@ -2906,6 +2969,13 @@ inline bool mmap::open(const char *path) {
|
||||
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
#endif
|
||||
|
||||
// Special treatment for an empty file...
|
||||
if (hMapping_ == NULL && size_ == 0) {
|
||||
close();
|
||||
is_open_empty_file = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hMapping_ == NULL) {
|
||||
close();
|
||||
return false;
|
||||
@@ -2916,6 +2986,11 @@ inline bool mmap::open(const char *path) {
|
||||
#else
|
||||
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
|
||||
#endif
|
||||
|
||||
if (addr_ == nullptr) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
fd_ = ::open(path, O_RDONLY);
|
||||
if (fd_ == -1) { return false; }
|
||||
@@ -2928,22 +3003,26 @@ inline bool mmap::open(const char *path) {
|
||||
size_ = static_cast<size_t>(sb.st_size);
|
||||
|
||||
addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
|
||||
#endif
|
||||
|
||||
if (addr_ == nullptr) {
|
||||
// Special treatment for an empty file...
|
||||
if (addr_ == MAP_FAILED && size_ == 0) {
|
||||
close();
|
||||
is_open_empty_file = true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool mmap::is_open() const { return addr_ != nullptr; }
|
||||
inline bool mmap::is_open() const {
|
||||
return is_open_empty_file ? true : addr_ != nullptr;
|
||||
}
|
||||
|
||||
inline size_t mmap::size() const { return size_; }
|
||||
|
||||
inline const char *mmap::data() const {
|
||||
return static_cast<const char *>(addr_);
|
||||
return is_open_empty_file ? "" : static_cast<const char *>(addr_);
|
||||
}
|
||||
|
||||
inline void mmap::close() {
|
||||
@@ -2962,6 +3041,8 @@ inline void mmap::close() {
|
||||
::CloseHandle(hFile_);
|
||||
hFile_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
is_open_empty_file = false;
|
||||
#else
|
||||
if (addr_ != nullptr) {
|
||||
munmap(addr_, size_);
|
||||
@@ -3199,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,
|
||||
@@ -3207,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);
|
||||
@@ -3343,6 +3456,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
#endif
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
|
||||
|
||||
for (auto rp = result; rp; rp = rp->ai_next) {
|
||||
// Create a socket
|
||||
@@ -3412,17 +3526,13 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
|
||||
// bind or connect
|
||||
auto quit = false;
|
||||
if (bind_or_connect(sock, *rp, quit)) {
|
||||
freeaddrinfo(result);
|
||||
return sock;
|
||||
}
|
||||
if (bind_or_connect(sock, *rp, quit)) { return sock; }
|
||||
|
||||
close_socket(sock);
|
||||
|
||||
if (quit) { break; }
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
@@ -3455,6 +3565,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
|
||||
hints.ai_protocol = 0;
|
||||
|
||||
if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; }
|
||||
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
|
||||
|
||||
auto ret = false;
|
||||
for (auto rp = result; rp; rp = rp->ai_next) {
|
||||
@@ -3465,7 +3576,6 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -3477,6 +3587,8 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
|
||||
inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
|
||||
|
||||
std::string addr_candidate;
|
||||
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr && ifn == ifa->ifa_name &&
|
||||
@@ -3486,7 +3598,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
|
||||
freeifaddrs(ifap);
|
||||
return std::string(buf, INET_ADDRSTRLEN);
|
||||
}
|
||||
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
@@ -3499,7 +3610,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
|
||||
addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
|
||||
} else {
|
||||
freeifaddrs(ifap);
|
||||
return std::string(buf, INET6_ADDRSTRLEN);
|
||||
}
|
||||
}
|
||||
@@ -3507,7 +3617,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
return addr_candidate;
|
||||
}
|
||||
#endif
|
||||
@@ -3674,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);
|
||||
}
|
||||
|
||||
@@ -3759,8 +3868,9 @@ inline bool can_compress_content_type(const std::string &content_type) {
|
||||
case "application/protobuf"_t:
|
||||
case "application/xhtml+xml"_t: return true;
|
||||
|
||||
default:
|
||||
return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
|
||||
case "text/event-stream"_t: return false;
|
||||
|
||||
default: return !content_type.rfind("text/", 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4028,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; }
|
||||
|
||||
@@ -4176,8 +4286,19 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
|
||||
assert(chunk_len == 0);
|
||||
|
||||
// Trailer
|
||||
if (!line_reader.getline()) { return false; }
|
||||
// NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked
|
||||
// transfer coding is complete when a chunk with a chunk-size of zero is
|
||||
// received, possibly followed by a trailer section, and finally terminated by
|
||||
// an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
|
||||
//
|
||||
// In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
|
||||
// does't care for the existence of the final CRLF. In other words, it seems
|
||||
// to be ok whether the final CRLF exists or not in the chunked data.
|
||||
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
|
||||
//
|
||||
// According to the reference code in RFC 9112, cpp-htpplib now allows
|
||||
// chuncked transfer coding data without the final CRLF.
|
||||
if (!line_reader.getline()) { return true; }
|
||||
|
||||
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
|
||||
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
||||
@@ -5496,6 +5617,7 @@ inline void hosted_at(const std::string &hostname,
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
|
||||
|
||||
for (auto rp = result; rp; rp = rp->ai_next) {
|
||||
const auto &addr =
|
||||
@@ -5507,8 +5629,6 @@ inline void hosted_at(const std::string &hostname,
|
||||
addrs.push_back(ip);
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
inline std::string append_query_params(const std::string &path,
|
||||
@@ -5703,6 +5823,16 @@ inline void Response::set_chunked_content_provider(
|
||||
is_chunked_content_provider_ = true;
|
||||
}
|
||||
|
||||
inline void Response::set_file_content(const std::string &path,
|
||||
const std::string &content_type) {
|
||||
file_content_path_ = path;
|
||||
file_content_content_type_ = content_type;
|
||||
}
|
||||
|
||||
inline void Response::set_file_content(const std::string &path) {
|
||||
file_content_path_ = path;
|
||||
}
|
||||
|
||||
// Result implementation
|
||||
inline bool Result::has_request_header(const std::string &key) const {
|
||||
return request_headers_.find(key) != request_headers_.end();
|
||||
@@ -6034,7 +6164,8 @@ inline bool Server::set_base_dir(const std::string &dir,
|
||||
|
||||
inline bool Server::set_mount_point(const std::string &mount_point,
|
||||
const std::string &dir, Headers headers) {
|
||||
if (detail::is_dir(dir)) {
|
||||
detail::FileStat stat(dir);
|
||||
if (stat.is_dir()) {
|
||||
std::string mnt = !mount_point.empty() ? mount_point : "/";
|
||||
if (!mnt.empty() && mnt[0] == '/') {
|
||||
base_dirs_.push_back({mnt, dir, std::move(headers)});
|
||||
@@ -6518,12 +6649,14 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
|
||||
auto path = entry.base_dir + sub_path;
|
||||
if (path.back() == '/') { path += "index.html"; }
|
||||
|
||||
if (detail::is_dir(path)) {
|
||||
detail::FileStat stat(path);
|
||||
|
||||
if (stat.is_dir()) {
|
||||
res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (detail::is_file(path)) {
|
||||
if (stat.is_file()) {
|
||||
for (const auto &kv : entry.headers) {
|
||||
res.set_header(kv.first, kv.second);
|
||||
}
|
||||
@@ -6904,7 +7037,9 @@ inline bool Server::dispatch_request_for_content_reader(
|
||||
}
|
||||
|
||||
inline bool
|
||||
Server::process_request(Stream &strm, bool close_connection,
|
||||
Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
int remote_port, const std::string &local_addr,
|
||||
int local_port, bool close_connection,
|
||||
bool &connection_closed,
|
||||
const std::function<void(Request &)> &setup_request) {
|
||||
std::array<char, 2048> buf{};
|
||||
@@ -6958,11 +7093,13 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
connection_closed = true;
|
||||
}
|
||||
|
||||
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
||||
req.remote_addr = remote_addr;
|
||||
req.remote_port = remote_port;
|
||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
||||
|
||||
strm.get_local_ip_and_port(req.local_addr, req.local_port);
|
||||
req.local_addr = local_addr;
|
||||
req.local_port = local_port;
|
||||
req.set_header("LOCAL_ADDR", req.local_addr);
|
||||
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
|
||||
|
||||
@@ -7043,6 +7180,32 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// Serve file content by using a content provider
|
||||
if (!res.file_content_path_.empty()) {
|
||||
const auto &path = res.file_content_path_;
|
||||
auto mm = std::make_shared<detail::mmap>(path.c_str());
|
||||
if (!mm->is_open()) {
|
||||
res.body.clear();
|
||||
res.content_length_ = 0;
|
||||
res.content_provider_ = nullptr;
|
||||
res.status = StatusCode::NotFound_404;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
auto content_type = res.file_content_content_type_;
|
||||
if (content_type.empty()) {
|
||||
content_type = detail::find_content_type(
|
||||
path, file_extension_and_mimetype_map_, default_file_mimetype_);
|
||||
}
|
||||
|
||||
res.set_content_provider(
|
||||
mm->size(), content_type,
|
||||
[mm](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||
sink.write(mm->data() + offset, length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return write_response_with_content(strm, close_connection, req, res);
|
||||
} else {
|
||||
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
||||
@@ -7054,12 +7217,21 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
inline bool Server::is_valid() const { return true; }
|
||||
|
||||
inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
std::string remote_addr;
|
||||
int remote_port = 0;
|
||||
detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
|
||||
|
||||
std::string local_addr;
|
||||
int local_port = 0;
|
||||
detail::get_local_ip_and_port(sock, local_addr, local_port);
|
||||
|
||||
auto ret = detail::process_server_socket(
|
||||
svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
[this](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||
return process_request(strm, close_connection, connection_closed,
|
||||
[&](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||
return process_request(strm, remote_addr, remote_port, local_addr,
|
||||
local_port, close_connection, connection_closed,
|
||||
nullptr);
|
||||
});
|
||||
|
||||
@@ -7131,6 +7303,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
#endif
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
server_certificate_verification_ = rhs.server_certificate_verification_;
|
||||
server_hostname_verification_ = rhs.server_hostname_verification_;
|
||||
server_certificate_verifier_ = rhs.server_certificate_verifier_;
|
||||
#endif
|
||||
logger_ = rhs.logger_;
|
||||
}
|
||||
@@ -7242,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_);
|
||||
@@ -7253,6 +7435,15 @@ 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()) {
|
||||
detail::set_nonblocking(socket_.sock, true);
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
|
||||
detail::set_nonblocking(socket_.sock, 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
|
||||
@@ -7529,12 +7720,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_) {
|
||||
@@ -7594,7 +7799,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);
|
||||
@@ -7735,9 +7946,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;
|
||||
}
|
||||
@@ -7755,7 +7964,9 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// Body
|
||||
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
|
||||
req.method != "CONNECT") {
|
||||
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
|
||||
auto redirect = 300 < res.status && res.status < 400 &&
|
||||
res.status != StatusCode::NotModified_304 &&
|
||||
follow_location_;
|
||||
|
||||
if (req.response_handler && !redirect) {
|
||||
if (!req.response_handler(res)) {
|
||||
@@ -7776,9 +7987,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;
|
||||
});
|
||||
@@ -7792,18 +8001,23 @@ 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(static_cast<size_t>(len));
|
||||
}
|
||||
}
|
||||
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress), std::move(out),
|
||||
decompress_)) {
|
||||
if (error != Error::Canceled) { error = Error::Read; }
|
||||
return false;
|
||||
if (res.status != StatusCode::NotModified_304) {
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress),
|
||||
std::move(out), decompress_)) {
|
||||
if (error != Error::Canceled) { error = Error::Read; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8608,13 +8822,11 @@ inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
|
||||
std::size_t size) const {
|
||||
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
||||
auto se = detail::scope_exit([&] { BIO_free_all(mem); });
|
||||
if (!mem) { return nullptr; }
|
||||
|
||||
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
||||
if (!inf) {
|
||||
BIO_free_all(mem);
|
||||
return nullptr;
|
||||
}
|
||||
if (!inf) { return nullptr; }
|
||||
|
||||
auto cts = X509_STORE_new();
|
||||
if (cts) {
|
||||
@@ -8628,13 +8840,21 @@ inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
|
||||
}
|
||||
|
||||
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
||||
BIO_free_all(mem);
|
||||
return cts;
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
|
||||
server_hostname_verification_ = enabled;
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_server_certificate_verifier(
|
||||
std::function<bool(SSL *ssl)> verifier) {
|
||||
server_certificate_verifier_ = verifier;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void ClientImpl::set_logger(Logger logger) {
|
||||
@@ -8886,7 +9106,8 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
|
||||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
|
||||
1) {
|
||||
1 ||
|
||||
SSL_CTX_check_private_key(ctx_) != 1) {
|
||||
SSL_CTX_free(ctx_);
|
||||
ctx_ = nullptr;
|
||||
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
|
||||
@@ -8966,13 +9187,22 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
|
||||
auto ret = false;
|
||||
if (ssl) {
|
||||
std::string remote_addr;
|
||||
int remote_port = 0;
|
||||
detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
|
||||
|
||||
std::string local_addr;
|
||||
int local_port = 0;
|
||||
detail::get_local_ip_and_port(sock, local_addr, local_port);
|
||||
|
||||
ret = detail::process_server_socket_ssl(
|
||||
svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
[this, ssl](Stream &strm, bool close_connection,
|
||||
bool &connection_closed) {
|
||||
return process_request(strm, close_connection, connection_closed,
|
||||
[&](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||
return process_request(strm, remote_addr, remote_port, local_addr,
|
||||
local_port, close_connection,
|
||||
connection_closed,
|
||||
[&](Request &req) { req.ssl = ssl; });
|
||||
});
|
||||
|
||||
@@ -9001,6 +9231,8 @@ inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
: ClientImpl(host, port, client_cert_path, client_key_path) {
|
||||
ctx_ = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
|
||||
|
||||
detail::split(&host_[0], &host_[host_.size()], '.',
|
||||
[&](const char *b, const char *e) {
|
||||
host_components_.emplace_back(b, e);
|
||||
@@ -9208,26 +9440,34 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
if (server_certificate_verification_) {
|
||||
verify_result_ = SSL_get_verify_result(ssl2);
|
||||
if (server_certificate_verifier_) {
|
||||
if (!server_certificate_verifier_(ssl2)) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
verify_result_ = SSL_get_verify_result(ssl2);
|
||||
|
||||
if (verify_result_ != X509_V_OK) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
if (verify_result_ != X509_V_OK) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto server_cert = SSL_get1_peer_certificate(ssl2);
|
||||
auto se = detail::scope_exit([&] { X509_free(server_cert); });
|
||||
|
||||
if (server_cert == nullptr) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server_hostname_verification_) {
|
||||
if (!verify_host(server_cert)) {
|
||||
error = Error::SSLServerHostnameVerification;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto server_cert = SSL_get1_peer_certificate(ssl2);
|
||||
|
||||
if (server_cert == nullptr) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verify_host(server_cert)) {
|
||||
X509_free(server_cert);
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
X509_free(server_cert);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -9315,8 +9555,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__
|
||||
@@ -9958,6 +10198,15 @@ inline void Client::set_proxy_digest_auth(const std::string &username,
|
||||
inline void Client::enable_server_certificate_verification(bool enabled) {
|
||||
cli_->enable_server_certificate_verification(enabled);
|
||||
}
|
||||
|
||||
inline void Client::enable_server_hostname_verification(bool enabled) {
|
||||
cli_->enable_server_hostname_verification(enabled);
|
||||
}
|
||||
|
||||
inline void Client::set_server_certificate_verifier(
|
||||
std::function<bool(SSL *ssl)> verifier) {
|
||||
cli_->set_server_certificate_verifier(verifier);
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Client::set_logger(Logger logger) {
|
||||
|
||||
+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
|
||||
|
||||
|
||||
@@ -50,8 +50,12 @@ class FuzzableServer : public httplib::Server {
|
||||
public:
|
||||
void ProcessFuzzedRequest(FuzzedStream &stream) {
|
||||
bool connection_close = false;
|
||||
process_request(stream, /*last_connection=*/false, connection_close,
|
||||
nullptr);
|
||||
process_request(stream,
|
||||
/*remote_addr=*/"",
|
||||
/*remote_port =*/0,
|
||||
/*local_addr=*/"",
|
||||
/*local_port =*/0,
|
||||
/*last_connection=*/false, connection_close, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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 = []
|
||||
|
||||
+305
-60
@@ -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) {
|
||||
@@ -2300,6 +2402,18 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
})
|
||||
.Get("/file_content",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_file_content("./www/dir/test.html");
|
||||
})
|
||||
.Get("/file_content_with_content_type",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_file_content("./www/file", "text/plain");
|
||||
})
|
||||
.Get("/invalid_file_content",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_file_content("./www/dir/invalid_file_path");
|
||||
})
|
||||
.Get("/http_response_splitting",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
||||
@@ -2904,6 +3018,39 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetEmptyFile) {
|
||||
auto res = cli_.Get("/empty_file");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("application/octet-stream", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(0, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetFileContent) {
|
||||
auto res = cli_.Get("/file_content");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(9, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("test.html", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetFileContentWithContentType) {
|
||||
auto res = cli_.Get("/file_content_with_content_type");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(5, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("file\n", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetInvalidFileContent) {
|
||||
auto res = cli_.Get("/invalid_file_content");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3655,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}})}});
|
||||
@@ -3742,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"));
|
||||
@@ -3752,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"));
|
||||
@@ -3762,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"));
|
||||
@@ -3772,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"));
|
||||
@@ -4086,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"));
|
||||
}
|
||||
|
||||
@@ -4517,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());
|
||||
@@ -4566,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());
|
||||
@@ -4722,9 +4903,10 @@ static void test_raw_request(const std::string &req,
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Get("/header_field_value_check", [&](const Request &/*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Get("/header_field_value_check",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
// Server read timeout must be longer than the client read timeout for the
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
@@ -4866,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;
|
||||
|
||||
@@ -5105,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();
|
||||
@@ -5124,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;
|
||||
@@ -5212,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);
|
||||
@@ -5406,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;
|
||||
@@ -6336,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) {
|
||||
@@ -7630,17 +7871,19 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
||||
auto file_path = "./www/dir/index.html";
|
||||
auto dir_path = "./www/dir";
|
||||
|
||||
EXPECT_TRUE(detail::is_file(file_path));
|
||||
EXPECT_FALSE(detail::is_dir(file_path));
|
||||
detail::FileStat stat_file(file_path);
|
||||
EXPECT_TRUE(stat_file.is_file());
|
||||
EXPECT_FALSE(stat_file.is_dir());
|
||||
|
||||
EXPECT_FALSE(detail::is_file(dir_path));
|
||||
EXPECT_TRUE(detail::is_dir(dir_path));
|
||||
detail::FileStat stat_dir(dir_path);
|
||||
EXPECT_FALSE(stat_dir.is_file());
|
||||
EXPECT_TRUE(stat_dir.is_dir());
|
||||
}
|
||||
|
||||
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/test", [&](const Request &/*req*/, Response &res) {
|
||||
svr.Get("/test", [&](const Request & /*req*/, Response &res) {
|
||||
EXPECT_EQ(res.status, 400);
|
||||
});
|
||||
|
||||
@@ -7666,11 +7909,12 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
|
||||
Server svr;
|
||||
|
||||
svr.set_expect_100_continue_handler([](const Request &/*req*/, Response &res) {
|
||||
res.status = StatusCode::Unauthorized_401;
|
||||
res.set_content(reject, "text/plain");
|
||||
return res.status;
|
||||
});
|
||||
svr.set_expect_100_continue_handler(
|
||||
[](const Request & /*req*/, Response &res) {
|
||||
res.status = StatusCode::Unauthorized_401;
|
||||
res.set_content(reject, "text/plain");
|
||||
return res.status;
|
||||
});
|
||||
svr.Post("/", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(accept, "text/plain");
|
||||
});
|
||||
@@ -7745,7 +7989,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
|
||||
{
|
||||
auto dl = curl_off_t{};
|
||||
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||
const auto res =
|
||||
curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||
ASSERT_EQ(res, CURLE_OK);
|
||||
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
|
||||
}
|
||||
|
||||
+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