mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
55 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.h
|
||||
test/test
|
||||
test/server_fuzzer
|
||||
test/test_proxy
|
||||
test/test_split
|
||||
test/test.xcodeproj/xcuser*
|
||||
test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
|
||||
|
||||
@@ -172,6 +172,11 @@ if(HTTPLIB_COMPILE)
|
||||
$<BUILD_INTERFACE:${_httplib_build_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()
|
||||
# This is for header-only.
|
||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||
|
||||
@@ -183,6 +183,15 @@ The followings are built-in mappings:
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| 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.
|
||||
|
||||
### Logging
|
||||
@@ -303,7 +312,7 @@ Without content length:
|
||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
res.set_content_provider(
|
||||
"text/plain", // Content type
|
||||
[&](size_t offset, size_t length, DataSink &sink) {
|
||||
[&](size_t offset, DataSink &sink) {
|
||||
if (/* there is still data */) {
|
||||
std::vector<char> 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++
|
||||
CXXFLAGS = -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ int main(void) {
|
||||
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
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
python = import('python').find_installation('python3')
|
||||
python3 = find_program('python3')
|
||||
if version == 'undefined'
|
||||
# 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
|
||||
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()
|
||||
endif
|
||||
|
||||
@@ -68,7 +69,7 @@ if get_option('cpp-httplib_compile')
|
||||
'split',
|
||||
input: '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_dir: [false, get_option('includedir')]
|
||||
)
|
||||
@@ -92,6 +93,14 @@ 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('.'))
|
||||
|
||||
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
|
||||
|
||||
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_zlib', type: 'feature', value: 'auto', description: 'Enable zlib 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')
|
||||
|
||||
@@ -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 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
|
||||
#c_rehash .
|
||||
|
||||
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']
|
||||
)
|
||||
|
||||
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',
|
||||
output: 'rootCA.key.pem',
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
docker-compose down --rmi all
|
||||
@@ -1 +0,0 @@
|
||||
docker-compose up -d
|
||||
+238
-22
@@ -18,6 +18,9 @@
|
||||
#define CLIENT_CA_CERT_DIR "."
|
||||
#define CLIENT_CERT_FILE "./client.cert.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 httplib;
|
||||
@@ -58,6 +61,14 @@ TEST(StartupTest, WSAStartup) {
|
||||
}
|
||||
#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) {
|
||||
string unescapedCharacters = "-_.!~*'()";
|
||||
|
||||
@@ -393,6 +404,31 @@ TEST(ChunkedEncodingTest, FromHTTPWatch_Online) {
|
||||
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) {
|
||||
auto host = "www.httpwatch.com";
|
||||
|
||||
@@ -574,10 +610,11 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
|
||||
auto res = cli.Get("/");
|
||||
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";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -589,9 +626,14 @@ TEST(ConnectionErrorTest, Timeout) {
|
||||
#endif
|
||||
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("/");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_TRUE(res.error() == Error::Connection);
|
||||
EXPECT_EQ(Error::ConnectionTimeout, res.error());
|
||||
}
|
||||
|
||||
TEST(CancelTest, NoCancel_Online) {
|
||||
@@ -750,7 +792,7 @@ TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
|
||||
auto host = "google.com";
|
||||
auto another_host = "example.com";
|
||||
auto wrong_ip = "0.0.0.0";
|
||||
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
@@ -766,7 +808,7 @@ TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
|
||||
TEST(SpecifyServerIPAddressTest, RealHostname_Online) {
|
||||
auto host = "google.com";
|
||||
auto wrong_ip = "0.0.0.0";
|
||||
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
@@ -1156,6 +1198,17 @@ TEST(BindServerTest, BindAndListenSeparatelySSL) {
|
||||
}
|
||||
#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) {
|
||||
Server svr;
|
||||
|
||||
@@ -1195,12 +1248,13 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13
|
||||
});
|
||||
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||
std::exception &e) {
|
||||
EXPECT_EQ("abc", std::string(e.what()));
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13 at this point
|
||||
});
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
@@ -1212,15 +1266,28 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -1349,6 +1416,34 @@ TEST(InvalidFormatTest, StatusCode) {
|
||||
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 {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1616,6 +1711,11 @@ protected:
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
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",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
@@ -2021,6 +2121,13 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
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) {
|
||||
auto res = cli_.Put("/empty-no-content-type");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3625,10 +3732,11 @@ TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
|
||||
auto get_thread = std::thread([&client]() {
|
||||
std::string s;
|
||||
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||
s += std::string(data, len);
|
||||
return true;
|
||||
});
|
||||
auto res =
|
||||
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||
s += std::string(data, len);
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ("aaabbb", s);
|
||||
});
|
||||
|
||||
@@ -3756,6 +3864,36 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
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) {
|
||||
Server svr;
|
||||
|
||||
@@ -4230,6 +4368,20 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
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) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
CLIENT_CA_CERT_DIR);
|
||||
@@ -4681,4 +4833,68 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
|
||||
t.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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user