Skip to content

Commit 5921d1f

Browse files
committed
Add comprehensive tests for C++20 Streaming API in httplib
- Implement tests for StreamHandle struct to verify its existence and validity checks. - Create StreamingServerTest to validate the open_stream() method and its behavior with valid and invalid paths. - Add tests for reading data from streams, including edge cases for buffer sizes and end-of-stream conditions. - Introduce tests for the Generator API to ensure correct functionality with streaming responses. - Implement integration tests for chunked transfer encoding, validating both small and large responses. - Add tests for ClientConnection and BodyReader structs to ensure proper initialization and error handling. - Include SSL support tests for streaming over secure connections.
1 parent ad61419 commit 5921d1f

File tree

7 files changed

+300
-80
lines changed

7 files changed

+300
-80
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ example/benchmark
1616
example/redirect
1717
!example/redirect.*
1818
example/ssecli
19+
!example/ssecli.*
20+
example/ssecli-stream
21+
!example/ssecli-stream.*
1922
example/ssesvr
23+
!example/ssesvr.*
2024
example/upload
2125
!example/upload.*
2226
example/one_time_request
@@ -29,7 +33,7 @@ example/*.pem
2933
test/httplib.cc
3034
test/httplib.h
3135
test/test
32-
test/test20
36+
test/test-stream
3337
test/server_fuzzer
3438
test/test_proxy
3539
test/test_split

README20.md renamed to README-stream.md

Lines changed: 51 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ The C++20 streaming API allows you to process HTTP response bodies chunk by chun
1414
## Requirements
1515

1616
- C++20 compiler with coroutine support
17-
- Include `httplib20.h` (which includes `httplib.h`)
17+
- Include `httplib-stream.h` (which includes `httplib.h`)
1818

1919
## Quick Start
2020

2121
```cpp
22-
#include "httplib20.h"
22+
#include "httplib-stream.h"
2323

2424
int main() {
2525
httplib::Client cli("http://localhost:8080");
@@ -82,10 +82,10 @@ if (handle.is_valid()) {
8282
8383
### High-Level API: `GetStream()` and `StreamingResult`
8484
85-
The `httplib20.h` header provides a more ergonomic API using C++20 coroutines.
85+
The `httplib-stream.h` header provides a more ergonomic API using C++20 coroutines.
8686
8787
```cpp
88-
#include "httplib20.h"
88+
#include "httplib-stream.h"
8989
9090
httplib::Client cli("http://localhost:8080");
9191
@@ -124,68 +124,55 @@ for (auto chunk : result.body(1024)) {
124124
### Example 1: SSE (Server-Sent Events) Client
125125

126126
```cpp
127-
#include "httplib20.h"
127+
#include "httplib-stream.h"
128128
#include <iostream>
129129

130130
int main() {
131-
httplib::Client cli("http://localhost:8080");
131+
httplib::Client cli("http://localhost:1234");
132+
132133
auto result = httplib::GetStream(cli, "/events");
134+
if (!result) { return 1; }
133135

134-
if (!result) {
135-
std::cerr << "Connection failed\n";
136-
return 1;
137-
}
138-
139-
std::cout << "Connected, receiving events...\n";
140-
141-
std::string buffer;
142-
for (auto chunk : result.body(256)) {
143-
buffer += chunk;
144-
145-
// Process complete SSE messages
146-
size_t pos;
147-
while ((pos = buffer.find("\n\n")) != std::string::npos) {
148-
std::string message = buffer.substr(0, pos);
149-
buffer.erase(0, pos + 2);
150-
151-
std::cout << "Event: " << message << "\n";
152-
}
136+
for (auto chunk : result.body()) {
137+
std::cout << chunk << std::flush;
153138
}
154139

155140
return 0;
156141
}
157142
```
158143

159-
### Example 2: LLM Streaming Response (Ollama-style)
144+
For a complete SSE client with auto-reconnection and event parsing, see `example/ssecli-stream.cc`.
145+
146+
### Example 2: LLM Streaming Response
160147

161148
```cpp
162-
#include "httplib20.h"
149+
#include "httplib-stream.h"
163150
#include <iostream>
164151

165152
int main() {
166-
httplib::Client cli("http://localhost:11434");
167-
168-
// Note: For POST requests, use open_stream with custom request
169-
// This example shows the pattern for streaming responses
153+
httplib::Client cli("http://localhost:11434"); // Ollama
170154

171-
httplib::Headers headers = {{"Content-Type", "application/json"}};
172-
auto result = httplib::GetStream(cli, "/api/generate", headers);
155+
auto result = httplib::GetStream(cli, "/api/generate");
173156

174157
if (result && result.status() == 200) {
175-
for (auto chunk : result.body(1024)) {
176-
// Each chunk may contain JSON like: {"response": "Hello"}
158+
for (auto chunk : result.body()) {
177159
std::cout << chunk << std::flush;
178160
}
179161
}
180162

163+
// Check for connection errors
164+
if (result.read_error() != httplib::Error::Success) {
165+
std::cerr << "Connection lost\n";
166+
}
167+
181168
return 0;
182169
}
183170
```
184171

185172
### Example 3: Large File Download with Progress
186173

187174
```cpp
188-
#include "httplib20.h"
175+
#include "httplib-stream.h"
189176
#include <fstream>
190177
#include <iostream>
191178

@@ -215,44 +202,36 @@ int main() {
215202
### Example 4: Reverse Proxy Streaming
216203

217204
```cpp
218-
#include "httplib20.h"
205+
#include "httplib.h"
219206

220-
int main() {
221-
httplib::Server svr;
207+
httplib::Server svr;
208+
209+
svr.Get("/proxy/(.*)", [](const httplib::Request& req, httplib::Response& res) {
210+
httplib::Client upstream("http://backend:8080");
211+
auto handle = upstream.open_stream("/" + req.matches[1].str());
222212

223-
svr.Get("/proxy/(.*)", [](const httplib::Request& req, httplib::Response& res) {
224-
httplib::Client upstream("http://backend:8080");
225-
auto handle = upstream.open_stream("/" + req.matches[1].str());
226-
227-
if (!handle.is_valid()) {
228-
res.status = 502;
229-
return;
230-
}
231-
232-
// Forward status and headers
233-
res.status = handle.response->status;
234-
for (const auto& h : handle.response->headers) {
235-
res.set_header(h.first, h.second);
236-
}
237-
238-
// Stream body using chunked transfer
239-
res.set_chunked_content_provider(
240-
handle.response->get_header_value("Content-Type"),
241-
[handle = std::move(handle)](size_t, httplib::DataSink& sink) mutable {
242-
char buf[8192];
243-
ssize_t n = handle.read(buf, sizeof(buf));
244-
if (n > 0) {
245-
sink.write(buf, static_cast<size_t>(n));
246-
return true;
247-
}
248-
sink.done();
213+
if (!handle.is_valid()) {
214+
res.status = 502;
215+
return;
216+
}
217+
218+
res.status = handle.response->status;
219+
res.set_chunked_content_provider(
220+
handle.response->get_header_value("Content-Type"),
221+
[handle = std::move(handle)](size_t, httplib::DataSink& sink) mutable {
222+
char buf[8192];
223+
auto n = handle.read(buf, sizeof(buf));
224+
if (n > 0) {
225+
sink.write(buf, static_cast<size_t>(n));
249226
return true;
250227
}
251-
);
252-
});
253-
254-
svr.listen("0.0.0.0", 3000);
255-
}
228+
sink.done();
229+
return true;
230+
}
231+
);
232+
});
233+
234+
svr.listen("0.0.0.0", 3000);
256235
```
257236

258237
## Comparison with Existing APIs
@@ -310,4 +289,5 @@ clang++ -std=c++20 -o myapp myapp.cpp -lpthread -lssl -lcrypto
310289

311290
- [Issue #2269](https://github.com/yhirose/cpp-httplib/issues/2269) - Original feature request
312291
- [httplib.h](./httplib.h) - Main library
313-
- [httplib20.h](./httplib20.h) - C++20 extensions
292+
- [httplib-stream.h](./httplib-stream.h) - C++20 extensions
293+
- [example/ssecli-stream.cc](./example/ssecli-stream.cc) - SSE client with auto-reconnection

example/Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
1818
BROTLI_DIR = $(PREFIX)/opt/brotli
1919
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
2020

21-
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header
21+
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli ssecli-stream benchmark one_time_request server_and_client accept_header
2222

2323
server : server.cc ../httplib.h Makefile
2424
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
@@ -47,6 +47,9 @@ ssesvr : ssesvr.cc ../httplib.h Makefile
4747
ssecli : ssecli.cc ../httplib.h Makefile
4848
$(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
4949

50+
ssecli-stream : ssecli-stream.cc ../httplib.h ../httplib-stream.h Makefile
51+
$(CXX) -o ssecli-stream -std=c++20 -I.. -Wall -Wextra -pthread ssecli-stream.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
52+
5053
benchmark : benchmark.cc ../httplib.h Makefile
5154
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
5255

@@ -64,4 +67,4 @@ pem:
6467
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
6568

6669
clean:
67-
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header *.pem
70+
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli ssecli-stream benchmark one_time_request server_and_client accept_header *.pem

0 commit comments

Comments
 (0)