mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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,7 +5,7 @@ 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.
|
||||
|
||||
@@ -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,26 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### 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 true; // This request is handled
|
||||
}
|
||||
return false; // Let the router handle this request
|
||||
});
|
||||
```
|
||||
|
||||
### 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 +210,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 +237,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 +281,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 +295,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 +326,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 +404,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 +512,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 +539,7 @@ auto res = cli.Get(
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
### Send content with a content provider
|
||||
|
||||
```cpp
|
||||
std::string body = ...;
|
||||
@@ -621,7 +662,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 +670,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 +706,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 +763,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
|
||||
-------
|
||||
|
||||
@@ -192,6 +192,7 @@ using socket_t = int;
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
@@ -214,7 +215,6 @@ using socket_t = int;
|
||||
#include <openssl/applink.c>
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
@@ -597,6 +597,7 @@ inline void default_socket_options(socket_t sock) {
|
||||
class Server {
|
||||
public:
|
||||
using Handler = std::function<void(const Request &, Response &)>;
|
||||
using HandlerWithReturn = std::function<bool(const Request &, Response &)>;
|
||||
using HandlerWithContentReader = std::function<void(
|
||||
const Request &, Response &, const ContentReader &content_reader)>;
|
||||
using Expect100ContinueHandler =
|
||||
@@ -627,7 +628,11 @@ public:
|
||||
const char *mime);
|
||||
void set_file_request_handler(Handler handler);
|
||||
|
||||
void set_error_handler(HandlerWithReturn handler);
|
||||
void set_error_handler(Handler handler);
|
||||
void set_pre_routing_handler(HandlerWithReturn handler);
|
||||
void set_post_routing_handler(Handler handler);
|
||||
|
||||
void set_expect_100_continue_handler(Expect100ContinueHandler handler);
|
||||
void set_logger(Logger logger);
|
||||
|
||||
@@ -680,7 +685,8 @@ private:
|
||||
bool listen_internal();
|
||||
|
||||
bool routing(Request &req, Response &res, Stream &strm);
|
||||
bool handle_file_request(Request &req, Response &res, bool head = false);
|
||||
bool handle_file_request(const Request &req, Response &res,
|
||||
bool head = false);
|
||||
bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
|
||||
bool
|
||||
dispatch_request_for_content_reader(Request &req, Response &res,
|
||||
@@ -733,7 +739,9 @@ private:
|
||||
Handlers delete_handlers_;
|
||||
HandlersForContentReader delete_handlers_for_content_reader_;
|
||||
Handlers options_handlers_;
|
||||
Handler error_handler_;
|
||||
HandlerWithReturn error_handler_;
|
||||
HandlerWithReturn pre_routing_handler_;
|
||||
Handler post_routing_handler_;
|
||||
Logger logger_;
|
||||
Expect100ContinueHandler expect_100_continue_handler_;
|
||||
|
||||
@@ -817,6 +825,10 @@ public:
|
||||
Result Head(const char *path, const Headers &headers);
|
||||
|
||||
Result Post(const char *path);
|
||||
Result Post(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Post(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Post(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Post(const char *path, const Headers &headers, const std::string &body,
|
||||
@@ -839,6 +851,10 @@ public:
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
|
||||
Result Put(const char *path);
|
||||
Result Put(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Put(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Put(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Put(const char *path, const Headers &headers, const std::string &body,
|
||||
@@ -855,6 +871,11 @@ public:
|
||||
Result Put(const char *path, const Params ¶ms);
|
||||
Result Put(const char *path, const Headers &headers, const Params ¶ms);
|
||||
|
||||
Result Patch(const char *path);
|
||||
Result Patch(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Patch(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Patch(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Patch(const char *path, const Headers &headers,
|
||||
@@ -870,9 +891,13 @@ public:
|
||||
const char *content_type);
|
||||
|
||||
Result Delete(const char *path);
|
||||
Result Delete(const char *path, const Headers &headers);
|
||||
Result Delete(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Delete(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Delete(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Delete(const char *path, const Headers &headers);
|
||||
Result Delete(const char *path, const Headers &headers,
|
||||
const std::string &body, const char *content_type);
|
||||
|
||||
@@ -1025,7 +1050,7 @@ protected:
|
||||
|
||||
private:
|
||||
socket_t create_client_socket(Error &error) const;
|
||||
bool read_response_line(Stream &strm, Response &res);
|
||||
bool read_response_line(Stream &strm, const Request &req, Response &res);
|
||||
bool write_request(Stream &strm, const Request &req, bool close_connection,
|
||||
Error &error);
|
||||
bool redirect(const Request &req, Response &res, Error &error);
|
||||
@@ -1033,14 +1058,12 @@ private:
|
||||
bool close_connection, Error &error);
|
||||
std::unique_ptr<Response> send_with_content_provider(
|
||||
const char *method, const char *path, const Headers &headers,
|
||||
const std::string &body, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *body, size_t content_length, ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const char *content_type, Error &error);
|
||||
Result send_with_content_provider(
|
||||
const char *method, const char *path, const Headers &headers,
|
||||
const std::string &body, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *body, size_t content_length, ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const char *content_type);
|
||||
|
||||
@@ -1095,6 +1118,10 @@ public:
|
||||
Result Head(const char *path, const Headers &headers);
|
||||
|
||||
Result Post(const char *path);
|
||||
Result Post(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Post(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Post(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Post(const char *path, const Headers &headers, const std::string &body,
|
||||
@@ -1116,6 +1143,10 @@ public:
|
||||
Result Post(const char *path, const Headers &headers,
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
Result Put(const char *path);
|
||||
Result Put(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Put(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Put(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Put(const char *path, const Headers &headers, const std::string &body,
|
||||
@@ -1131,6 +1162,11 @@ public:
|
||||
const char *content_type);
|
||||
Result Put(const char *path, const Params ¶ms);
|
||||
Result Put(const char *path, const Headers &headers, const Params ¶ms);
|
||||
Result Patch(const char *path);
|
||||
Result Patch(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Patch(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Patch(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Patch(const char *path, const Headers &headers,
|
||||
@@ -1146,9 +1182,13 @@ public:
|
||||
const char *content_type);
|
||||
|
||||
Result Delete(const char *path);
|
||||
Result Delete(const char *path, const Headers &headers);
|
||||
Result Delete(const char *path, const char *body, size_t content_length,
|
||||
const char *content_type);
|
||||
Result Delete(const char *path, const Headers &headers, const char *body,
|
||||
size_t content_length, const char *content_type);
|
||||
Result Delete(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
Result Delete(const char *path, const Headers &headers);
|
||||
Result Delete(const char *path, const Headers &headers,
|
||||
const std::string &body, const char *content_type);
|
||||
|
||||
@@ -1457,6 +1497,27 @@ inline bool is_valid_path(const std::string &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string encode_query_param(const std::string &value) {
|
||||
std::ostringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex;
|
||||
|
||||
for (auto c : value) {
|
||||
if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
|
||||
c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
|
||||
c == ')') {
|
||||
escaped << c;
|
||||
} else {
|
||||
escaped << std::uppercase;
|
||||
escaped << '%' << std::setw(2)
|
||||
<< static_cast<int>(static_cast<unsigned char>(c));
|
||||
escaped << std::nouppercase;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
inline std::string encode_url(const std::string &s) {
|
||||
std::string result;
|
||||
|
||||
@@ -2135,6 +2196,25 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr unsigned int str2tag_core(const char *s, size_t l,
|
||||
unsigned int h) {
|
||||
return (l == 0) ? h
|
||||
: str2tag_core(s + 1, l - 1,
|
||||
(h * 33) ^ static_cast<unsigned char>(*s));
|
||||
}
|
||||
|
||||
inline unsigned int str2tag(const std::string &s) {
|
||||
return str2tag_core(s.data(), s.size(), 0);
|
||||
}
|
||||
|
||||
namespace udl {
|
||||
|
||||
inline constexpr unsigned int operator"" _(const char *s, size_t l) {
|
||||
return str2tag_core(s, l, 0);
|
||||
}
|
||||
|
||||
} // namespace udl
|
||||
|
||||
inline const char *
|
||||
find_content_type(const std::string &path,
|
||||
const std::map<std::string, std::string> &user_data) {
|
||||
@@ -2143,36 +2223,60 @@ find_content_type(const std::string &path,
|
||||
auto it = user_data.find(ext);
|
||||
if (it != user_data.end()) { return it->second.c_str(); }
|
||||
|
||||
if (ext == "txt") {
|
||||
return "text/plain";
|
||||
} else if (ext == "html" || ext == "htm") {
|
||||
return "text/html";
|
||||
} else if (ext == "css") {
|
||||
return "text/css";
|
||||
} else if (ext == "jpeg" || ext == "jpg") {
|
||||
return "image/jpg";
|
||||
} else if (ext == "png") {
|
||||
return "image/png";
|
||||
} else if (ext == "gif") {
|
||||
return "image/gif";
|
||||
} else if (ext == "svg") {
|
||||
return "image/svg+xml";
|
||||
} else if (ext == "ico") {
|
||||
return "image/x-icon";
|
||||
} else if (ext == "json") {
|
||||
return "application/json";
|
||||
} else if (ext == "pdf") {
|
||||
return "application/pdf";
|
||||
} else if (ext == "js") {
|
||||
return "application/javascript";
|
||||
} else if (ext == "wasm") {
|
||||
return "application/wasm";
|
||||
} else if (ext == "xml") {
|
||||
return "application/xml";
|
||||
} else if (ext == "xhtml") {
|
||||
return "application/xhtml+xml";
|
||||
using udl::operator""_;
|
||||
|
||||
switch (str2tag(ext)) {
|
||||
default: return nullptr;
|
||||
case "css"_: return "text/css";
|
||||
case "csv"_: return "text/csv";
|
||||
case "txt"_: return "text/plain";
|
||||
case "vtt"_: return "text/vtt";
|
||||
case "htm"_:
|
||||
case "html"_: return "text/html";
|
||||
|
||||
case "apng"_: return "image/apng";
|
||||
case "avif"_: return "image/avif";
|
||||
case "bmp"_: return "image/bmp";
|
||||
case "gif"_: return "image/gif";
|
||||
case "png"_: return "image/png";
|
||||
case "svg"_: return "image/svg+xml";
|
||||
case "webp"_: return "image/webp";
|
||||
case "ico"_: return "image/x-icon";
|
||||
case "tif"_: return "image/tiff";
|
||||
case "tiff"_: return "image/tiff";
|
||||
case "jpg"_:
|
||||
case "jpeg"_: return "image/jpeg";
|
||||
|
||||
case "mp4"_: return "video/mp4";
|
||||
case "mpeg"_: return "video/mpeg";
|
||||
case "webm"_: return "video/webm";
|
||||
|
||||
case "mp3"_: return "audio/mp3";
|
||||
case "mpga"_: return "audio/mpeg";
|
||||
case "weba"_: return "audio/webm";
|
||||
case "wav"_: return "audio/wave";
|
||||
|
||||
case "otf"_: return "font/otf";
|
||||
case "ttf"_: return "font/ttf";
|
||||
case "woff"_: return "font/woff";
|
||||
case "woff2"_: return "font/woff2";
|
||||
|
||||
case "7z"_: return "application/x-7z-compressed";
|
||||
case "atom"_: return "application/atom+xml";
|
||||
case "pdf"_: return "application/pdf";
|
||||
case "js"_:
|
||||
case "mjs"_: return "application/javascript";
|
||||
case "json"_: return "application/json";
|
||||
case "rss"_: return "application/rss+xml";
|
||||
case "tar"_: return "application/x-tar";
|
||||
case "xht"_:
|
||||
case "xhtml"_: return "application/xhtml+xml";
|
||||
case "xslt"_: return "application/xslt+xml";
|
||||
case "xml"_: return "application/xml";
|
||||
case "gz"_: return "application/gzip";
|
||||
case "zip"_: return "application/zip";
|
||||
case "wasm"_: return "application/wasm";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline const char *status_message(int status) {
|
||||
@@ -3002,7 +3106,7 @@ inline std::string params_to_query_str(const Params ¶ms) {
|
||||
if (it != params.begin()) { query += "&"; }
|
||||
query += it->first;
|
||||
query += "=";
|
||||
query += encode_url(it->second);
|
||||
query += encode_query_param(it->second);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
@@ -3490,7 +3594,8 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
|
||||
if (!hStore) { return false; }
|
||||
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
while (pContext = CertEnumCertificatesInStore(hStore, pContext)) {
|
||||
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
|
||||
nullptr) {
|
||||
auto encoded_cert =
|
||||
static_cast<const unsigned char *>(pContext->pbCertEncoded);
|
||||
|
||||
@@ -4062,14 +4167,23 @@ inline void Server::set_file_request_handler(Handler handler) {
|
||||
file_request_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
inline void Server::set_error_handler(Handler handler) {
|
||||
inline void Server::set_error_handler(HandlerWithReturn handler) {
|
||||
error_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
|
||||
inline void Server::set_error_handler(Handler handler) {
|
||||
error_handler_ = [handler](const Request &req, Response &res) {
|
||||
handler(req, res);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
inline void Server::set_socket_options(SocketOptions socket_options) {
|
||||
socket_options_ = std::move(socket_options);
|
||||
inline void Server::set_pre_routing_handler(HandlerWithReturn handler) {
|
||||
pre_routing_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
inline void Server::set_post_routing_handler(Handler handler) {
|
||||
post_routing_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
|
||||
@@ -4079,6 +4193,12 @@ Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
|
||||
expect_100_continue_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
|
||||
|
||||
inline void Server::set_socket_options(SocketOptions socket_options) {
|
||||
socket_options_ = std::move(socket_options);
|
||||
}
|
||||
|
||||
inline void Server::set_keep_alive_max_count(size_t count) {
|
||||
keep_alive_max_count_ = count;
|
||||
}
|
||||
@@ -4134,7 +4254,7 @@ inline void Server::stop() {
|
||||
inline bool Server::parse_request_line(const char *s, Request &req) {
|
||||
const static std::regex re(
|
||||
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
|
||||
"(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
|
||||
"(([^? ]+)(?:\\?([^ ]*?))?) (HTTP/1\\.[01])\r\n");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(s, m, re)) {
|
||||
@@ -4170,13 +4290,15 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
bool need_apply_ranges) {
|
||||
assert(res.status != -1);
|
||||
|
||||
if (400 <= res.status && error_handler_) { error_handler_(req, res); }
|
||||
if (400 <= res.status && error_handler_ && error_handler_(req, res)) {
|
||||
need_apply_ranges = true;
|
||||
}
|
||||
|
||||
std::string content_type;
|
||||
std::string boundary;
|
||||
if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
|
||||
|
||||
// Headers
|
||||
// Prepare additional headers
|
||||
if (close_connection || req.get_header_value("Connection") == "close") {
|
||||
res.set_header("Connection", "close");
|
||||
} else {
|
||||
@@ -4200,20 +4322,24 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
res.set_header("Accept-Ranges", "bytes");
|
||||
}
|
||||
|
||||
detail::BufferStream bstrm;
|
||||
if (post_routing_handler_) { post_routing_handler_(req, res); }
|
||||
|
||||
// Response line
|
||||
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||
detail::status_message(res.status))) {
|
||||
return false;
|
||||
// Response line and headers
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||
detail::status_message(res.status))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!detail::write_headers(bstrm, res, Headers())) { return false; }
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
strm.write(data.data(), data.size());
|
||||
}
|
||||
|
||||
if (!detail::write_headers(bstrm, res, Headers())) { return false; }
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
strm.write(data.data(), data.size());
|
||||
|
||||
// Body
|
||||
auto ret = true;
|
||||
if (req.method != "HEAD") {
|
||||
@@ -4281,7 +4407,6 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
is_shutting_down);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
||||
@@ -4378,7 +4503,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Server::handle_file_request(Request &req, Response &res,
|
||||
inline bool Server::handle_file_request(const Request &req, Response &res,
|
||||
bool head) {
|
||||
for (const auto &entry : base_dirs_) {
|
||||
// Prefix match
|
||||
@@ -4396,7 +4521,7 @@ inline bool Server::handle_file_request(Request &req, Response &res,
|
||||
for (const auto &kv : entry.headers) {
|
||||
res.set_header(kv.first.c_str(), kv.second);
|
||||
}
|
||||
res.status = 200;
|
||||
res.status = req.has_header("Range") ? 206 : 200;
|
||||
if (!head && file_request_handler_) {
|
||||
file_request_handler_(req, res);
|
||||
}
|
||||
@@ -4502,6 +4627,8 @@ inline bool Server::listen_internal() {
|
||||
}
|
||||
|
||||
inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
if (pre_routing_handler_ && pre_routing_handler_(req, res)) { return true; }
|
||||
|
||||
// File handler
|
||||
bool is_head_request = req.method == "HEAD";
|
||||
if ((req.method == "GET" || is_head_request) &&
|
||||
@@ -4943,17 +5070,20 @@ inline void ClientImpl::lock_socket_and_shutdown_and_close() {
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
|
||||
const static std::regex re("(HTTP/1\\.[01]) (\\d{3}) (.*?)\r\n");
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(line_reader.ptr(), m, re)) { return true; }
|
||||
if (!std::regex_match(line_reader.ptr(), m, re)) {
|
||||
return req.method == "CONNECT";
|
||||
}
|
||||
res.version = std::string(m[1]);
|
||||
res.status = std::stoi(std::string(m[2]));
|
||||
res.reason = std::string(m[3]);
|
||||
@@ -5197,14 +5327,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
|
||||
|
||||
inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
bool close_connection, Error &error) {
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
// Request line
|
||||
const auto &path = detail::encode_url(req.path);
|
||||
|
||||
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
||||
|
||||
// Additonal headers
|
||||
// Prepare additional headers
|
||||
Headers headers;
|
||||
if (close_connection) { headers.emplace("Connection", "close"); }
|
||||
|
||||
@@ -5274,13 +5397,21 @@ inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
proxy_bearer_token_auth_token_, true));
|
||||
}
|
||||
|
||||
detail::write_headers(bstrm, req, headers);
|
||||
// Request line and headers
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
if (!detail::write_data(strm, data.data(), data.size())) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
const auto &path = detail::encode_url(req.path);
|
||||
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
||||
|
||||
detail::write_headers(bstrm, req, headers);
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
if (!detail::write_data(strm, data.data(), data.size())) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
@@ -5295,8 +5426,7 @@ inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
|
||||
inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
const char *method, const char *path, const Headers &headers,
|
||||
const std::string &body, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *body, size_t content_length, ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const char *content_type, Error &error) {
|
||||
|
||||
@@ -5349,7 +5479,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!compressor.compress(body.data(), body.size(), true,
|
||||
if (!compressor.compress(body, content_length, true,
|
||||
[&](const char *data, size_t data_len) {
|
||||
req.body.append(data, data_len);
|
||||
return true;
|
||||
@@ -5372,7 +5502,8 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
req.is_chunked_content_provider_ = true;
|
||||
req.headers.emplace("Transfer-Encoding", "chunked");
|
||||
} else {
|
||||
req.body = body;
|
||||
req.body.assign(body, content_length);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5382,8 +5513,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
|
||||
inline Result ClientImpl::send_with_content_provider(
|
||||
const char *method, const char *path, const Headers &headers,
|
||||
const std::string &body, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *body, size_t content_length, ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const char *content_type) {
|
||||
auto error = Error::Success;
|
||||
@@ -5400,7 +5530,7 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
|
||||
if (!write_request(strm, req, close_connection, error)) { return false; }
|
||||
|
||||
// Receive response and headers
|
||||
if (!read_response_line(strm, res) ||
|
||||
if (!read_response_line(strm, req, res) ||
|
||||
!detail::read_headers(strm, res.headers)) {
|
||||
error = Error::Read;
|
||||
return false;
|
||||
@@ -5414,7 +5544,7 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
|
||||
}
|
||||
|
||||
// Body
|
||||
if (req.method != "HEAD" && req.method != "CONNECT") {
|
||||
if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
|
||||
auto out =
|
||||
req.content_receiver_
|
||||
? static_cast<ContentReceiverWithProgress>(
|
||||
@@ -5441,18 +5571,12 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
|
||||
};
|
||||
|
||||
int dummy_status;
|
||||
// std::cout << "A" << std::endl;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress), std::move(out),
|
||||
decompress_)) {
|
||||
// std::cout << "B" << std::endl;
|
||||
if (error != Error::Canceled) {
|
||||
// std::cout << "C" << std::endl;
|
||||
error = Error::Read;
|
||||
}
|
||||
if (error != Error::Canceled) { error = Error::Read; }
|
||||
return false;
|
||||
}
|
||||
// std::cout << "D" << std::endl;
|
||||
}
|
||||
|
||||
if (res.get_header_value("Connection") == "close" ||
|
||||
@@ -5594,6 +5718,19 @@ inline Result ClientImpl::Post(const char *path) {
|
||||
return Post(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const char *path, const char *body,
|
||||
size_t content_length,
|
||||
const char *content_type) {
|
||||
return Post(path, Headers(), body, content_length, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("POST", path, headers, body, content_length,
|
||||
nullptr, nullptr, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return Post(path, Headers(), body, content_type);
|
||||
@@ -5602,8 +5739,9 @@ inline Result ClientImpl::Post(const char *path, const std::string &body,
|
||||
inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("POST", path, headers, body, 0, nullptr,
|
||||
nullptr, content_type);
|
||||
return send_with_content_provider("POST", path, headers, body.data(),
|
||||
body.size(), nullptr, nullptr,
|
||||
content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const char *path, const Params ¶ms) {
|
||||
@@ -5627,7 +5765,7 @@ inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("POST", path, headers, std::string(),
|
||||
return send_with_content_provider("POST", path, headers, nullptr,
|
||||
content_length, std::move(content_provider),
|
||||
nullptr, content_type);
|
||||
}
|
||||
@@ -5635,9 +5773,8 @@ inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
ContentProviderWithoutLength content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("POST", path, headers, std::string(), 0,
|
||||
nullptr, std::move(content_provider),
|
||||
content_type);
|
||||
return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr,
|
||||
std::move(content_provider), content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const char *path, const Headers &headers,
|
||||
@@ -5691,6 +5828,18 @@ inline Result ClientImpl::Put(const char *path) {
|
||||
return Put(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const char *path, const char *body,
|
||||
size_t content_length, const char *content_type) {
|
||||
return Put(path, Headers(), body, content_length, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PUT", path, headers, body, content_length,
|
||||
nullptr, nullptr, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return Put(path, Headers(), body, content_type);
|
||||
@@ -5699,8 +5848,9 @@ inline Result ClientImpl::Put(const char *path, const std::string &body,
|
||||
inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
|
||||
nullptr, content_type);
|
||||
return send_with_content_provider("PUT", path, headers, body.data(),
|
||||
body.size(), nullptr, nullptr,
|
||||
content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const char *path, size_t content_length,
|
||||
@@ -5720,7 +5870,7 @@ inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PUT", path, headers, std::string(),
|
||||
return send_with_content_provider("PUT", path, headers, nullptr,
|
||||
content_length, std::move(content_provider),
|
||||
nullptr, content_type);
|
||||
}
|
||||
@@ -5728,9 +5878,8 @@ inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
ContentProviderWithoutLength content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PUT", path, headers, std::string(), 0,
|
||||
nullptr, std::move(content_provider),
|
||||
content_type);
|
||||
return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr,
|
||||
std::move(content_provider), content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const char *path, const Params ¶ms) {
|
||||
@@ -5743,6 +5892,24 @@ inline Result ClientImpl::Put(const char *path, const Headers &headers,
|
||||
return Put(path, headers, query, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Patch(const char *path) {
|
||||
return Patch(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Patch(const char *path, const char *body,
|
||||
size_t content_length,
|
||||
const char *content_type) {
|
||||
return Patch(path, Headers(), body, content_length, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Patch(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PATCH", path, headers, body,
|
||||
content_length, nullptr, nullptr,
|
||||
content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Patch(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return Patch(path, Headers(), body, content_type);
|
||||
@@ -5751,8 +5918,9 @@ inline Result ClientImpl::Patch(const char *path, const std::string &body,
|
||||
inline Result ClientImpl::Patch(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
|
||||
nullptr, content_type);
|
||||
return send_with_content_provider("PATCH", path, headers, body.data(),
|
||||
body.size(), nullptr, nullptr,
|
||||
content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Patch(const char *path, size_t content_length,
|
||||
@@ -5772,7 +5940,7 @@ inline Result ClientImpl::Patch(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PATCH", path, headers, std::string(),
|
||||
return send_with_content_provider("PATCH", path, headers, nullptr,
|
||||
content_length, std::move(content_provider),
|
||||
nullptr, content_type);
|
||||
}
|
||||
@@ -5780,26 +5948,26 @@ inline Result ClientImpl::Patch(const char *path, const Headers &headers,
|
||||
inline Result ClientImpl::Patch(const char *path, const Headers &headers,
|
||||
ContentProviderWithoutLength content_provider,
|
||||
const char *content_type) {
|
||||
return send_with_content_provider("PATCH", path, headers, std::string(), 0,
|
||||
nullptr, std::move(content_provider),
|
||||
content_type);
|
||||
return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr,
|
||||
std::move(content_provider), content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path) {
|
||||
return Delete(path, Headers(), std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return Delete(path, Headers(), body, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const Headers &headers) {
|
||||
return Delete(path, headers, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const char *body,
|
||||
size_t content_length,
|
||||
const char *content_type) {
|
||||
return Delete(path, Headers(), body, content_length, content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
Request req;
|
||||
req.method = "DELETE";
|
||||
@@ -5808,11 +5976,22 @@ inline Result ClientImpl::Delete(const char *path, const Headers &headers,
|
||||
req.path = path;
|
||||
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
req.body = body;
|
||||
req.body.assign(body, content_length);
|
||||
|
||||
return send(req);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return Delete(path, Headers(), body.data(), body.size(), content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Delete(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return Delete(path, headers, body.data(), body.size(), content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Options(const char *path) {
|
||||
return Options(path, Headers());
|
||||
}
|
||||
@@ -6234,8 +6413,9 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
},
|
||||
[](SSL * /*ssl*/) { return true; });
|
||||
|
||||
bool ret = false;
|
||||
if (ssl) {
|
||||
auto ret = detail::process_server_socket_ssl(
|
||||
ret = detail::process_server_socket_ssl(
|
||||
ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
@@ -6249,12 +6429,11 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
// the connection appeared to be closed.
|
||||
const bool shutdown_gracefully = ret;
|
||||
detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
|
||||
return ret;
|
||||
}
|
||||
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// SSL HTTP client implementation
|
||||
@@ -6752,6 +6931,15 @@ inline Result Client::Head(const char *path, const Headers &headers) {
|
||||
}
|
||||
|
||||
inline Result Client::Post(const char *path) { return cli_->Post(path); }
|
||||
inline Result Client::Post(const char *path, const char *body,
|
||||
size_t content_length, const char *content_type) {
|
||||
return cli_->Post(path, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Post(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, headers, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Post(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, body, content_type);
|
||||
@@ -6804,6 +6992,15 @@ inline Result Client::Post(const char *path, const Headers &headers,
|
||||
return cli_->Post(path, headers, items, boundary);
|
||||
}
|
||||
inline Result Client::Put(const char *path) { return cli_->Put(path); }
|
||||
inline Result Client::Put(const char *path, const char *body,
|
||||
size_t content_length, const char *content_type) {
|
||||
return cli_->Put(path, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Put(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, headers, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Put(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, body, content_type);
|
||||
@@ -6842,6 +7039,16 @@ inline Result Client::Put(const char *path, const Headers &headers,
|
||||
const Params ¶ms) {
|
||||
return cli_->Put(path, headers, params);
|
||||
}
|
||||
inline Result Client::Patch(const char *path) { return cli_->Patch(path); }
|
||||
inline Result Client::Patch(const char *path, const char *body,
|
||||
size_t content_length, const char *content_type) {
|
||||
return cli_->Patch(path, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Patch(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, headers, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Patch(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, body, content_type);
|
||||
@@ -6874,13 +7081,22 @@ inline Result Client::Patch(const char *path, const Headers &headers,
|
||||
return cli_->Patch(path, headers, std::move(content_provider), content_type);
|
||||
}
|
||||
inline Result Client::Delete(const char *path) { return cli_->Delete(path); }
|
||||
inline Result Client::Delete(const char *path, const Headers &headers) {
|
||||
return cli_->Delete(path, headers);
|
||||
}
|
||||
inline Result Client::Delete(const char *path, const char *body,
|
||||
size_t content_length, const char *content_type) {
|
||||
return cli_->Delete(path, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Delete(const char *path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const char *content_type) {
|
||||
return cli_->Delete(path, headers, body, content_length, content_type);
|
||||
}
|
||||
inline Result Client::Delete(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Delete(path, body, content_type);
|
||||
}
|
||||
inline Result Client::Delete(const char *path, const Headers &headers) {
|
||||
return cli_->Delete(path, headers);
|
||||
}
|
||||
inline Result Client::Delete(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
|
||||
+298
-22
@@ -46,6 +46,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 "));
|
||||
@@ -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,14 @@ 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);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
@@ -930,6 +953,127 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
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 true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
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()
|
||||
@@ -1336,6 +1480,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) {
|
||||
@@ -1676,10 +1852,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);
|
||||
@@ -2957,6 +3195,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;
|
||||
|
||||
@@ -3083,9 +3327,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); });
|
||||
@@ -3146,6 +3390,39 @@ 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());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
@@ -3702,15 +3979,14 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest2, 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
|
||||
#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