mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-03 14:03:15 +07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -0,0 +1,5 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
AllowShortBlocksOnASingleLine: true
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
@@ -6,6 +6,7 @@ example/hello
|
|||||||
example/simplesvr
|
example/simplesvr
|
||||||
example/benchmark
|
example/benchmark
|
||||||
example/redirect
|
example/redirect
|
||||||
|
example/upload
|
||||||
example/*.pem
|
example/*.pem
|
||||||
test/test
|
test/test
|
||||||
test/test.xcodeproj/xcuser*
|
test/test.xcodeproj/xcuser*
|
||||||
|
|||||||
@@ -119,15 +119,14 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
|||||||
svr.Post("/content_receiver",
|
svr.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()) {
|
if (req.is_multipart_form_data()) {
|
||||||
MultipartFiles files;
|
MultipartFormDataItems files;
|
||||||
content_reader(
|
content_reader(
|
||||||
[&](const std::string &name, const MultipartFile &file) {
|
[&](const MultipartFormData &file) {
|
||||||
files.emplace(name, file);
|
files.push_back(file);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[&](const std::string &name, const char *data, size_t data_length) {
|
[&](const char *data, size_t data_length) {
|
||||||
auto &file = files.find(name)->second;
|
files.back().content.append(data, data_length);
|
||||||
file.content.append(data, data_length);
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} 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:
|
Set thread count to 8:
|
||||||
|
|
||||||
@@ -324,16 +323,23 @@ std::shared_ptr<httplib::Response> res =
|
|||||||
|
|
||||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
||||||
|
|
||||||
### Basic Authentication
|
### Authentication
|
||||||
|
|
||||||
|
NOTE: OpenSSL is required for Digest Authentication, since cpp-httplib uses message digest functions in OpenSSL.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
httplib::Client cli("httplib.org");
|
httplib::Client cli("httplib.org");
|
||||||
|
cli.set_auth("user", "pass");
|
||||||
|
|
||||||
auto res = cli.Get("/basic-auth/hello/world", {
|
// Basic
|
||||||
httplib::make_basic_authentication_header("hello", "world")
|
auto res = cli.Get("/basic-auth/user/pass");
|
||||||
});
|
|
||||||
// res->status should be 200
|
// res->status should be 200
|
||||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
|
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
|
||||||
|
|
||||||
|
// Digest
|
||||||
|
res = cli.Get("/digest-auth/auth/user/pass/SHA-256");
|
||||||
|
// res->status should be 200
|
||||||
|
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
|
||||||
```
|
```
|
||||||
|
|
||||||
### Range
|
### Range
|
||||||
@@ -381,11 +387,19 @@ httplib::Client cli("yahoo.com");
|
|||||||
auto res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
res->status; // 301
|
res->status; // 301
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
res = cli.Get("/");
|
res = cli.Get("/");
|
||||||
res->status; // 200
|
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
|
OpenSSL Support
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@@ -417,6 +431,22 @@ The server applies gzip compression to the following MIME type contents:
|
|||||||
* application/xml
|
* application/xml
|
||||||
* application/xhtml+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
|
NOTE
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -33,4 +33,4 @@ pem:
|
|||||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm server client hello simplesvr upload redirect *.pem
|
rm server client hello simplesvr upload redirect benchmark *.pem
|
||||||
|
|||||||
+2
-6
@@ -44,14 +44,10 @@ int main(void) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Run servers
|
// Run servers
|
||||||
auto httpThread = std::thread([&]() {
|
auto httpThread = std::thread([&]() { http.listen("localhost", 8080); });
|
||||||
http.listen("localhost", 8080);
|
|
||||||
});
|
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
auto httpsThread = std::thread([&]() {
|
auto httpsThread = std::thread([&]() { https.listen("localhost", 8081); });
|
||||||
https.listen("localhost", 8081);
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
httpThread.join();
|
httpThread.join();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ string dump_headers(const Headers &headers) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
string dump_multipart_files(const MultipartFiles &files) {
|
string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||||
string s;
|
string s;
|
||||||
char buf[BUFSIZ];
|
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());
|
snprintf(buf, sizeof(buf), "content type: %s\n", file.content_type.c_str());
|
||||||
s += buf;
|
s += buf;
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "text offset: %lu\n", file.offset);
|
snprintf(buf, sizeof(buf), "text length: %lu\n", file.content.size());
|
||||||
s += buf;
|
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "text length: %lu\n", file.length);
|
|
||||||
s += buf;
|
s += buf;
|
||||||
|
|
||||||
s += "----------------\n";
|
s += "----------------\n";
|
||||||
|
|||||||
+6
-5
@@ -5,13 +5,13 @@
|
|||||||
// MIT License
|
// MIT License
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
|
||||||
using namespace httplib;
|
using namespace httplib;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
const char* html = R"(
|
const char *html = R"(
|
||||||
<form id="formElem">
|
<form id="formElem">
|
||||||
<input type="file" name="file" accept="image/*">
|
<input type="file" name="file" accept="image/*">
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
@@ -35,12 +35,13 @@ int main(void) {
|
|||||||
res.set_content(html, "text/html");
|
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");
|
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);
|
ofstream ofs(file.filename, ios::binary);
|
||||||
ofs << req.body.substr(file.offset, file.length);
|
ofs << file.content;
|
||||||
|
|
||||||
res.set_content("done", "text/plain");
|
res.set_content("done", "text/plain");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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')
|
||||||
+154
-25
@@ -30,9 +30,12 @@ const std::string JSON_DATA = "{\"hello\":\"world\"}";
|
|||||||
|
|
||||||
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
|
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
|
||||||
|
|
||||||
MultipartFile& get_file_value(MultipartFiles &files, const char *key) {
|
MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||||
auto it = files.find(key);
|
const char *key) {
|
||||||
if (it != files.end()) { return it->second; }
|
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");
|
throw std::runtime_error("invalid mulitpart form data name error");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,8 +472,50 @@ TEST(BaseAuthTest, FromHTTPWatch) {
|
|||||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cli.set_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(AbsoluteRedirectTest, Redirect) {
|
TEST(AbsoluteRedirectTest, Redirect) {
|
||||||
auto host = "httpbin.org";
|
auto host = "httpbin.org";
|
||||||
|
|
||||||
@@ -480,7 +525,7 @@ TEST(AbsoluteRedirectTest, Redirect) {
|
|||||||
httplib::Client cli(host);
|
httplib::Client cli(host);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
auto res = cli.Get("/absolute-redirect/3");
|
auto res = cli.Get("/absolute-redirect/3");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -495,7 +540,7 @@ TEST(RedirectTest, Redirect) {
|
|||||||
httplib::Client cli(host);
|
httplib::Client cli(host);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
auto res = cli.Get("/redirect/3");
|
auto res = cli.Get("/redirect/3");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -510,7 +555,7 @@ TEST(RelativeRedirectTest, Redirect) {
|
|||||||
httplib::Client cli(host);
|
httplib::Client cli(host);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
auto res = cli.Get("/relative-redirect/3");
|
auto res = cli.Get("/relative-redirect/3");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -525,7 +570,7 @@ TEST(TooManyRedirectTest, Redirect) {
|
|||||||
httplib::Client cli(host);
|
httplib::Client cli(host);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
auto res = cli.Get("/redirect/21");
|
auto res = cli.Get("/redirect/21");
|
||||||
ASSERT_TRUE(res == nullptr);
|
ASSERT_TRUE(res == nullptr);
|
||||||
}
|
}
|
||||||
@@ -538,7 +583,7 @@ TEST(YahooRedirectTest, Redirect) {
|
|||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(301, res->status);
|
EXPECT_EQ(301, res->status);
|
||||||
|
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
res = cli.Get("/");
|
res = cli.Get("/");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -546,7 +591,7 @@ TEST(YahooRedirectTest, Redirect) {
|
|||||||
|
|
||||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||||
httplib::SSLClient cli("httpbin.org");
|
httplib::SSLClient cli("httpbin.org");
|
||||||
cli.follow_location(true);
|
cli.set_follow_location(true);
|
||||||
auto res =
|
auto res =
|
||||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
@@ -757,19 +802,19 @@ protected:
|
|||||||
EXPECT_EQ("5", req.get_header_value("Content-Length"));
|
EXPECT_EQ("5", req.get_header_value("Content-Length"));
|
||||||
})
|
})
|
||||||
.Post("/content_receiver",
|
.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()) {
|
if (req.is_multipart_form_data()) {
|
||||||
MultipartFiles files;
|
MultipartFormDataItems files;
|
||||||
content_reader(
|
content_reader(
|
||||||
[&](const std::string &name, const MultipartFile &file) {
|
[&](const MultipartFormData &file) {
|
||||||
files.emplace(name, file);
|
files.push_back(file);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[&](const std::string &name, const char *data, size_t data_length) {
|
[&](const char *data, size_t data_length) {
|
||||||
auto &file = files.find(name)->second;
|
files.back().content.append(data, data_length);
|
||||||
file.content.append(data, data_length);
|
return true;
|
||||||
return true;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
EXPECT_EQ(5u, files.size());
|
EXPECT_EQ(5u, files.size());
|
||||||
|
|
||||||
@@ -1357,7 +1402,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, GetStreamedEndless) {
|
TEST_F(ServerTest, GetStreamedEndless) {
|
||||||
size_t offset = 0;
|
uint64_t offset = 0;
|
||||||
auto res = cli_.Get("/streamed-cancel",
|
auto res = cli_.Get("/streamed-cancel",
|
||||||
[&](const char * /*data*/, uint64_t data_length) {
|
[&](const char * /*data*/, uint64_t data_length) {
|
||||||
if (offset < 100) {
|
if (offset < 100) {
|
||||||
@@ -1492,12 +1537,13 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
|||||||
|
|
||||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||||
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||||
|
cli_.set_compress(true);
|
||||||
auto res = cli_.Put(
|
auto res = cli_.Put(
|
||||||
"/put", 3,
|
"/put", 3,
|
||||||
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
|
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
|
||||||
sink("PUT", 3);
|
sink("PUT", 3);
|
||||||
},
|
},
|
||||||
"text/plain", true);
|
"text/plain");
|
||||||
|
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -1505,7 +1551,8 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, PutLargeFileWithGzip) {
|
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);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
@@ -1578,7 +1625,8 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, PostContentReceiverGzip) {
|
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_TRUE(res != nullptr);
|
||||||
ASSERT_EQ(200, res->status);
|
ASSERT_EQ(200, res->status);
|
||||||
ASSERT_EQ("content", res->body);
|
ASSERT_EQ("content", res->body);
|
||||||
@@ -1759,13 +1807,94 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
|||||||
{"key2", "--abcdefg123", "", ""},
|
{"key2", "--abcdefg123", "", ""},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res = cli_.Post("/gzipmultipart", items, true);
|
cli_.set_compress(true);
|
||||||
|
auto res = cli_.Post("/gzipmultipart", items);
|
||||||
|
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
#endif
|
#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 {
|
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
ServerTestWithAI_PASSIVE()
|
ServerTestWithAI_PASSIVE()
|
||||||
|
|||||||
Reference in New Issue
Block a user