mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-12 01:27:15 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca5a50d2c9 | ||
|
|
b251668522 | ||
|
|
9ee740fe8f | ||
|
|
851edaf77f | ||
|
|
1a2a6e2b01 | ||
|
|
ac7742bb32 | ||
|
|
82c11168c1 | ||
|
|
7c5197c86c | ||
|
|
8801e51138 |
70
README.md
70
README.md
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
63
httplib.h
63
httplib.h
@@ -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; }
|
||||
|
||||
52
test/test.cc
52
test/test.cc
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user