Compare commits

..

9 Commits

Author SHA1 Message Date
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 156 additions and 32 deletions

View File

@@ -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,22 +79,24 @@ 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
@@ -97,7 +106,7 @@ svr.set_logger([](const auto& req, const auto& res) {
});
```
### Error Handler
### Error handler
```cpp
svr.set_error_handler([](const auto& req, auto& res) {
@@ -223,6 +232,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
--------------
@@ -532,3 +559,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)

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;
}

View File

@@ -11,6 +11,7 @@
#include <httplib.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
using namespace httplib;

View File

@@ -448,6 +448,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 +467,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 +478,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 +557,7 @@ private:
Handlers options_handlers_;
Handler error_handler_;
Logger logger_;
Expect100ContinueHandler expect_100_continue_handler_;
};
class Client {
@@ -1591,6 +1599,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 +1616,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:
@@ -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;
}
@@ -2995,11 +3024,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");
}
@@ -3212,7 +3242,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 +3261,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 +3365,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 +3504,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; }

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"));
@@ -662,8 +662,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",
@@ -1245,7 +1245,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) {
@@ -2069,6 +2069,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()