Compare commits

...

19 Commits

Author SHA1 Message Date
rymis f2bb9c45d6 Support for deflate compression (#360) 2020-02-24 11:48:00 -05:00
yhirose 9a663aa94e Added a unit test. 2020-02-21 11:48:47 -05:00
yhirose d0d744d520 Fixed compiler warning with Visual C++. Close #358 2020-02-20 17:30:04 -05:00
yhirose fce8e6fefd Merge pull request #356 from samhocevar/fix-shadow-declarations
Fix a few shadowed variable compilation warnings.
2020-02-17 09:01:47 -05:00
Sam Hocevar 180aa32ebf Fix a few shadowed variable compilation warnings. 2020-02-17 06:58:30 +01:00
yhirose fe01fa760b Update README 2020-02-14 22:07:39 -05:00
yhirose d61d63dd97 Added unit tests for SSLServer::bind_to_any_port 2020-02-14 21:59:37 -05:00
yhirose 3fe13ecc91 Updated README 2020-02-14 21:49:09 -05:00
yhirose 064cc6810e Fix #352 2020-02-13 17:40:06 -05:00
yhirose 464cc89b77 Fix #351 2020-02-12 22:35:52 -05:00
yhirose ca5a50d2c9 Fix #349 2020-02-11 00:26:15 -05:00
yhirose b251668522 Fixed build problem. 2020-02-03 22:45:50 -05:00
yhirose 9ee740fe8f Updated README 2020-02-02 20:57:29 -05:00
yhirose 851edaf77f Code format 2020-02-02 20:02:16 -05:00
yhirose 1a2a6e2b01 Updated README 2020-02-01 09:24:42 -05:00
yhirose ac7742bb32 Changed the order of parameters of set_mounting_point. 2020-02-01 09:18:24 -05:00
yhirose 82c11168c1 Merge pull request #342 from yhirose/remove_mount_point
Fix #341
2020-01-31 20:41:39 -05:00
yhirose 7c5197c86c Updated README 2020-01-31 20:40:33 -05:00
yhirose 8801e51138 Fix #341 2020-01-31 20:35:20 -05:00
5 changed files with 303 additions and 144 deletions
+122 -99
View File
@@ -17,24 +17,24 @@ Server Example
int main(void)
{
using namespace httplib;
using namespace httplib;
Server svr;
Server svr;
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.listen("localhost", 1234);
svr.listen("localhost", 1234);
}
```
@@ -50,17 +50,24 @@ svr.listen_after_bind();
### Static File Server
```cpp
auto ret = svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
// Mount / to ./www directory
auto ret = svr.set_mount_point("/", "./www");
if (!ret) {
// The specified base directory doesn't exist...
}
// Mount /public to ./www directory
ret = svr.set_base_dir("./www", "/public");
ret = svr.set_mount_point("/public", "./www");
// Mount /public to ./www1 and ./www2 directories
ret = svr.set_base_dir("./www1", "/public"); // 1st order to search
ret = svr.set_base_dir("./www2", "/public"); // 2nd order to search
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
// Remove mount /
ret = svr.remove_mount_point("/");
// Remove mount /public
ret = svr.remove_mount_point("/public");
```
```cpp
@@ -72,39 +79,41 @@ 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 |
| :-------- | :--------------------- |
| txt | text/plain |
| html, htm | text/html |
| css | text/css |
| jpeg, jpg | image/jpg |
| png | image/png |
| gif | image/gif |
| svg | image/svg+xml |
| ico | image/x-icon |
| json | application/json |
| pdf | application/pdf |
| js | application/javascript |
| wasm | application/wasm |
| xml | application/xml |
| xhtml | application/xhtml+xml |
NOTE: These the static file server methods are not thread safe.
### Logging
```cpp
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
your_logger(req, res);
});
```
### Error Handler
### Error handler
```cpp
svr.set_error_handler([](const auto& req, auto& res) {
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
```
@@ -112,31 +121,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
```cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
});
```
### Send content with Content provider
```cpp
const uint64_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), // Content length
[data](uint64_t offset, uint64_t length, DataSink &sink) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
[data] { delete data; });
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
});
```
@@ -167,6 +157,24 @@ svr.Post("/content_receiver",
});
```
### Send content with Content provider
```cpp
const uint64_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), // Content length
[data](uint64_t offset, uint64_t length, DataSink &sink) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
[data] { delete data; });
});
```
### Chunked transfer encoding
```cpp
@@ -191,11 +199,7 @@ Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/s
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
Set thread count to 8:
```cpp
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
```
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
### Override the default thread pool with yours
@@ -223,6 +227,24 @@ svr.new_task_queue = [] {
};
```
### 'Expect: 100-continue' handler
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
```cpp
// Send a '417 Expectation Failed' response.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return 417;
});
```
```cpp
// Send a final status without reading the message body.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return res.status = 401;
});
```
Client Example
--------------
@@ -234,36 +256,36 @@ Client Example
int main(void)
{
httplib::Client cli("localhost", 1234);
httplib::Client cli("localhost", 1234);
auto res = cli.Get("/hi");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
auto res = cli.Get("/hi");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
}
```
### GET with HTTP headers
```c++
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
```
### GET with Content Receiver
```c++
std::string body;
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length) {
body.append(data, data_length);
return true;
});
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length) {
body.append(data, data_length);
return true;
});
assert(res->body.empty());
assert(res->body.empty());
```
### POST
@@ -296,15 +318,15 @@ auto res = cli.Post("/post", params);
### POST with Multipart Form Data
```c++
httplib::MultipartFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};
httplib::MultipartFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};
auto res = cli.Post("/multipart", items);
auto res = cli.Post("/multipart", items);
```
### PUT
@@ -338,12 +360,12 @@ httplib::Client client(url, port);
// prints: 0 / 000 bytes => 50% complete
std::shared_ptr<httplib::Response> res =
cli.Get("/", [](uint64_t len, uint64_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)((len/total)*100));
return true; // return 'false' if you want to cancel the request.
}
cli.Get("/", [](uint64_t len, uint64_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)((len/total)*100));
return true; // return 'false' if you want to cancel the request.
}
);
```
@@ -532,3 +554,4 @@ The following folks made great contributions to polish this library to totally a
* [DraTeots](https://github.com/DraTeots)
* [BastienDurel](https://github.com/BastienDurel)
* [vitalyster](https://github.com/vitalyster)
* [trollixx](https://github.com/trollixx)
+1 -1
View File
@@ -122,7 +122,7 @@ int main(int argc, const char **argv) {
auto base_dir = "./";
if (argc > 2) { base_dir = argv[2]; }
if (!svr.set_base_dir(base_dir)) {
if (!svr.set_mount_point("/", base_dir)) {
cout << "The specified base directory doesn't exist...";
return 1;
}
+1
View File
@@ -11,6 +11,7 @@
#include <httplib.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
using namespace httplib;
+96 -39
View File
@@ -41,7 +41,7 @@
#endif
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max)()
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max())
#endif
#ifndef CPPHTTPLIB_RECV_BUFSIZ
@@ -49,12 +49,8 @@
#endif
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
// if hardware_concurrency() outputs 0 we still wants to use threads for this.
// -1 because we have one thread already in the main function.
#define CPPHTTPLIB_THREAD_POOL_COUNT \
(std::thread::hardware_concurrency() \
? std::thread::hardware_concurrency() - 1 \
: 2)
(std::max(1u, std::thread::hardware_concurrency() - 1))
#endif
/*
@@ -448,6 +444,8 @@ public:
using Handler = std::function<void(const Request &, Response &)>;
using HandlerWithContentReader = std::function<void(
const Request &, Response &, const ContentReader &content_reader)>;
using Expect100ContinueHandler =
std::function<int(const Request &, Response &)>;
Server();
@@ -465,7 +463,10 @@ public:
Server &Delete(const char *pattern, Handler handler);
Server &Options(const char *pattern, Handler handler);
bool set_base_dir(const char *dir, const char *mount_point = nullptr);
[[deprecated]] bool set_base_dir(const char *dir,
const char *mount_point = nullptr);
bool set_mount_point(const char *mount_point, const char *dir);
bool remove_mount_point(const char *mount_point);
void set_file_extension_and_mimetype_mapping(const char *ext,
const char *mime);
void set_file_request_handler(Handler handler);
@@ -473,6 +474,8 @@ public:
void set_error_handler(Handler handler);
void set_logger(Logger logger);
void set_expect_100_continue_handler(Expect100ContinueHandler handler);
void set_keep_alive_max_count(size_t count);
void set_read_timeout(time_t sec, time_t usec);
void set_payload_max_length(size_t length);
@@ -550,6 +553,7 @@ private:
Handlers options_handlers_;
Handler error_handler_;
Logger logger_;
Expect100ContinueHandler expect_100_continue_handler_;
};
class Client {
@@ -1541,7 +1545,8 @@ inline std::string get_remote_addr(socket_t sock) {
std::array<char, NI_MAXHOST> ipstr{};
if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
ipstr.data(), ipstr.size(), nullptr, 0, NI_NUMERICHOST)) {
ipstr.data(), static_cast<socklen_t>(ipstr.size()),
nullptr, 0, NI_NUMERICHOST)) {
return ipstr.data();
}
}
@@ -1591,6 +1596,7 @@ find_content_type(const std::string &path,
inline const char *status_message(int status) {
switch (status) {
case 100: return "Continue";
case 200: return "OK";
case 202: return "Accepted";
case 204: return "No Content";
@@ -1607,6 +1613,7 @@ inline const char *status_message(int status) {
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Range Not Satisfiable";
case 417: return "Expectation Failed";
case 503: return "Service Unavailable";
default:
@@ -1660,14 +1667,15 @@ inline bool compress(std::string &content) {
class decompressor {
public:
decompressor() {
std::memset(&strm, 0, sizeof(strm));
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// 15 is the value of wbits, which should be at the maximum possible value
// to ensure that any gzip stream can be decoded. The offset of 16 specifies
// that the stream to decompress will be formatted with a gzip wrapper.
is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK;
// to ensure that any gzip stream can be decoded. The offset of 32 specifies
// that the stream type should be automatically detected either gzip or deflate.
is_valid_ = inflateInit2(&strm, 32 + 15) == Z_OK;
}
~decompressor() { inflateEnd(&strm); }
@@ -1865,12 +1873,14 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
decompressor decompressor;
if (!decompressor.is_valid()) {
status = 500;
return false;
}
std::string content_encoding = x.get_header_value("Content-Encoding");
if (content_encoding.find("gzip") != std::string::npos
|| content_encoding.find("deflate") != std::string::npos) {
if (!decompressor.is_valid()) {
status = 500;
return false;
}
if (x.get_header_value("Content-Encoding") == "gzip") {
out = [&](const char *buf, size_t n) {
return decompressor.decompress(
buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
@@ -2066,11 +2076,11 @@ inline void parse_query_text(const std::string &s, Params &params) {
split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
std::string key;
std::string val;
split(b, e, '=', [&](const char *b, const char *e) {
split(b, e, '=', [&](const char *b2, const char *e2) {
if (key.empty()) {
key.assign(b, e);
key.assign(b2, e2);
} else {
val.assign(b, e);
val.assign(b2, e2);
}
});
params.emplace(key, decode_url(val));
@@ -2096,16 +2106,16 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
if (!all_valid_ranges) return;
static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
std::cmatch m;
if (std::regex_match(b, e, m, re_another_range)) {
std::cmatch cm;
if (std::regex_match(b, e, cm, re_another_range)) {
ssize_t first = -1;
if (!m.str(1).empty()) {
first = static_cast<ssize_t>(std::stoll(m.str(1)));
if (!cm.str(1).empty()) {
first = static_cast<ssize_t>(std::stoll(cm.str(1)));
}
ssize_t last = -1;
if (!m.str(2).empty()) {
last = static_cast<ssize_t>(std::stoll(m.str(2)));
if (!cm.str(2).empty()) {
last = static_cast<ssize_t>(std::stoll(cm.str(2)));
}
if (first != -1 && last != -1 && first > last) {
@@ -2573,10 +2583,10 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
inline bool parse_www_authenticate(const httplib::Response &res,
std::map<std::string, std::string> &auth,
bool is_proxy) {
auto key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
if (res.has_header(key)) {
auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
if (res.has_header(auth_key)) {
static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
auto s = res.get_header_value(key);
auto s = res.get_header_value(auth_key);
auto pos = s.find(' ');
if (pos != std::string::npos) {
auto type = s.substr(0, pos);
@@ -2707,11 +2717,11 @@ inline void Response::set_content(const std::string &s,
}
inline void Response::set_content_provider(
size_t length,
size_t in_length,
std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
std::function<void()> resource_releaser) {
assert(length > 0);
content_length = length;
assert(in_length > 0);
content_length = in_length;
content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
provider(offset, length, sink);
};
@@ -2889,6 +2899,10 @@ inline Server &Server::Options(const char *pattern, Handler handler) {
}
inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
return set_mount_point(mount_point, dir);
}
inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
if (detail::is_dir(dir)) {
std::string mnt = mount_point ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
@@ -2899,6 +2913,16 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
return false;
}
inline bool Server::remove_mount_point(const char *mount_point) {
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
if (it->first == mount_point) {
base_dirs_.erase(it);
return true;
}
}
return false;
}
inline void Server::set_file_extension_and_mimetype_mapping(const char *ext,
const char *mime) {
file_extension_and_mimetype_map_[ext] = mime;
@@ -2914,6 +2938,11 @@ inline void Server::set_error_handler(Handler handler) {
inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
inline void
Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
expect_100_continue_handler_ = std::move(handler);
}
inline void Server::set_keep_alive_max_count(size_t count) {
keep_alive_max_count_ = count;
}
@@ -2980,9 +3009,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
if (400 <= res.status && error_handler_) { error_handler_(req, res); }
detail::BufferStream bstrm;
// Response line
if (!strm.write_format("HTTP/1.1 %d %s\r\n", res.status,
detail::status_message(res.status))) {
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
detail::status_message(res.status))) {
return false;
}
@@ -2995,11 +3026,12 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
res.set_header("Connection", "Keep-Alive");
}
if (!res.has_header("Content-Type")) {
if (!res.has_header("Content-Type") &&
(!res.body.empty() || res.content_length > 0)) {
res.set_header("Content-Type", "text/plain");
}
if (!res.has_header("Accept-Ranges")) {
if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
res.set_header("Accept-Ranges", "bytes");
}
@@ -3076,7 +3108,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
res.set_header("Content-Length", length);
}
if (!detail::write_headers(strm, res, Headers())) { return false; }
if (!detail::write_headers(bstrm, res, Headers())) { return false; }
// Flush buffer
auto &data = bstrm.get_buffer();
strm.write(data.data(), data.size());
// Body
if (req.method != "HEAD") {
@@ -3212,7 +3248,8 @@ inline bool Server::read_content_core(Stream &strm, bool last_connection,
return true;
}
inline bool Server::handle_file_request(Request &req, Response &res, bool head) {
inline bool Server::handle_file_request(Request &req, Response &res,
bool head) {
for (const auto &kv : base_dirs_) {
const auto &mount_point = kv.first;
const auto &base_dir = kv.second;
@@ -3230,7 +3267,9 @@ inline bool Server::handle_file_request(Request &req, Response &res, bool head)
detail::find_content_type(path, file_extension_and_mimetype_map_);
if (type) { res.set_header("Content-Type", type); }
res.status = 200;
if (!head && file_request_handler_) { file_request_handler_(req, res); }
if (!head && file_request_handler_) {
file_request_handler_(req, res);
}
return true;
}
}
@@ -3332,7 +3371,10 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
bool last_connection) {
// File handler
bool is_head_request = req.method == "HEAD";
if ((req.method == "GET" || is_head_request) && handle_file_request(req, res, is_head_request)) { return true; }
if ((req.method == "GET" || is_head_request) &&
handle_file_request(req, res, is_head_request)) {
return true;
}
if (detail::expect_content(req)) {
// Content reader handler
@@ -3468,6 +3510,21 @@ Server::process_request(Stream &strm, bool last_connection,
if (setup_request) { setup_request(req); }
if (req.get_header_value("Expect") == "100-continue") {
auto status = 100;
if (expect_100_continue_handler_) {
status = expect_100_continue_handler_(req, res);
}
switch (status) {
case 100:
case 417:
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
detail::status_message(status));
break;
default: return write_response(strm, last_connection, req, res);
}
}
// Rounting
if (routing(req, res, strm, last_connection)) {
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
+83 -5
View File
@@ -201,7 +201,7 @@ TEST(ParseHeaderValueTest, Range) {
TEST(BufferStreamTest, read) {
detail::BufferStream strm1;
Stream& strm = strm1;
Stream &strm = strm1;
EXPECT_EQ(5, strm.write("hello"));
@@ -645,11 +645,22 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
TEST(Server, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("localhost");
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
svr.stop();
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLServer, BindAndListenSeparately) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE, CLIENT_CA_CERT_DIR);
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
svr.stop();
}
#endif
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -662,8 +673,8 @@ protected:
}
virtual void SetUp() {
svr_.set_base_dir("./www");
svr_.set_base_dir("./www2", "/mount");
svr_.set_mount_point("/", "./www");
svr_.set_mount_point("/mount", "./www2");
svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde");
svr_.Get("/hi",
@@ -1030,6 +1041,16 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("HTTP/1.1", res->version);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetMethod302) {
auto res = cli_.Get("/");
ASSERT_TRUE(res != nullptr);
@@ -1245,7 +1266,7 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
}
TEST_F(ServerTest, InvalidBaseDirMount) {
EXPECT_EQ(false, svr_.set_base_dir("./www3", "invalid_mount_point"));
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
}
TEST_F(ServerTest, EmptyRequest) {
@@ -1684,6 +1705,19 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
}
TEST_F(ServerTest, PutContentWithDeflate) {
cli_.set_compress(false);
httplib::Headers headers;
headers.emplace("Content-Encoding", "deflate");
// PUT in deflate format:
auto res = cli_.Put("/put", headers, "\170\234\013\010\015\001\0\001\361\0\372", "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
#endif
TEST_F(ServerTest, Patch) {
@@ -2069,6 +2103,50 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
ASSERT_FALSE(svr.is_running());
}
TEST(MountTest, Unmount) {
Server svr;
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
svr.set_mount_point("/mount2", "./www2");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
res = cli.Get("/mount2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
svr.set_mount_point("/", "./www");
res = cli.Get("/dir/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
svr.remove_mount_point("/");
res = cli.Get("/dir/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
svr.remove_mount_point("/mount2");
res = cli.Get("/mount2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTestWithAI_PASSIVE : public ::testing::Test {
protected:
ServerTestWithAI_PASSIVE()