Add server stream handler

Add complementary stream_handler callback to response. If set, it
replaces all other content-serving mechanisms. No content-, or
range-related headers are added.

This provides a minimal interface to implement a WebSocket server.
This commit is contained in:
Florian Albrechtskirchinger 2025-02-04 18:17:53 +01:00
parent 71ba7e7b1b
commit fc2daf930c
No known key found for this signature in database
GPG Key ID: 9C4E2AE92D3A9595

View File

@ -524,6 +524,11 @@ using Progress = std::function<bool(uint64_t current, uint64_t total)>;
struct Response; struct Response;
using ResponseHandler = std::function<bool(const Response &response)>; using ResponseHandler = std::function<bool(const Response &response)>;
class Stream;
// Note: do not replace 'std::function<bool(Stream &strm)>' with StreamHandler;
// signature is not final
using StreamHandler = std::function<bool(Stream &strm)>;
struct MultipartFormData { struct MultipartFormData {
std::string name; std::string name;
std::string content; std::string content;
@ -712,6 +717,9 @@ struct Response {
const std::string &content_type); const std::string &content_type);
void set_file_content(const std::string &path); void set_file_content(const std::string &path);
// EXPERIMENTAL callback function signature may change
void set_stream_handler(StreamHandler stream_handler);
Response() = default; Response() = default;
Response(const Response &) = default; Response(const Response &) = default;
Response &operator=(const Response &) = default; Response &operator=(const Response &) = default;
@ -731,6 +739,8 @@ struct Response {
bool content_provider_success_ = false; bool content_provider_success_ = false;
std::string file_content_path_; std::string file_content_path_;
std::string file_content_content_type_; std::string file_content_content_type_;
// EXPERIMENTAL function signature may change
StreamHandler stream_handler_;
}; };
class Stream { class Stream {
@ -5906,6 +5916,10 @@ inline void Response::set_file_content(const std::string &path) {
file_content_path_ = path; file_content_path_ = path;
} }
inline void Response::set_stream_handler(StreamHandler stream_handler) {
stream_handler_ = std::move(stream_handler);
}
// 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();
@ -6548,7 +6562,9 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
res.set_header("Keep-Alive", s); res.set_header("Keep-Alive", s);
} }
if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && if (!res.stream_handler_) {
if ((!res.body.empty() || res.content_length_ > 0 ||
res.content_provider_) &&
!res.has_header("Content-Type")) { !res.has_header("Content-Type")) {
res.set_header("Content-Type", "text/plain"); res.set_header("Content-Type", "text/plain");
} }
@ -6561,6 +6577,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
res.set_header("Accept-Ranges", "bytes"); res.set_header("Accept-Ranges", "bytes");
} }
}
if (post_routing_handler_) { post_routing_handler_(req, res); } if (post_routing_handler_) { post_routing_handler_(req, res); }
@ -6577,19 +6594,27 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
// Body // Body
auto ret = true; auto ret = true;
if (res.stream_handler_) {
// Log early
if (logger_) { logger_(req, res); }
return res.stream_handler_(strm);
} else {
if (req.method != "HEAD") { if (req.method != "HEAD") {
if (!res.body.empty()) { if (!res.body.empty()) {
if (!detail::write_data(strm, res.body.data(), res.body.size())) { if (!detail::write_data(strm, res.body.data(), res.body.size())) {
ret = false; ret = false;
} }
} else if (res.content_provider_) { } else if (res.content_provider_) {
if (write_content_with_provider(strm, req, res, boundary, content_type)) { if (write_content_with_provider(strm, req, res, boundary,
content_type)) {
res.content_provider_success_ = true; res.content_provider_success_ = true;
} else { } else {
ret = false; ret = false;
} }
} }
} }
}
// Log // Log
if (logger_) { logger_(req, res); } if (logger_) { logger_(req, res); }