mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-12 01:27:15 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a444b612af | ||
|
|
ed6d949f42 | ||
|
|
d28cd3f937 | ||
|
|
8cc3e6c434 | ||
|
|
26fbc1b7c0 | ||
|
|
0dc653f45a | ||
|
|
7a58c0a430 | ||
|
|
dabaa51a7d | ||
|
|
a1cfc0f377 | ||
|
|
eb4fcb5003 | ||
|
|
ae43c96984 | ||
|
|
9c81693801 | ||
|
|
80202c9f62 | ||
|
|
094a6a614a | ||
|
|
39c7bba7b9 | ||
|
|
f2476f21fc | ||
|
|
c776454c84 | ||
|
|
82a5ac735f | ||
|
|
08bf806e92 | ||
|
|
9a41b16cbb | ||
|
|
10759f0a38 | ||
|
|
58b2814fda | ||
|
|
260422b7d7 | ||
|
|
d2c7b447d5 | ||
|
|
72b20c08da | ||
|
|
afd6d5f9dc | ||
|
|
e5827ad16f | ||
|
|
5324b3d661 | ||
|
|
151ccba57e | ||
|
|
69a28d50f6 | ||
|
|
048f31109f | ||
|
|
d064fb7ff2 | ||
|
|
3c2736bb2a | ||
|
|
fd4e1b4112 | ||
|
|
f6a2365ca5 | ||
|
|
df1ff7510b | ||
|
|
379905bd34 | ||
|
|
66719ae3d4 | ||
|
|
bc9251ea49 | ||
|
|
a9e942d755 | ||
|
|
e1785d6723 | ||
|
|
b9539b8921 | ||
|
|
4c93b973ff |
5
.clang-format
Normal file
5
.clang-format
Normal file
@@ -0,0 +1,5 @@
|
||||
BasedOnStyle: LLVM
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
Cpp11BracedListStyle: true
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ example/hello
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
example/upload
|
||||
example/*.pem
|
||||
test/test
|
||||
test/test.xcodeproj/xcuser*
|
||||
|
||||
73
README.md
73
README.md
@@ -74,7 +74,7 @@ svr.set_logger([](const auto& req, const auto& res) {
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto& req, auto& res) {
|
||||
const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
@@ -119,15 +119,14 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
svr.Post("/content_receiver",
|
||||
[&](const Request &req, Response &res, const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFiles files;
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const std::string &name, const MultipartFile &file) {
|
||||
files.emplace(name, file);
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const std::string &name, const char *data, size_t data_length) {
|
||||
auto &file = files.find(name)->second;
|
||||
file.content.append(data, data_length);
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
@@ -156,7 +155,7 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Default thread pool supporet
|
||||
### Default thread pool support
|
||||
|
||||
Set thread count to 8:
|
||||
|
||||
@@ -302,7 +301,7 @@ res = cli.Options("/resource/foo");
|
||||
### Connection Timeout
|
||||
|
||||
```c++
|
||||
httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds
|
||||
cli.set_timeout_sec(5); // timeouts in 5 seconds
|
||||
```
|
||||
### With Progress Callback
|
||||
|
||||
@@ -324,18 +323,32 @@ std::shared_ptr<httplib::Response> res =
|
||||
|
||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
||||
|
||||
### Basic Authentication
|
||||
### Authentication
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("httplib.org");
|
||||
// Basic Authentication
|
||||
cli.set_basic_auth("user", "pass");
|
||||
|
||||
auto res = cli.Get("/basic-auth/hello/world", {
|
||||
httplib::make_basic_authentication_header("hello", "world")
|
||||
});
|
||||
// res->status should be 200
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
|
||||
// Digest Authentication
|
||||
cli.set_digest_auth("user", "pass");
|
||||
```
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication.
|
||||
|
||||
### Proxy server support
|
||||
|
||||
```cpp
|
||||
cli.set_proxy("host", port);
|
||||
|
||||
// Basic Authentication
|
||||
cli.set_proxy_basic_auth("user", "pass");
|
||||
|
||||
// Digest Authentication
|
||||
cli.set_proxy_digest_auth("user", "pass");
|
||||
```
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication.
|
||||
|
||||
### Range
|
||||
|
||||
```cpp
|
||||
@@ -381,11 +394,19 @@ httplib::Client cli("yahoo.com");
|
||||
auto res = cli.Get("/");
|
||||
res->status; // 301
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
res->status; // 200
|
||||
```
|
||||
|
||||
### Use a specitic network interface
|
||||
|
||||
NOTE: This feature is not available on Windows, yet.
|
||||
|
||||
```cpp
|
||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||
```
|
||||
|
||||
OpenSSL Support
|
||||
---------------
|
||||
|
||||
@@ -417,10 +438,26 @@ The server applies gzip compression to the following MIME type contents:
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
### Compress content on client
|
||||
|
||||
```c++
|
||||
cli.set_compress(true);
|
||||
res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
```
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
```bash
|
||||
> python3 split.py
|
||||
> ls out
|
||||
httplib.h httplib.cc
|
||||
```
|
||||
|
||||
NOTE
|
||||
----
|
||||
|
||||
g++ 4.8 cannot build this library since `<regex>` in g++4.8 is [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
|
||||
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
@@ -33,4 +33,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplesvr upload redirect *.pem
|
||||
rm server client hello simplesvr upload redirect benchmark *.pem
|
||||
|
||||
@@ -44,14 +44,10 @@ int main(void) {
|
||||
#endif
|
||||
|
||||
// Run servers
|
||||
auto httpThread = std::thread([&]() {
|
||||
http.listen("localhost", 8080);
|
||||
});
|
||||
auto httpThread = std::thread([&]() { http.listen("localhost", 8080); });
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto httpsThread = std::thread([&]() {
|
||||
https.listen("localhost", 8081);
|
||||
});
|
||||
auto httpsThread = std::thread([&]() { https.listen("localhost", 8081); });
|
||||
#endif
|
||||
|
||||
httpThread.join();
|
||||
|
||||
@@ -27,7 +27,7 @@ string dump_headers(const Headers &headers) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string dump_multipart_files(const MultipartFiles &files) {
|
||||
string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||
string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
@@ -46,10 +46,7 @@ string dump_multipart_files(const MultipartFiles &files) {
|
||||
snprintf(buf, sizeof(buf), "content type: %s\n", file.content_type.c_str());
|
||||
s += buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), "text offset: %lu\n", file.offset);
|
||||
s += buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), "text length: %lu\n", file.length);
|
||||
snprintf(buf, sizeof(buf), "text length: %lu\n", file.content.size());
|
||||
s += buf;
|
||||
|
||||
s += "----------------\n";
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <fstream>
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
using namespace httplib;
|
||||
using namespace std;
|
||||
|
||||
const char* html = R"(
|
||||
const char *html = R"(
|
||||
<form id="formElem">
|
||||
<input type="file" name="file" accept="image/*">
|
||||
<input type="submit">
|
||||
@@ -35,12 +35,13 @@ int main(void) {
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request & req, Response &res) {
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto file = req.get_file_value("file");
|
||||
cout << "file: " << file.offset << ":" << file.length << ":" << file.filename << endl;
|
||||
cout << "file length: " << file.content.length() << ":" << file.filename
|
||||
<< endl;
|
||||
|
||||
ofstream ofs(file.filename, ios::binary);
|
||||
ofs << req.body.substr(file.offset, file.length);
|
||||
ofs << file.content;
|
||||
|
||||
res.set_content("done", "text/plain");
|
||||
});
|
||||
|
||||
24
split.py
Normal file
24
split.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
border = '// ----------------------------------------------------------------------------'
|
||||
|
||||
with open('httplib.h') as f:
|
||||
lines = f.readlines()
|
||||
inImplementation = False
|
||||
os.makedirs('out', exist_ok=True)
|
||||
with open('out/httplib.h', 'w') as fh:
|
||||
with open('out/httplib.cc', 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
fc.write('namespace httplib {\n')
|
||||
for line in lines:
|
||||
isBorderLine = border in line
|
||||
if isBorderLine:
|
||||
inImplementation = not inImplementation
|
||||
else:
|
||||
if inImplementation:
|
||||
fc.write(line.replace('inline ', ''))
|
||||
pass
|
||||
else:
|
||||
fh.write(line)
|
||||
pass
|
||||
fc.write('} // namespace httplib\n')
|
||||
276
test/test.cc
276
test/test.cc
@@ -30,9 +30,12 @@ const std::string JSON_DATA = "{\"hello\":\"world\"}";
|
||||
|
||||
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
|
||||
|
||||
MultipartFile& get_file_value(MultipartFiles &files, const char *key) {
|
||||
auto it = files.find(key);
|
||||
if (it != files.end()) { return it->second; }
|
||||
MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
const char *key) {
|
||||
auto it = std::find_if(
|
||||
files.begin(), files.end(),
|
||||
[&](const MultipartFormData &file) { return file.name == key; });
|
||||
if (it != files.end()) { return *it; }
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
}
|
||||
|
||||
@@ -201,15 +204,15 @@ TEST(ParseHeaderValueTest, Range) {
|
||||
|
||||
TEST(ChunkedEncodingTest, FromHTTPWatch) {
|
||||
auto host = "www.httpwatch.com";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
auto res =
|
||||
cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137");
|
||||
@@ -224,15 +227,15 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) {
|
||||
|
||||
TEST(ChunkedEncodingTest, WithContentReceiver) {
|
||||
auto host = "www.httpwatch.com";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
std::string body;
|
||||
auto res =
|
||||
@@ -252,15 +255,15 @@ TEST(ChunkedEncodingTest, WithContentReceiver) {
|
||||
|
||||
TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
auto host = "www.httpwatch.com";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get(
|
||||
@@ -284,15 +287,15 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
|
||||
TEST(RangeTest, FromHTTPBin) {
|
||||
auto host = "httpbin.org";
|
||||
auto sec = 5;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
|
||||
{
|
||||
httplib::Headers headers;
|
||||
@@ -344,15 +347,15 @@ TEST(RangeTest, FromHTTPBin) {
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidHost) {
|
||||
auto host = "-abcde.com";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -360,15 +363,15 @@ TEST(ConnectionErrorTest, InvalidHost) {
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto host = "localhost";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 44380;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 8080;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -376,15 +379,15 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
|
||||
TEST(ConnectionErrorTest, Timeout) {
|
||||
auto host = "google.com";
|
||||
auto sec = 2;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 44380;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 8080;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -392,15 +395,15 @@ TEST(ConnectionErrorTest, Timeout) {
|
||||
|
||||
TEST(CancelTest, NoCancel) {
|
||||
auto host = "httpbin.org";
|
||||
auto sec = 5;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -410,31 +413,31 @@ TEST(CancelTest, NoCancel) {
|
||||
|
||||
TEST(CancelTest, WithCancelSmallPayload) {
|
||||
auto host = "httpbin.org";
|
||||
auto sec = 5;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
|
||||
cli.set_timeout_sec(5);
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST(CancelTest, WithCancelLargePayload) {
|
||||
auto host = "httpbin.org";
|
||||
auto sec = 5;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port, sec);
|
||||
httplib::SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port, sec);
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
|
||||
uint32_t count = 0;
|
||||
httplib::Headers headers;
|
||||
@@ -469,8 +472,78 @@ TEST(BaseAuthTest, FromHTTPWatch) {
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("hello", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(res->body,
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("hello", "bad");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("bad", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
auto host = "httpbin.org";
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/digest-auth/auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<std::string> paths = {
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-init/hello/world/MD5",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
cli.set_digest_auth("hello", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(res->body,
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
cli.set_digest_auth("hello", "bad");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
|
||||
cli.set_digest_auth("bad", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(AbsoluteRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
|
||||
@@ -480,7 +553,7 @@ TEST(AbsoluteRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/absolute-redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -495,7 +568,7 @@ TEST(RedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -510,7 +583,7 @@ TEST(RelativeRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/relative-redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -525,7 +598,7 @@ TEST(TooManyRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/21");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
@@ -538,7 +611,7 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -546,7 +619,7 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -757,19 +830,19 @@ protected:
|
||||
EXPECT_EQ("5", req.get_header_value("Content-Length"));
|
||||
})
|
||||
.Post("/content_receiver",
|
||||
[&](const Request & req, Response &res, const ContentReader &content_reader) {
|
||||
[&](const Request &req, Response &res,
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFiles files;
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const std::string &name, const MultipartFile &file) {
|
||||
files.emplace(name, file);
|
||||
return true;
|
||||
},
|
||||
[&](const std::string &name, const char *data, size_t data_length) {
|
||||
auto &file = files.find(name)->second;
|
||||
file.content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
[&](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_EQ(5u, files.size());
|
||||
|
||||
@@ -1357,7 +1430,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedEndless) {
|
||||
size_t offset = 0;
|
||||
uint64_t offset = 0;
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
[&](const char * /*data*/, uint64_t data_length) {
|
||||
if (offset < 100) {
|
||||
@@ -1492,12 +1565,13 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
|
||||
sink("PUT", 3);
|
||||
},
|
||||
"text/plain", true);
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1505,7 +1579,8 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain", true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1578,7 +1653,8 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostContentReceiverGzip) {
|
||||
auto res = cli_.Post("/content_receiver", "content", "text/plain", true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/content_receiver", "content", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("content", res->body);
|
||||
@@ -1759,13 +1835,94 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
{"key2", "--abcdefg123", "", ""},
|
||||
};
|
||||
|
||||
auto res = cli_.Post("/gzipmultipart", items, true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/gzipmultipart", items);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Sends a raw request to a server listening at HOST:PORT.
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
std::string());
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
return detail::process_and_close_socket(
|
||||
true, client_sock, 1, read_timeout_sec, 0,
|
||||
[&](Stream &strm, bool /*last_connection*/, bool &
|
||||
/*connection_close*/) -> bool {
|
||||
if (req.size() !=
|
||||
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[512];
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf, sizeof(buf));
|
||||
while (line_reader.getline()) {}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
Server svr;
|
||||
std::string header_value;
|
||||
svr.Get("/validate-ws-in-headers", [&](const Request &req, Response &res) {
|
||||
header_value = req.get_header_value("foo");
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
// Only space and horizontal tab are whitespace. Make sure other whitespace-
|
||||
// like characters are not treated the same - use vertical tab and escape.
|
||||
const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
|
||||
"foo: \t \v bar \e\t \r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
ASSERT_TRUE(send_request(5, req));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_EQ(header_value, "\v bar \e");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](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
|
||||
// without a trailing blank line.
|
||||
const time_t client_read_timeout_sec = 1;
|
||||
svr.set_read_timeout(client_read_timeout_sec + 1, 0);
|
||||
bool listen_thread_ok = false;
|
||||
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
// A certain header line causes an exception if the header property is parsed
|
||||
// naively with a single regex. This occurs with libc++ but not libstdc++.
|
||||
const std::string req =
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" ";
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
}
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -1961,9 +2118,10 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_FILE,
|
||||
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
@@ -1980,8 +2138,9 @@ TEST(SSLClientServerTest, ClientCertMissing) {
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, 30);
|
||||
httplib::SSLClient cli(HOST, PORT);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
|
||||
svr.stop();
|
||||
@@ -2001,9 +2160,10 @@ TEST(SSLClientServerTest, TrustDirOptional) {
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_FILE,
|
||||
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user