mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2025-05-10 09:43:51 +00:00
Resolve #1906
This commit is contained in:
parent
327ff263f5
commit
8415bf0823
12
README.md
12
README.md
@ -384,6 +384,18 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send file content
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||||
|
res.set_file_content("./path/to/conent.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||||
|
res.set_file_content("./path/to/conent", "text/html");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### 'Expect: 100-continue' handler
|
### 'Expect: 100-continue' handler
|
||||||
|
|
||||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||||
|
42
httplib.h
42
httplib.h
@ -675,6 +675,10 @@ struct Response {
|
|||||||
const std::string &content_type, ContentProviderWithoutLength provider,
|
const std::string &content_type, ContentProviderWithoutLength provider,
|
||||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||||
|
|
||||||
|
void set_file_content(const std::string &path,
|
||||||
|
const std::string &content_type);
|
||||||
|
void set_file_content(const std::string &path);
|
||||||
|
|
||||||
Response() = default;
|
Response() = default;
|
||||||
Response(const Response &) = default;
|
Response(const Response &) = default;
|
||||||
Response &operator=(const Response &) = default;
|
Response &operator=(const Response &) = default;
|
||||||
@ -692,6 +696,8 @@ struct Response {
|
|||||||
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
||||||
bool is_chunked_content_provider_ = false;
|
bool is_chunked_content_provider_ = false;
|
||||||
bool content_provider_success_ = false;
|
bool content_provider_success_ = false;
|
||||||
|
std::string file_content_path_;
|
||||||
|
std::string file_content_content_type_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Stream {
|
class Stream {
|
||||||
@ -5703,6 +5709,16 @@ inline void Response::set_chunked_content_provider(
|
|||||||
is_chunked_content_provider_ = true;
|
is_chunked_content_provider_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Response::set_file_content(const std::string &path,
|
||||||
|
const std::string &content_type) {
|
||||||
|
file_content_path_ = path;
|
||||||
|
file_content_content_type_ = content_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Response::set_file_content(const std::string &path) {
|
||||||
|
file_content_path_ = path;
|
||||||
|
}
|
||||||
|
|
||||||
// Result implementation
|
// Result implementation
|
||||||
inline bool Result::has_request_header(const std::string &key) const {
|
inline bool Result::has_request_header(const std::string &key) const {
|
||||||
return request_headers_.find(key) != request_headers_.end();
|
return request_headers_.find(key) != request_headers_.end();
|
||||||
@ -7043,6 +7059,32 @@ Server::process_request(Stream &strm, bool close_connection,
|
|||||||
return write_response(strm, close_connection, req, res);
|
return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serve file content by using a content provider
|
||||||
|
if (!res.file_content_path_.empty()) {
|
||||||
|
const auto &path = res.file_content_path_;
|
||||||
|
auto mm = std::make_shared<detail::mmap>(path.c_str());
|
||||||
|
if (!mm->is_open()) {
|
||||||
|
res.body.clear();
|
||||||
|
res.content_length_ = 0;
|
||||||
|
res.content_provider_ = nullptr;
|
||||||
|
res.status = StatusCode::NotFound_404;
|
||||||
|
return write_response(strm, close_connection, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto content_type = res.file_content_content_type_;
|
||||||
|
if (content_type.empty()) {
|
||||||
|
content_type = detail::find_content_type(
|
||||||
|
path, file_extension_and_mimetype_map_, default_file_mimetype_);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set_content_provider(
|
||||||
|
mm->size(), content_type,
|
||||||
|
[mm](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||||
|
sink.write(mm->data() + offset, length);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return write_response_with_content(strm, close_connection, req, res);
|
return write_response_with_content(strm, close_connection, req, res);
|
||||||
} else {
|
} else {
|
||||||
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
||||||
|
47
test/test.cc
47
test/test.cc
@ -2300,6 +2300,18 @@ protected:
|
|||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
})
|
})
|
||||||
|
.Get("/file_content",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/dir/test.html");
|
||||||
|
})
|
||||||
|
.Get("/file_content_with_content_type",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/file", "text/plain");
|
||||||
|
})
|
||||||
|
.Get("/invalid_file_content",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/dir/invalid_file_path");
|
||||||
|
})
|
||||||
.Get("/http_response_splitting",
|
.Get("/http_response_splitting",
|
||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
||||||
@ -2904,6 +2916,30 @@ TEST_F(ServerTest, GetMethod200) {
|
|||||||
EXPECT_EQ("Hello World!", res->body);
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetFileContent) {
|
||||||
|
auto res = cli_.Get("/file_content");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ(9, std::stoi(res->get_header_value("Content-Length")));
|
||||||
|
EXPECT_EQ("test.html", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetFileContentWithContentType) {
|
||||||
|
auto res = cli_.Get("/file_content_with_content_type");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ(5, std::stoi(res->get_header_value("Content-Length")));
|
||||||
|
EXPECT_EQ("file\n", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetInvalidFileContent) {
|
||||||
|
auto res = cli_.Get("/invalid_file_content");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@ -4722,7 +4758,8 @@ static void test_raw_request(const std::string &req,
|
|||||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("ok", "text/plain");
|
res.set_content("ok", "text/plain");
|
||||||
});
|
});
|
||||||
svr.Get("/header_field_value_check", [&](const Request &/*req*/, Response &res) {
|
svr.Get("/header_field_value_check",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("ok", "text/plain");
|
res.set_content("ok", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -7640,7 +7677,7 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
|||||||
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
svr.Get("/test", [&](const Request &/*req*/, Response &res) {
|
svr.Get("/test", [&](const Request & /*req*/, Response &res) {
|
||||||
EXPECT_EQ(res.status, 400);
|
EXPECT_EQ(res.status, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -7666,7 +7703,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
|||||||
|
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
svr.set_expect_100_continue_handler([](const Request &/*req*/, Response &res) {
|
svr.set_expect_100_continue_handler(
|
||||||
|
[](const Request & /*req*/, Response &res) {
|
||||||
res.status = StatusCode::Unauthorized_401;
|
res.status = StatusCode::Unauthorized_401;
|
||||||
res.set_content(reject, "text/plain");
|
res.set_content(reject, "text/plain");
|
||||||
return res.status;
|
return res.status;
|
||||||
@ -7745,7 +7783,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto dl = curl_off_t{};
|
auto dl = curl_off_t{};
|
||||||
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
const auto res =
|
||||||
|
curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||||
ASSERT_EQ(res, CURLE_OK);
|
ASSERT_EQ(res, CURLE_OK);
|
||||||
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
|
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user