Skip to content

Commit e973890

Browse files
committed
Enhance StreamHandle API by adding get_read_error() and has_read_error() methods for improved error handling
1 parent 93d37ec commit e973890

File tree

2 files changed

+24
-43
lines changed

2 files changed

+24
-43
lines changed

README-stream.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ if (handle.is_valid()) {
107107
| `response` | `std::unique_ptr<Response>` | HTTP response with headers |
108108
| `error` | `Error` | Error code if request failed |
109109
| `is_valid()` | `bool` | Returns true if response is valid |
110-
| `is_socket_direct_mode()` | `bool` | Returns true (always direct socket reading) |
111110
| `read(buf, len)` | `ssize_t` | Read up to `len` bytes directly from socket |
111+
| `get_read_error()` | `Error` | Get the last read error |
112+
| `has_read_error()` | `bool` | Check if a read error occurred |
112113
113114
### High-Level API: `stream::Get()` and `stream::Result`
114115

httplib.h

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,28 +1496,12 @@ class ClientImpl {
14961496

14971497
// Streaming handle for reading response body incrementally
14981498
struct StreamHandle {
1499-
// Common fields
1499+
// Public fields
15001500
std::unique_ptr<Response> response;
15011501
Error error = Error::Success;
15021502

1503-
// Mode 1: Memory buffer (existing behavior)
1504-
size_t read_offset_ = 0;
1505-
1506-
// Mode 2: Socket direct (true streaming)
1507-
std::unique_ptr<ClientConnection> connection_; // Socket ownership
1508-
std::unique_ptr<Stream> socket_stream_; // SocketStream ownership
1509-
Stream *stream_ = nullptr; // Stream for reading
1510-
detail::BodyReader body_reader_; // Body reading state
1511-
1512-
// Compression support
1513-
std::unique_ptr<detail::decompressor> decompressor_;
1514-
std::string decompress_buffer_; // Buffer for decompressed data
1515-
size_t decompress_offset_ = 0; // Read position in decompress_buffer_
1516-
1517-
// Default constructor
1503+
// Constructors and assignment operators
15181504
StreamHandle() = default;
1519-
1520-
// Move-only semantics (non-copyable due to unique_ptr members)
15211505
StreamHandle(const StreamHandle &) = delete;
15221506
StreamHandle &operator=(const StreamHandle &) = delete;
15231507
StreamHandle(StreamHandle &&) = default;
@@ -1526,30 +1510,39 @@ class ClientImpl {
15261510
// Destructor: Cleans up socket connection.
15271511
// In socket direct mode, if the body was not fully read, remaining data
15281512
// is discarded and the connection is closed (cannot be reused).
1529-
// This is safe but may leave unread data in the socket buffer.
15301513
~StreamHandle() = default;
15311514

1515+
// Check if the handle is valid
15321516
bool is_valid() const {
15331517
return response != nullptr && error == Error::Success;
15341518
}
15351519

1536-
// Check if using socket direct mode (true streaming)
1537-
bool is_socket_direct_mode() const { return stream_ != nullptr; }
1538-
15391520
// Read up to len bytes into buf, returns number of bytes read (0 at EOF)
15401521
// Implementation is below decompressor class definition
15411522
ssize_t read(char *buf, size_t len);
15421523

1543-
private:
1544-
// Read with decompression support (implemented after decompressor class)
1545-
ssize_t read_with_decompression(char *buf, size_t len);
1546-
1547-
public:
15481524
// Get the last error that occurred during reading (socket direct mode only)
15491525
Error get_read_error() const { return body_reader_.last_error; }
15501526

15511527
// Check if a read error occurred (socket direct mode only)
15521528
bool has_read_error() const { return body_reader_.has_error(); }
1529+
1530+
private:
1531+
friend class ClientImpl;
1532+
1533+
// Read with decompression support (implemented after decompressor class)
1534+
ssize_t read_with_decompression(char *buf, size_t len);
1535+
1536+
// Socket connection ownership
1537+
std::unique_ptr<ClientConnection> connection_; // Socket ownership
1538+
std::unique_ptr<Stream> socket_stream_; // SocketStream ownership
1539+
Stream *stream_ = nullptr; // Stream for reading
1540+
detail::BodyReader body_reader_; // Body reading state
1541+
1542+
// Compression support
1543+
std::unique_ptr<detail::decompressor> decompressor_;
1544+
std::string decompress_buffer_; // Buffer for decompressed data
1545+
size_t decompress_offset_ = 0; // Read position in decompress_buffer_
15531546
};
15541547

15551548
// clang-format off
@@ -2923,21 +2916,8 @@ inline bool is_field_value(const std::string &s) { return is_field_content(s); }
29232916
inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
29242917
if (!is_valid() || !response) { return -1; }
29252918

2926-
if (is_socket_direct_mode()) {
2927-
// Socket direct mode: read from stream via BodyReader
2928-
if (decompressor_) { return read_with_decompression(buf, len); }
2929-
return body_reader_.read(buf, len);
2930-
} else {
2931-
// Memory buffer mode: read from pre-loaded response body
2932-
const auto &body = response->body;
2933-
if (read_offset_ >= body.size()) { return 0; }
2934-
2935-
auto remaining = body.size() - read_offset_;
2936-
auto to_read = (std::min)(len, remaining);
2937-
std::memcpy(buf, body.data() + read_offset_, to_read);
2938-
read_offset_ += to_read;
2939-
return static_cast<ssize_t>(to_read);
2940-
}
2919+
if (decompressor_) { return read_with_decompression(buf, len); }
2920+
return body_reader_.read(buf, len);
29412921
}
29422922

29432923
inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,

0 commit comments

Comments
 (0)