Add client stream handler

Introduce a new request callback, stream_handler. If set, it replaces
all other content-serving mechanisms. The handler is not called in
response to HEAD or CONNECT requests, or when following a redirect.
Content-related default header fields are not added to the request.

In conjunction with a response handler, it provides a minimal interface
to implement a WebSocket client.
This commit is contained in:
Florian Albrechtskirchinger 2025-02-03 11:42:03 +01:00
parent fc2daf930c
commit b18a6e8456
No known key found for this signature in database
GPG Key ID: 9C4E2AE92D3A9595

View File

@ -646,6 +646,7 @@ struct Request {
// for client // for client
ResponseHandler response_handler; ResponseHandler response_handler;
StreamHandler stream_handler; // EXPERIMENTAL function signature may change
ContentReceiverWithProgress content_receiver; ContentReceiverWithProgress content_receiver;
Progress progress; Progress progress;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@ -1180,6 +1181,7 @@ enum class Error {
Compression, Compression,
ConnectionTimeout, ConnectionTimeout,
ProxyConnection, ProxyConnection,
StreamHandler,
// For internal use only // For internal use only
SSLPeerCouldBeClosed_, SSLPeerCouldBeClosed_,
@ -2270,6 +2272,7 @@ inline std::string to_string(const Error error) {
case Error::Compression: return "Compression failed"; case Error::Compression: return "Compression failed";
case Error::ConnectionTimeout: return "Connection timed out"; case Error::ConnectionTimeout: return "Connection timed out";
case Error::ProxyConnection: return "Proxy connection failed"; case Error::ProxyConnection: return "Proxy connection failed";
case Error::StreamHandler: return "Stream handler failed";
case Error::Unknown: return "Unknown"; case Error::Unknown: return "Unknown";
default: break; default: break;
} }
@ -7825,10 +7828,12 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
} }
} }
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } if (!req.stream_handler && !req.has_header("Accept")) {
req.set_header("Accept", "*/*");
}
if (!req.content_receiver) { if (!req.content_receiver) {
if (!req.has_header("Accept-Encoding")) { if (!req.stream_handler && !req.has_header("Accept-Encoding")) {
std::string accept_encoding; std::string accept_encoding;
#ifdef CPPHTTPLIB_BROTLI_SUPPORT #ifdef CPPHTTPLIB_BROTLI_SUPPORT
accept_encoding = "br"; accept_encoding = "br";
@ -7846,7 +7851,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
req.set_header("User-Agent", agent); req.set_header("User-Agent", agent);
} }
#endif #endif
}; }
if (req.body.empty()) { if (req.body.empty()) {
if (req.content_provider_) { if (req.content_provider_) {
@ -8078,13 +8083,26 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
res.status != StatusCode::NotModified_304 && res.status != StatusCode::NotModified_304 &&
follow_location_; follow_location_;
if (req.response_handler && !redirect) { if (!redirect) {
if (req.response_handler) {
if (!req.response_handler(res)) { if (!req.response_handler(res)) {
error = Error::Canceled; error = Error::Canceled;
return false; return false;
} }
} }
if (req.stream_handler) {
// Log early
if (logger_) { logger_(req, res); }
if (!req.stream_handler(strm)) {
error = Error::StreamHandler;
return false;
}
return true;
}
}
auto out = auto out =
req.content_receiver req.content_receiver
? static_cast<ContentReceiverWithProgress>( ? static_cast<ContentReceiverWithProgress>(