2019-02-21 03:59:07 +01:00
|
|
|
/*
|
|
|
|
* 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>
|
2019-02-21 03:59:07 +01:00
|
|
|
#include <ixcrypto/IXBase64.h>
|
|
|
|
#include <ixcrypto/IXHash.h>
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <ixcrypto/IXUuid.h>
|
|
|
|
#include <ixwebsocket/IXSocket.h>
|
2019-09-30 03:29:51 +02:00
|
|
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <ixwebsocket/IXWebSocket.h>
|
2019-02-22 06:24:53 +01:00
|
|
|
#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>
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
using msgpack11::MsgPack;
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
namespace ix
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
|
|
|
class WebSocketSender
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
public:
|
2019-10-01 03:21:20 +02:00
|
|
|
WebSocketSender(const std::string& _url,
|
|
|
|
bool enablePerMessageDeflate,
|
|
|
|
const ix::SocketTLSOptions& tlsOptions);
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void subscribe(const std::string& channel);
|
|
|
|
void start();
|
|
|
|
void stop();
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void waitForConnection();
|
|
|
|
void waitForAck();
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void sendMessage(const std::string& filename, bool throttle);
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
private:
|
|
|
|
std::string _url;
|
|
|
|
std::string _id;
|
|
|
|
ix::WebSocket _webSocket;
|
|
|
|
bool _enablePerMessageDeflate;
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
std::mutex _conditionVariableMutex;
|
|
|
|
std::condition_variable _condition;
|
2019-02-22 06:24:53 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void log(const std::string& msg);
|
2019-02-21 03:59:07 +01:00
|
|
|
};
|
|
|
|
|
2019-10-01 03:21:20 +02:00
|
|
|
WebSocketSender::WebSocketSender(const std::string& url,
|
|
|
|
bool enablePerMessageDeflate,
|
|
|
|
const ix::SocketTLSOptions& tlsOptions)
|
2019-09-23 19:25:23 +02:00
|
|
|
: _url(url)
|
|
|
|
, _enablePerMessageDeflate(enablePerMessageDeflate)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-09-26 00:39:43 +02:00
|
|
|
_webSocket.disableAutomaticReconnection();
|
2019-10-01 03:21:20 +02:00
|
|
|
_webSocket.setTLSOptions(tlsOptions);
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketSender::stop()
|
|
|
|
{
|
|
|
|
_webSocket.stop();
|
|
|
|
}
|
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
void WebSocketSender::log(const std::string& msg)
|
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info(msg);
|
2019-02-22 06:24:53 +01:00
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
void WebSocketSender::waitForConnection()
|
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("{}: Connecting...", "ws_send");
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
|
|
_condition.wait(lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketSender::waitForAck()
|
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("{}: Waiting for ack...", "ws_send");
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
|
|
_condition.wait(lock);
|
|
|
|
}
|
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
std::vector<uint8_t> load(const std::string& path)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-02-22 06:24:53 +01:00
|
|
|
std::vector<uint8_t> memblock;
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
std::ifstream file(path);
|
2019-02-22 06:24:53 +01:00
|
|
|
if (!file.is_open()) return memblock;
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
file.seekg(0, file.end);
|
|
|
|
std::streamoff size = file.tellg();
|
|
|
|
file.seekg(0, file.beg);
|
|
|
|
|
2019-06-03 05:46:20 +02:00
|
|
|
memblock.resize((size_t) size);
|
2019-09-23 19:25:23 +02:00
|
|
|
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
return memblock;
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketSender::start()
|
|
|
|
{
|
|
|
|
_webSocket.setUrl(_url);
|
|
|
|
|
|
|
|
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
|
|
|
|
_enablePerMessageDeflate, false, false, 15, 15);
|
|
|
|
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
|
|
|
|
|
|
|
std::stringstream ss;
|
2019-09-30 02:21:52 +02:00
|
|
|
log(std::string("ws_send: Connecting to url: ") + _url);
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
_webSocket.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
|
|
|
std::stringstream ss;
|
|
|
|
if (msg->type == ix::WebSocketMessageType::Open)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
_condition.notify_one();
|
|
|
|
|
|
|
|
log("ws_send: connected");
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("Uri: {}", msg->openInfo.uri);
|
|
|
|
spdlog::info("Headers:");
|
2019-09-23 19:25:23 +02:00
|
|
|
for (auto it : msg->openInfo.headers)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("{}: {}", it.first, it.second);
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
}
|
|
|
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
|
|
{
|
|
|
|
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())
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("Invalid MsgPack response");
|
2019-09-23 19:25:23 +02:00
|
|
|
return;
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
|
|
|
|
std::string id = data["id"].string_value();
|
|
|
|
if (_id != id)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("Invalid id");
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
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());
|
|
|
|
}
|
2020-01-04 22:45:07 +01:00
|
|
|
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
|
|
|
|
{
|
2019-09-30 02:21:52 +02:00
|
|
|
ss << "ws_send: Invalid ix::WebSocketMessageType";
|
2019-09-23 19:25:23 +02:00
|
|
|
log(ss.str());
|
|
|
|
}
|
|
|
|
});
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
_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-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
~Bench()
|
|
|
|
{
|
|
|
|
if (!_reported)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
report();
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
}
|
2019-02-21 03:59:07 +01: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-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
_ms = milliseconds.count();
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("{} completed in {}", _description, _ms);
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
_reported = true;
|
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
uint64_t getDuration() const
|
|
|
|
{
|
|
|
|
return _ms;
|
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
|
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;
|
2019-02-21 03:59:07 +01:00
|
|
|
};
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void WebSocketSender::sendMessage(const std::string& filename, bool throttle)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-02-22 06:24:53 +01:00
|
|
|
std::vector<uint8_t> content;
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
|
|
|
Bench bench("load file from disk");
|
|
|
|
content = load(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
_id = uuid4();
|
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
std::map<MsgPack, MsgPack> pdu;
|
2019-02-21 03:59:07 +01:00
|
|
|
pdu["kind"] = "send";
|
|
|
|
pdu["id"] = _id;
|
2019-02-22 06:24:53 +01:00
|
|
|
pdu["content"] = content;
|
|
|
|
auto hash = djb2Hash(content);
|
|
|
|
pdu["djb2_hash"] = std::to_string(hash);
|
2019-02-21 03:59:07 +01:00
|
|
|
pdu["filename"] = filename;
|
|
|
|
|
2019-02-22 06:24:53 +01:00
|
|
|
MsgPack msg(pdu);
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
Bench bench("Sending file through websocket");
|
2019-09-23 19:25:23 +02:00
|
|
|
_webSocket.sendBinary(msg.dump(), [throttle](int current, int total) -> bool {
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("ws_send: Step {} out of {}", current, total);
|
2019-02-21 03:59:07 +01:00
|
|
|
|
|
|
|
if (throttle)
|
|
|
|
{
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
|
|
std::this_thread::sleep_for(duration);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2019-03-14 07:09:45 +01:00
|
|
|
do
|
|
|
|
{
|
|
|
|
size_t bufferedAmount = _webSocket.bufferedAmount();
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("ws_send: {} bytes left to be sent", bufferedAmount);
|
2019-03-14 07:09:45 +01:00
|
|
|
|
|
|
|
std::chrono::duration<double, std::milli> duration(10);
|
|
|
|
std::this_thread::sleep_for(duration);
|
|
|
|
} while (_webSocket.bufferedAmount() != 0);
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
bench.report();
|
|
|
|
auto duration = bench.getDuration();
|
2019-02-22 06:24:53 +01:00
|
|
|
auto transferRate = 1000 * content.size() / duration;
|
2019-02-21 03:59:07 +01:00
|
|
|
transferRate /= (1024 * 1024);
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("ws_send: Send transfer rate: {} MB/s", transferRate);
|
2019-02-21 03:59:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void wsSend(const std::string& url,
|
|
|
|
const std::string& path,
|
|
|
|
bool enablePerMessageDeflate,
|
2019-09-30 03:29:51 +02:00
|
|
|
bool throttle,
|
|
|
|
const ix::SocketTLSOptions& tlsOptions)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-10-01 03:21:20 +02:00
|
|
|
WebSocketSender webSocketSender(url, enablePerMessageDeflate, tlsOptions);
|
2019-02-21 03:59:07 +01:00
|
|
|
webSocketSender.start();
|
|
|
|
|
|
|
|
webSocketSender.waitForConnection();
|
|
|
|
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("ws_send: Sending...");
|
2019-02-21 03:59:07 +01:00
|
|
|
webSocketSender.sendMessage(path, throttle);
|
|
|
|
|
|
|
|
webSocketSender.waitForAck();
|
|
|
|
|
2019-12-25 06:55:34 +01:00
|
|
|
spdlog::info("ws_send: Done !");
|
2019-02-21 03:59:07 +01:00
|
|
|
webSocketSender.stop();
|
|
|
|
}
|
|
|
|
|
2019-09-30 03:29:51 +02:00
|
|
|
int ws_send_main(const std::string& url,
|
|
|
|
const std::string& path,
|
2020-01-05 00:08:36 +01:00
|
|
|
bool disablePerMessageDeflate,
|
2019-09-30 03:29:51 +02:00
|
|
|
const ix::SocketTLSOptions& tlsOptions)
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
2019-02-22 06:24:53 +01:00
|
|
|
bool throttle = false;
|
2020-01-05 00:08:36 +01:00
|
|
|
bool enablePerMessageDeflate = !disablePerMessageDeflate;
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-09-30 03:29:51 +02:00
|
|
|
wsSend(url, path, enablePerMessageDeflate, throttle, tlsOptions);
|
2019-02-22 06:24:53 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
} // namespace ix
|