mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-12 09:37:15 +00:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf7700d192 | ||
|
|
3da925d6fe | ||
|
|
319417f26d | ||
|
|
4c3b119dde | ||
|
|
6de8684328 | ||
|
|
ccc9a9b3f4 | ||
|
|
f2bb9c45d6 | ||
|
|
9a663aa94e | ||
|
|
d0d744d520 | ||
|
|
fce8e6fefd | ||
|
|
180aa32ebf | ||
|
|
fe01fa760b | ||
|
|
d61d63dd97 | ||
|
|
3fe13ecc91 | ||
|
|
064cc6810e | ||
|
|
464cc89b77 | ||
|
|
ca5a50d2c9 | ||
|
|
b251668522 | ||
|
|
9ee740fe8f | ||
|
|
851edaf77f | ||
|
|
1a2a6e2b01 | ||
|
|
ac7742bb32 | ||
|
|
82c11168c1 | ||
|
|
7c5197c86c | ||
|
|
8801e51138 | ||
|
|
89740a808d | ||
|
|
2377a20e0b | ||
|
|
5e43680486 | ||
|
|
79df842cc5 | ||
|
|
48a4a5812e | ||
|
|
a7a6df49a4 | ||
|
|
e932c7e1ec | ||
|
|
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 | ||
|
|
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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,8 +6,11 @@ example/hello
|
|||||||
example/simplesvr
|
example/simplesvr
|
||||||
example/benchmark
|
example/benchmark
|
||||||
example/redirect
|
example/redirect
|
||||||
|
example/sse
|
||||||
|
example/upload
|
||||||
example/*.pem
|
example/*.pem
|
||||||
test/test
|
test/test
|
||||||
|
test/test_proxy
|
||||||
test/test.xcodeproj/xcuser*
|
test/test.xcodeproj/xcuser*
|
||||||
test/test.xcodeproj/*/xcuser*
|
test/test.xcodeproj/*/xcuser*
|
||||||
test/*.pem
|
test/*.pem
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Environment
|
# Environment
|
||||||
language: cpp
|
language: cpp
|
||||||
os:
|
os:
|
||||||
|
- linux
|
||||||
- osx
|
- osx
|
||||||
|
|
||||||
# Compiler selection
|
# Compiler selection
|
||||||
|
|||||||
360
README.md
360
README.md
@@ -5,7 +5,7 @@ cpp-httplib
|
|||||||
[](https://travis-ci.org/yhirose/cpp-httplib)
|
[](https://travis-ci.org/yhirose/cpp-httplib)
|
||||||
[](https://ci.appveyor.com/project/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!
|
It's extremely easy to setup. Just include **httplib.h** file in your code!
|
||||||
|
|
||||||
@@ -17,24 +17,24 @@ Server Example
|
|||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
using namespace httplib;
|
using namespace httplib;
|
||||||
|
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||||
auto numbers = req.matches[1];
|
auto numbers = req.matches[1];
|
||||||
res.set_content(numbers, "text/plain");
|
res.set_content(numbers, "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||||
svr.stop();
|
svr.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.listen("localhost", 1234);
|
svr.listen("localhost", 1234);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -50,34 +50,70 @@ svr.listen_after_bind();
|
|||||||
### Static File Server
|
### Static File Server
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
|
// Mount / to ./www directory
|
||||||
|
auto ret = svr.set_mount_point("/", "./www");
|
||||||
|
if (!ret) {
|
||||||
|
// The specified base directory doesn't exist...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount /public to ./www directory
|
||||||
|
ret = svr.set_mount_point("/public", "./www");
|
||||||
|
|
||||||
|
// Mount /public to ./www1 and ./www2 directories
|
||||||
|
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
|
||||||
|
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
|
||||||
|
|
||||||
|
// Remove mount /
|
||||||
|
ret = svr.remove_mount_point("/");
|
||||||
|
|
||||||
|
// Remove mount /public
|
||||||
|
ret = svr.remove_mount_point("/public");
|
||||||
```
|
```
|
||||||
|
|
||||||
```cpp
|
```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
|
The followings are built-in mappings:
|
||||||
svr.set_base_dir("./www1", "/public"); // 1st order
|
|
||||||
svr.set_base_dir("./www2", "/public"); // 2nd order
|
| 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 |
|
||||||
|
|
||||||
|
NOTE: These the static file server methods are not thread safe.
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.set_logger([](const auto& req, const auto& res) {
|
svr.set_logger([](const auto& req, const auto& res) {
|
||||||
your_logger(req, res);
|
your_logger(req, res);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Handler
|
### Error handler
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.set_error_handler([](const auto& req, auto& res) {
|
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];
|
char buf[BUFSIZ];
|
||||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||||
res.set_content(buf, "text/html");
|
res.set_content(buf, "text/html");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -85,31 +121,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||||
auto size = req.files.size();
|
auto size = req.files.size();
|
||||||
auto ret = req.has_file("name1");
|
auto ret = req.has_file("name1");
|
||||||
const auto& file = req.get_file_value("name1");
|
const auto& file = req.get_file_value("name1");
|
||||||
// file.filename;
|
// file.filename;
|
||||||
// file.content_type;
|
// file.content_type;
|
||||||
// file.content;
|
// file.content;
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send content with Content provider
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
const uint64_t DATA_CHUNK_SIZE = 4;
|
|
||||||
|
|
||||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
|
||||||
auto data = new std::string("abcdefg");
|
|
||||||
|
|
||||||
res.set_content_provider(
|
|
||||||
data->size(), // Content length
|
|
||||||
[data](uint64_t offset, uint64_t length, DataSink sink) {
|
|
||||||
const auto &d = *data;
|
|
||||||
sink(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
|
||||||
},
|
|
||||||
[data] { delete data; });
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,15 +136,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 {
|
||||||
@@ -141,34 +157,49 @@ svr.Post("/content_receiver",
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send content with Content provider
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const uint64_t DATA_CHUNK_SIZE = 4;
|
||||||
|
|
||||||
|
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||||
|
auto data = new std::string("abcdefg");
|
||||||
|
|
||||||
|
res.set_content_provider(
|
||||||
|
data->size(), // Content length
|
||||||
|
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||||
|
const auto &d = *data;
|
||||||
|
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||||
|
},
|
||||||
|
[data] { delete data; });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Chunked transfer encoding
|
### Chunked transfer encoding
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||||
res.set_chunked_content_provider(
|
res.set_chunked_content_provider(
|
||||||
[](uint64_t offset, DataSink sink, Done done) {
|
[](uint64_t offset, DataSink &sink) {
|
||||||
sink("123", 3);
|
sink.write("123", 3);
|
||||||
sink("345", 3);
|
sink.write("345", 3);
|
||||||
sink("789", 3);
|
sink.write("789", 3);
|
||||||
done();
|
sink.done();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Default thread pool supporet
|
### Server-Sent Events
|
||||||
|
|
||||||
Set thread count to 8:
|
Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/sse.cc).
|
||||||
|
|
||||||
```cpp
|
### Default thread pool support
|
||||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
|
|
||||||
```
|
|
||||||
|
|
||||||
Disable the default thread pool:
|
|
||||||
|
|
||||||
```cpp
|
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 0
|
|
||||||
```
|
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||||
|
|
||||||
### Override the default thread pool with yours
|
### Override the default thread pool with yours
|
||||||
|
|
||||||
@@ -196,47 +227,64 @@ svr.new_task_queue = [] {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 'Expect: 100-continue' handler
|
||||||
|
|
||||||
|
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Send a '417 Expectation Failed' response.
|
||||||
|
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||||
|
return 417;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Send a final status without reading the message body.
|
||||||
|
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||||
|
return res.status = 401;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Client Example
|
Client Example
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
### GET
|
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
httplib::Client cli("localhost", 1234);
|
// IMPORTANT: 1st parameter must be a hostname or an IP adress string.
|
||||||
|
httplib::Client cli("localhost", 1234);
|
||||||
|
|
||||||
auto res = cli.Get("/hi");
|
auto res = cli.Get("/hi");
|
||||||
if (res && res->status == 200) {
|
if (res && res->status == 200) {
|
||||||
std::cout << res->body << std::endl;
|
std::cout << res->body << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### GET with HTTP headers
|
### GET with HTTP headers
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
httplib::Headers headers = {
|
httplib::Headers headers = {
|
||||||
{ "Accept-Encoding", "gzip, deflate" }
|
{ "Accept-Encoding", "gzip, deflate" }
|
||||||
};
|
};
|
||||||
auto res = cli.Get("/hi", headers);
|
auto res = cli.Get("/hi", headers);
|
||||||
```
|
```
|
||||||
|
|
||||||
### GET with Content Receiver
|
### GET with Content Receiver
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
std::string body;
|
std::string body;
|
||||||
|
|
||||||
auto res = cli.Get("/large-data",
|
auto res = cli.Get("/large-data",
|
||||||
[&](const char *data, uint64_t data_length) {
|
[&](const char *data, uint64_t data_length) {
|
||||||
body.append(data, data_length);
|
body.append(data, data_length);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(res->body.empty());
|
assert(res->body.empty());
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST
|
### POST
|
||||||
@@ -269,15 +317,15 @@ auto res = cli.Post("/post", params);
|
|||||||
### POST with Multipart Form Data
|
### POST with Multipart Form Data
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
httplib::MultipartFormDataItems items = {
|
httplib::MultipartFormDataItems items = {
|
||||||
{ "text1", "text default", "", "" },
|
{ "text1", "text default", "", "" },
|
||||||
{ "text2", "aωb", "", "" },
|
{ "text2", "aωb", "", "" },
|
||||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||||
{ "file3", "", "", "application/octet-stream" },
|
{ "file3", "", "", "application/octet-stream" },
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res = cli.Post("/multipart", items);
|
auto res = cli.Post("/multipart", items);
|
||||||
```
|
```
|
||||||
|
|
||||||
### PUT
|
### PUT
|
||||||
@@ -302,7 +350,7 @@ res = cli.Options("/resource/foo");
|
|||||||
### Connection Timeout
|
### Connection Timeout
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds
|
cli.set_timeout_sec(5); // timeouts in 5 seconds
|
||||||
```
|
```
|
||||||
### With Progress Callback
|
### With Progress Callback
|
||||||
|
|
||||||
@@ -311,31 +359,43 @@ httplib::Client client(url, port);
|
|||||||
|
|
||||||
// prints: 0 / 000 bytes => 50% complete
|
// prints: 0 / 000 bytes => 50% complete
|
||||||
std::shared_ptr<httplib::Response> res =
|
std::shared_ptr<httplib::Response> res =
|
||||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||||
printf("%lld / %lld bytes => %d%% complete\n",
|
printf("%lld / %lld bytes => %d%% complete\n",
|
||||||
len, total,
|
len, total,
|
||||||
(int)((len/total)*100));
|
(int)((len/total)*100));
|
||||||
return true; // return 'false' if you want to cancel the request.
|
return true; // return 'false' if you want to cancel the request.
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
### Authentication
|
||||||
|
|
||||||
### Basic Authentication
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
httplib::Client cli("httplib.org");
|
// Basic Authentication
|
||||||
|
cli.set_basic_auth("user", "pass");
|
||||||
|
|
||||||
auto res = cli.Get("/basic-auth/hello/world", {
|
// Digest Authentication
|
||||||
httplib::make_basic_authentication_header("hello", "world")
|
cli.set_digest_auth("user", "pass");
|
||||||
});
|
|
||||||
// res->status should be 200
|
|
||||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
### Range
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -381,11 +441,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,12 +485,72 @@ 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
|
||||||
----
|
----
|
||||||
|
|
||||||
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
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
MIT license (© 2019 Yuji Hirose)
|
MIT license (© 2020 Yuji Hirose)
|
||||||
|
|
||||||
|
Special Thanks To
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The following folks made great contributions to polish this library to totally another level from a simple toy!
|
||||||
|
|
||||||
|
* [Zefz](https://github.com/Zefz)
|
||||||
|
* [PixlRainbow](https://github.com/PixlRainbow)
|
||||||
|
* [sgraham](https://github.com/sgraham)
|
||||||
|
* [mrexodia](https://github.com/mrexodia)
|
||||||
|
* [hyperxor](https://github.com/hyperxor)
|
||||||
|
* [omaralvarez](https://github.com/omaralvarez)
|
||||||
|
* [vvanelslande](https://github.com/vvanelslande)
|
||||||
|
* [underscorediscovery](https://github.com/underscorediscovery)
|
||||||
|
* [sux2mfgj](https://github.com/sux2mfgj)
|
||||||
|
* [matvore](https://github.com/matvore)
|
||||||
|
* [intmain-io](https://github.com/intmain)
|
||||||
|
* [davidgfnet](https://github.com/davidgfnet)
|
||||||
|
* [crtxcr](https://github.com/crtxcr)
|
||||||
|
* [const-volatile](https://github.com/const)
|
||||||
|
* [aguadoenzo](https://github.com/aguadoenzo)
|
||||||
|
* [TheMaverickProgrammer](https://github.com/TheMaverickProgrammer)
|
||||||
|
* [vdudouyt](https://github.com/vdudouyt)
|
||||||
|
* [stupedama](https://github.com/stupedama)
|
||||||
|
* [rockwotj](https://github.com/rockwotj)
|
||||||
|
* [marknelson](https://github.com/marknelson)
|
||||||
|
* [jaspervandeven](https://github.com/jaspervandeven)
|
||||||
|
* [hans-erickson](https://github.com/hans)
|
||||||
|
* [ha11owed](https://github.com/ha11owed)
|
||||||
|
* [gulrak](https://github.com/gulrak)
|
||||||
|
* [dolphineye](https://github.com/dolphineye)
|
||||||
|
* [danielzehe](https://github.com/danielzehe)
|
||||||
|
* [batist73](https://github.com/batist73)
|
||||||
|
* [barryam3](https://github.com/barryam3)
|
||||||
|
* [adikabintang](https://github.com/adikabintang)
|
||||||
|
* [aaronalbers](https://github.com/aaronalbers)
|
||||||
|
* [Whitetigerswt](https://github.com/Whitetigerswt)
|
||||||
|
* [TangHuaiZhe](https://github.com/TangHuaiZhe)
|
||||||
|
* [Sil3ntStorm](https://github.com/Sil3ntStorm)
|
||||||
|
* [MannyClicks](https://github.com/MannyClicks)
|
||||||
|
* [DraTeots](https://github.com/DraTeots)
|
||||||
|
* [BastienDurel](https://github.com/BastienDurel)
|
||||||
|
* [vitalyster](https://github.com/vitalyster)
|
||||||
|
* [trollixx](https://github.com/trollixx)
|
||||||
|
|||||||
@@ -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
|
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
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
|
server : server.cc ../httplib.h Makefile
|
||||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
$(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
|
redirect : redirect.cc ../httplib.h Makefile
|
||||||
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
$(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
|
benchmark : benchmark.cc ../httplib.h Makefile
|
||||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
$(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
|
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 sse benchmark *.pem
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -125,7 +122,10 @@ int main(int argc, const char **argv) {
|
|||||||
auto base_dir = "./";
|
auto base_dir = "./";
|
||||||
if (argc > 2) { base_dir = argv[2]; }
|
if (argc > 2) { base_dir = argv[2]; }
|
||||||
|
|
||||||
svr.set_base_dir(base_dir);
|
if (!svr.set_mount_point("/", base_dir)) {
|
||||||
|
cout << "The specified base directory doesn't exist...";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
cout << "The server started at port " << port << "...";
|
cout << "The server started at port " << port << "...";
|
||||||
|
|
||||||
|
|||||||
107
example/sse.cc
Normal file
107
example/sse.cc
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// 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 <sstream>
|
||||||
|
#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
|
// 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="image_file" accept="image/*">
|
||||||
|
<input type="file" name="text_file" accept="text/*">
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
@@ -35,12 +36,23 @@ 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 image_file = req.get_file_value("image_file");
|
||||||
cout << "file: " << file.offset << ":" << file.length << ":" << file.filename << endl;
|
auto text_file = req.get_file_value("text_file");
|
||||||
|
|
||||||
ofstream ofs(file.filename, ios::binary);
|
cout << "image file length: " << image_file.content.length() << endl
|
||||||
ofs << req.body.substr(file.offset, file.length);
|
<< "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");
|
res.set_content("done", "text/plain");
|
||||||
});
|
});
|
||||||
|
|||||||
32
split.py
Normal file
32
split.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
border = '// ----------------------------------------------------------------------------'
|
||||||
|
|
||||||
|
PythonVersion = sys.version_info[0];
|
||||||
|
|
||||||
|
with open('httplib.h') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
inImplementation = False
|
||||||
|
|
||||||
|
if PythonVersion < 3:
|
||||||
|
os.makedirs('out')
|
||||||
|
else:
|
||||||
|
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
|
all : test
|
||||||
./test
|
./test
|
||||||
|
|
||||||
|
proxy : test_proxy
|
||||||
|
./test_proxy
|
||||||
|
|
||||||
test : test.cc ../httplib.h Makefile cert.pem
|
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
|
$(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:
|
cert.pem:
|
||||||
openssl genrsa 2048 > key.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
|
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 .
|
#c_rehash .
|
||||||
|
|
||||||
clean:
|
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
|
||||||
644
test/test.cc
644
test/test.cc
File diff suppressed because it is too large
Load Diff
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