mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c60e69c33 | |||
| 33e94891ee | |||
| 73e0729f63 | |||
| 21c529229c | |||
| 63643e6386 | |||
| 6cc2edce99 | |||
| d122ff3ca8 | |||
| 14c6d526b4 | |||
| 28e07bca16 | |||
| faa5f1d802 | |||
| 9d3365df54 | |||
| 6ff84d34d1 | |||
| b845425cd0 | |||
| 89519c88e2 | |||
| ff813bf99d | |||
| cf475bcb50 | |||
| bc80d7c789 | |||
| b7566f6961 | |||
| 0542fdb8e4 | |||
| 78c474c744 | |||
| 88411a1f52 | |||
| ae6cf70bc4 | |||
| 68d1281759 | |||
| 0308d60cb2 | |||
| 59f5fdbb33 | |||
| 13184f5f80 | |||
| 8d9a477edb | |||
| 85b4abbf16 | |||
| e42a358da8 | |||
| f008fe4539 | |||
| ddf41d29ef | |||
| 3f88a46c4a | |||
| 242706ea34 | |||
| a9f5f8683f | |||
| 60c2213893 | |||
| eb2d28bca2 | |||
| 86f637a246 | |||
| 2c07ec4600 | |||
| 871d8d67b0 | |||
| 7299713195 | |||
| 96afa7e108 | |||
| 55f57af0b9 | |||
| 6b35cd0116 | |||
| 99f2229e48 | |||
| b9641048fc | |||
| e9c6c6e609 | |||
| 40db42108f | |||
| 24bb1387d6 | |||
| d0bd4afb0b | |||
| 78ea786abd | |||
| 9cac2c9ceb | |||
| 0cff3245df | |||
| 0e3925db3f | |||
| c9a13d214b | |||
| 0954af2d4c | |||
| 7c1c952f5a | |||
| a6edfc730a | |||
| c1264bfedc | |||
| 90a5b6ceb0 | |||
| eb240ad2e5 |
@@ -5,9 +5,9 @@ cpp-httplib
|
||||
|
||||
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 the **httplib.h** file in your code!
|
||||
|
||||
NOTE: This is a 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
||||
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
||||
|
||||
Simple examples
|
||||
---------------
|
||||
@@ -15,8 +15,15 @@ Simple examples
|
||||
#### Server
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// HTTP
|
||||
httplib::Server svr;
|
||||
|
||||
// HTTPS
|
||||
httplib::SSLServer svr;
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
@@ -27,12 +34,18 @@ svr.listen("0.0.0.0", 8080);
|
||||
#### Client
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// HTTP
|
||||
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
// HTTPS
|
||||
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
|
||||
|
||||
res->status; // 200
|
||||
res->body; // "Hello World!"
|
||||
auto res = cli.Get("/hi");
|
||||
res->status;
|
||||
res->body;
|
||||
```
|
||||
|
||||
### Try out the examples on Repl.it!
|
||||
@@ -120,24 +133,30 @@ svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
|
||||
|
||||
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 |
|
||||
| Extension | MIME Type | Extension | MIME Type |
|
||||
| :--------- | :-------------------------- | :--------- | :-------------------------- |
|
||||
| css | text/css | mpga | audio/mpeg |
|
||||
| csv | text/csv | weba | audio/webm |
|
||||
| txt | text/plain | wav | audio/wave |
|
||||
| vtt | text/vtt | otf | font/otf |
|
||||
| html, htm | text/html | ttf | font/ttf |
|
||||
| apng | image/apng | woff | font/woff |
|
||||
| avif | image/avif | woff2 | font/woff2 |
|
||||
| bmp | image/bmp | 7z | application/x-7z-compressed |
|
||||
| gif | image/gif | atom | application/atom+xml |
|
||||
| png | image/png | pdf | application/pdf |
|
||||
| svg | image/svg+xml | mjs, js | application/javascript |
|
||||
| webp | image/webp | json | application/json |
|
||||
| ico | image/x-icon | rss | application/rss+xml |
|
||||
| tif | image/tiff | tar | application/x-tar |
|
||||
| tiff | image/tiff | xhtml, xht | application/xhtml+xml |
|
||||
| jpeg, jpg | image/jpeg | xslt | application/xslt+xml |
|
||||
| mp4 | video/mp4 | xml | application/xml |
|
||||
| mpeg | video/mpeg | gz | application/gzip |
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||
|
||||
NOTE: These the static file server methods are not thread safe.
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### Logging
|
||||
|
||||
@@ -158,6 +177,39 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Exception handler
|
||||
The exception handler gets called if a user routing handler throws an error.
|
||||
|
||||
```cpp
|
||||
svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) {
|
||||
res.status = 500;
|
||||
auto fmt = "<h1>Error 500</h1><p>%s</p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, e.what());
|
||||
res.set_content(buf, "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
### Pre routing handler
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto& req, auto& res) -> bool {
|
||||
if (req.path == "/hello") {
|
||||
res.set_content("world", "text/html");
|
||||
return Server::HandlerResponse::Handled;
|
||||
}
|
||||
return Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
### Post routing handler
|
||||
|
||||
```cpp
|
||||
svr.set_post_routing_handler([](const auto& req, auto& res) {
|
||||
res.set_header("ADDITIONAL_HEADER", "value");
|
||||
});
|
||||
```
|
||||
|
||||
### 'multipart/form-data' POST data
|
||||
|
||||
```cpp
|
||||
@@ -171,7 +223,7 @@ svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Receive content with Content receiver
|
||||
### Receive content with a content receiver
|
||||
|
||||
```cpp
|
||||
svr.Post("/content_receiver",
|
||||
@@ -198,7 +250,7 @@ svr.Post("/content_receiver",
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
### Send content with the content provider
|
||||
|
||||
```cpp
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
@@ -242,6 +294,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
@@ -255,7 +308,7 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
@@ -286,7 +339,7 @@ svr.set_write_timeout(5, 0); // 5 seconds
|
||||
svr.set_idle_interval(0, 100000); // 100 milliseconds
|
||||
```
|
||||
|
||||
### Set maximum payload length for reading request body
|
||||
### Set maximum payload length for reading a request body
|
||||
|
||||
```c++
|
||||
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
|
||||
@@ -364,6 +417,7 @@ httplib::Client cli("localhost:8080");
|
||||
httplib::Client cli("http://localhost");
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::Client cli("https://localhost");
|
||||
httplib::SSLClient cli("localhost");
|
||||
```
|
||||
|
||||
### Error code
|
||||
@@ -471,7 +525,7 @@ cli.set_read_timeout(5, 0); // 5 seconds
|
||||
cli.set_write_timeout(5, 0); // 5 seconds
|
||||
```
|
||||
|
||||
### Receive content with Content receiver
|
||||
### Receive content with a content receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
@@ -498,7 +552,7 @@ auto res = cli.Get(
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
### Send content with a content provider
|
||||
|
||||
```cpp
|
||||
std::string body = ...;
|
||||
@@ -621,7 +675,7 @@ res = cli.Get("/");
|
||||
res->status; // 200
|
||||
```
|
||||
|
||||
### Use a specitic network interface
|
||||
### Use a specific network interface
|
||||
|
||||
NOTE: This feature is not available on Windows, yet.
|
||||
|
||||
@@ -629,33 +683,10 @@ NOTE: This feature is not available on Windows, yet.
|
||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||
```
|
||||
|
||||
OpenSSL Support
|
||||
---------------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
httplib::SSLClient cli("localhost", 1234); // or `httplib::Client cli("https://localhost:1234");`
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(true);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE
|
||||
can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its
|
||||
internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might
|
||||
be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
|
||||
Compression
|
||||
-----------
|
||||
|
||||
The server can applie compression to the following MIME type contents:
|
||||
The server can apply compression to the following MIME type contents:
|
||||
|
||||
* all text types except text/event-stream
|
||||
* image/svg+xml
|
||||
@@ -688,6 +719,32 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res->body; // Compressed data
|
||||
```
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// Server
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234");
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
@@ -719,7 +776,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: Cygwin on Windows is not supported.
|
||||
Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
+2
-4
@@ -20,8 +20,6 @@ using namespace std;
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {
|
||||
id_ = 0;
|
||||
cid_ = -1;
|
||||
}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
@@ -41,8 +39,8 @@ public:
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_;
|
||||
atomic_int cid_;
|
||||
atomic_int id_ = 0;
|
||||
atomic_int cid_ = -1;
|
||||
string message_;
|
||||
};
|
||||
|
||||
|
||||
+565
-44
@@ -5,6 +5,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
#define SERVER_CERT_FILE "./cert.pem"
|
||||
@@ -46,6 +47,33 @@ TEST(StartupTest, WSAStartup) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||
string unescapedCharacters = "-_.!~*'()";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(unescapedCharacters), "-_.!~*'()");
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseReservedCharactersTest) {
|
||||
string reservedCharacters = ";,/?:@&=+$";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(reservedCharacters),
|
||||
"%3B%2C%2F%3F%3A%40%26%3D%2B%24");
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, TestUTF8Characters) {
|
||||
string chineseCharacters = "中国語";
|
||||
string russianCharacters = "дом";
|
||||
string brazilianCharacters = "óculos";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(chineseCharacters),
|
||||
"%E4%B8%AD%E5%9B%BD%E8%AA%9E");
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(russianCharacters),
|
||||
"%D0%B4%D0%BE%D0%BC");
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(brazilianCharacters), "%C3%B3culos");
|
||||
}
|
||||
|
||||
TEST(TrimTests, TrimStringTests) {
|
||||
EXPECT_EQ("abc", detail::trim_copy("abc"));
|
||||
EXPECT_EQ("abc", detail::trim_copy(" abc "));
|
||||
@@ -497,7 +525,7 @@ TEST(ConnectionErrorTest, InvalidHost) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -512,7 +540,7 @@ TEST(ConnectionErrorTest, InvalidHost2) {
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -521,15 +549,14 @@ TEST(ConnectionErrorTest, InvalidHost2) {
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto host = "localhost";
|
||||
auto port = 44380;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 44380;
|
||||
SSLClient cli(host, port);
|
||||
#else
|
||||
auto port = 8080;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -546,7 +573,7 @@ TEST(ConnectionErrorTest, Timeout) {
|
||||
auto port = 8080;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -563,7 +590,7 @@ TEST(CancelTest, NoCancel) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res);
|
||||
@@ -583,7 +610,7 @@ TEST(CancelTest, WithCancelSmallPayload) {
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
}
|
||||
@@ -598,7 +625,7 @@ TEST(CancelTest, WithCancelLargePayload) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
uint32_t count = 0;
|
||||
auto res = cli.Get("/range/65536",
|
||||
@@ -705,9 +732,8 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(AbsoluteRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -716,13 +742,13 @@ TEST(AbsoluteRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/absolute-redirect/3");
|
||||
auto res = cli.Get("/httpbin/absolute-redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -731,13 +757,13 @@ TEST(RedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/3");
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RelativeRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -746,13 +772,13 @@ TEST(RelativeRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/relative-redirect/3");
|
||||
auto res = cli.Get("/httpbin/relative-redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(TooManyRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -761,11 +787,10 @@ TEST(TooManyRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/21");
|
||||
auto res = cli.Get("/httpbin/redirect/21");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::ExceedRedirectCount, res.error());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(YahooRedirectTest, Redirect) {
|
||||
@@ -782,16 +807,39 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
EXPECT_EQ("https://yahoo.com/", res->location);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
auto res = cli.Get(
|
||||
"/httpbin/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest2, Redirect) {
|
||||
SSLClient cli("nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
Params params;
|
||||
params.emplace("url", "http://www.google.com");
|
||||
params.emplace("status_code", "302");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest3, Redirect) {
|
||||
SSLClient cli("nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
Params params;
|
||||
params.emplace("url", "http://www.google.com");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
@@ -841,6 +889,62 @@ TEST(UrlWithSpace, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
|
||||
}
|
||||
|
||||
TEST(RedirectFromPageWithContent, Redirect) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("___", "text/plain");
|
||||
res.set_redirect("/2");
|
||||
});
|
||||
|
||||
svr.Get("/2", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto th = std::thread([&]() { 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);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Hello World!", body);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(302, res->status);
|
||||
EXPECT_EQ("___", body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
th.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(BindServerTest, BindDualStack) {
|
||||
@@ -930,6 +1034,162 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13
|
||||
});
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
throw std::runtime_error("abc");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(NoContentTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi",
|
||||
[](const Request & /*req*/, Response &res) { res.status = 204; });
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(204, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
res.set_header("PRE_ROUTING", "on");
|
||||
res.set_content("Routing Handler", "text/plain");
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
}
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Error", "text/html");
|
||||
});
|
||||
|
||||
svr.set_post_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
res.set_header("POST_ROUTING", "on");
|
||||
}
|
||||
});
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/routing_handler");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Routing Handler", res->body);
|
||||
EXPECT_EQ(1, res->get_header_value_count("PRE_ROUTING"));
|
||||
EXPECT_EQ("on", res->get_header_value("PRE_ROUTING"));
|
||||
EXPECT_EQ(1, res->get_header_value_count("POST_ROUTING"));
|
||||
EXPECT_EQ("on", res->get_header_value("POST_ROUTING"));
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Hello World!\n", res->body);
|
||||
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
|
||||
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/aaa");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("Error", res->body);
|
||||
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
|
||||
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(InvalidFormatTest, StatusCode) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
res.status = 9999; // Status should be a three-digit code...
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1106,7 +1366,8 @@ protected:
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
auto ret = sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
[data] { delete data; });
|
||||
@@ -1336,6 +1597,38 @@ protected:
|
||||
std::string url = "/redirect/" + std::to_string(num);
|
||||
res.set_redirect(url);
|
||||
})
|
||||
.Post("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Put("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Patch("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Delete("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
|
||||
.Get("/compress",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -1408,6 +1701,7 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("HTTP/1.1", res->version);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("OK", res->reason);
|
||||
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);
|
||||
@@ -1676,10 +1970,72 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
|
||||
EXPECT_EQ("abcde", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRange) {
|
||||
auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(206, res->status);
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("cd"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, Binary) {
|
||||
std::vector<char> binary{0x00, 0x01, 0x02, 0x03};
|
||||
|
||||
auto res = cli_.Post("/binary", binary.data(), binary.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Put("/binary", binary.data(), binary.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Patch("/binary", binary.data(), binary.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Delete("/binary", binary.data(), binary.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, BinaryString) {
|
||||
auto binary = std::string("\x00\x01\x02\x03", 4);
|
||||
|
||||
auto res = cli_.Post("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Put("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Patch("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
|
||||
res = cli_.Delete("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, EmptyRequest) {
|
||||
auto res = cli_.Get("");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -2017,7 +2373,8 @@ TEST_F(ServerTest, ClientStop) {
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_TRUE(res.error() == Error::Canceled || res.error() == Error::Read);
|
||||
EXPECT_TRUE(res.error() == Error::Canceled ||
|
||||
res.error() == Error::Read || res.error() == Error::Write);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2165,7 +2522,8 @@ TEST_F(ServerTest, SlowPost) {
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(buffer, sizeof(buffer));
|
||||
auto ret = sink.write(buffer, sizeof(buffer));
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
@@ -2178,7 +2536,7 @@ TEST_F(ServerTest, SlowPostFail) {
|
||||
char buffer[64 * 1024];
|
||||
memset(buffer, 0x42, sizeof(buffer));
|
||||
|
||||
cli_.set_write_timeout(0, 0);
|
||||
cli_.set_write_timeout(std::chrono::seconds(0));
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
@@ -2316,6 +2674,24 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutLargeFileWithGzip2) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client cli("https://localhost:1234");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli("http://localhost:1234");
|
||||
#endif
|
||||
cli.set_compress(true);
|
||||
|
||||
auto res = cli.Put("/put-large", LARGE_DATA, "text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
EXPECT_EQ(101942u, res.get_request_header_value<uint64_t>("Content-Length"));
|
||||
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutContentWithDeflate) {
|
||||
cli_.set_compress(false);
|
||||
Headers headers;
|
||||
@@ -2770,8 +3146,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, false, nullptr,
|
||||
/*timeout_sec=*/5, 0, std::string(), error);
|
||||
detail::create_client_socket(HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0,
|
||||
std::string(), error);
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
@@ -2837,7 +3216,7 @@ static void test_raw_request(const std::string &req,
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
// without a trailing blank line.
|
||||
const time_t client_read_timeout_sec = 1;
|
||||
svr.set_read_timeout(client_read_timeout_sec + 1, 0);
|
||||
svr.set_read_timeout(std::chrono::seconds(client_read_timeout_sec + 1));
|
||||
bool listen_thread_ok = false;
|
||||
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
@@ -2957,6 +3336,12 @@ TEST(ServerRequestParsingTest, InvalidHeaderTextWithExtraCR) {
|
||||
"Content-Type: text/plain\r\n\r");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
|
||||
std::string out;
|
||||
test_raw_request("GET /h i HTTP/1.1\r\n\r\n", &out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -2966,7 +3351,8 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
DataSink &sink) {
|
||||
char buffer[27];
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
auto ret = sink.write(buffer, size);
|
||||
EXPECT_TRUE(ret);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
@@ -3083,9 +3469,9 @@ TEST(MountTest, Unmount) {
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
// res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -3101,7 +3487,8 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_FALSE(res->has_header("EXCEPTION_WHAT"));
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
@@ -3130,7 +3517,7 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
@@ -3146,6 +3533,103 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_error_handler([](Request const &, Response &res) -> void {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain", [](std::size_t const, DataSink &sink) -> bool {
|
||||
sink.os << "hello";
|
||||
sink.os << "world";
|
||||
sink.done();
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
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("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("helloworld", res->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(GetWithParametersTest, GetWithParameters) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response &res) {
|
||||
auto text = req.get_param_value("hello");
|
||||
res.set_content(text, "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));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
auto res = cli.Get("/", params, Headers{});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("world", res->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response &res) {
|
||||
auto text = req.get_param_value("hello");
|
||||
res.set_content(text, "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));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/", params, Headers{},
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("world", body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
@@ -3171,7 +3655,7 @@ TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
@@ -3396,6 +3880,7 @@ TEST(SSLClientTest, WildcardHostNameMatch) {
|
||||
|
||||
cli.set_ca_cert_path(CA_CERT_FILE);
|
||||
cli.enable_server_certificate_verification(true);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3626,7 +4111,6 @@ TEST(NoSSLSupport, SimpleInterface) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(InvalidScheme, SimpleInterface) {
|
||||
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
|
||||
}
|
||||
@@ -3636,6 +4120,19 @@ TEST(NoScheme, SimpleInterface) {
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
}
|
||||
|
||||
TEST(SendAPI, SimpleInterface) {
|
||||
Client cli("http://yahoo.com");
|
||||
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
req.path = "/";
|
||||
auto res = cli.send(req);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(YahooRedirectTest2, SimpleInterface) {
|
||||
Client cli("http://yahoo.com");
|
||||
|
||||
@@ -3702,15 +4199,39 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
|
||||
TEST(HttpsToHttpRedirectTest, SimpleInterface) {
|
||||
Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
Client("https://httpbin.org")
|
||||
.set_follow_location(true)
|
||||
.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
cli.Get("/httpbin/"
|
||||
"redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
|
||||
Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
Params params;
|
||||
params.emplace("url", "http://www.google.com");
|
||||
params.emplace("status_code", "302");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
|
||||
Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
Params params;
|
||||
params.emplace("url", "http://www.google.com");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
+27
-30
@@ -8,29 +8,29 @@ using namespace httplib;
|
||||
template <typename T>
|
||||
void ProxyTest(T& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(407, res->status);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(ProxyTest, SSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, SSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
@@ -54,29 +54,27 @@ void RedirectProxyText(T& cli, const char *path, bool basic) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(RedirectTest, HTTPBinNoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
Client cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, HTTPBinNoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
Client cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", false);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
SSLClient cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
SSLClient cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", false);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, YouTubeNoSSLBasic) {
|
||||
@@ -218,7 +216,8 @@ TEST(DigestAuthTest, NoSSL) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void KeepAliveTest(Client& cli, bool basic) {
|
||||
template <typename T>
|
||||
void KeepAliveTest(T& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
@@ -234,20 +233,20 @@ void KeepAliveTest(Client& cli, bool basic) {
|
||||
#endif
|
||||
|
||||
{
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
{
|
||||
auto res = cli.Get("/redirect/2");
|
||||
auto res = cli.Get("/httpbin/redirect/2");
|
||||
EXPECT_EQ(200, 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",
|
||||
"/httpbin/digest-auth/auth/hello/world/MD5",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-256",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-512",
|
||||
"/httpbin/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
for (auto path: paths) {
|
||||
@@ -258,34 +257,32 @@ void KeepAliveTest(Client& cli, bool basic) {
|
||||
}
|
||||
|
||||
{
|
||||
int count = 100;
|
||||
int count = 10;
|
||||
while (count--) {
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, NoSSLWithBasic) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, NoSSLWithDigest) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user