Skip to content

Commit 41115cd

Browse files
committed
onDataReceived method has been added
1 parent 2c0f892 commit 41115cd

File tree

4 files changed

+161
-47
lines changed

4 files changed

+161
-47
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Modern, non-blocking and exception free HTTP Client library for C++ (17+)
2929
* [Setting the User Agent](#setting-the-user-agent)
3030
* [How can I limit download and upload bandwidth?](#how-can-i-limit-download-and-upload-bandwidth)
3131
* [How do I get the request as a curl command?](#how-do-i-get-the-request-as-a-curl-command)
32+
* [How to stream data?](#how-to-stream-data)
3233
* [Semantic Versioning](#semantic-versioning)
3334
* [Full function list](#full-function-list)
3435
* [License](#license)
@@ -518,6 +519,39 @@ int main() {
518519
```
519520

520521

522+
## How to stream data?
523+
524+
Instead of receiving the data all at once, you can also receive it in parts using the **"onDataReceived"** callback method.
525+
526+
```cpp
527+
#include <fstream>
528+
#include "libcpp-http-client.hpp"
529+
530+
using namespace lklibs;
531+
532+
int main() {
533+
534+
HttpRequest httpRequest("https://api.myproject.com/image/5000");
535+
536+
// You can stream the data by onDataReceived methods
537+
httpRequest.onDataReceived([&](const unsigned char* chunk, const size_t dataLength)
538+
{
539+
std::cout << "Received chunk of size: " << dataLength << std::endl;
540+
});
541+
542+
auto response = httpRequest
543+
.returnAsBinary()
544+
.send()
545+
.get();
546+
547+
std::cout << "Succeed: " << response.succeed << std::endl;
548+
std::cout << "Http Status Code: " << response.statusCode << std::endl;
549+
550+
return 0;
551+
}
552+
```
553+
554+
521555
## Semantic Versioning
522556

523557
Versioning of the library is done using conventional semantic versioning. Accordingly,

examples/main.cpp

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -265,39 +265,60 @@ void getCurlCommand()
265265
std::cout << "Curl Command: " << response.toCurlCommand() << std::endl;
266266
}
267267

268-
int main()
268+
void streamData()
269269
{
270-
simpleGet();
271-
272-
nonBlockingGet();
273-
274-
receiveBinaryData();
275-
276-
receiveError();
277-
278-
sendingHttpHeaders();
279-
280-
simplePostWithFormData();
281-
282-
simplePostWithJSONData();
283-
284-
simplePutWithFormData();
285-
286-
simpleDeleteWithFormData();
287-
288-
simplePatch();
289-
290-
ignoreSslErrors();
270+
HttpRequest httpRequest("https://httpbun.com/bytes/5000");
291271

292-
setTLSVersion();
272+
// You can stream the data by onDataReceived methods
273+
httpRequest.onDataReceived([&](const unsigned char* chunk, const size_t dataLength)
274+
{
275+
std::cout << "Received chunk of size: " << dataLength << std::endl;
276+
});
293277

294-
setUserAgent();
295-
296-
setTimeout();
278+
auto response = httpRequest
279+
.returnAsBinary()
280+
.send()
281+
.get();
297282

298-
setDownloadAndUploadBandwidthLimit();
283+
std::cout << "Succeed: " << response.succeed << std::endl;
284+
std::cout << "Http Status Code: " << response.statusCode << std::endl;
285+
}
299286

300-
getCurlCommand();
287+
int main()
288+
{
289+
// simpleGet();
290+
//
291+
// nonBlockingGet();
292+
//
293+
// receiveBinaryData();
294+
//
295+
// receiveError();
296+
//
297+
// sendingHttpHeaders();
298+
//
299+
// simplePostWithFormData();
300+
//
301+
// simplePostWithJSONData();
302+
//
303+
// simplePutWithFormData();
304+
//
305+
// simpleDeleteWithFormData();
306+
//
307+
// simplePatch();
308+
//
309+
// ignoreSslErrors();
310+
//
311+
// setTLSVersion();
312+
//
313+
// setUserAgent();
314+
//
315+
// setTimeout();
316+
//
317+
// setDownloadAndUploadBandwidthLimit();
318+
//
319+
// getCurlCommand();
320+
321+
streamData();
301322

302323
return 0;
303324
}

src/libcpp-http-client.hpp

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
33
Modern, non-blocking and exception free HTTP Client library for C++ (17+)
4-
version 1.4.0
4+
version 1.5.0
55
https://github.com/leventkaragol/libcpp-http-client
66
77
If you encounter any issues, please submit a ticket at https://github.com/leventkaragol/libcpp-http-client/issues
@@ -134,7 +134,6 @@ namespace lklibs
134134
{
135135
initialized = true;
136136

137-
// Cleanup is called at program exit.
138137
std::atexit(cleanup);
139138
}
140139
}
@@ -303,6 +302,18 @@ namespace lklibs
303302
return *this;
304303
}
305304

305+
/**
306+
* @brief The callback that will be triggered when each piece (chunk) of incoming data is processed
307+
*
308+
* @param callback: Callback function that will be called with the data and its length
309+
*/
310+
HttpRequest& onDataReceived(std::function<void(const unsigned char* data, size_t dataLength)> callback) noexcept
311+
{
312+
dataCallback = std::move(callback);
313+
314+
return *this;
315+
}
316+
306317
/**
307318
* @brief Convert the request to a cURL command string
308319
* This can be useful for debugging or logging purposes
@@ -394,7 +405,7 @@ namespace lklibs
394405

395406
struct CurlDeleter
396407
{
397-
void operator()(CURL* ptr)
408+
void operator()(CURL* ptr) const
398409
{
399410
if (ptr)
400411
{
@@ -405,7 +416,7 @@ namespace lklibs
405416

406417
struct CurlSlistDeleter
407418
{
408-
void operator()(curl_slist* ptr)
419+
void operator()(curl_slist* ptr) const
409420
{
410421
if (ptr)
411422
{
@@ -414,6 +425,8 @@ namespace lklibs
414425
}
415426
};
416427

428+
std::function<void(const unsigned char* data, size_t dataLength)> dataCallback;
429+
417430
std::future<HttpResult> sendRequest() noexcept
418431
{
419432
return std::async(std::launch::async, [this]() -> HttpResult
@@ -436,17 +449,17 @@ namespace lklibs
436449

437450
std::string stringBuffer;
438451
std::vector<unsigned char> binaryBuffer;
439-
int statusCode = 0;
452+
long statusCode = 0;
440453

441454
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerList.get());
442455
curl_easy_setopt(curl.get(), CURLOPT_URL, this->url.c_str());
443456
curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST, this->method.c_str());
444457
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, this->sslErrorsWillBeIgnored ? 0L : 1L);
445458
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, this->sslErrorsWillBeIgnored ? 0L : 1L);
446-
curl_easy_setopt(curl.get(), CURLOPT_SSLVERSION, static_cast<int>(this->tlsVersion));
459+
curl_easy_setopt(curl.get(), CURLOPT_SSLVERSION, static_cast<long>(this->tlsVersion));
447460
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, this->timeout);
448-
curl_easy_setopt(curl.get(), CURLOPT_MAX_SEND_SPEED_LARGE, this->uploadBandwidthLimit);
449-
curl_easy_setopt(curl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, this->downloadBandwidthLimit);
461+
curl_easy_setopt(curl.get(), CURLOPT_MAX_SEND_SPEED_LARGE, static_cast<curl_off_t>(this->uploadBandwidthLimit));
462+
curl_easy_setopt(curl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, static_cast<curl_off_t>(this->downloadBandwidthLimit));
450463

451464
if (!this->userAgent.empty())
452465
{
@@ -458,7 +471,12 @@ namespace lklibs
458471
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, this->payload.c_str());
459472
}
460473

461-
if (this->returnFormat == ReturnFormat::BINARY)
474+
if (dataCallback)
475+
{
476+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, streamingWriteCallback);
477+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, this);
478+
}
479+
else if (this->returnFormat == ReturnFormat::BINARY)
462480
{
463481
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, binaryWriteCallback);
464482
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &binaryBuffer);
@@ -469,35 +487,58 @@ namespace lklibs
469487
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &stringBuffer);
470488
}
471489

472-
CURLcode res = curl_easy_perform(curl.get());
490+
const auto res = curl_easy_perform(curl.get());
473491
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
474492

475493
if (res == CURLE_OK && statusCode >= 200 && statusCode < 300)
476494
{
477-
return {true, stringBuffer, binaryBuffer, statusCode, ""};
495+
return {
496+
true,
497+
std::move(stringBuffer),
498+
std::move(binaryBuffer),
499+
static_cast<int>(statusCode),
500+
""
501+
};
478502
}
479503
else
480504
{
481-
std::string errorMessage = curl_easy_strerror(res);
482-
505+
std::string err = curl_easy_strerror(res);
483506
if (res == CURLE_OK)
484507
{
485-
errorMessage = "HTTP Error: " + std::to_string(statusCode);
508+
err = "HTTP Error: " + std::to_string(statusCode);
486509
}
487-
488-
return {false, stringBuffer, binaryBuffer, statusCode, errorMessage};
510+
return {
511+
false,
512+
std::move(stringBuffer),
513+
std::move(binaryBuffer),
514+
static_cast<int>(statusCode),
515+
std::move(err)
516+
};
489517
}
490518
});
491519
}
492520

493-
static size_t textWriteCallback(void* contents, size_t size, size_t nmemb, void* userp)
521+
static size_t streamingWriteCallback(const void* contents, const size_t size, size_t nmemb, void* userp)
522+
{
523+
const auto* self = static_cast<HttpRequest*>(userp);
524+
525+
const size_t total = size * nmemb;
526+
527+
const auto* data = static_cast<const unsigned char*>(contents);
528+
529+
self->dataCallback(data, total);
530+
531+
return total;
532+
}
533+
534+
static size_t textWriteCallback(void* contents, const size_t size, size_t nmemb, void* userp)
494535
{
495-
((std::string*)userp)->append((char*)contents, size * nmemb);
536+
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
496537

497538
return size * nmemb;
498539
}
499540

500-
static size_t binaryWriteCallback(void* contents, size_t size, size_t nmemb, void* userp)
541+
static size_t binaryWriteCallback(void* contents, const size_t size, size_t nmemb, void* userp)
501542
{
502543
auto& buffer = *static_cast<std::vector<unsigned char>*>(userp);
503544

test/test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,24 @@ TEST(CurlCommandTest, CurlCommandCanBeGet)
841841
ASSERT_EQ(response.toCurlCommand(), "curl -X POST -H \"Content-Type: application/json\" -A \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0\" --max-time 3 --limit-rate 10240 --data '{\"param1\": 7, \"param2\": \"test\"}' \"https://httpbun.com/post\"") << "Curl command is invalid";
842842
}
843843

844+
TEST(StreamData, ResponseCanBeStreamedByOnDataReceivedCallback)
845+
{
846+
HttpRequest httpRequest("https://httpbun.com/bytes/5000");
847+
848+
httpRequest.onDataReceived([&](const unsigned char* chunk, const size_t dataLength)
849+
{
850+
ASSERT_TRUE(dataLength > 0) << "Binary data length is invalid";
851+
});
852+
853+
auto response = httpRequest
854+
.returnAsBinary()
855+
.send()
856+
.get();
857+
858+
ASSERT_TRUE(response.succeed) << "HTTP Request failed";
859+
ASSERT_EQ(response.statusCode, 200) << "HTTP Status Code is not 200";
860+
}
861+
844862
int main(int argc, char** argv)
845863
{
846864
testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)