mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-08 07:54:42 +07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d05c343602 | ||
|
|
33f67386fe | ||
|
|
56d8168dc4 | ||
|
|
5d87cc0558 | ||
|
|
cb41947eb4 | ||
|
|
0857eba17b | ||
|
|
020b0db090 | ||
|
|
bf0760fde4 | ||
|
|
bb8e45383e | ||
|
|
a1df576e4f | ||
|
|
7fb0254794 | ||
|
|
c82d1e52cc | ||
|
|
846151b605 | ||
|
|
e44e31dd5b | ||
|
|
f7b9501662 | ||
|
|
e12fe4cbbb | ||
|
|
49d2e1f135 | ||
|
|
8191fd8e6c | ||
|
|
d73395e1dc | ||
|
|
64d001162b | ||
|
|
bb00a23116 | ||
|
|
63d6e9b91b | ||
|
|
66eed5681a | ||
|
|
8ecdb11979 | ||
|
|
894fcc8e02 | ||
|
|
7f43f0f3ff | ||
|
|
87e03dd1ce | ||
|
|
e5cacb465d | ||
|
|
ee8371f753 | ||
|
|
081723f983 | ||
|
|
b61f36579c | ||
|
|
33f53aa458 | ||
|
|
412ab5f063 | ||
|
|
11e02e901c | ||
|
|
65a8f4cf44 | ||
|
|
27d128bbb4 | ||
|
|
070f9bec58 | ||
|
|
f817032513 | ||
|
|
17abe221c0 | ||
|
|
4a7a81e039 | ||
|
|
37fd4eb643 | ||
|
|
865b0e4c03 | ||
|
|
b324921c1a | ||
|
|
63f72caf30 | ||
|
|
99ac17b90a | ||
|
|
4b0ed9ee88 | ||
|
|
20056f6cda | ||
|
|
3b35279b16 | ||
|
|
27deb44df5 | ||
|
|
24a3ef949b | ||
|
|
bc3e098964 | ||
|
|
c247dcdd7b | ||
|
|
793ae9855e | ||
|
|
9fa426d51b | ||
|
|
cec6288a99 |
@@ -13,10 +13,12 @@ example/*.pem
|
|||||||
test/httplib.cc
|
test/httplib.cc
|
||||||
test/httplib.h
|
test/httplib.h
|
||||||
test/test
|
test/test
|
||||||
|
test/server_fuzzer
|
||||||
test/test_proxy
|
test/test_proxy
|
||||||
test/test_split
|
test/test_split
|
||||||
test/test.xcodeproj/xcuser*
|
test/test.xcodeproj/xcuser*
|
||||||
test/test.xcodeproj/*/xcuser*
|
test/test.xcodeproj/*/xcuser*
|
||||||
|
test/*.o
|
||||||
test/*.pem
|
test/*.pem
|
||||||
test/*.srl
|
test/*.srl
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,11 @@ if(HTTPLIB_COMPILE)
|
|||||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||||
)
|
)
|
||||||
|
set_target_properties(${PROJECT_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
VERSION ${${PROJECT_NAME}_VERSION}
|
||||||
|
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
# This is for header-only.
|
# This is for header-only.
|
||||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||||
|
|||||||
@@ -183,6 +183,15 @@ The followings are built-in mappings:
|
|||||||
| webm | video/webm | zip | application/zip |
|
| webm | video/webm | zip | application/zip |
|
||||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||||
|
|
||||||
|
### File request handler
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// The handler is called right before the response is sent to a client
|
||||||
|
svr.set_file_request_handler([](const Request &req, Response &res) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
NOTE: These static file server methods are not thread-safe.
|
NOTE: These static file server methods are not thread-safe.
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
@@ -303,7 +312,7 @@ Without content length:
|
|||||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||||
res.set_content_provider(
|
res.set_content_provider(
|
||||||
"text/plain", // Content type
|
"text/plain", // Content type
|
||||||
[&](size_t offset, size_t length, DataSink &sink) {
|
[&](size_t offset, DataSink &sink) {
|
||||||
if (/* there is still data */) {
|
if (/* there is still data */) {
|
||||||
std::vector<char> data;
|
std::vector<char> data;
|
||||||
// prepare data...
|
// prepare data...
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM alpine as builder
|
||||||
|
WORKDIR /src/example
|
||||||
|
RUN apk add g++ make openssl-dev zlib-dev brotli-dev
|
||||||
|
COPY ./httplib.h /src
|
||||||
|
COPY ./example/hello.cc /src/example
|
||||||
|
COPY ./example/Makefile /src/example
|
||||||
|
RUN make hello
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
RUN apk --no-cache add brotli libstdc++
|
||||||
|
COPY --from=builder /src/example/hello /bin/hello
|
||||||
|
CMD ["/bin/hello"]
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#CXX = clang++
|
#CXX = clang++
|
||||||
CXXFLAGS = -std=c++11 -I.. -Wall -Wextra -pthread
|
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||||
|
|
||||||
PREFIX = /usr/local
|
PREFIX = /usr/local
|
||||||
#PREFIX = $(shell brew --prefix)
|
#PREFIX = $(shell brew --prefix)
|
||||||
|
|||||||
+1
-1
@@ -15,5 +15,5 @@ int main(void) {
|
|||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.listen("localhost", 8080);
|
svr.listen("0.0.0.0", 8080);
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
for i in {1..10000}
|
||||||
|
do
|
||||||
|
echo "#### $i ####"
|
||||||
|
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
|
||||||
|
done
|
||||||
+12
-3
@@ -19,12 +19,13 @@ project(
|
|||||||
# Check just in case downstream decides to edit the source
|
# Check just in case downstream decides to edit the source
|
||||||
# and add a project version
|
# and add a project version
|
||||||
version = meson.project_version()
|
version = meson.project_version()
|
||||||
python = import('python').find_installation('python3')
|
python3 = find_program('python3')
|
||||||
if version == 'undefined'
|
if version == 'undefined'
|
||||||
# Meson doesn't have regular expressions, but since it is implemented
|
# Meson doesn't have regular expressions, but since it is implemented
|
||||||
# in python we can be sure we can use it to parse the file manually
|
# in python we can be sure we can use it to parse the file manually
|
||||||
version = run_command(
|
version = run_command(
|
||||||
python, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))'
|
python3, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))',
|
||||||
|
check: true
|
||||||
).stdout().strip()
|
).stdout().strip()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ if get_option('cpp-httplib_compile')
|
|||||||
'split',
|
'split',
|
||||||
input: 'httplib.h',
|
input: 'httplib.h',
|
||||||
output: ['httplib.cc', 'httplib.h'],
|
output: ['httplib.cc', 'httplib.h'],
|
||||||
command: [python, files('split.py'), '--out', meson.current_build_dir()],
|
command: [python3, files('split.py'), '--out', meson.current_build_dir()],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: [false, get_option('includedir')]
|
install_dir: [false, get_option('includedir')]
|
||||||
)
|
)
|
||||||
@@ -92,6 +93,14 @@ if get_option('cpp-httplib_compile')
|
|||||||
else
|
else
|
||||||
install_headers('httplib.h')
|
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: 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'),
|
||||||
|
url: 'https://github.com/yhirose/cpp-httplib',
|
||||||
|
version: version
|
||||||
|
)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if meson.version().version_compare('>=0.54.0')
|
if meson.version().version_compare('>=0.54.0')
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@
|
|||||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires Python 3)')
|
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file')
|
||||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ cert.pem:
|
|||||||
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.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 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 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
|
||||||
#c_rehash .
|
#c_rehash .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -33,6 +33,19 @@ cert2_pem = custom_target(
|
|||||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
key_encrypted_pem = custom_target(
|
||||||
|
'key_encrypted_pem',
|
||||||
|
output: 'key_encrypted.pem',
|
||||||
|
command: [openssl, 'genrsa', '-passout', 'pass:test123!', '-out', '@OUTPUT@', '2048']
|
||||||
|
)
|
||||||
|
|
||||||
|
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']
|
||||||
|
)
|
||||||
|
|
||||||
rootca_key_pem = custom_target(
|
rootca_key_pem = custom_target(
|
||||||
'rootca_key_pem',
|
'rootca_key_pem',
|
||||||
output: 'rootCA.key.pem',
|
output: 'rootCA.key.pem',
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
docker-compose down --rmi all
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
docker-compose up -d
|
|
||||||
+236
-20
@@ -18,6 +18,9 @@
|
|||||||
#define CLIENT_CA_CERT_DIR "."
|
#define CLIENT_CA_CERT_DIR "."
|
||||||
#define CLIENT_CERT_FILE "./client.cert.pem"
|
#define CLIENT_CERT_FILE "./client.cert.pem"
|
||||||
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
|
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
|
||||||
|
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
|
||||||
|
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
|
||||||
|
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace httplib;
|
using namespace httplib;
|
||||||
@@ -58,6 +61,14 @@ TEST(StartupTest, WSAStartup) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
TEST(DecodeURLTest, PercentCharacter) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||||
string unescapedCharacters = "-_.!~*'()";
|
string unescapedCharacters = "-_.!~*'()";
|
||||||
|
|
||||||
@@ -393,6 +404,31 @@ TEST(ChunkedEncodingTest, FromHTTPWatch_Online) {
|
|||||||
EXPECT_EQ(out, res->body);
|
EXPECT_EQ(out, res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(HostnameToIPConversionTest, HTTPWatch_Online) {
|
||||||
|
auto host = "www.httpwatch.com";
|
||||||
|
|
||||||
|
auto ip = hosted_at(host);
|
||||||
|
EXPECT_EQ("191.236.16.12", ip);
|
||||||
|
|
||||||
|
std::vector<std::string> addrs;
|
||||||
|
hosted_at(host, addrs);
|
||||||
|
EXPECT_EQ(1u, addrs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // It depends on each test environment...
|
||||||
|
TEST(HostnameToIPConversionTest, YouTube_Online) {
|
||||||
|
auto host = "www.youtube.com";
|
||||||
|
|
||||||
|
std::vector<std::string> addrs;
|
||||||
|
hosted_at(host, addrs);
|
||||||
|
|
||||||
|
EXPECT_EQ(20u, addrs.size());
|
||||||
|
|
||||||
|
auto it = std::find(addrs.begin(), addrs.end(), "2607:f8b0:4006:809::200e");
|
||||||
|
EXPECT_TRUE(it != addrs.end());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(ChunkedEncodingTest, WithContentReceiver_Online) {
|
TEST(ChunkedEncodingTest, WithContentReceiver_Online) {
|
||||||
auto host = "www.httpwatch.com";
|
auto host = "www.httpwatch.com";
|
||||||
|
|
||||||
@@ -574,10 +610,11 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
|||||||
|
|
||||||
auto res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
ASSERT_TRUE(!res);
|
ASSERT_TRUE(!res);
|
||||||
EXPECT_EQ(Error::Connection, res.error());
|
EXPECT_TRUE(Error::Connection == res.error() ||
|
||||||
|
Error::ConnectionTimeout == res.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConnectionErrorTest, Timeout) {
|
TEST(ConnectionErrorTest, Timeout_Online) {
|
||||||
auto host = "google.com";
|
auto host = "google.com";
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
@@ -589,9 +626,14 @@ TEST(ConnectionErrorTest, Timeout) {
|
|||||||
#endif
|
#endif
|
||||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||||
|
|
||||||
|
// only probe one address type so that the error reason
|
||||||
|
// correlates to the timed-out IPv4, not the unsupported
|
||||||
|
// IPv6 connection attempt
|
||||||
|
cli.set_address_family(AF_INET);
|
||||||
|
|
||||||
auto res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
ASSERT_TRUE(!res);
|
ASSERT_TRUE(!res);
|
||||||
EXPECT_TRUE(res.error() == Error::Connection);
|
EXPECT_EQ(Error::ConnectionTimeout, res.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CancelTest, NoCancel_Online) {
|
TEST(CancelTest, NoCancel_Online) {
|
||||||
@@ -1156,6 +1198,17 @@ TEST(BindServerTest, BindAndListenSeparatelySSL) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
|
||||||
|
SSLServer svr(SERVER_ENCRYPTED_CERT_FILE, SERVER_ENCRYPTED_PRIVATE_KEY_FILE,
|
||||||
|
nullptr, nullptr, SERVER_ENCRYPTED_PRIVATE_KEY_PASS);
|
||||||
|
int port = svr.bind_to_any_port("0.0.0.0");
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
ASSERT_TRUE(port > 0);
|
||||||
|
svr.stop();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(ErrorHandlerTest, ContentLength) {
|
TEST(ErrorHandlerTest, ContentLength) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
@@ -1195,12 +1248,13 @@ TEST(ErrorHandlerTest, ContentLength) {
|
|||||||
TEST(ExceptionHandlerTest, ContentLength) {
|
TEST(ExceptionHandlerTest, ContentLength) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
svr.set_exception_handler(
|
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||||
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
|
std::exception &e) {
|
||||||
res.status = 500;
|
EXPECT_EQ("abc", std::string(e.what()));
|
||||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
res.status = 500;
|
||||||
"text/html"); // <= Content-Length still 13
|
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||||
});
|
"text/html"); // <= Content-Length still 13 at this point
|
||||||
|
});
|
||||||
|
|
||||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("Hello World!\n", "text/plain");
|
res.set_content("Hello World!\n", "text/plain");
|
||||||
@@ -1212,15 +1266,28 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
|||||||
// Give GET time to get a few messages.
|
// Give GET time to get a few messages.
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
{
|
for (size_t i = 0; i < 10; i++) {
|
||||||
Client cli(HOST, PORT);
|
Client cli(HOST, PORT);
|
||||||
|
|
||||||
auto res = cli.Get("/hi");
|
for (size_t j = 0; j < 100; j++) {
|
||||||
ASSERT_TRUE(res);
|
auto res = cli.Get("/hi");
|
||||||
EXPECT_EQ(500, res->status);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
EXPECT_EQ(500, res->status);
|
||||||
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||||
|
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.set_keep_alive(true);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < 100; j++) {
|
||||||
|
auto res = cli.Get("/hi");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(500, res->status);
|
||||||
|
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||||
|
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svr.stop();
|
svr.stop();
|
||||||
@@ -1349,6 +1416,34 @@ TEST(InvalidFormatTest, StatusCode) {
|
|||||||
ASSERT_FALSE(svr.is_running());
|
ASSERT_FALSE(svr.is_running());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(URLFragmentTest, WithFragment) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.Get("/hi", [](const Request &req, Response & /*res*/) {
|
||||||
|
EXPECT_TRUE(req.target == "/hi");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
{
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
|
||||||
|
auto res = cli.Get("/hi#key1=val1=key2=val2");
|
||||||
|
EXPECT_TRUE(res);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
|
||||||
|
res = cli.Get("/hi%23key1=val1=key2=val2");
|
||||||
|
EXPECT_TRUE(res);
|
||||||
|
EXPECT_EQ(404, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
class ServerTest : public ::testing::Test {
|
class ServerTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
ServerTest()
|
ServerTest()
|
||||||
@@ -1616,6 +1711,11 @@ protected:
|
|||||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||||
res.set_content("empty-no-content-type", "text/plain");
|
res.set_content("empty-no-content-type", "text/plain");
|
||||||
})
|
})
|
||||||
|
.Post("/post-large",
|
||||||
|
[&](const Request &req, Response &res) {
|
||||||
|
EXPECT_EQ(req.body, LARGE_DATA);
|
||||||
|
res.set_content(req.body, "text/plain");
|
||||||
|
})
|
||||||
.Put("/empty-no-content-type",
|
.Put("/empty-no-content-type",
|
||||||
[&](const Request &req, Response &res) {
|
[&](const Request &req, Response &res) {
|
||||||
EXPECT_EQ(req.body, "");
|
EXPECT_EQ(req.body, "");
|
||||||
@@ -2021,6 +2121,13 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
|||||||
ASSERT_EQ("empty-no-content-type", res->body);
|
ASSERT_EQ("empty-no-content-type", res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, PostLarge) {
|
||||||
|
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
EXPECT_EQ(LARGE_DATA, res->body);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
|
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
|
||||||
auto res = cli_.Put("/empty-no-content-type");
|
auto res = cli_.Put("/empty-no-content-type");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@@ -3625,10 +3732,11 @@ TEST(StreamingTest, NoContentLengthStreaming) {
|
|||||||
|
|
||||||
auto get_thread = std::thread([&client]() {
|
auto get_thread = std::thread([&client]() {
|
||||||
std::string s;
|
std::string s;
|
||||||
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
auto res =
|
||||||
s += std::string(data, len);
|
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||||
return true;
|
s += std::string(data, len);
|
||||||
});
|
return true;
|
||||||
|
});
|
||||||
EXPECT_EQ("aaabbb", s);
|
EXPECT_EQ("aaabbb", s);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3756,6 +3864,36 @@ TEST(KeepAliveTest, ReadTimeout) {
|
|||||||
ASSERT_FALSE(svr.is_running());
|
ASSERT_FALSE(svr.is_running());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(KeepAliveTest, Issue1041) {
|
||||||
|
const auto resourcePath = "/hi";
|
||||||
|
|
||||||
|
Server svr;
|
||||||
|
svr.set_keep_alive_timeout(3);
|
||||||
|
|
||||||
|
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
|
||||||
|
res.set_content("Hello World!", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
cli.set_keep_alive(true);
|
||||||
|
|
||||||
|
auto result = cli.Get(resourcePath);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
EXPECT_EQ(200, result->status);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
|
|
||||||
|
result = cli.Get(resourcePath);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
EXPECT_EQ(200, result->status);
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
a2.wait();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
@@ -4230,6 +4368,20 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
|||||||
ASSERT_EQ(200, res->status);
|
ASSERT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
TEST(SSLClientTest, SetInterfaceWithINET6) {
|
||||||
|
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
|
||||||
|
ASSERT_TRUE(cli != nullptr);
|
||||||
|
|
||||||
|
cli->set_address_family(AF_INET6);
|
||||||
|
cli->set_interface("en0");
|
||||||
|
|
||||||
|
auto res = cli->Get("/get");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(SSLClientServerTest, ClientCertPresent) {
|
TEST(SSLClientServerTest, ClientCertPresent) {
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||||
CLIENT_CA_CERT_DIR);
|
CLIENT_CA_CERT_DIR);
|
||||||
@@ -4681,4 +4833,68 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
|
|||||||
t.join();
|
t.join();
|
||||||
t2.join();
|
t2.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, LargeData) {
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
|
||||||
|
svr.Post("/post", [&](const Request &req, Response & /*res*/,
|
||||||
|
const ContentReader &content_reader) {
|
||||||
|
if (req.is_multipart_form_data()) {
|
||||||
|
MultipartFormDataItems files;
|
||||||
|
content_reader(
|
||||||
|
[&](const MultipartFormData &file) {
|
||||||
|
files.push_back(file);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[&](const char *data, size_t data_length) {
|
||||||
|
files.back().content.append(data, data_length);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_TRUE(std::string(files[0].name) == "document");
|
||||||
|
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
|
||||||
|
EXPECT_TRUE(files[0].filename == "2MB_data");
|
||||||
|
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
|
||||||
|
|
||||||
|
EXPECT_TRUE(files[1].name == "hello");
|
||||||
|
EXPECT_TRUE(files[1].content == "world");
|
||||||
|
EXPECT_TRUE(files[1].filename == "");
|
||||||
|
EXPECT_TRUE(files[1].content_type == "");
|
||||||
|
} else {
|
||||||
|
std::string body;
|
||||||
|
content_reader([&](const char *data, size_t data_length) {
|
||||||
|
body.append(data, data_length);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||||
|
while (!svr.is_running()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string data(1024 * 1024 * 2, '.');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = cli.Post("/post", items);
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user