mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-11 17:17:17 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f94c3f1dc9 | ||
|
|
4d545cb932 | ||
|
|
f5e19faae7 | ||
|
|
2e360f9dd6 | ||
|
|
e5ca863de5 | ||
|
|
126f1d177e | ||
|
|
b557ac9328 | ||
|
|
f6db19959f | ||
|
|
6b4df41b30 | ||
|
|
0d81e20129 | ||
|
|
26cb83ed6c | ||
|
|
b4f808da74 | ||
|
|
7fbb8bb3fd | ||
|
|
db27812198 | ||
|
|
6fe6fd5dbe | ||
|
|
0ee9660f3d | ||
|
|
a15d16a9de | ||
|
|
dfebfd9033 | ||
|
|
fcdaa24fc5 | ||
|
|
7f749740f7 | ||
|
|
7b4494748e | ||
|
|
7e1c107029 | ||
|
|
db7ae0ec21 | ||
|
|
b16905ec8b | ||
|
|
2f72845008 | ||
|
|
68aeb4a06a | ||
|
|
568fda62b4 | ||
|
|
04957c0f08 | ||
|
|
96e9ec0663 | ||
|
|
c58fca5dba | ||
|
|
d2fae4031c | ||
|
|
de844e67ef | ||
|
|
6c0e021554 | ||
|
|
e8d33a77e0 | ||
|
|
aa630e3062 | ||
|
|
301a419c02 | ||
|
|
fcbcbd53bd | ||
|
|
1bf616d653 | ||
|
|
ba7c7dc4a3 | ||
|
|
aa543240db | ||
|
|
5675cad407 | ||
|
|
079d3605ea | ||
|
|
2c6da365d9 | ||
|
|
38adeaf02c | ||
|
|
b3814b2b80 | ||
|
|
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 |
5
.clang-format
Normal file
5
.clang-format
Normal file
@@ -0,0 +1,5 @@
|
||||
BasedOnStyle: LLVM
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
Cpp11BracedListStyle: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,8 +6,11 @@ example/hello
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
example/sse
|
||||
example/upload
|
||||
example/*.pem
|
||||
test/test
|
||||
test/test_proxy
|
||||
test/test.xcodeproj/xcuser*
|
||||
test/test.xcodeproj/*/xcuser*
|
||||
test/*.pem
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Environment
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
# Compiler selection
|
||||
|
||||
140
README.md
140
README.md
@@ -5,7 +5,7 @@ cpp-httplib
|
||||
[](https://travis-ci.org/yhirose/cpp-httplib)
|
||||
[](https://ci.appveyor.com/project/yhirose/cpp-httplib)
|
||||
|
||||
A C++ single-file header-only cross platform HTTP/HTTPS library.
|
||||
A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
|
||||
It's extremely easy to setup. Just include **httplib.h** file in your code!
|
||||
|
||||
@@ -50,17 +50,44 @@ svr.listen_after_bind();
|
||||
### Static File Server
|
||||
|
||||
```cpp
|
||||
svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
|
||||
auto ret = svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
|
||||
if (!ret) {
|
||||
// The specified base directory doesn't exist...
|
||||
}
|
||||
|
||||
// Mount /public to ./www directory
|
||||
ret = svr.set_base_dir("./www", "/public");
|
||||
|
||||
// Mount /public to ./www1 and ./www2 directories
|
||||
ret = svr.set_base_dir("./www1", "/public"); // 1st order to search
|
||||
ret = svr.set_base_dir("./www2", "/public"); // 2nd order to search
|
||||
```
|
||||
|
||||
```cpp
|
||||
svr.set_base_dir("./www", "/public");
|
||||
// User defined file extension and MIME type mappings
|
||||
svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
|
||||
svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
|
||||
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
|
||||
```
|
||||
|
||||
```cpp
|
||||
svr.set_base_dir("./www1", "/public"); // 1st order
|
||||
svr.set_base_dir("./www2", "/public"); // 2nd order
|
||||
```
|
||||
The followings are built-in mappings:
|
||||
|
||||
| Extension | MIME Type |
|
||||
| :--------- | :--------------------- |
|
||||
| .txt | text/plain |
|
||||
| .html .htm | text/html |
|
||||
| .css | text/css |
|
||||
| .jpeg .jpg | image/jpg |
|
||||
| .png | image/png |
|
||||
| .gif | image/gif |
|
||||
| .svg | image/svg+xml |
|
||||
| .ico | image/x-icon |
|
||||
| .json | application/json |
|
||||
| .pdf | application/pdf |
|
||||
| .js | application/javascript |
|
||||
| .wasm | application/wasm |
|
||||
| .xml | application/xml |
|
||||
| .xhtml | application/xhtml+xml |
|
||||
|
||||
### Logging
|
||||
|
||||
@@ -74,7 +101,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");
|
||||
@@ -105,9 +132,9 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
[data](uint64_t offset, uint64_t length, DataSink sink) {
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
[data] { delete data; });
|
||||
});
|
||||
@@ -119,15 +146,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 {
|
||||
@@ -146,30 +172,31 @@ svr.Post("/content_receiver",
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](uint64_t offset, DataSink sink, Done done) {
|
||||
sink("123", 3);
|
||||
sink("345", 3);
|
||||
sink("789", 3);
|
||||
done();
|
||||
[](uint64_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done();
|
||||
}
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Server-Sent Events
|
||||
|
||||
Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/sse.cc).
|
||||
|
||||
### Default thread pool support
|
||||
|
||||
|
||||
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||
|
||||
Set thread count to 8:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
|
||||
```
|
||||
|
||||
Disable the default thread pool:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 0
|
||||
```
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
```cpp
|
||||
@@ -302,7 +329,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
|
||||
|
||||
@@ -326,23 +353,30 @@ This feature was contributed by [underscorediscovery](https://github.com/yhirose
|
||||
|
||||
### Authentication
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication, since cpp-httplib uses message digest functions in OpenSSL.
|
||||
```cpp
|
||||
// Basic Authentication
|
||||
cli.set_basic_auth("user", "pass");
|
||||
|
||||
// Digest Authentication
|
||||
cli.set_digest_auth("user", "pass");
|
||||
```
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication.
|
||||
|
||||
### Proxy server support
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("httplib.org");
|
||||
cli.set_auth("user", "pass");
|
||||
cli.set_proxy("host", port);
|
||||
|
||||
// Basic
|
||||
auto res = cli.Get("/basic-auth/user/pass");
|
||||
// res->status should be 200
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
|
||||
// Basic Authentication
|
||||
cli.set_proxy_basic_auth("user", "pass");
|
||||
|
||||
// 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".
|
||||
// Digest Authentication
|
||||
cli.set_proxy_digest_auth("user", "pass");
|
||||
```
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication.
|
||||
|
||||
### Range
|
||||
|
||||
```cpp
|
||||
@@ -388,11 +422,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
|
||||
---------------
|
||||
|
||||
@@ -424,10 +466,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
|
||||
-------
|
||||
|
||||
@@ -5,7 +5,7 @@ OPENSSL_DIR = /usr/local/opt/openssl
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
all: server client hello simplesvr upload redirect benchmark
|
||||
all: server client hello simplesvr upload redirect sse benchmark
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
@@ -25,6 +25,9 @@ upload : upload.cc ../httplib.h Makefile
|
||||
redirect : redirect.cc ../httplib.h Makefile
|
||||
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
sse : sse.cc ../httplib.h Makefile
|
||||
$(CXX) -o sse $(CXXFLAGS) sse.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
@@ -33,4 +36,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 benchmark *.pem
|
||||
rm server client hello simplesvr upload redirect sse 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];
|
||||
|
||||
@@ -122,7 +122,10 @@ int main(int argc, const char **argv) {
|
||||
auto base_dir = "./";
|
||||
if (argc > 2) { base_dir = argv[2]; }
|
||||
|
||||
svr.set_base_dir(base_dir);
|
||||
if (!svr.set_base_dir(base_dir)) {
|
||||
cout << "The specified base directory doesn't exist...";
|
||||
return 1;
|
||||
}
|
||||
|
||||
cout << "The server started at port " << port << "...";
|
||||
|
||||
|
||||
106
example/sse.cc
Normal file
106
example/sse.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// sse.cc
|
||||
//
|
||||
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
using namespace httplib;
|
||||
using namespace std;
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {
|
||||
id_ = 0;
|
||||
cid_ = -1;
|
||||
}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
lock_guard<mutex> lk(m_);
|
||||
cid_ = id_++;
|
||||
message_ = message;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_;
|
||||
atomic_int cid_;
|
||||
string message_;
|
||||
};
|
||||
|
||||
const auto html = R"(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SSE demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const ev1 = new EventSource("event1");
|
||||
ev1.onmessage = function(e) {
|
||||
console.log('ev1', e.data);
|
||||
}
|
||||
const ev2 = new EventSource("event2");
|
||||
ev2.onmessage = function(e) {
|
||||
console.log('ev2', e.data);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
|
||||
int main(void) {
|
||||
EventDispatcher ed;
|
||||
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
|
||||
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event1..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider(
|
||||
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
|
||||
});
|
||||
|
||||
svr.Get("/event2", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event2..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider(
|
||||
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
|
||||
});
|
||||
|
||||
thread t([&] {
|
||||
int id = 0;
|
||||
while (true) {
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
cout << "send event: " << id << std::endl;
|
||||
std::stringstream ss;
|
||||
ss << "data: " << id << "\n\n";
|
||||
ed.send_event(ss.str());
|
||||
id++;
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
}
|
||||
@@ -5,15 +5,16 @@
|
||||
// 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="file" name="image_file" accept="image/*">
|
||||
<input type="file" name="text_file" accept="text/*">
|
||||
<input type="submit">
|
||||
</form>
|
||||
<script>
|
||||
@@ -35,12 +36,23 @@ int main(void) {
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request & req, Response &res) {
|
||||
auto file = req.get_file_value("file");
|
||||
cout << "file length: " << file.content.length() << ":" << file.filename << endl;
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto image_file = req.get_file_value("image_file");
|
||||
auto text_file = req.get_file_value("text_file");
|
||||
|
||||
ofstream ofs(file.filename, ios::binary);
|
||||
ofs << file.content;
|
||||
cout << "image file length: " << image_file.content.length() << endl
|
||||
<< "image file name: " << image_file.filename << endl
|
||||
<< "text file length: " << text_file.content.length() << endl
|
||||
<< "text file name: " << text_file.filename << endl;
|
||||
|
||||
{
|
||||
ofstream ofs(image_file.filename, ios::binary);
|
||||
ofs << image_file.content;
|
||||
}
|
||||
{
|
||||
ofstream ofs(text_file.filename);
|
||||
ofs << text_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')
|
||||
@@ -8,9 +8,15 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
all : test
|
||||
./test
|
||||
|
||||
proxy : test_proxy
|
||||
./test_proxy
|
||||
|
||||
test : test.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
|
||||
|
||||
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
|
||||
|
||||
cert.pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
@@ -21,4 +27,4 @@ cert.pem:
|
||||
#c_rehash .
|
||||
|
||||
clean:
|
||||
rm -f test *.pem *.0 *.1 *.srl
|
||||
rm -f test test_proxy pem *.0 *.1 *.srl
|
||||
|
||||
13
test/proxy/Dockerfile
Normal file
13
test/proxy/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM centos:7
|
||||
|
||||
ARG auth="basic"
|
||||
ARG port="3128"
|
||||
|
||||
RUN yum install -y squid
|
||||
|
||||
COPY ./${auth}_squid.conf /etc/squid/squid.conf
|
||||
COPY ./${auth}_passwd /etc/squid/passwd
|
||||
|
||||
EXPOSE ${port}
|
||||
|
||||
CMD ["/usr/sbin/squid", "-N"]
|
||||
1
test/proxy/basic_passwd
Normal file
1
test/proxy/basic_passwd
Normal file
@@ -0,0 +1 @@
|
||||
hello:$apr1$O6S28OBL$8dr3ixl4Mohf97hgsYvLy/
|
||||
81
test/proxy/basic_squid.conf
Normal file
81
test/proxy/basic_squid.conf
Normal file
@@ -0,0 +1,81 @@
|
||||
#
|
||||
# Recommended minimum configuration:
|
||||
#
|
||||
|
||||
# Example rule allowing access from your local networks.
|
||||
# Adapt to list your (internal) IP networks from where browsing
|
||||
# should be allowed
|
||||
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
|
||||
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
|
||||
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
|
||||
acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
|
||||
acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
|
||||
acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
|
||||
acl localnet src fc00::/7 # RFC 4193 local private network range
|
||||
acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
|
||||
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80 # http
|
||||
acl Safe_ports port 21 # ftp
|
||||
acl Safe_ports port 443 # https
|
||||
acl Safe_ports port 70 # gopher
|
||||
acl Safe_ports port 210 # wais
|
||||
acl Safe_ports port 1025-65535 # unregistered ports
|
||||
acl Safe_ports port 280 # http-mgmt
|
||||
acl Safe_ports port 488 # gss-http
|
||||
acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwd
|
||||
auth_param basic realm proxy
|
||||
acl authenticated proxy_auth REQUIRED
|
||||
http_access allow authenticated
|
||||
|
||||
#
|
||||
# Recommended minimum Access Permission configuration:
|
||||
#
|
||||
# Deny requests to certain unsafe ports
|
||||
http_access deny !Safe_ports
|
||||
|
||||
# Deny CONNECT to other than secure SSL ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
|
||||
# Only allow cachemgr access from localhost
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
|
||||
# We strongly recommend the following be uncommented to protect innocent
|
||||
# web applications running on the proxy server who think the only
|
||||
# one who can access services on "localhost" is a local user
|
||||
#http_access deny to_localhost
|
||||
|
||||
#
|
||||
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
|
||||
#
|
||||
|
||||
# Example rule allowing access from your local networks.
|
||||
# Adapt localnet in the ACL section to list your (internal) IP networks
|
||||
# from where browsing should be allowed
|
||||
http_access allow localnet
|
||||
http_access allow localhost
|
||||
|
||||
# And finally deny all other access to this proxy
|
||||
http_access deny all
|
||||
|
||||
# Squid normally listens to port 3128
|
||||
http_port 3128
|
||||
|
||||
# Uncomment and adjust the following to add a disk cache directory.
|
||||
#cache_dir ufs /var/spool/squid 100 16 256
|
||||
|
||||
# Leave coredumps in the first cache dir
|
||||
coredump_dir /var/spool/squid
|
||||
|
||||
#
|
||||
# Add any of your own refresh_pattern entries above these.
|
||||
#
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern . 0 20% 4320
|
||||
1
test/proxy/digest_passwd
Normal file
1
test/proxy/digest_passwd
Normal file
@@ -0,0 +1 @@
|
||||
hello:world
|
||||
81
test/proxy/digest_squid.conf
Normal file
81
test/proxy/digest_squid.conf
Normal file
@@ -0,0 +1,81 @@
|
||||
#
|
||||
# Recommended minimum configuration:
|
||||
#
|
||||
|
||||
# Example rule allowing access from your local networks.
|
||||
# Adapt to list your (internal) IP networks from where browsing
|
||||
# should be allowed
|
||||
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
|
||||
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
|
||||
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
|
||||
acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
|
||||
acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
|
||||
acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
|
||||
acl localnet src fc00::/7 # RFC 4193 local private network range
|
||||
acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
|
||||
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80 # http
|
||||
acl Safe_ports port 21 # ftp
|
||||
acl Safe_ports port 443 # https
|
||||
acl Safe_ports port 70 # gopher
|
||||
acl Safe_ports port 210 # wais
|
||||
acl Safe_ports port 1025-65535 # unregistered ports
|
||||
acl Safe_ports port 280 # http-mgmt
|
||||
acl Safe_ports port 488 # gss-http
|
||||
acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
auth_param digest program /usr/lib64/squid/digest_file_auth /etc/squid/passwd
|
||||
auth_param digest realm proxy
|
||||
acl authenticated proxy_auth REQUIRED
|
||||
http_access allow authenticated
|
||||
|
||||
#
|
||||
# Recommended minimum Access Permission configuration:
|
||||
#
|
||||
# Deny requests to certain unsafe ports
|
||||
http_access deny !Safe_ports
|
||||
|
||||
# Deny CONNECT to other than secure SSL ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
|
||||
# Only allow cachemgr access from localhost
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
|
||||
# We strongly recommend the following be uncommented to protect innocent
|
||||
# web applications running on the proxy server who think the only
|
||||
# one who can access services on "localhost" is a local user
|
||||
#http_access deny to_localhost
|
||||
|
||||
#
|
||||
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
|
||||
#
|
||||
|
||||
# Example rule allowing access from your local networks.
|
||||
# Adapt localnet in the ACL section to list your (internal) IP networks
|
||||
# from where browsing should be allowed
|
||||
http_access allow localnet
|
||||
http_access allow localhost
|
||||
|
||||
# And finally deny all other access to this proxy
|
||||
http_access deny all
|
||||
|
||||
# Squid normally listens to port 3128
|
||||
http_port 3129
|
||||
|
||||
# Uncomment and adjust the following to add a disk cache directory.
|
||||
#cache_dir ufs /var/spool/squid 100 16 256
|
||||
|
||||
# Leave coredumps in the first cache dir
|
||||
coredump_dir /var/spool/squid
|
||||
|
||||
#
|
||||
# Add any of your own refresh_pattern entries above these.
|
||||
#
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern . 0 20% 4320
|
||||
22
test/proxy/docker-compose.yml
Normal file
22
test/proxy/docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
squid_basic:
|
||||
image: squid_basic
|
||||
restart: always
|
||||
ports:
|
||||
- "3128:3128"
|
||||
build:
|
||||
context: ./
|
||||
args:
|
||||
auth: basic
|
||||
|
||||
squid_digest:
|
||||
image: squid_digest
|
||||
restart: always
|
||||
ports:
|
||||
- "3129:3129"
|
||||
build:
|
||||
context: ./
|
||||
args:
|
||||
auth: digest
|
||||
1
test/proxy/down.sh
Executable file
1
test/proxy/down.sh
Executable file
@@ -0,0 +1 @@
|
||||
docker-compose down --rmi all
|
||||
1
test/proxy/up.sh
Executable file
1
test/proxy/up.sh
Executable file
@@ -0,0 +1 @@
|
||||
docker-compose up -d
|
||||
422
test/test.cc
422
test/test.cc
@@ -1,7 +1,11 @@
|
||||
#include <future>
|
||||
#include <gtest/gtest.h>
|
||||
#include <httplib.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
#define SERVER_CERT_FILE "./cert.pem"
|
||||
#define SERVER_PRIVATE_KEY_FILE "./key.pem"
|
||||
#define CA_CERT_FILE "./ca-bundle.crt"
|
||||
@@ -10,13 +14,6 @@
|
||||
#define CLIENT_CERT_FILE "./client.cert.pem"
|
||||
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#define msleep(n) ::Sleep(n)
|
||||
#else
|
||||
#define msleep(n) ::usleep(n * 1000)
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
|
||||
@@ -30,9 +27,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");
|
||||
}
|
||||
|
||||
@@ -199,17 +199,38 @@ TEST(ParseHeaderValueTest, Range) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BufferStreamTest, read) {
|
||||
detail::BufferStream strm1;
|
||||
Stream& strm = strm1;
|
||||
|
||||
EXPECT_EQ(5, strm.write("hello"));
|
||||
|
||||
char buf[512];
|
||||
EXPECT_EQ(2, strm.read(buf, 2));
|
||||
EXPECT_EQ('h', buf[0]);
|
||||
EXPECT_EQ('e', buf[1]);
|
||||
|
||||
EXPECT_EQ(2, strm.read(buf, 2));
|
||||
EXPECT_EQ('l', buf[0]);
|
||||
EXPECT_EQ('l', buf[1]);
|
||||
|
||||
EXPECT_EQ(1, strm.read(buf, 1));
|
||||
EXPECT_EQ('o', buf[0]);
|
||||
|
||||
EXPECT_EQ(0, strm.read(buf, 1));
|
||||
}
|
||||
|
||||
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 +245,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 +273,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 +305,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 +365,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 +381,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 +397,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 +413,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 +431,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;
|
||||
@@ -471,13 +492,27 @@ TEST(BaseAuthTest, FromHTTPWatch) {
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_auth("hello", "world");
|
||||
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
|
||||
@@ -494,21 +529,34 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
|
||||
{
|
||||
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",
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
cli.set_auth("hello", "world");
|
||||
for (auto path: paths) {
|
||||
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
|
||||
@@ -522,7 +570,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);
|
||||
@@ -537,7 +585,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);
|
||||
@@ -552,7 +600,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);
|
||||
@@ -567,7 +615,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);
|
||||
}
|
||||
@@ -580,7 +628,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);
|
||||
@@ -588,7 +636,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);
|
||||
@@ -616,6 +664,7 @@ protected:
|
||||
virtual void SetUp() {
|
||||
svr_.set_base_dir("./www");
|
||||
svr_.set_base_dir("./www2", "/mount");
|
||||
svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde");
|
||||
|
||||
svr_.Get("/hi",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -623,7 +672,7 @@ protected:
|
||||
})
|
||||
.Get("/slow",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
msleep(2000);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
res.set_content("slow", "text/plain");
|
||||
})
|
||||
.Get("/remote_addr",
|
||||
@@ -646,6 +695,15 @@ protected:
|
||||
res.status = 400;
|
||||
}
|
||||
})
|
||||
.Put("/person",
|
||||
[&](const Request &req, Response &res) {
|
||||
if (req.has_param("name") && req.has_param("note")) {
|
||||
persons_[req.get_param_value("name")] =
|
||||
req.get_param_value("note");
|
||||
} else {
|
||||
res.status = 400;
|
||||
}
|
||||
})
|
||||
.Get("/person/(.*)",
|
||||
[&](const Request &req, Response &res) {
|
||||
string name = req.matches[1];
|
||||
@@ -666,18 +724,35 @@ protected:
|
||||
.Get("/streamed-chunked",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](uint64_t /*offset*/, DataSink sink, Done done) {
|
||||
sink("123", 3);
|
||||
sink("456", 3);
|
||||
sink("789", 3);
|
||||
done();
|
||||
[](uint64_t /*offset*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
sink.write("123", 3);
|
||||
sink.write("456", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done();
|
||||
});
|
||||
})
|
||||
.Get("/streamed-chunked2",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto i = new int(0);
|
||||
res.set_chunked_content_provider(
|
||||
[i](uint64_t /*offset*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
switch (*i) {
|
||||
case 0: sink.write("123", 3); break;
|
||||
case 1: sink.write("456", 3); break;
|
||||
case 2: sink.write("789", 3); break;
|
||||
case 3: sink.done(); break;
|
||||
}
|
||||
(*i)++;
|
||||
},
|
||||
[i] { delete i; });
|
||||
})
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
6, [](uint64_t offset, uint64_t /*length*/, DataSink sink) {
|
||||
sink(offset < 3 ? "a" : "b", 1);
|
||||
6, [](uint64_t offset, uint64_t /*length*/, DataSink &sink) {
|
||||
sink.write(offset < 3 ? "a" : "b", 1);
|
||||
});
|
||||
})
|
||||
.Get("/streamed-with-range",
|
||||
@@ -685,23 +760,25 @@ protected:
|
||||
auto data = new std::string("abcdefg");
|
||||
res.set_content_provider(
|
||||
data->size(),
|
||||
[data](uint64_t offset, uint64_t length, DataSink sink) {
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
sink(&d[static_cast<size_t>(offset)], out_len);
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
},
|
||||
[data] { delete data; });
|
||||
})
|
||||
.Get("/streamed-cancel",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
size_t(-1),
|
||||
[](uint64_t /*offset*/, uint64_t /*length*/, DataSink sink) {
|
||||
std::string data = "data_chunk";
|
||||
sink(data.data(), data.size());
|
||||
});
|
||||
res.set_content_provider(size_t(-1), [](uint64_t /*offset*/,
|
||||
uint64_t /*length*/,
|
||||
DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
std::string data = "data_chunk";
|
||||
sink.write(data.data(), data.size());
|
||||
});
|
||||
})
|
||||
.Get("/with-range",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -799,19 +876,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());
|
||||
|
||||
@@ -873,6 +950,12 @@ protected:
|
||||
EXPECT_EQ(body, "content");
|
||||
res.set_content(body, "text/plain");
|
||||
})
|
||||
.Post("/query-string-and-body",
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
ASSERT_TRUE(req.has_param("key"));
|
||||
EXPECT_EQ(req.get_param_value("key"), "value");
|
||||
EXPECT_EQ(req.body, "content");
|
||||
})
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
.Get("/gzip",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -913,7 +996,7 @@ protected:
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1020,6 +1103,26 @@ TEST_F(ServerTest, PostMethod2) {
|
||||
ASSERT_EQ("coder", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutMethod3) {
|
||||
auto res = cli_.Get("/person/john3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(404, res->status);
|
||||
|
||||
Params params;
|
||||
params.emplace("name", "john3");
|
||||
params.emplace("note", "coder");
|
||||
|
||||
res = cli_.Put("/person", params);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
res = cli_.Get("/person/john3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
ASSERT_EQ("coder", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostWwwFormUrlEncodedJson) {
|
||||
Params params;
|
||||
params.emplace("json", JSON_DATA);
|
||||
@@ -1124,6 +1227,14 @@ TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
|
||||
auto res = cli_.Get("/dir/test.abcde");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("abcde", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||
EXPECT_EQ(false, svr_.set_base_dir("./www3", "invalid_mount_point"));
|
||||
}
|
||||
@@ -1463,6 +1574,13 @@ TEST_F(ServerTest, GetStreamedChunked) {
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedChunked2) {
|
||||
auto res = cli_.Get("/streamed-chunked2");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, LargeChunkedPost) {
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
@@ -1509,7 +1627,7 @@ TEST_F(ServerTest, SlowRequest) {
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
request_threads_.push_back(
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
msleep(100);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, Put) {
|
||||
@@ -1522,8 +1640,9 @@ TEST_F(ServerTest, Put) {
|
||||
TEST_F(ServerTest, PutWithContentProvider) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
|
||||
sink("PUT", 3);
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
@@ -1534,12 +1653,14 @@ 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);
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
},
|
||||
"text/plain", true);
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1547,7 +1668,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);
|
||||
@@ -1620,7 +1742,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);
|
||||
@@ -1640,6 +1763,13 @@ TEST_F(ServerTest, PatchContentReceiver) {
|
||||
ASSERT_EQ("content", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostQueryStringAndBody) {
|
||||
auto res =
|
||||
cli_.Post("/query-string-and-body?key=value", "content", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HTTP2Magic) {
|
||||
Request req;
|
||||
req.method = "PRI";
|
||||
@@ -1652,6 +1782,7 @@ TEST_F(ServerTest, HTTP2Magic) {
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, KeepAlive) {
|
||||
cli_.set_keep_alive_max_count(4);
|
||||
|
||||
@@ -1801,7 +1932,8 @@ 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);
|
||||
@@ -1809,16 +1941,16 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
#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);
|
||||
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 {
|
||||
[&](Stream &strm, bool /*last_connection*/, bool &
|
||||
/*connection_close*/) -> bool {
|
||||
if (req.size() !=
|
||||
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
||||
return false;
|
||||
@@ -1835,24 +1967,22 @@ static bool send_request(time_t read_timeout_sec, const std::string& req) {
|
||||
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");
|
||||
});
|
||||
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);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(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";
|
||||
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();
|
||||
@@ -1862,10 +1992,9 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
Server svr;
|
||||
svr.Get("/hi",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
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
|
||||
@@ -1875,7 +2004,7 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
bool listen_thread_ok = false;
|
||||
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// A certain header line causes an exception if the header property is parsed
|
||||
@@ -1891,6 +2020,46 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/events", [](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_header("Cache-Control", "no-cache");
|
||||
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
|
||||
char buffer[27];
|
||||
int size = sprintf(buffer, "data:%ld\n\n", offset);
|
||||
sink.write(buffer, size);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
Client client(HOST, PORT);
|
||||
const Headers headers = {{"Accept", "text/event-stream"},
|
||||
{"Connection", "Keep-Alive"}};
|
||||
|
||||
auto get_thread = std::thread([&client, &headers]() {
|
||||
std::shared_ptr<Response> res = client.Get(
|
||||
"/events", headers,
|
||||
[](const char * /*data*/, size_t /*len*/) -> bool { return true; });
|
||||
});
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
svr.stop();
|
||||
|
||||
listen_thread.join();
|
||||
get_thread.join();
|
||||
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -1910,7 +2079,7 @@ protected:
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(nullptr, PORT, AI_PASSIVE)); });
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1944,12 +2113,12 @@ protected:
|
||||
virtual void SetUp() {
|
||||
t_ = thread([&]() {
|
||||
svr_.bind_to_any_port(HOST);
|
||||
msleep(500);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
ASSERT_TRUE(svr_.listen_after_bind());
|
||||
});
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1989,7 +2158,7 @@ protected:
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2084,11 +2253,11 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
});
|
||||
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_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);
|
||||
|
||||
@@ -2103,10 +2272,11 @@ TEST(SSLClientServerTest, ClientCertMissing) {
|
||||
svr.Get("/test", [&](const Request &, Response &) { ASSERT_TRUE(false); });
|
||||
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(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();
|
||||
@@ -2124,11 +2294,11 @@ TEST(SSLClientServerTest, TrustDirOptional) {
|
||||
});
|
||||
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
msleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_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);
|
||||
|
||||
|
||||
303
test/test_proxy.cc
Normal file
303
test/test_proxy.cc
Normal file
@@ -0,0 +1,303 @@
|
||||
#include <future>
|
||||
#include <gtest/gtest.h>
|
||||
#include <httplib.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
|
||||
void ProxyTest(Client& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/get");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(407, res->status);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(ProxyTest, SSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, SSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void RedirectProxyText(Client& cli, const char *path, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
} else {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
cli.set_proxy_digest_auth("hello", "world");
|
||||
#endif
|
||||
}
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinNoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, HTTPBinNoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, YouTubeNoSSLBasic) {
|
||||
Client cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, YouTubeNoSSLDigest) {
|
||||
Client cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", false);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, YouTubeSSLBasic) {
|
||||
SSLClient cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, YouTubeSSLDigest) {
|
||||
SSLClient cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void BaseAuthTestFromHTTPWatch(Client& cli) {
|
||||
cli.set_proxy("localhost", 3128);
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
|
||||
{
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res =
|
||||
cli.Get("/basic-auth/hello/world",
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
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("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BaseAuthTest, NoSSL) {
|
||||
Client cli("httpbin.org");
|
||||
BaseAuthTestFromHTTPWatch(cli);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(BaseAuthTest, SSL) {
|
||||
SSLClient cli("httpbin.org");
|
||||
BaseAuthTestFromHTTPWatch(cli);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void DigestAuthTestFromHTTPWatch(Client& cli) {
|
||||
cli.set_proxy("localhost", 3129);
|
||||
cli.set_proxy_digest_auth("hello", "world");
|
||||
|
||||
{
|
||||
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-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("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DigestAuthTest, SSL) {
|
||||
SSLClient cli("httpbin.org");
|
||||
DigestAuthTestFromHTTPWatch(cli);
|
||||
}
|
||||
|
||||
TEST(DigestAuthTest, NoSSL) {
|
||||
Client cli("httpbin.org");
|
||||
DigestAuthTestFromHTTPWatch(cli);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void KeepAliveTest(Client& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
} else {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
cli.set_proxy_digest_auth("hello", "world");
|
||||
#endif
|
||||
}
|
||||
|
||||
cli.set_keep_alive_max_count(4);
|
||||
cli.set_follow_location(true);
|
||||
cli.set_digest_auth("hello", "world");
|
||||
|
||||
std::vector<Request> requests;
|
||||
|
||||
Get(requests, "/get");
|
||||
Get(requests, "/redirect/2");
|
||||
|
||||
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-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
for (auto path : paths) {
|
||||
Get(requests, path.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
int count = 100;
|
||||
while (count--) {
|
||||
Get(requests, "/get");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Response> responses;
|
||||
auto ret = cli.send(requests, responses);
|
||||
ASSERT_TRUE(ret == true);
|
||||
ASSERT_TRUE(requests.size() == responses.size());
|
||||
|
||||
size_t i = 0;
|
||||
|
||||
{
|
||||
auto &res = responses[i++];
|
||||
EXPECT_EQ(200, res.status);
|
||||
}
|
||||
|
||||
{
|
||||
auto &res = responses[i++];
|
||||
EXPECT_EQ(200, res.status);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
int count = paths.size();
|
||||
while (count--) {
|
||||
auto &res = responses[i++];
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res.body);
|
||||
EXPECT_EQ(200, res.status);
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < responses.size(); i++) {
|
||||
auto &res = responses[i];
|
||||
EXPECT_EQ(200, res.status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, NoSSLWithBasic) {
|
||||
Client cli("httpbin.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, NoSSLWithDigest) {
|
||||
Client cli("httpbin.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
1
test/www/dir/test.abcde
Normal file
1
test/www/dir/test.abcde
Normal file
@@ -0,0 +1 @@
|
||||
abcde
|
||||
Reference in New Issue
Block a user