mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 402d47e2cd | |||
| 171fc2e353 | |||
| ced4160d05 | |||
| 5b51aa6851 | |||
| dc13cde820 | |||
| b0af78e340 | |||
| 914c8860e8 | |||
| dc6a72a0fd | |||
| 685533ba50 | |||
| 6e46ccb37c | |||
| ac18b70a0f | |||
| e1acb949e7 | |||
| ab96f49766 | |||
| 7b3cea5317 | |||
| 26deffe0c6 | |||
| e07c5fec01 | |||
| 6e473a7c5c | |||
| c74129a1c2 | |||
| 18e750b4e7 | |||
| 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 |
@@ -6,9 +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
|
||||
|
||||
@@ -5,10 +5,12 @@ 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!
|
||||
|
||||
For Windows users: Please read [this note](https://github.com/yhirose/cpp-httplib#windows).
|
||||
|
||||
Server Example
|
||||
--------------
|
||||
|
||||
@@ -17,24 +19,34 @@ Server Example
|
||||
|
||||
int main(void)
|
||||
{
|
||||
using namespace httplib;
|
||||
using namespace httplib;
|
||||
|
||||
Server svr;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
svr.Get("/body-header-param", [](const Request& req, Response& res) {
|
||||
if (req.has_header("Content-Length")) {
|
||||
auto val = req.get_header_value("Content-Length");
|
||||
}
|
||||
if (req.has_param("key")) {
|
||||
auto val = req.get_param_value("key");
|
||||
}
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,34 +62,70 @@ svr.listen_after_bind();
|
||||
### Static File Server
|
||||
|
||||
```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
|
||||
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 |
|
||||
|
||||
NOTE: These the static file server methods are not thread safe.
|
||||
|
||||
### Logging
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto& req, const auto& res) {
|
||||
your_logger(req, res);
|
||||
your_logger(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handler
|
||||
### Error handler
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto& req, auto& res) {
|
||||
const char* 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");
|
||||
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");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -85,31 +133,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// 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; });
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
});
|
||||
```
|
||||
|
||||
@@ -140,34 +169,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
|
||||
|
||||
```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
|
||||
|
||||
Set thread count to 8:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
|
||||
```
|
||||
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||
|
||||
Disable the default thread pool:
|
||||
|
||||
```cpp
|
||||
#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
|
||||
|
||||
@@ -195,47 +239,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
|
||||
--------------
|
||||
|
||||
### GET
|
||||
|
||||
```c++
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
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");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET with HTTP headers
|
||||
|
||||
```c++
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
|
||||
### GET with Content Receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert(res->body.empty());
|
||||
assert(res->body.empty());
|
||||
```
|
||||
|
||||
### POST
|
||||
@@ -268,15 +329,15 @@ auto res = cli.Post("/post", params);
|
||||
### POST with Multipart Form Data
|
||||
|
||||
```c++
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
|
||||
auto res = cli.Post("/multipart", items);
|
||||
auto res = cli.Post("/multipart", items);
|
||||
```
|
||||
|
||||
### PUT
|
||||
@@ -301,7 +362,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
|
||||
|
||||
@@ -310,38 +371,43 @@ httplib::Client client(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
std::shared_ptr<httplib::Response> res =
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)((len/total)*100));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)(len*100/total));
|
||||
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
|
||||
|
||||
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
|
||||
@@ -450,9 +516,31 @@ 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++
|
||||
|
||||
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).
|
||||
|
||||
### Windows
|
||||
|
||||
Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand.
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
#include <Windows.h>
|
||||
```
|
||||
|
||||
```cpp
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2019 Yuji Hirose)
|
||||
MIT license (© 2020 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy!
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
@@ -122,9 +122,12 @@ 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_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 << "..." << endl;
|
||||
|
||||
svr.listen("localhost", port);
|
||||
|
||||
|
||||
+107
@@ -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);
|
||||
}
|
||||
+17
-6
@@ -13,7 +13,8 @@ using namespace std;
|
||||
|
||||
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>
|
||||
@@ -36,12 +37,22 @@ int main(void) {
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto file = req.get_file_value("file");
|
||||
cout << "file length: " << file.content.length() << ":" << file.filename
|
||||
<< endl;
|
||||
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");
|
||||
});
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
border = '// ----------------------------------------------------------------------------'
|
||||
|
||||
PythonVersion = sys.version_info[0];
|
||||
|
||||
with open('httplib.h') as f:
|
||||
lines = f.readlines()
|
||||
inImplementation = False
|
||||
os.makedirs('out', exist_ok=True)
|
||||
|
||||
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')
|
||||
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
|
||||
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
|
||||
@@ -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
|
||||
|
||||
+14
-1
@@ -38,6 +38,14 @@
|
||||
// when it's fused.
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-conversion"
|
||||
#endif
|
||||
|
||||
// The following lines pull in the real gtest *.cc files.
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
@@ -9039,7 +9047,6 @@ void HasNewFatalFailureHelper::ReportTestPartResult(
|
||||
//
|
||||
// Author: wan@google.com (Zhanyong Wan)
|
||||
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
@@ -9116,3 +9123,9 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames(
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#endif
|
||||
|
||||
+15
-1
@@ -59,6 +59,14 @@
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-compare"
|
||||
#endif
|
||||
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
@@ -7311,7 +7319,7 @@ inline const char* SkipComma(const char* str) {
|
||||
// the entire string if it contains no comma.
|
||||
inline String GetPrefixUntilComma(const char* str) {
|
||||
const char* comma = strchr(str, ',');
|
||||
return comma == NULL ? String(str) : String(str, comma - str);
|
||||
return comma == NULL ? String(str) : String(str, static_cast<size_t>(comma - str));
|
||||
}
|
||||
|
||||
// TypeParameterizedTest<Fixture, TestSel, Types>::Register()
|
||||
@@ -19553,4 +19561,10 @@ bool StaticAssertTypeEq() {
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // GTEST_INCLUDE_GTEST_GTEST_H_
|
||||
|
||||
@@ -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"]
|
||||
@@ -0,0 +1 @@
|
||||
hello:$apr1$O6S28OBL$8dr3ixl4Mohf97hgsYvLy/
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
hello:world
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker-compose down --rmi all
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker-compose up -d
|
||||
+446
-92
@@ -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;
|
||||
|
||||
@@ -80,6 +77,21 @@ TEST(ParseQueryTest, ParseQueryString) {
|
||||
EXPECT_EQ("val3", dic.find("key3")->second);
|
||||
}
|
||||
|
||||
TEST(ParamsToQueryTest, ConvertParamsToQuery) {
|
||||
Params dic;
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "");
|
||||
|
||||
dic.emplace("key1", "val1");
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1");
|
||||
|
||||
dic.emplace("key2", "val2");
|
||||
dic.emplace("key3", "val3");
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
@@ -202,17 +214,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");
|
||||
@@ -227,15 +260,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 =
|
||||
@@ -255,15 +288,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(
|
||||
@@ -287,15 +320,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;
|
||||
@@ -347,15 +380,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);
|
||||
@@ -363,15 +396,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);
|
||||
@@ -379,15 +412,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);
|
||||
@@ -395,15 +428,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);
|
||||
@@ -413,31 +446,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;
|
||||
@@ -474,13 +507,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
|
||||
@@ -500,11 +547,10 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-init/hello/world/MD5",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
cli.set_auth("hello", "world");
|
||||
cli.set_digest_auth("hello", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -512,6 +558,20 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
"{\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
|
||||
@@ -600,11 +660,23 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
|
||||
TEST(Server, BindAndListenSeparately) {
|
||||
Server svr;
|
||||
int port = svr.bind_to_any_port("localhost");
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLServer, BindAndListenSeparately) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
CLIENT_CA_CERT_DIR);
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -617,8 +689,9 @@ protected:
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
svr_.set_base_dir("./www");
|
||||
svr_.set_base_dir("./www2", "/mount");
|
||||
svr_.set_mount_point("/", "./www");
|
||||
svr_.set_mount_point("/mount", "./www2");
|
||||
svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde");
|
||||
|
||||
svr_.Get("/hi",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -626,7 +699,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",
|
||||
@@ -638,6 +711,12 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
})
|
||||
.Get("/a\\+\\+b",
|
||||
[&](const Request &req, Response &res) {
|
||||
ASSERT_TRUE(req.has_param("a +b"));
|
||||
auto val = req.get_param_value("a +b");
|
||||
res.set_content(val, "text/plain");
|
||||
})
|
||||
.Get("/", [&](const Request & /*req*/,
|
||||
Response &res) { res.set_redirect("/hi"); })
|
||||
.Post("/person",
|
||||
@@ -649,6 +728,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];
|
||||
@@ -669,18 +757,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",
|
||||
@@ -688,23 +793,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) {
|
||||
@@ -723,6 +830,7 @@ protected:
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_EQ(5u, req.files.size());
|
||||
ASSERT_TRUE(!req.has_file("???"));
|
||||
ASSERT_TRUE(req.body.empty());
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text1");
|
||||
@@ -876,6 +984,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) {
|
||||
@@ -916,7 +1030,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,6 +1064,16 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("HTTP/1.1", res->version);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod302) {
|
||||
auto res = cli_.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -971,6 +1095,15 @@ TEST_F(ServerTest, HeadMethod200) {
|
||||
EXPECT_EQ("", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod200Static) {
|
||||
auto res = cli_.Head("/mount/dir/index.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(104, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod404) {
|
||||
auto res = cli_.Head("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1023,6 +1156,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);
|
||||
@@ -1127,8 +1280,16 @@ 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"));
|
||||
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, EmptyRequest) {
|
||||
@@ -1305,6 +1466,13 @@ TEST_F(ServerTest, EndWithPercentCharacterInQuery) {
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PlusSignEncoding) {
|
||||
auto res = cli_.Get("/a+%2Bb?a %2bb=a %2Bb");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("a +b", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, MultipartFormData) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
@@ -1466,6 +1634,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";
|
||||
@@ -1512,7 +1687,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) {
|
||||
@@ -1525,8 +1700,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");
|
||||
|
||||
@@ -1540,8 +1716,9 @@ 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");
|
||||
|
||||
@@ -1558,6 +1735,20 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutContentWithDeflate) {
|
||||
cli_.set_compress(false);
|
||||
httplib::Headers headers;
|
||||
headers.emplace("Content-Encoding", "deflate");
|
||||
// PUT in deflate format:
|
||||
auto res = cli_.Put("/put", headers,
|
||||
"\170\234\013\010\015\001\0\001\361\0\372", "text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_F(ServerTest, Patch) {
|
||||
@@ -1646,6 +1837,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";
|
||||
@@ -1658,6 +1856,7 @@ TEST_F(ServerTest, HTTP2Magic) {
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, KeepAlive) {
|
||||
cli_.set_keep_alive_max_count(4);
|
||||
|
||||
@@ -1674,7 +1873,7 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
ASSERT_TRUE(ret == true);
|
||||
ASSERT_TRUE(requests.size() == responses.size());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
auto &res = responses[i];
|
||||
EXPECT_EQ(200, res.status);
|
||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
||||
@@ -1849,7 +2048,7 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
|
||||
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-
|
||||
@@ -1865,7 +2064,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
EXPECT_EQ(header_value, "\v bar \e");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
static void test_raw_request(const std::string &req) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
@@ -1879,22 +2079,175 @@ 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
|
||||
// naively with a single regex. This occurs with libc++ but not libstdc++.
|
||||
const std::string req =
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" ";
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
// A certain header line causes an exception if the header property is parsed
|
||||
// naively with a single regex. This occurs with libc++ but not libstdc++.
|
||||
test_raw_request(
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" "
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
// A certain header line causes an exception if the header property *name* is
|
||||
// parsed with a regular expression starting with "(.+?):" - this is a non-
|
||||
// greedy matcher and requires backtracking when there are a lot of ":"
|
||||
// characters.
|
||||
// This occurs with libc++ but not libstdc++.
|
||||
test_raw_request(
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
":-:::::::::::::::::::::::::::-::::::::::::::::::::::::@-&&&&&&&&&&&"
|
||||
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
|
||||
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-:::::"
|
||||
"::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::::::::::::::::::::"
|
||||
":::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::"
|
||||
"::::::::-:::::::::::::::::@-&&&&&&&--:::::::-::::::::::::::::::::::"
|
||||
":::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::"
|
||||
"::::::::::-:::::::::::::::::@-&&&&&::::::::::::-:::::::::::::::::@-"
|
||||
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
|
||||
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
|
||||
"::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::@-&&"
|
||||
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
|
||||
"::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&"
|
||||
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
|
||||
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&"
|
||||
"&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&"
|
||||
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
|
||||
"-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::"
|
||||
"::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::"
|
||||
":::::@-&&&&&&&&&&&::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::"
|
||||
":::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::"
|
||||
"::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-"
|
||||
":::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&---&&:&"
|
||||
"&&.0------------:-:::::::::::::::::::::::::::::-:::::::::::::::::@-"
|
||||
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
|
||||
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
|
||||
"::::@-&&&&&&&&&&&---&&:&&&.0------------O--------\rH PUTHTTP/1.1\r\n"
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
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];
|
||||
auto size = static_cast<size_t>(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());
|
||||
}
|
||||
|
||||
TEST(MountTest, Unmount) {
|
||||
Server svr;
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
svr.set_mount_point("/mount2", "./www2");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
res = cli.Get("/mount2/dir/test.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.set_mount_point("/", "./www");
|
||||
|
||||
res = cli.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.remove_mount_point("/");
|
||||
res = cli.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
svr.remove_mount_point("/mount2");
|
||||
res = cli.Get("/mount2/dir/test.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
throw std::runtime_error("exception...");
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_FALSE(res->has_header("EXCEPTION_WHAT"));
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -1914,7 +2267,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1948,12 +2301,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1993,7 +2346,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,7 +2432,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
char name[BUFSIZ];
|
||||
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
|
||||
name, sizeof(name));
|
||||
common_name.assign(name, name_len);
|
||||
common_name.assign(name, static_cast<size_t>(name_len));
|
||||
}
|
||||
|
||||
EXPECT_EQ("Common Name", common_name);
|
||||
@@ -2088,11 +2441,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);
|
||||
|
||||
@@ -2107,10 +2460,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();
|
||||
@@ -2128,11 +2482,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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
abcde
|
||||
Reference in New Issue
Block a user