db7057de69
This change adds onChunkCallback to the request. If defined it will be called repeatedly with the incoming data. This allows to process data on the go or write it to disk instead of accumulating the data in memory.
346 lines
11 KiB
C++
346 lines
11 KiB
C++
/*
|
|
* IXSocketTest.cpp
|
|
* Author: Benjamin Sergeant
|
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
|
*/
|
|
|
|
#include "catch.hpp"
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <ixwebsocket/IXHttpClient.h>
|
|
|
|
using namespace ix;
|
|
|
|
TEST_CASE("http_client", "[http]")
|
|
{
|
|
SECTION("Connect to a remote HTTP server")
|
|
{
|
|
HttpClient httpClient;
|
|
WebSocketHttpHeaders headers;
|
|
|
|
std::string url("http://httpbin.org/");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 60;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = true;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
auto response = httpClient.get(url, args);
|
|
|
|
for (auto it : response->headers)
|
|
{
|
|
std::cerr << it.first << ": " << it.second << std::endl;
|
|
}
|
|
|
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
|
|
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
|
REQUIRE(response->statusCode == 200);
|
|
}
|
|
|
|
#ifdef IXWEBSOCKET_USE_TLS
|
|
SECTION("Connect to a remote HTTPS server")
|
|
{
|
|
HttpClient httpClient;
|
|
WebSocketHttpHeaders headers;
|
|
|
|
SocketTLSOptions tlsOptions;
|
|
tlsOptions.caFile = "cacert.pem";
|
|
httpClient.setTLSOptions(tlsOptions);
|
|
|
|
std::string url("https://httpbin.org/");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 60;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = true;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
auto response = httpClient.get(url, args);
|
|
|
|
for (auto it : response->headers)
|
|
{
|
|
std::cerr << it.first << ": " << it.second << std::endl;
|
|
}
|
|
|
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
|
|
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
|
REQUIRE(response->statusCode == 200);
|
|
}
|
|
#endif
|
|
|
|
SECTION("Async API, one call")
|
|
{
|
|
bool async = true;
|
|
HttpClient httpClient(async);
|
|
WebSocketHttpHeaders headers;
|
|
|
|
SocketTLSOptions tlsOptions;
|
|
tlsOptions.caFile = "cacert.pem";
|
|
httpClient.setTLSOptions(tlsOptions);
|
|
|
|
std::string url("http://httpbin.org/");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 60;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = true;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
std::atomic<bool> requestCompleted(false);
|
|
std::atomic<int> statusCode(0);
|
|
|
|
httpClient.performRequest(
|
|
args, [&requestCompleted, &statusCode](const HttpResponsePtr& response) {
|
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
|
|
|
// In case of failure, print response->errorMsg
|
|
statusCode = response->statusCode;
|
|
requestCompleted = true;
|
|
});
|
|
|
|
int wait = 0;
|
|
while (wait < 5000)
|
|
{
|
|
if (requestCompleted) break;
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
std::this_thread::sleep_for(duration);
|
|
wait += 10;
|
|
}
|
|
|
|
std::cerr << "Done" << std::endl;
|
|
REQUIRE(statusCode == 200);
|
|
}
|
|
|
|
SECTION("Async API, multiple calls")
|
|
{
|
|
bool async = true;
|
|
HttpClient httpClient(async);
|
|
WebSocketHttpHeaders headers;
|
|
|
|
std::string url("http://httpbin.org/");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 60;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = true;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
std::atomic<bool> requestCompleted(false);
|
|
std::atomic<int> statusCode0(0);
|
|
std::atomic<int> statusCode1(0);
|
|
std::atomic<int> statusCode2(0);
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
httpClient.performRequest(
|
|
args,
|
|
[i, &requestCompleted, &statusCode0, &statusCode1, &statusCode2](
|
|
const HttpResponsePtr& response) {
|
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
|
|
|
// In case of failure, print response->errorMsg
|
|
if (i == 0)
|
|
{
|
|
statusCode0 = response->statusCode;
|
|
}
|
|
else if (i == 1)
|
|
{
|
|
statusCode1 = response->statusCode;
|
|
}
|
|
else if (i == 2)
|
|
{
|
|
statusCode2 = response->statusCode;
|
|
requestCompleted = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
int wait = 0;
|
|
while (wait < 10000)
|
|
{
|
|
if (requestCompleted) break;
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
std::this_thread::sleep_for(duration);
|
|
wait += 10;
|
|
}
|
|
|
|
std::cerr << "Done" << std::endl;
|
|
REQUIRE(statusCode0 == 200);
|
|
REQUIRE(statusCode1 == 200);
|
|
REQUIRE(statusCode2 == 200);
|
|
}
|
|
|
|
SECTION("Async API, cancel")
|
|
{
|
|
bool async = true;
|
|
HttpClient httpClient(async);
|
|
WebSocketHttpHeaders headers;
|
|
|
|
SocketTLSOptions tlsOptions;
|
|
tlsOptions.caFile = "cacert.pem";
|
|
httpClient.setTLSOptions(tlsOptions);
|
|
|
|
std::string url("http://httpbin.org/delay/10");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 60;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = true;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
std::atomic<bool> requestCompleted(false);
|
|
std::atomic<HttpErrorCode> errorCode(HttpErrorCode::Invalid);
|
|
|
|
httpClient.performRequest(
|
|
args, [&requestCompleted, &errorCode](const HttpResponsePtr& response) {
|
|
errorCode = response->errorCode;
|
|
requestCompleted = true;
|
|
});
|
|
|
|
// cancel immediately
|
|
args->cancel = true;
|
|
|
|
int wait = 0;
|
|
while (wait < 5000)
|
|
{
|
|
if (requestCompleted) break;
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
std::this_thread::sleep_for(duration);
|
|
wait += 10;
|
|
}
|
|
|
|
std::cerr << "Done" << std::endl;
|
|
REQUIRE(errorCode == HttpErrorCode::Cancelled);
|
|
}
|
|
|
|
SECTION("Async API, streaming transfer")
|
|
{
|
|
bool async = true;
|
|
HttpClient httpClient(async);
|
|
WebSocketHttpHeaders headers;
|
|
|
|
SocketTLSOptions tlsOptions;
|
|
tlsOptions.caFile = "cacert.pem";
|
|
httpClient.setTLSOptions(tlsOptions);
|
|
|
|
std::string url("http://speedtest.belwue.net/random-100M");
|
|
auto args = httpClient.createRequest(url);
|
|
|
|
args->extraHeaders = headers;
|
|
args->connectTimeout = 60;
|
|
args->transferTimeout = 120;
|
|
args->followRedirects = true;
|
|
args->maxRedirects = 10;
|
|
args->verbose = true;
|
|
args->compress = false;
|
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
|
args->onProgressCallback = [](int current, int total) -> bool {
|
|
std::cerr << "\r"
|
|
<< "Downloaded " << current << " bytes out of " << total;
|
|
return true;
|
|
};
|
|
|
|
// compute Adler-32 checksum of received data
|
|
uint32_t a = 1, b = 0;
|
|
args->onChunkCallback = [&](const std::string& data) {
|
|
for (const char c: data)
|
|
{
|
|
a = (a + (unsigned char)c) % 65521;
|
|
b = (b + a) % 65521;
|
|
}
|
|
};
|
|
|
|
std::atomic<bool> requestCompleted(false);
|
|
std::atomic<HttpErrorCode> errorCode(HttpErrorCode::Invalid);
|
|
std::atomic<int> statusCode(0);
|
|
|
|
httpClient.performRequest(
|
|
args, [&](const HttpResponsePtr& response) {
|
|
errorCode = response->errorCode;
|
|
statusCode = response->statusCode;
|
|
requestCompleted = true;
|
|
});
|
|
|
|
int wait = 0;
|
|
while (wait < 120000)
|
|
{
|
|
if (requestCompleted) break;
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
std::this_thread::sleep_for(duration);
|
|
wait += 10;
|
|
}
|
|
|
|
std::cerr << "Done" << std::endl;
|
|
REQUIRE(errorCode == HttpErrorCode::Ok);
|
|
REQUIRE(statusCode == 200);
|
|
|
|
// compare checksum with a known good value
|
|
uint32_t checksum = (b << 16) | a;
|
|
REQUIRE(checksum == 1440194471);
|
|
}
|
|
}
|