IXWebSocket/ws/ws_send.cpp

348 lines
9.9 KiB
C++
Raw Normal View History

/*
* ws_send.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include <chrono>
2019-09-23 19:25:23 +02:00
#include <condition_variable>
#include <fstream>
#include <ixcrypto/IXBase64.h>
#include <ixcrypto/IXHash.h>
2019-09-23 19:25:23 +02:00
#include <ixcrypto/IXUuid.h>
#include <ixwebsocket/IXSocket.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
2019-09-23 19:25:23 +02:00
#include <ixwebsocket/IXWebSocket.h>
#include <msgpack11/msgpack11.hpp>
2019-09-23 19:25:23 +02:00
#include <mutex>
2019-12-30 17:46:18 +01:00
#include <spdlog/spdlog.h>
2019-09-23 19:25:23 +02:00
#include <sstream>
#include <vector>
using msgpack11::MsgPack;
namespace ix
{
class WebSocketSender
{
2019-09-23 19:25:23 +02:00
public:
WebSocketSender(const std::string& _url,
bool enablePerMessageDeflate,
const ix::SocketTLSOptions& tlsOptions);
2019-09-23 19:25:23 +02:00
void subscribe(const std::string& channel);
void start();
void stop();
2019-09-23 19:25:23 +02:00
void waitForConnection();
void waitForAck();
bool sendMessage(const std::string& filename, bool throttle);
2019-09-23 19:25:23 +02:00
private:
std::string _url;
std::string _id;
ix::WebSocket _webSocket;
bool _enablePerMessageDeflate;
std::atomic<bool> _connected;
2019-09-23 19:25:23 +02:00
std::mutex _conditionVariableMutex;
std::condition_variable _condition;
2019-09-23 19:25:23 +02:00
void log(const std::string& msg);
};
WebSocketSender::WebSocketSender(const std::string& url,
bool enablePerMessageDeflate,
const ix::SocketTLSOptions& tlsOptions)
2019-09-23 19:25:23 +02:00
: _url(url)
, _enablePerMessageDeflate(enablePerMessageDeflate)
, _connected(false)
{
_webSocket.disableAutomaticReconnection();
_webSocket.setTLSOptions(tlsOptions);
}
void WebSocketSender::stop()
{
_webSocket.stop();
}
void WebSocketSender::log(const std::string& msg)
{
spdlog::info(msg);
}
void WebSocketSender::waitForConnection()
{
spdlog::info("{}: Connecting...", "ws_send");
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_condition.wait(lock);
}
void WebSocketSender::waitForAck()
{
spdlog::info("{}: Waiting for ack...", "ws_send");
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_condition.wait(lock);
}
std::vector<uint8_t> load(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
if (!file.is_open()) return memblock;
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.resize((size_t) size);
2019-09-23 19:25:23 +02:00
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
return memblock;
}
void WebSocketSender::start()
{
_webSocket.setUrl(_url);
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
_enablePerMessageDeflate, false, false, 15, 15);
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
std::stringstream ss;
log(std::string("ws_send: Connecting to url: ") + _url);
2019-09-23 19:25:23 +02:00
_webSocket.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
std::stringstream ss;
if (msg->type == ix::WebSocketMessageType::Open)
{
_connected = true;
2019-09-23 19:25:23 +02:00
_condition.notify_one();
log("ws_send: connected");
spdlog::info("Uri: {}", msg->openInfo.uri);
spdlog::info("Headers:");
2019-09-23 19:25:23 +02:00
for (auto it : msg->openInfo.headers)
{
spdlog::info("{}: {}", it.first, it.second);
}
2019-09-23 19:25:23 +02:00
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
_connected = false;
2019-09-23 19:25:23 +02:00
ss << "ws_send: connection closed:";
ss << " code " << msg->closeInfo.code;
ss << " reason " << msg->closeInfo.reason << std::endl;
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
_condition.notify_one();
ss << "ws_send: received message (" << msg->wireSize << " bytes)";
log(ss.str());
std::string errMsg;
MsgPack data = MsgPack::parse(msg->str, errMsg);
if (!errMsg.empty())
{
spdlog::info("Invalid MsgPack response");
2019-09-23 19:25:23 +02:00
return;
}
2019-09-23 19:25:23 +02:00
std::string id = data["id"].string_value();
if (_id != id)
{
spdlog::info("Invalid id");
}
2019-09-23 19:25:23 +02:00
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
ss << "ws_send ";
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Ping)
{
spdlog::info("ws_send: received ping");
}
else if (msg->type == ix::WebSocketMessageType::Pong)
{
spdlog::info("ws_send: received pong");
}
else if (msg->type == ix::WebSocketMessageType::Fragment)
{
spdlog::info("ws_send: received fragment");
}
2019-09-23 19:25:23 +02:00
else
{
ss << "ws_send: Invalid ix::WebSocketMessageType";
2019-09-23 19:25:23 +02:00
log(ss.str());
}
});
_webSocket.start();
}
class Bench
{
2019-09-23 19:25:23 +02:00
public:
Bench(const std::string& description)
: _description(description)
, _start(std::chrono::system_clock::now())
, _reported(false)
{
;
}
2019-09-23 19:25:23 +02:00
~Bench()
{
if (!_reported)
{
2019-09-23 19:25:23 +02:00
report();
}
2019-09-23 19:25:23 +02:00
}
2019-09-23 19:25:23 +02:00
void report()
{
auto now = std::chrono::system_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
2019-09-23 19:25:23 +02:00
_ms = milliseconds.count();
spdlog::info("{} completed in {} ms", _description, _ms);
2019-09-23 19:25:23 +02:00
_reported = true;
}
2019-09-23 19:25:23 +02:00
uint64_t getDuration() const
{
return _ms;
}
2019-09-23 19:25:23 +02:00
private:
std::string _description;
std::chrono::time_point<std::chrono::system_clock> _start;
uint64_t _ms;
bool _reported;
};
bool WebSocketSender::sendMessage(const std::string& filename, bool throttle)
{
std::vector<uint8_t> content;
{
Bench bench("load file from disk");
content = load(filename);
}
_id = uuid4();
std::map<MsgPack, MsgPack> pdu;
pdu["kind"] = "send";
pdu["id"] = _id;
pdu["content"] = content;
auto hash = djb2Hash(content);
pdu["djb2_hash"] = std::to_string(hash);
pdu["filename"] = filename;
MsgPack msg(pdu);
Bench bench("Sending file through websocket");
2020-01-09 22:45:31 +01:00
auto result =
_webSocket.sendBinary(msg.dump(), [this, throttle](int current, int total) -> bool {
spdlog::info("ws_send: Step {} out of {}", current + 1, total);
2020-01-09 22:45:31 +01:00
if (throttle)
{
std::chrono::duration<double, std::milli> duration(10);
std::this_thread::sleep_for(duration);
}
2020-01-09 22:45:31 +01:00
return _connected;
});
if (!result.success)
{
spdlog::error("ws_send: Error sending file.");
return false;
}
2020-01-09 22:30:08 +01:00
if (!_connected)
{
spdlog::error("ws_send: Got disconnected from the server");
return false;
}
do
{
size_t bufferedAmount = _webSocket.bufferedAmount();
spdlog::info("ws_send: {} bytes left to be sent", bufferedAmount);
2020-01-09 22:30:08 +01:00
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (_webSocket.bufferedAmount() != 0 && _connected);
if (_connected)
{
bench.report();
auto duration = bench.getDuration();
auto transferRate = 1000 * content.size() / duration;
transferRate /= (1024 * 1024);
spdlog::info("ws_send: Send transfer rate: {} MB/s", transferRate);
}
else
{
spdlog::error("ws_send: Got disconnected from the server");
}
return _connected;
}
void wsSend(const std::string& url,
const std::string& path,
bool enablePerMessageDeflate,
bool throttle,
const ix::SocketTLSOptions& tlsOptions)
{
WebSocketSender webSocketSender(url, enablePerMessageDeflate, tlsOptions);
webSocketSender.start();
webSocketSender.waitForConnection();
spdlog::info("ws_send: Sending...");
if (webSocketSender.sendMessage(path, throttle))
{
webSocketSender.waitForAck();
spdlog::info("ws_send: Done !");
}
else
{
spdlog::error("ws_send: Error sending file.");
}
webSocketSender.stop();
}
int ws_send_main(const std::string& url,
const std::string& path,
bool disablePerMessageDeflate,
const ix::SocketTLSOptions& tlsOptions)
{
bool throttle = false;
bool enablePerMessageDeflate = !disablePerMessageDeflate;
wsSend(url, path, enablePerMessageDeflate, throttle, tlsOptions);
return 0;
}
2019-09-23 19:25:23 +02:00
} // namespace ix