I use boost::beast to get data over HTTPS stream. (in particular from EXANTE API ).
My code is almost identical to the example from the official documentation of the Beast library: Chunked encoding.
Everything works correctly, but periodically reading chunks is interrupted by "Stream truncated" or "Partial message" errors. I can't understand the cause of these errors. Is it in my code, or is it a problem with the internet connection or the server, or maybe the connection is interrupted by a proxy server or firewall (idk).
UPD: Traffic analysis via Wireshark showed that before the error occurs, I receive a TLS message "Encrypted alert (21)". But I don't understand what it can mean and how it can be interpreted.
A maximally simplified version of my code is shown below. P.S. The data is from a test account, so it is not secret.
#include <string>
#include <iostream>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/beast.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/http.hpp>
using namespace std::string_literals;
namespace net = boost::asio;
namespace ssl = boost::asio::ssl;
namespace ip = boost::asio::ip;
namespace beast = boost::beast;
namespace http = boost::beast::http;
using tcp = boost::asio::ip::tcp;
std::string base64_encode(const std::string& val) {
using namespace boost::archive::iterators;
using It = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
return tmp.append((3 - val.size() % 3) % 3, '=');
}
int main() {
const auto clientID = "6dc42e50-0b01-444e-bae0-3a431c88b525"s;
const auto key = "TTQmWnbHlr5OWlNSWl1P"s;
const auto host = "api-demo.exante.eu"s;
const auto target = "/trade/3.0/stream/orders"s;
const auto port = "443"s;
const auto http_version = 11;
const auto auth_token = "Basic "s + base64_encode(clientID + ':' + key);
const auto accept_value = "application/x-json-stream"s;
const auto timeout = std::chrono::seconds(12);
http::request<http::string_body> request{http::verb::get, target, http_version};
request.set(http::field::host, host);
request.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
request.set(http::field::authorization, auth_token);
request.set(http::field::accept, accept_value);
request.prepare_payload();
net::io_context ioc;
ssl::context ctx(ssl::context::tlsv12_client);
tcp::resolver resolver(ioc);
beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
throw beast::system_error{ec};
}
auto const results = resolver.resolve(host, port);
beast::get_lowest_layer(stream).connect(results);
stream.handshake(ssl::stream_base::client);
http::write(stream, request);
beast::flat_buffer buffer;
http::parser<false, http::empty_body> p;
http::read_header(stream, buffer, p);
http::chunk_extensions ce;
std::string chunk;
auto header_cb =
[&](std::uint64_t size,
beast::string_view extensions,
beast::error_code &ev) {
ce.parse(extensions, ev);
if (ev)
return;
if (size > (std::numeric_limits<std::size_t>::max)()) {
ev = http::error::body_limit;
return;
}
chunk.reserve(static_cast<std::size_t>(size));
chunk.clear();
};
p.on_chunk_header(header_cb);
auto body_cb =
[&](std::uint64_t remain,
beast::string_view body,
beast::error_code &ec) {
if (remain == body.size())
ec = http::error::end_of_chunk;
chunk.append(body.data(), body.size());
return body.size();
};
p.on_chunk_body(body_cb);
std::function<void(const beast::error_code &, size_t)> onRead = [&](const auto &ec, auto bytes_transferred) {
if (ec && ec != http::error::end_of_chunk)
throw std::runtime_error(ec.what());
for (auto const &extension: ce) {
std::cout << "Extension: " << extension.first;
if (!extension.second.empty())
std::cout << " = " << extension.second << std::endl;
else
std::cout << std::endl;
}
static int counter = 0;
std::cout << "Chunk Body: " << chunk << counter++ << " @@@!" << std::endl;
chunk.clear();
if (!p.is_done()) {
stream.next_layer().expires_after(timeout);
http::async_read(stream, buffer, p, onRead);
}
};
stream.next_layer().expires_after(timeout);
http::async_read(stream, buffer, p, onRead);
ioc.run();
return 0;
}
I have tried both synchronous and asynchronous versions of http::read functions, the problem occurs in any case.
The error occurs on three different devices on different networks.
According to the API developers, there are no restrictions on the duration of HTTP stream listening. The problem occurs every time after a random period of time from a couple of minutes to several hours.