mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-15 10:39:37 +00: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 |
No files matched your search
@@ -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.
@@ -0,0 +1,7 @@
|
|||||||
|
PUT { HTTP/1.0
|
||||||
|
Content-Type:multipart/form-databoundary=m
|
||||||
|
Range:bytes=-
|
||||||
|
|
||||||
|
--m
|
||||||
|
C
|
||||||
|
c
Line truncated
|
||||||
@@ -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
|
|
||||||
+224
-8
@@ -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,11 +1248,12 @@ 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) {
|
||||||
|
EXPECT_EQ("abc", std::string(e.what()));
|
||||||
res.status = 500;
|
res.status = 500;
|
||||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||||
"text/html"); // <= Content-Length still 13
|
"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) {
|
||||||
@@ -1212,9 +1266,10 @@ 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);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < 100; j++) {
|
||||||
auto res = cli.Get("/hi");
|
auto res = cli.Get("/hi");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(500, res->status);
|
EXPECT_EQ(500, res->status);
|
||||||
@@ -1223,6 +1278,18 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
|||||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
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();
|
||||||
thread.join();
|
thread.join();
|
||||||
ASSERT_FALSE(svr.is_running());
|
ASSERT_FALSE(svr.is_running());
|
||||||
@@ -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,7 +3732,8 @@ 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 =
|
||||||
|
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||||
s += std::string(data, len);
|
s += std::string(data, len);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -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