I am developing a simple test code using Websocket client using c++ boost. A server I get response from says I need to decompress messages using inflate algorithm. I found out there is deflate option in boost Websocket library but it did not work. Please let me know how to convert data to decompressed string.
#include <iostream>
#include <string>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/ssl.hpp>
#include <chrono>
using tcp = boost::asio::ip::tcp;
namespace websocket = boost::beast::websocket;
int main()
{
std::ostringstream stream;
std::string host = "real.okex.com";
auto const port = "8443";
auto const path = "/ws/v3";
boost::beast::multi_buffer buffer;
boost::asio::io_context ioc;
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
tcp::resolver resolver{ioc};
websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> wss{ioc, ctx};
ctx.set_verify_mode(boost::asio::ssl::verify_none);
tcp::resolver::results_type results = resolver.resolve(host, port);
boost::asio::connect(wss.next_layer().next_layer(), results.begin(), results.end());
// SSL handshake
wss.next_layer().handshake(boost::asio::ssl::stream_base::client);
// websocket handshake
wss.handshake(host, path);
std::cout << "connected" << std::endl;
// send request to the websocket
wss.write(boost::asio::buffer("{'op':'subscribe', 'args':['spot/ticker:ETH-USDT']}"));
// read message
wss.read(buffer);
std::cout << buffer.size() << std::endl;
buffer.consume(buffer.size());
/*
stream << boost::beast::buffers(buffer.data());
buffer.consume(buffer.size());
std::string incoming = stream.str();
std::cout << incoming << std::endl;
*/
}
Thanks !
I struggled for a long time, then I figured, what if I try with a different server?
That helped. I took echo_compressed/server.py from Autobahn:
wget 'https://github.com/crossbario/autobahn-python/raw/master/examples/twisted/websocket/echo_compressed/server.py'
virtualenv venv && . venv/bin/activate && pip install autobahn twisted
python server.py
That starts a WS server on port 9000. It's not using SSL though, so I disabled that in the code (see #ifdef SSL below).
Now the key is to set the permessage_deflate extension option before WS handshake:
websocket::permessage_deflate opt;
opt.client_enable = true; // for clients
opt.server_enable = true; // for servers
s.set_option(opt);
Also noted that some servers require the port name be present in the Host header when not running on standard ports:
s.handshake(host + ":" + port, path);
Now reading works just fine and deflates as you'd expect, e.g. write it to response.txt:
beast::multi_buffer buffer;
s.read(buffer);
{
std::ofstream ofs("response.txt", std::ios::binary);
std::copy(
net::buffers_begin(buffer.data()),
net::buffers_end(buffer.data()),
std::ostreambuf_iterator<char>(ofs));
}
Or, when replacing the multi_buffer with an Asio streambuf, it's easy to just stream it:
net::streambuf buffer;
s.read(buffer);
std::cout << &buffer;
Proof That It Was Deflating
Inspecting the traffic with tcpdump/Wireshark shows this. Also, the Autobahn logging confirms it:
2020-06-22 02:12:05+0200 [-] Log opened.
2020-06-22 02:12:05+0200 [-] WebSocketServerFactory starting on 9000
2020-06-22 02:12:05+0200 [-] Starting factory <autobahn.twisted.websocket.WebSocketServerFactory object at 0x7f7af3fa5710>
2020-06-22 02:12:05+0200 [-] Site starting on 8080
2020-06-22 02:12:05+0200 [-] Starting factory <twisted.web.server.Site instance at 0x7f7af3850910>
2020-06-22 02:12:11+0200 [-] WebSocket connection request by tcp4:127.0.0.1:48658
2020-06-22 02:12:11+0200 [-] WebSocket extensions in use: [PerMessageDeflate(is_server = True, server_no_context_takeover = False, client_no_context_takeover = False, server_max_window_bits = 15, client_max_window_bits = 15, mem_level = 8)]
The Problem With That Server (real.okex.com)
I don't know what about it, really, but it seems that server is not sending standard responses. Perhaps someone else can tell. Writing the responses to a file did NOT result in a file that looks like it is zlib compressed.
Other tools tried ALSO fail to decode the data:
zlib-flate -uncompress < response.txt
Same with a python oneliner:
python -c 'import zlib; import sys; sys.stdout.write(zlib.decompress(sys.stdin.read()))' < response.txt
Full Listing
As I tested it with:
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <iostream>
#include <string>
#include <fstream>
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;
//#define SSL
#ifdef SSL
using stream_t = websocket::stream<ssl::stream<tcp::socket>>;
#else
using stream_t = websocket::stream<tcp::socket/*, true*/>;
#endif
int main(int argc, char** argv) {
if (argc<4) {
std::cerr << "Usage: " << argv[0] << " host port path\n";
return 1;
}
std::string host = argc>=2? argv[1] : "real.okex.com";
auto const port = argc>=3? argv[2] : "8443";
auto const path = argc>=3? argv[3] : "/ws/v3";
net::io_context ioc;
ssl::context ctx{ ssl::context::sslv23 };
tcp::resolver resolver{ ioc };
#ifdef SSL
stream_t s{ ioc, ctx };
#else
stream_t s{ ioc };
#endif
ctx.set_verify_mode(ssl::verify_none);
tcp::resolver::results_type results = resolver.resolve(host, port);
net::connect(
beast::get_lowest_layer(s),
//s.next_layer().next_layer(),
results.begin());
#ifdef SSL
// SSL handshake
s.next_layer().handshake(ssl::stream_base::client);
#endif
// websocket handshake
websocket::permessage_deflate opt;
opt.client_enable = true; // for clients
opt.server_enable = true; // for servers
s.set_option(opt);
s.handshake(host + ":" + port, path);
std::cout << "connected" << std::endl;
// send request to the websocket
s.write(net::buffer("{'op':'subscribe', 'args':['spot/ticker:ETH-USDT']}"));
{
net::streambuf buffer;
s.read(buffer);
std::cout << &buffer << std::endl;
}
}
Then I ran with
In the protocol upgrade response, The websocket server should have included a field "Sec-WebSocket-Extensions" which tell the client to use Compression Extensions for WebSocket.
But lots of websocket servers of the crypto exchanges like okex/huobi don't do this. You have to deflate the message in your application code.
You can think of this as moving the deflate/inflate from the protocol layer up to the application layer.
Related
Imagine that you have some websocket client, that downloading some data in loop like this:
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include "nlohmann/json.hpp"
namespace beast = boost::beast;
namespace websocket = beast::websocket;
using tcp = boost::asio::ip::tcp;
class Client {
public:
Client(boost::asio::io_context &ctx) : ws_{ctx}, ctx_{ctx} {
ws_.set_option(websocket::stream_base::timeout::suggested(boost::beast::role_type::client));
#define HOST "127.0.0.1"
#define PORT "8000"
boost::asio::connect(ws_.next_layer(), tcp::resolver{ctx_}.resolve(HOST, PORT));
ws_.handshake(HOST ":" PORT, "/api/v1/music");
#undef HOST
#undef PORT
}
~Client() {
if (ws_.is_open()) {
ws_.close(websocket::normal);
}
}
nlohmann::json NextPacket(std::size_t offset) {
nlohmann::json request;
request["offset"] = offset;
ws_.write(boost::asio::buffer(request.dump()));
beast::flat_buffer buffer;
ws_.read(buffer);
return nlohmann::json::parse(std::string_view{reinterpret_cast<const char *>(buffer.data().data()), buffer.size()});
}
private:
boost::beast::websocket::stream<boost::asio::ip::tcp::socket> ws_;
boost::asio::io_context &ctx_;
};
// ... some function
int main() {
boost::asio::io_context context;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard{context.get_executor()};
std::thread{[&context]() { context.run(); }}.detach();
static constexpr std::size_t kSomeVeryBigConstant{1'000'000'000};
Client client{context};
std::size_t offset{};
while (offset < kSomeVeryBigConstant) {
offset += client.NextPacket(offset)["offset"].get<std::size_t>();
// UPDATE:
userDefinedLongPauseHere();
}
}
On the server side we have ping requests with some frequency. Were should I handle ping requests? As I understand it, control_callback controls calls to ping, pong and close functions, not requests. With the read or read_async functions, I also cannot catch the ping request.
Beast responds to pings with pongs automatically, as described here: https://github.com/boostorg/beast/issues/899#issuecomment-346333014
Whenever you call read(), it can process a ping and send a pong without you knowing about that.
I want to send 3-4 headers to the WebSocket server that I have and the headers are action = subscribe,userID = <some email address>,agentID =831C5DFC-1643-40C4-A5A3-9C918556D3A1 , I am unable to understand how to send these headers to the server, like what is the typical method? this is my client code👇🏼
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
namespace http = boost::beast::http;
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
// Sends a WebSocket message and prints the response
#define SERVER_URL "127.0.0.1"
#define SERVER_PORT "80"
class set_subprotocols
{
std::string s_;
public:
explicit
set_subprotocols(std::string s)
: s_(s) {}
template<bool isRequest, class Body, class Headers>
void
operator()(boost::beast::http::message<isRequest, Body, Headers>& m) const
{
m.set("X-Custome-Id", s_);
}
};
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
auto const host = SERVER_URL;
auto const port = SERVER_PORT;
auto const text = "hello world";
// The io_context is required for all I/O
net::io_context ioc;
// These objects perform our I/O
tcp::resolver resolver{ioc};
websocket::stream<tcp::socket> ws{ioc};
// Look up the domain name
auto const results = resolver.resolve(host, port,boost::asio::ip::resolver_query_base::numeric_service);
// Make the connection on the IP address we get from a lookup
net::connect(ws.next_layer(), results.begin(), results.end());
// Set a decorator to change the User-Agent of the handshake
ws.set_option(websocket::stream_base::decorator(
[](websocket::request_type& req)
{
req.set(http::field::user_agent,
std::string("agent"));
}));
ws.set_option(websocket::stream_base::decorator(set_subprotocols{"action = subscribe"}));
ws.set_option(websocket::stream_base::decorator(set_subprotocols{"userID = madhur#ayraa.io"}));
ws.set_option(websocket::stream_base::decorator(set_subprotocols{"agentID = 831C5DFC-1643-40C4-A5A3-9C918556D3A1"}));
// Perform the websocket handshake
ws.handshake(host, "/");
// Send the message
ws.write(net::buffer(std::string(text)));
// This buffer will hold the incoming message
beast::multi_buffer buffer;
// Read a message into our buffer
ws.read(buffer);
// The make_printable() function helps print a ConstBufferSequence
std::cout << beast::make_printable(buffer.data()) << std::endl;
// If we get here then the connection is closed gracefully
// Close the WebSocket connection
ws.close(websocket::close_code::normal);
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return EXIT_SUCCESS;
}
I currently don't have the server code but will provide it If I get it in the future.
You should do all that in a single decorator. In fact, you can use lambdas:
ws.set_option(websocket::stream_base::decorator([](auto& m) {
m.set("action", "subscribe");
m.set("userID", "madhur#ayraa.io");
m.set("agentID", "831C5DFC-1643-40C4-A5A3-9C918556D3A1");
}));
Now your request is:
GET / HTTP/1.1
Host: 127.0.0.1
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Key: xCj08Lz6IzEzH4s422aT5w==
Sec-WebSocket-Version: 13
action: subscribe
userID: madhur#ayraa.io
agentID: 831C5DFC-1643-40C4-A5A3-9C918556D3A1
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: pbg8RLbrPirBbYgmG0EptdCm2tQ=
...8.x.].......T..r{
"timestamp": "Wed Dec 29 2021 20:20:27 GMT+0000 (UTC)",
"url": "http://sockb.in/",
"reqData": "hello world"
}.. .H.
B....
I am trying to use beast::websocket for my Raspberry pi project where Rpi is an IoT websocket client and Microsoft Azure WebPubSub is server-side.
I am new to C++ and Unix system. Sadly, I was unable to find that many useful reference projects using the C++ Websocket.
The first thing I wanted to do was to use the beast::websocket client example to successfully connect to a test websocket server.
Below is the example I used. beast::websocket example 1.67
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp>
// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: websocket-client-sync <host> <port> <text>\n" <<
"Example:\n" <<
" websocket-client-sync echo.websocket.org 80 \"Hello, world!\"\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const text = argv[3];
// The io_context is required for all I/O
boost::asio::io_context ioc;
// These objects perform our I/O
tcp::resolver resolver{ioc};
websocket::stream<tcp::socket> ws{ioc};
// Look up the domain name
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(ws.next_layer(), results.begin(), results.end());
// Perform the websocket handshake
ws.handshake(host, "/");
// Send the message
ws.write(boost::asio::buffer(std::string(text)));
// This buffer will hold the incoming message
boost::beast::multi_buffer buffer;
// Read a message into our buffer
ws.read(buffer);
// Close the WebSocket connection
ws.close(websocket::close_code::normal);
// If we get here then the connection is closed gracefully
// The buffers() function helps print a ConstBufferSequence
std::cout << boost::beast::buffers(buffer.data()) << std::endl;
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Because echo.websocket.org is closed now. I used different test servers.
These are the command line for two different websocket test servers.
sudo websocket-client-sync streamer.finance.yahoo.com 80 "Hello, world!"
Returned with Error: The WebSocket stream was gracefully closed at both endpoints
sudo websocket-client-sync demo.piesocket.com/v3/channel_1?api_key=oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm¬ify_self 80 "Hello, world!"
Returned with Error: resolve: Host not found (authoritative)
I was able to connect both URLs with a websocket test client.
But unable to connect or keep connected through this beast example.
Especially, for the second server with an API key and forward slashes, I wasn't even able to find a host.
I thought this could be something to do with those special characters not recognized as literal?
So so far these are what I have tried.
I tried replacing / with /.
I specified the host in the cpp file
(ex. auto const host = "demo.piesocket.com/v3/channel_1?api_key=oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm¬ify_self";)
Used encoded URL
(ex. demo.piesocket.com%2Fv3%2Fchannel_1%3Fapi_key%3DoCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm%26notify_self)
used port 443 instead of 80
tried secure beast::websocket example over SSL for wss servers.
Nothing worked...
In the end, I need to use an Azure websocket URL with an access token which looks like this
wss://lupo.webpubsub.azure.com/client/hubs/Hub?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ3c3M6Ly9sdXBvLndlYnB1YnN1Yi5henVyZS5jb20vY2xpZW50L2h1YnMvSHViIiwiaWF0IjoxNjMzNTc3ODA1LCJleHAiOjE2MzM1ODE0MDV9.1xGRvCsyc1QDTBWJ01PcTarx0judpa6ZuQ8
Because this is the secure websocket, I used this example
Successfully compiled with sudo g++ -v websocket_client_sync_ssl.o -o wsstest -lpthread -lboost_system -lcrypto -lssl
But no hope. Same result.. Error: resolve: Host not found (authoritative)
I feel like I am missing something pretty simple here. Could you help me out?!
Thanks!
I use the excellent websocketpp library to provide a Websockets (and HTTP) server in a C++ application. I also need a HTTP client in the same app to connect to REST APIs. I have been attempting this in websocketpp also, but so far I have had little success. The following preliminary attempt gives me this log output:
[2015-03-06 18:01:18] [connect] Successful connection
[2015-03-06 18:01:18] [error] Server handshake response error: websocketpp.processor:20 (Invalid HTTP status.)
[2015-03-06 18:01:18] [disconnect] Failed: Invalid HTTP status.
This suggests my http_ handler method may need something more. Any advice would be appreciated. The websocketpp docs and examples don't seem to include a simple HTTP client.
#define _WEBSOCKETPP_CPP11_STL_
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/client.hpp>
#include <websocketpp/common/thread.hpp>
namespace {
using namespace websocketpp;
typedef client<websocketpp::config::asio_client> client;
class Client {
public:
Client(void){
client_.init_asio();
client_.set_http_handler(bind(&Client::http_,this,_1));
}
std::string get(const std::string& url) {
websocketpp::lib::error_code error;
client::connection_ptr con = client_.get_connection(url,error);
if(error) std::runtime_error("Unable to connnect.\n url: "+url+"\n message: "+error.message());
client_.connect(con);
websocketpp::lib::thread asio_thread(&client::run, &client_);
asio_thread.join();
return data_;
}
private:
void http_(connection_hdl hdl){
std::cout<<"Connected\n";
data_ = "http payload";
}
client client_;
std::string data_;
};
}
int main(void){
Client client;
client.get("http://google.com/");
}
WebSocket++'s HTTP handling features are a convenience feature designed to allow WebSocket servers to serve HTTP responses in a limited capacity. WebSocket++ is not intended for use as a generic HTTP library and does not contain the ability to play the role of a (non-WebSocket) HTTP client.
Using a separate library (such as cpp-netlib) for HTTP client functionality is a good solution.
If you're trying to do both WebSocket and HTTP in C++ there's a great library called Beast that has BOTH of these things! Its open source and builds on Boost.Asio:
https://github.com/vinniefalco/Beast/
Here's some example code:
Use HTTP to request the root page from a website and print the response:
#include <beast/http.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "boost.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r(ios);
boost::asio::ip::tcp::socket sock(ios);
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
// Send HTTP request using beast
beast::http::request_v1<beast::http::empty_body> req;
req.method = "GET";
req.url = "/";
req.version = 11;
req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port()));
req.headers.replace("User-Agent", "Beast");
beast::http::prepare(req);
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::streambuf sb;
beast::http::response_v1<beast::http::streambuf_body> resp;
beast::http::read(sock, sb, resp);
std::cout << resp;
}
Establish a WebSocket connection, send a message and receive the reply:
#include <beast/to_string.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "echo.websocket.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r(ios);
boost::asio::ip::tcp::socket sock(ios);
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
// WebSocket connect and send message using beast
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
ws.handshake(host, "/");
ws.write(boost::asio::buffer("Hello, world!"));
// Receive WebSocket message, print and close using beast
beast::streambuf sb;
beast::websocket::opcode op;
ws.read(op, sb);
ws.close(beast::websocket::close_code::normal);
std::cout << to_string(sb.data()) << "\n";
}
I did not know how to prevent the websocketpp client from asking for a Upgrade: connection so I ended up using cpp-netlib for a HTTP client instead.
I'm working on an application that needs to perform network communication and decided to use the poco c++ libraries. After going through the network tutorial I can't seem to find any forms of validation on establishing a network connection.
In the following example a client tries to connect to a server using a tcp socket stream:
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/StreamCopier.h"
#include <iostream>
int main(int argc, char** argv)
{
Poco::Net::SocketAddress sa("www.appinf.com", 80);
Poco::Net::StreamSocket socket(sa);
Poco::Net::SocketStream str(socket);
str << "GET / HTTP/1.1\r\n"
"Host: www.appinf.com\r\n"
"\r\n";
str.flush();
Poco::StreamCopier::copyStream(str, std::cout);
return 0;
}
However, I couldn't find any information related to:
Error checking(what if www.appinf.com is unavailable or doesn't exist for that matter)
The type of exception these calls may raise
The only mention is that a SocketStream may hang if the receive timeout is not set for the socket when using formated inputs.
How can I check if a host is alive and may set up a tcp connection, implement a method such as:
void TCPClient::connectTo(std::string host, bool& connected, unsigned int port) {
std::string hi = "hi";
Poco::Net::SocketAddress clientSocketAddress(host, port);
Poco::Net::StreamSocket clientStreamSocket;
// try to connect and avoid hang by setting a timeout
clientStreamSocket.connect(clientSocketAddress, timeout);
// check if the connection has failed or not,
// set the connected parameter accordingly
// additionally try to send bytes over this connection
Poco::Net::SocketStream clientSocketStream(clientStreamSocket);
clientSocketStream << hi << std::endl;
clientSocketStream.flush();
// close the socket stream
clientSocketStream.close();
// close stream
clientStreamSocket.shutdown();
}