mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-09-30 12:42:26 +07:00
Send small static files with the headers and large bodies without a copy (#2589)
A file served from a mount point or through set_file_content() left in two writes, one for the status line and headers and one for the body, because the body came from a content provider. A small file is now read into the header buffer so the whole response leaves in a single write. Only file-backed providers are coalesced this way: a user-supplied provider may produce its data over time, and holding the headers back until it finishes would stall the client. A large set_content() body was copied into the header buffer before being sent. A body of CPPHTTPLIB_SEND_BUFSIZ or more is now written directly after the headers, which saves the copy at the cost of one extra write.
This commit is contained in:
@@ -1884,6 +1884,7 @@ struct Response {
|
||||
ContentProvider content_provider_;
|
||||
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
||||
bool is_chunked_content_provider_ = false;
|
||||
bool is_file_content_provider_ = false;
|
||||
bool content_provider_success_ = false;
|
||||
std::string file_content_path_;
|
||||
std::string file_content_content_type_;
|
||||
@@ -8788,9 +8789,9 @@ inline bool compress_content_provider(const ContentProvider &content_provider,
|
||||
return cmp.compress(nullptr, 0, true, append);
|
||||
}
|
||||
|
||||
// Serves `m` as the response body. `set_content_provider()` clears the coding,
|
||||
// so recording it has to come after; keeping both here means a third
|
||||
// file-serving path cannot get that order wrong.
|
||||
// Serves `m` as the response body. `set_content_provider()` clears the coding
|
||||
// and the file flag, so recording them has to come after; keeping all of it
|
||||
// here means a third file-serving path cannot get that order wrong.
|
||||
inline void set_file_content_provider(Response &res,
|
||||
const std::shared_ptr<mmap> &m,
|
||||
const std::string &content_type,
|
||||
@@ -8802,6 +8803,7 @@ inline void set_file_content_provider(Response &res,
|
||||
return true;
|
||||
});
|
||||
|
||||
res.is_file_content_provider_ = true;
|
||||
res.content_coding_ = encoding;
|
||||
}
|
||||
|
||||
@@ -11702,6 +11704,7 @@ inline void Response::set_content_provider(
|
||||
if (in_length > 0) { content_provider_ = std::move(provider); }
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = false;
|
||||
is_file_content_provider_ = false;
|
||||
content_coding_ = detail::EncodingType::None;
|
||||
}
|
||||
|
||||
@@ -11713,6 +11716,7 @@ inline void Response::set_content_provider(
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = false;
|
||||
is_file_content_provider_ = false;
|
||||
content_coding_ = detail::EncodingType::None;
|
||||
}
|
||||
|
||||
@@ -11724,6 +11728,7 @@ inline void Response::set_chunked_content_provider(
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = true;
|
||||
is_file_content_provider_ = false;
|
||||
content_coding_ = detail::EncodingType::None;
|
||||
}
|
||||
|
||||
@@ -13377,9 +13382,27 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
||||
if (header_writer_(bstrm, res.headers) <= 0) { return false; }
|
||||
|
||||
// Combine small body with headers to reduce write syscalls
|
||||
if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
|
||||
bstrm.write(res.body.data(), res.body.size());
|
||||
// Combine a small body with the headers so the whole response leaves in a
|
||||
// single write. A large body is written on its own instead: a copy of it
|
||||
// costs more than the extra write saves.
|
||||
auto send_body = req.method != "HEAD";
|
||||
auto body_is_separate = false;
|
||||
auto provider_done = false;
|
||||
if (send_body && !res.body.empty() && !res.content_provider_) {
|
||||
if (res.body.size() < CPPHTTPLIB_SEND_BUFSIZ) {
|
||||
bstrm.write(res.body.data(), res.body.size());
|
||||
} else {
|
||||
body_is_separate = true;
|
||||
}
|
||||
} else if (send_body && res.content_provider_ &&
|
||||
res.is_file_content_provider_ &&
|
||||
res.content_length_ < CPPHTTPLIB_SEND_BUFSIZ) {
|
||||
// A small file is read into the same buffer. Other providers may produce
|
||||
// their data over time, so they are never held back.
|
||||
if (!write_content_with_provider(bstrm, req, res, boundary, content_type)) {
|
||||
return false;
|
||||
}
|
||||
provider_done = true;
|
||||
}
|
||||
|
||||
// Log before writing to avoid race condition with client-side code that
|
||||
@@ -13390,17 +13413,20 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
auto &data = bstrm.get_buffer();
|
||||
if (!detail::write_data(strm, data.data(), data.size())) { return false; }
|
||||
|
||||
// Streaming body
|
||||
auto ret = true;
|
||||
if (req.method != "HEAD" && res.content_provider_) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
if (body_is_separate) {
|
||||
return detail::write_data(strm, res.body.data(), res.body.size());
|
||||
}
|
||||
|
||||
return ret;
|
||||
// Streaming body
|
||||
if (send_body && res.content_provider_) {
|
||||
if (!provider_done &&
|
||||
!write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
return false;
|
||||
}
|
||||
res.content_provider_success_ = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
|
||||
Reference in New Issue
Block a user