#c #boost #websocket #beast
Вопрос:
Я пытаюсь подключиться к API Kraken websocket.Но я получил ошибку «Рукопожатие WebSocket было отклонено удаленным одноранговым узлом».
Я написал класс-оболочку для websocket и клиента rest api exchange.Он хорошо работает с API веб-сокетов Binance,но подключение к веб-сокету Kraken не удалось.
Я тоже пробовал разные типы проверок tls(ssl::контекст ctx_webSocket{ ssl::контекст::tlsv13_client};), но результат был тот же.
class Exchange {
public:
Exchange(std::string name, const std::stringamp; http_host) :m_name(std::move(name)) {
init_http(http_host);
}
void init_http(std::string constamp; host) {
const auto results{ resolver.resolve(host,"443") };
get_lowest_layer(stream).connect(results);
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str()))
{
boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
throw boost::system::system_error{ ec };
}
stream.handshake(ssl::stream_base::client);
}
void init_webSocket(std::string constamp; host, std::string constamp; port, std::string constamp; target) {
auto const results = resolver_webSocket.resolve(host, port);
net::connect(ws.next_layer().next_layer(), results.begin(), results.end());
ws.next_layer().handshake(ssl::stream_base::client);
// Set a decorator to change the User-Agent of the handshake
ws.set_option(websocket::stream_base::decorator(
[](websocket::request_typeamp; req)
{
req.set(http::field::user_agent,
std::string(BOOST_BEAST_VERSION_STRING)
" websocket-client-coro");
}));
ws.handshake(host, target.c_str());
}
void read_Socket() {
ws.read(buffer);
}
void write_Socket(const std::stringamp; text) {
ws.write(net::buffer(text));
}
std::string get_socket_data() {
return beast::buffers_to_string(buffer.data());
}
void buffer_clear() {
buffer.clear();
}
void webSocket_close() {
ws.close(websocket::close_code::none);
}
private:
// HTTP REQUEST SET //
std::string m_name;
net::io_context ioc;
ssl::context ctx{ ssl::context::tlsv12_client };
tcp::resolver resolver{ ioc };
Stream stream{ ioc, ctx };
// WEB SOCKET SET //
std::string m_web_socket_host;
std::string m_web_socket_port;
beast::flat_buffer buffer;
net::io_context ioc_webSocket;
ssl::context ctx_webSocket{ ssl::context::tlsv13_client };
tcp::resolver resolver_webSocket{ ioc_webSocket };
websocket::stream<beast::ssl_stream<tcp::socket>> ws{ ioc_webSocket, ctx_webSocket };
};
int main()
{
Exchange kraken{ "kraken","api.kraken.com" };
try
{
kraken.init_webSocket("ws.kraken.com", "443", "/");
while (true)
{
kraken.read_Socket();
std::cout << kraken.get_socket_data();
return 1;
kraken.buffer_clear();
}
kraken.webSocket_close();
}
catch (std::exception constamp; e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
}
Ответ №1:
решенный. Я выяснил, в чем проблема. Перед рукопожатием websocket необходимо задать имя хоста SNI, здесь ниже приведен рабочий тестовый код.
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <iostream>
namespace net = boost::asio;
namespace ssl = net::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
using tcp = net::ip::tcp;
using Request = http::request<http::string_body>;
using Stream = beast::ssl_stream<beast::tcp_stream>;
using Response = http::response<http::dynamic_body>;
class Exchange {
public:
Exchange(std::string name, const std::stringamp; http_host)
: m_name(std::move(name))
{
init_http(http_host);
}
void init_http(std::string constamp; host)
{
const auto results{resolver.resolve(host, "443")};
get_lowest_layer(stream).connect(results);
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
boost::system::error_code ec{
static_cast<int>(::ERR_get_error()),
boost::asio::error::get_ssl_category()};
throw boost::system::system_error{ec};
}
stream.handshake(ssl::stream_base::client);
}
void init_webSocket(std::string constamp; host, std::string constamp; port,
const char* p = "")
{
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(ws.next_layer().native_handle(),
host.c_str()))
throw beast::system_error(
beast::error_code(static_cast<int>(::ERR_get_error()),
net::error::get_ssl_category()),
"Failed to set SNI Hostname");
auto const results = resolver_webSocket.resolve(host, port);
net::connect(ws.next_layer().next_layer(), results.begin(),
results.end());
ws.next_layer().handshake(ssl::stream_base::client);
ws.handshake(host, p);
}
void read_Socket() { ws.read(buffer); }
bool is_socket_open()
{
if (ws.is_open())
return true;
return false;
}
void write_Socket(const std::stringamp; text) { ws.write(net::buffer(text)); }
std::string get_socket_data()
{
return beast::buffers_to_string(buffer.data());
}
void buffer_clear() { buffer.clear(); }
void webSocket_close() { ws.close(websocket::close_code::none); }
private:
// HTTP REQUEST SET //
std::string m_name;
net::io_context ioc;
ssl::context ctx{ssl::context::tlsv12_client};
tcp::resolver resolver{ioc};
Stream stream{ioc, ctx};
// WEB SOCKET SET //
std::string m_web_socket_host;
std::string m_web_socket_port;
beast::flat_buffer buffer;
net::io_context ioc_webSocket;
ssl::context ctx_webSocket{ssl::context::tlsv12_client};
tcp::resolver resolver_webSocket{ioc_webSocket};
websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc_webSocket,
ctx_webSocket};
};
int main()
{
Exchange kraken{"kraken", "ws.kraken.com"};
try {
kraken.init_webSocket("ws.kraken.com", "443", "/");
if (kraken.is_socket_open())
kraken.write_Socket(
R"({"event": "subscribe","pair": ["MINA/USD"],"subscription": {"name": "spread"}})");
while (true) {
kraken.read_Socket();
std::cout << kraken.get_socket_data();
kraken.buffer_clear();
}
kraken.webSocket_close();
} catch (std::exception constamp; e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}
Комментарии:
1. Я бы предложил упростить обработку ошибок ssl: pastebin.ubuntu.com/p/Rsf4yhRBh4 это основывается на
make_error_code
протоколе, чтобы получить правильную категорию. Это поможет предотвратить ошибки при переходе на используемую версию кодаstd::serror_code
(что может произойти без вашего ведома при обновлении библиотеки).2. Это гораздо лучше.