Compare commits
19 Commits
user/bserg
...
v1.0.2
Author | SHA1 | Date | |
---|---|---|---|
24859fef8a | |||
73d7280723 | |||
262de49c3c | |||
3a77e96a05 | |||
505dd6d50f | |||
3f8027b65c | |||
0f2c765f45 | |||
49077f8f44 | |||
6a23b8530f | |||
ae841af91a | |||
44f38849b2 | |||
ee12fbdb5f | |||
316c630830 | |||
1ea5db6110 | |||
986d9a00c0 | |||
7a05a11014 | |||
f09434263c | |||
335f594165 | |||
a02bd3f25c |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
14
.travis.yml
14
.travis.yml
@ -2,8 +2,16 @@ language: cpp
|
|||||||
dist: xenial
|
dist: xenial
|
||||||
|
|
||||||
compiler:
|
compiler:
|
||||||
|
- gcc
|
||||||
- clang
|
- clang
|
||||||
# - gcc
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
os: osx
|
matrix:
|
||||||
script: make test
|
exclude:
|
||||||
|
# GCC fails on recent Travis OSX images.
|
||||||
|
- compiler: gcc
|
||||||
|
os: osx
|
||||||
|
|
||||||
|
script: python test/run.py
|
||||||
|
@ -38,6 +38,7 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXSetThreadName.h
|
ixwebsocket/IXSetThreadName.h
|
||||||
ixwebsocket/IXDNSLookup.h
|
ixwebsocket/IXDNSLookup.h
|
||||||
ixwebsocket/IXCancellationRequest.h
|
ixwebsocket/IXCancellationRequest.h
|
||||||
|
ixwebsocket/IXProgressCallback.h
|
||||||
ixwebsocket/IXWebSocket.h
|
ixwebsocket/IXWebSocket.h
|
||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketServer.h
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
ixwebsocket/IXWebSocketTransport.h
|
||||||
|
@ -86,7 +86,7 @@ server.setOnConnectionCallback(
|
|||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||||
{
|
{
|
||||||
// For an echo server, we just send back to the client whatever was received by the client
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
webSocket->send(str);
|
webSocket->send(str);
|
||||||
}
|
}
|
||||||
@ -134,6 +134,10 @@ No manual polling to fetch data is required. Data is sent and received instantly
|
|||||||
|
|
||||||
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
|
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
|
||||||
|
|
||||||
|
### Large messages
|
||||||
|
|
||||||
|
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 500M were sent and received succesfully.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
|
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
|
||||||
@ -309,6 +313,7 @@ A ping message can be sent to the server, with an optional data string.
|
|||||||
|
|
||||||
```
|
```
|
||||||
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
|
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
|
||||||
|
```
|
||||||
|
|
||||||
### Heartbeat.
|
### Heartbeat.
|
||||||
|
|
||||||
|
@ -15,5 +15,8 @@ RUN apt-get -y install cmake
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
WORKDIR test
|
WORKDIR ws
|
||||||
RUN ["sh", "build_linux.sh"]
|
RUN ["sh", "docker_build.sh"]
|
||||||
|
|
||||||
|
EXPOSE 8765
|
||||||
|
CMD ["/ws/ws", "transfer", "8765"]
|
||||||
|
@ -47,6 +47,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||||
{
|
{
|
||||||
|
std::cerr << "Received " << wireSize << " bytes" << std::endl;
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client != webSocket)
|
||||||
|
@ -59,8 +59,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
||||||
const std::string& errorMsg,
|
const std::string& errorMsg,
|
||||||
const WebSocketHttpHeaders& headers)
|
const WebSocketHttpHeaders& headers)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||||
if (_eventCallback)
|
if (_eventCallback)
|
||||||
@ -176,10 +176,10 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::configure(const std::string& appkey,
|
void CobraConnection::configure(const std::string& appkey,
|
||||||
const std::string& endpoint,
|
const std::string& endpoint,
|
||||||
const std::string& rolename,
|
const std::string& rolename,
|
||||||
const std::string& rolesecret,
|
const std::string& rolesecret,
|
||||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions)
|
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions)
|
||||||
{
|
{
|
||||||
_appkey = appkey;
|
_appkey = appkey;
|
||||||
_endpoint = endpoint;
|
_endpoint = endpoint;
|
||||||
@ -339,7 +339,7 @@ namespace ix
|
|||||||
// publish is not thread safe as we are trying to reuse some Json objects.
|
// publish is not thread safe as we are trying to reuse some Json objects.
|
||||||
//
|
//
|
||||||
bool CobraConnection::publish(const Json::Value& channels,
|
bool CobraConnection::publish(const Json::Value& channels,
|
||||||
const Json::Value& msg)
|
const Json::Value& msg)
|
||||||
{
|
{
|
||||||
_body["channels"] = channels;
|
_body["channels"] = channels;
|
||||||
_body["message"] = msg;
|
_body["message"] = msg;
|
||||||
@ -371,7 +371,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::subscribe(const std::string& channel,
|
void CobraConnection::subscribe(const std::string& channel,
|
||||||
SubscriptionCallback cb)
|
SubscriptionCallback cb)
|
||||||
{
|
{
|
||||||
// Create and send a subscribe pdu
|
// Create and send a subscribe pdu
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
|
14
ixwebsocket/IXProgressCallback.h
Normal file
14
ixwebsocket/IXProgressCallback.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* IXProgressCallback.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
using OnProgressCallback = std::function<bool(int current, int total)>;
|
||||||
|
}
|
@ -37,6 +37,12 @@ namespace ix
|
|||||||
|
|
||||||
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
|
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
|
||||||
{
|
{
|
||||||
|
if (_sockfd == -1)
|
||||||
|
{
|
||||||
|
onPollCallback(PollResultType_Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
FD_SET(_sockfd, &rfds);
|
FD_SET(_sockfd, &rfds);
|
||||||
@ -52,7 +58,7 @@ namespace ix
|
|||||||
int sockfd = _sockfd;
|
int sockfd = _sockfd;
|
||||||
int nfds = (std::max)(sockfd, _eventfd.getFd());
|
int nfds = (std::max)(sockfd, _eventfd.getFd());
|
||||||
int ret = select(nfds + 1, &rfds, nullptr, nullptr,
|
int ret = select(nfds + 1, &rfds, nullptr, nullptr,
|
||||||
(timeoutSecs == kDefaultPollNoTimeout) ? nullptr : &timeout);
|
(timeoutSecs < 0) ? nullptr : &timeout);
|
||||||
|
|
||||||
PollResultType pollResult = PollResultType_ReadyForRead;
|
PollResultType pollResult = PollResultType_ReadyForRead;
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -294,9 +294,10 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::send(const std::string& text)
|
WebSocketSendInfo WebSocket::send(const std::string& text,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
return sendMessage(text, false);
|
return sendMessage(text, false, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
||||||
@ -308,7 +309,9 @@ namespace ix
|
|||||||
return sendMessage(text, true);
|
return sendMessage(text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendMessage(const std::string& text, bool ping)
|
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
||||||
|
bool ping,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
if (!isConnected()) return WebSocketSendInfo(false);
|
if (!isConnected()) return WebSocketSendInfo(false);
|
||||||
|
|
||||||
@ -330,7 +333,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
webSocketSendInfo = _ws.sendBinary(text);
|
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "IXWebSocketSendInfo.h"
|
#include "IXWebSocketSendInfo.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include "IXProgressCallback.h"
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -97,7 +98,8 @@ namespace ix
|
|||||||
WebSocketInitResult connect(int timeoutSecs);
|
WebSocketInitResult connect(int timeoutSecs);
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
WebSocketSendInfo send(const std::string& text);
|
WebSocketSendInfo send(const std::string& text,
|
||||||
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
WebSocketSendInfo ping(const std::string& text);
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@ -115,7 +117,9 @@ namespace ix
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
WebSocketSendInfo sendMessage(const std::string& text, bool ping);
|
WebSocketSendInfo sendMessage(const std::string& text,
|
||||||
|
bool ping,
|
||||||
|
const OnProgressCallback& callback = nullptr);
|
||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
bool isClosing() const;
|
bool isClosing() const;
|
||||||
|
@ -125,6 +125,16 @@ namespace ix
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||||
|
{
|
||||||
|
return std::equal(a.begin(), a.end(),
|
||||||
|
b.begin(), b.end(),
|
||||||
|
[](char a, char b)
|
||||||
|
{
|
||||||
|
return tolower(a) == tolower(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
|
std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
|
||||||
{
|
{
|
||||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||||
@ -354,14 +364,23 @@ namespace ix
|
|||||||
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the presence of the Upgrade field
|
// Check the presence of the connection field
|
||||||
if (headers.find("connection") == headers.end() ||
|
if (headers.find("connection") == headers.end())
|
||||||
headers["connection"] != "Upgrade")
|
|
||||||
{
|
{
|
||||||
std::string errorMsg("Invalid or missing connection value");
|
std::string errorMsg("Missing connection value");
|
||||||
return WebSocketInitResult(false, status, errorMsg);
|
return WebSocketInitResult(false, status, errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the value of the connection field
|
||||||
|
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
||||||
|
// connection header, so do a case insensitive comparison
|
||||||
|
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Invalid connection value: " << headers["connection"];
|
||||||
|
return WebSocketInitResult(false, status, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
char output[29] = {};
|
char output[29] = {};
|
||||||
WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
|
WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
|
||||||
if (std::string(output) != headers["sec-websocket-accept"])
|
if (std::string(output) != headers["sec-websocket-accept"])
|
||||||
|
@ -76,6 +76,7 @@ namespace ix
|
|||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
||||||
std::string trim(const std::string& str);
|
std::string trim(const std::string& str);
|
||||||
|
bool insensitiveStringCompare(const std::string& a, const std::string& b);
|
||||||
|
|
||||||
std::atomic<bool>& _requestInitCancellation;
|
std::atomic<bool>& _requestInitCancellation;
|
||||||
std::shared_ptr<Socket> _socket;
|
std::shared_ptr<Socket> _socket;
|
||||||
|
@ -29,12 +29,15 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
|
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
|
||||||
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
||||||
|
constexpr size_t WebSocketTransport::kChunkSize;
|
||||||
|
|
||||||
WebSocketTransport::WebSocketTransport() :
|
WebSocketTransport::WebSocketTransport() :
|
||||||
_readyState(CLOSED),
|
_readyState(CLOSED),
|
||||||
@ -45,7 +48,7 @@ namespace ix
|
|||||||
_heartBeatPeriod(kDefaultHeartBeatPeriod),
|
_heartBeatPeriod(kDefaultHeartBeatPeriod),
|
||||||
_lastSendTimePoint(std::chrono::steady_clock::now())
|
_lastSendTimePoint(std::chrono::steady_clock::now())
|
||||||
{
|
{
|
||||||
|
_readbuf.resize(kChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketTransport::~WebSocketTransport()
|
WebSocketTransport::~WebSocketTransport()
|
||||||
@ -156,7 +159,9 @@ namespace ix
|
|||||||
_onCloseCallback = onCloseCallback;
|
_onCloseCallback = onCloseCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketTransport::exceedSendHeartBeatTimeOut()
|
// Only consider send time points for that computation.
|
||||||
|
// The receive time points is taken into account in Socket::poll (second parameter).
|
||||||
|
bool WebSocketTransport::heartBeatPeriodExceeded()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
|
std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
@ -172,7 +177,7 @@ namespace ix
|
|||||||
// send for a duration exceeding our heart-beat period, send a
|
// send for a duration exceeding our heart-beat period, send a
|
||||||
// ping to the server.
|
// ping to the server.
|
||||||
if (pollResult == PollResultType_Timeout &&
|
if (pollResult == PollResultType_Timeout &&
|
||||||
exceedSendHeartBeatTimeOut())
|
heartBeatPeriodExceeded())
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
|
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
|
||||||
@ -182,27 +187,25 @@ namespace ix
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int N = (int) _rxbuf.size();
|
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
||||||
|
|
||||||
_rxbuf.resize(N + 1500);
|
|
||||||
ssize_t ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
|
|
||||||
|
|
||||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
||||||
_socket->getErrno() == EAGAIN)) {
|
_socket->getErrno() == EAGAIN))
|
||||||
_rxbuf.resize(N);
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (ret <= 0)
|
else if (ret <= 0)
|
||||||
{
|
{
|
||||||
_rxbuf.resize(N);
|
_rxbuf.clear();
|
||||||
|
|
||||||
_socket->close();
|
_socket->close();
|
||||||
setReadyState(CLOSED);
|
setReadyState(CLOSED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_rxbuf.resize(N + ret);
|
_rxbuf.insert(_rxbuf.end(),
|
||||||
|
_readbuf.begin(),
|
||||||
|
_readbuf.begin() + ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,17 +361,35 @@ namespace ix
|
|||||||
|| ws.opcode == wsheader_type::CONTINUATION
|
|| ws.opcode == wsheader_type::CONTINUATION
|
||||||
) {
|
) {
|
||||||
unmaskReceiveBuffer(ws);
|
unmaskReceiveBuffer(ws);
|
||||||
_receivedData.insert(_receivedData.end(),
|
|
||||||
_rxbuf.begin()+ws.header_size,
|
|
||||||
_rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
|
|
||||||
if (ws.fin)
|
|
||||||
{
|
|
||||||
// fire callback with a string message
|
|
||||||
std::string stringMessage(_receivedData.begin(),
|
|
||||||
_receivedData.end());
|
|
||||||
|
|
||||||
emitMessage(MSG, stringMessage, ws, onMessageCallback);
|
//
|
||||||
_receivedData.clear();
|
// Usual case. Small unfragmented messages
|
||||||
|
//
|
||||||
|
if (ws.fin && _chunks.empty())
|
||||||
|
{
|
||||||
|
emitMessage(MSG,
|
||||||
|
std::string(_rxbuf.begin()+ws.header_size,
|
||||||
|
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
||||||
|
ws,
|
||||||
|
onMessageCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Add intermediary message to our chunk list.
|
||||||
|
// We use a chunk list instead of a big buffer because resizing
|
||||||
|
// large buffer can be very costly when we need to re-allocate
|
||||||
|
// the internal buffer which is slow and can let the internal OS
|
||||||
|
// receive buffer fill out.
|
||||||
|
//
|
||||||
|
_chunks.emplace_back(
|
||||||
|
std::vector<uint8_t>(_rxbuf.begin()+ws.header_size,
|
||||||
|
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
||||||
|
if (ws.fin)
|
||||||
|
{
|
||||||
|
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
|
||||||
|
_chunks.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ws.opcode == wsheader_type::PING)
|
else if (ws.opcode == wsheader_type::PING)
|
||||||
@ -418,11 +439,32 @@ namespace ix
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Erase the message that has been processed from the input/read buffer
|
||||||
_rxbuf.erase(_rxbuf.begin(),
|
_rxbuf.erase(_rxbuf.begin(),
|
||||||
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string WebSocketTransport::getMergedChunks() const
|
||||||
|
{
|
||||||
|
size_t length = 0;
|
||||||
|
for (auto&& chunk : _chunks)
|
||||||
|
{
|
||||||
|
length += chunk.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string msg;
|
||||||
|
msg.reserve(length);
|
||||||
|
|
||||||
|
for (auto&& chunk : _chunks)
|
||||||
|
{
|
||||||
|
std::string str(chunk.begin(), chunk.end());
|
||||||
|
msg += str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketTransport::emitMessage(MessageKind messageKind,
|
void WebSocketTransport::emitMessage(MessageKind messageKind,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
const wsheader_type& ws,
|
const wsheader_type& ws,
|
||||||
@ -452,9 +494,11 @@ namespace ix
|
|||||||
return static_cast<unsigned>(seconds);
|
return static_cast<unsigned>(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
|
WebSocketSendInfo WebSocketTransport::sendData(
|
||||||
const std::string& message,
|
wsheader_type::opcode_type type,
|
||||||
bool compress)
|
const std::string& message,
|
||||||
|
bool compress,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
if (_readyState == CLOSING || _readyState == CLOSED)
|
if (_readyState == CLOSING || _readyState == CLOSED)
|
||||||
{
|
{
|
||||||
@ -471,15 +515,81 @@ namespace ix
|
|||||||
|
|
||||||
if (compress)
|
if (compress)
|
||||||
{
|
{
|
||||||
bool success = _perMessageDeflate.compress(message, compressedMessage);
|
if (!_perMessageDeflate.compress(message, compressedMessage))
|
||||||
compressionError = !success;
|
{
|
||||||
|
bool success = false;
|
||||||
|
compressionError = true;
|
||||||
|
payloadSize = 0;
|
||||||
|
wireSize = 0;
|
||||||
|
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||||
|
}
|
||||||
|
compressionError = false;
|
||||||
wireSize = compressedMessage.size();
|
wireSize = compressedMessage.size();
|
||||||
|
|
||||||
message_begin = compressedMessage.begin();
|
message_begin = compressedMessage.begin();
|
||||||
message_end = compressedMessage.end();
|
message_end = compressedMessage.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t message_size = wireSize;
|
// Common case for most message. No fragmentation required.
|
||||||
|
if (wireSize < kChunkSize)
|
||||||
|
{
|
||||||
|
sendFragment(type, true, message_begin, message_end, compress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Large messages need to be fragmented
|
||||||
|
//
|
||||||
|
// Rules:
|
||||||
|
// First message needs to specify a proper type (BINARY or TEXT)
|
||||||
|
// Intermediary and last messages need to be of type CONTINUATION
|
||||||
|
// Last message must set the fin byte.
|
||||||
|
//
|
||||||
|
auto steps = wireSize / kChunkSize;
|
||||||
|
|
||||||
|
std::string::const_iterator begin = message_begin;
|
||||||
|
std::string::const_iterator end = message_end;
|
||||||
|
|
||||||
|
for (uint64_t i = 0 ; i < steps; ++i)
|
||||||
|
{
|
||||||
|
bool firstStep = i == 0;
|
||||||
|
bool lastStep = (i+1) == steps;
|
||||||
|
bool fin = lastStep;
|
||||||
|
|
||||||
|
end = begin + kChunkSize;
|
||||||
|
if (lastStep)
|
||||||
|
{
|
||||||
|
end = message_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto opcodeType = type;
|
||||||
|
if (!firstStep)
|
||||||
|
{
|
||||||
|
opcodeType = wsheader_type::CONTINUATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message
|
||||||
|
sendFragment(opcodeType, fin, begin, end, compress);
|
||||||
|
|
||||||
|
if (onProgressCallback && !onProgressCallback(i, steps))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
begin += kChunkSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
|
||||||
|
bool fin,
|
||||||
|
std::string::const_iterator message_begin,
|
||||||
|
std::string::const_iterator message_end,
|
||||||
|
bool compress)
|
||||||
|
{
|
||||||
|
auto message_size = message_end - message_begin;
|
||||||
|
|
||||||
unsigned x = getRandomUnsigned();
|
unsigned x = getRandomUnsigned();
|
||||||
uint8_t masking_key[4] = {};
|
uint8_t masking_key[4] = {};
|
||||||
@ -492,7 +602,13 @@ namespace ix
|
|||||||
header.assign(2 +
|
header.assign(2 +
|
||||||
(message_size >= 126 ? 2 : 0) +
|
(message_size >= 126 ? 2 : 0) +
|
||||||
(message_size >= 65536 ? 6 : 0) + 4, 0);
|
(message_size >= 65536 ? 6 : 0) + 4, 0);
|
||||||
header[0] = 0x80 | type;
|
header[0] = type;
|
||||||
|
|
||||||
|
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
||||||
|
if (fin)
|
||||||
|
{
|
||||||
|
header[0] |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
// This bit indicate that the frame is compressed
|
// This bit indicate that the frame is compressed
|
||||||
if (compress)
|
if (compress)
|
||||||
@ -544,8 +660,6 @@ namespace ix
|
|||||||
|
|
||||||
// Now actually send this data
|
// Now actually send this data
|
||||||
sendOnSocket();
|
sendOnSocket();
|
||||||
|
|
||||||
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
|
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
|
||||||
@ -554,9 +668,13 @@ namespace ix
|
|||||||
return sendData(wsheader_type::PING, message, compress);
|
return sendData(wsheader_type::PING, message, compress);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message)
|
WebSocketSendInfo WebSocketTransport::sendBinary(
|
||||||
|
const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
|
|
||||||
{
|
{
|
||||||
return sendData(wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate);
|
return sendData(wsheader_type::BINARY_FRAME, message,
|
||||||
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::sendOnSocket()
|
void WebSocketTransport::sendOnSocket()
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#include "IXWebSocketSendInfo.h"
|
#include "IXWebSocketSendInfo.h"
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
#include "IXWebSocketPerMessageDeflate.h"
|
||||||
@ -23,6 +24,7 @@
|
|||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
|
#include "IXProgressCallback.h"
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -66,7 +68,8 @@ namespace ix
|
|||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
|
|
||||||
void poll();
|
void poll();
|
||||||
WebSocketSendInfo sendBinary(const std::string& message);
|
WebSocketSendInfo sendBinary(const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendPing(const std::string& message);
|
WebSocketSendInfo sendPing(const std::string& message);
|
||||||
void close();
|
void close();
|
||||||
ReadyStateValues getReadyState() const;
|
ReadyStateValues getReadyState() const;
|
||||||
@ -76,7 +79,6 @@ namespace ix
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _url;
|
std::string _url;
|
||||||
std::string _origin;
|
|
||||||
|
|
||||||
struct wsheader_type {
|
struct wsheader_type {
|
||||||
unsigned header_size;
|
unsigned header_size;
|
||||||
@ -96,13 +98,31 @@ namespace ix
|
|||||||
uint8_t masking_key[4];
|
uint8_t masking_key[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Buffer for reading from our socket. That buffer is never resized.
|
||||||
|
std::vector<uint8_t> _readbuf;
|
||||||
|
|
||||||
|
// Contains all messages that were fetched in the last socket read.
|
||||||
|
// This could be a mix of control messages (Close, Ping, etc...) and
|
||||||
|
// data messages. That buffer
|
||||||
std::vector<uint8_t> _rxbuf;
|
std::vector<uint8_t> _rxbuf;
|
||||||
|
|
||||||
|
// Contains all messages that are waiting to be sent
|
||||||
std::vector<uint8_t> _txbuf;
|
std::vector<uint8_t> _txbuf;
|
||||||
mutable std::mutex _txbufMutex;
|
mutable std::mutex _txbufMutex;
|
||||||
std::vector<uint8_t> _receivedData;
|
|
||||||
|
|
||||||
|
// Hold fragments for multi-fragments messages in a list. We support receiving very large
|
||||||
|
// messages (tested messages up to 700M) and we cannot put them in a single
|
||||||
|
// buffer that is resized, as this operation can be slow when a buffer has its
|
||||||
|
// size increased 2 fold, while appending to a list has a fixed cost.
|
||||||
|
std::list<std::vector<uint8_t>> _chunks;
|
||||||
|
|
||||||
|
// Fragments are 32K long
|
||||||
|
static constexpr size_t kChunkSize = 1 << 15;
|
||||||
|
|
||||||
|
// Underlying TCP socket
|
||||||
std::shared_ptr<Socket> _socket;
|
std::shared_ptr<Socket> _socket;
|
||||||
|
|
||||||
|
// Hold the state of the connection (OPEN, CLOSED, etc...)
|
||||||
std::atomic<ReadyStateValues> _readyState;
|
std::atomic<ReadyStateValues> _readyState;
|
||||||
|
|
||||||
OnCloseCallback _onCloseCallback;
|
OnCloseCallback _onCloseCallback;
|
||||||
@ -111,6 +131,7 @@ namespace ix
|
|||||||
size_t _closeWireSize;
|
size_t _closeWireSize;
|
||||||
mutable std::mutex _closeDataMutex;
|
mutable std::mutex _closeDataMutex;
|
||||||
|
|
||||||
|
// Data used for Per Message Deflate compression (with zlib)
|
||||||
WebSocketPerMessageDeflate _perMessageDeflate;
|
WebSocketPerMessageDeflate _perMessageDeflate;
|
||||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
||||||
std::atomic<bool> _enablePerMessageDeflate;
|
std::atomic<bool> _enablePerMessageDeflate;
|
||||||
@ -126,12 +147,19 @@ namespace ix
|
|||||||
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
||||||
|
|
||||||
// No data was send through the socket for longer that the hearbeat period
|
// No data was send through the socket for longer that the hearbeat period
|
||||||
bool exceedSendHeartBeatTimeOut();
|
bool heartBeatPeriodExceeded();
|
||||||
|
|
||||||
void sendOnSocket();
|
void sendOnSocket();
|
||||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
bool compress);
|
bool compress,
|
||||||
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
|
|
||||||
|
void sendFragment(wsheader_type::opcode_type type,
|
||||||
|
bool fin,
|
||||||
|
std::string::const_iterator begin,
|
||||||
|
std::string::const_iterator end,
|
||||||
|
bool compress);
|
||||||
|
|
||||||
void emitMessage(MessageKind messageKind,
|
void emitMessage(MessageKind messageKind,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
@ -148,5 +176,7 @@ namespace ix
|
|||||||
|
|
||||||
unsigned getRandomUnsigned();
|
unsigned getRandomUnsigned();
|
||||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
void unmaskReceiveBuffer(const wsheader_type& ws);
|
||||||
|
|
||||||
|
std::string getMergedChunks() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
15
makefile
15
makefile
@ -3,12 +3,19 @@
|
|||||||
#
|
#
|
||||||
all: run
|
all: run
|
||||||
|
|
||||||
|
brew:
|
||||||
|
mkdir -p ws/build && (cd ws/build ; cmake .. ; make)
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
docker build -t ws_connect:latest .
|
docker build -t broadcast_server:latest .
|
||||||
|
|
||||||
run: docker
|
run:
|
||||||
docker run --cap-add sys_ptrace -it ws_connect:latest bash
|
docker run --cap-add sys_ptrace -it broadcast_server:latest bash
|
||||||
|
|
||||||
|
# this is helpful to remove trailing whitespaces
|
||||||
|
trail:
|
||||||
|
sh third_party/remove_trailing_whitespaces.sh
|
||||||
|
|
||||||
build:
|
build:
|
||||||
(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
|
(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
|
||||||
@ -24,7 +31,7 @@ test_server:
|
|||||||
(cd test && npm i ws && node broadcast-server.js)
|
(cd test && npm i ws && node broadcast-server.js)
|
||||||
|
|
||||||
# env TEST=Websocket_server make test
|
# env TEST=Websocket_server make test
|
||||||
# env TEST=websocket_server make test
|
# env TEST=Websocket_chat make test
|
||||||
# env TEST=heartbeat make test
|
# env TEST=heartbeat make test
|
||||||
test:
|
test:
|
||||||
python test/run.py
|
python test/run.py
|
||||||
|
@ -18,13 +18,14 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
|
|||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||||
|
../third_party/msgpack11
|
||||||
)
|
)
|
||||||
|
|
||||||
# Shared sources
|
# Shared sources
|
||||||
set (SOURCES
|
set (SOURCES
|
||||||
test_runner.cpp
|
test_runner.cpp
|
||||||
IXTest.cpp
|
IXTest.cpp
|
||||||
msgpack11.cpp
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
|
|
||||||
IXDNSLookupTest.cpp
|
IXDNSLookupTest.cpp
|
||||||
IXSocketTest.cpp
|
IXSocketTest.cpp
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "IXTest.h"
|
#include "IXTest.h"
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@ -14,12 +15,14 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
std::atomic<size_t> incomingBytes(0);
|
std::atomic<size_t> incomingBytes(0);
|
||||||
std::atomic<size_t> outgoingBytes(0);
|
std::atomic<size_t> outgoingBytes(0);
|
||||||
std::mutex Logger::_mutex;
|
std::mutex Logger::_mutex;
|
||||||
|
std::stack<int> freePorts;
|
||||||
|
|
||||||
void setupWebSocketTrafficTrackerCallback()
|
void setupWebSocketTrafficTrackerCallback()
|
||||||
{
|
{
|
||||||
@ -66,4 +69,71 @@ namespace ix
|
|||||||
Logger() << msg;
|
Logger() << msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getAnyFreePort()
|
||||||
|
{
|
||||||
|
int defaultPort = 8090;
|
||||||
|
|
||||||
|
int sockfd;
|
||||||
|
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
|
{
|
||||||
|
log("Cannot compute a free port. socket error.");
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
int enable = 1;
|
||||||
|
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
|
||||||
|
(char*) &enable, sizeof(enable)) < 0)
|
||||||
|
{
|
||||||
|
log("Cannot compute a free port. setsockopt error.");
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to port 0. This is the standard way to get a free port.
|
||||||
|
struct sockaddr_in server; // server address information
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_port = htons(0);
|
||||||
|
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||||
|
|
||||||
|
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
|
||||||
|
{
|
||||||
|
log("Cannot compute a free port. bind error.");
|
||||||
|
|
||||||
|
::close(sockfd);
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in sa; // server address information
|
||||||
|
unsigned int len;
|
||||||
|
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
||||||
|
{
|
||||||
|
log("Cannot compute a free port. getsockname error.");
|
||||||
|
|
||||||
|
::close(sockfd);
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
int port = ntohs(sa.sin_port);
|
||||||
|
::close(sockfd);
|
||||||
|
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFreePort()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int port = getAnyFreePort();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Only port above 1024 can be used by non root users, but for some
|
||||||
|
// reason I got port 7 returned with macOS when binding on port 0...
|
||||||
|
//
|
||||||
|
if (port > 1024)
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,4 +51,7 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
void log(const std::string& msg);
|
void log(const std::string& msg);
|
||||||
|
|
||||||
|
bool computeFreePorts(int count);
|
||||||
|
int getFreePort();
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ TEST_CASE("Websocket_heartbeat", "[heartbeat]")
|
|||||||
{
|
{
|
||||||
ix::setupWebSocketTrafficTrackerCallback();
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
int port = 8093;
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
std::atomic<int> serverReceivedPingMessages(0);
|
std::atomic<int> serverReceivedPingMessages(0);
|
||||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||||
|
@ -75,7 +75,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
SECTION("Connect to the server, do not send anything. Should timeout and return 400")
|
SECTION("Connect to the server, do not send anything. Should timeout and return 400")
|
||||||
{
|
{
|
||||||
int port = 8091;
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
REQUIRE(startServer(server));
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
|
|
||||||
SECTION("Connect to the server. Send GET request without header. Should return 400")
|
SECTION("Connect to the server. Send GET request without header. Should return 400")
|
||||||
{
|
{
|
||||||
int port = 8092;
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
REQUIRE(startServer(server));
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
|
|
||||||
SECTION("Connect to the server. Send GET request with correct header")
|
SECTION("Connect to the server. Send GET request with correct header")
|
||||||
{
|
{
|
||||||
int port = 8093;
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
REQUIRE(startServer(server));
|
||||||
|
|
||||||
|
@ -4,9 +4,15 @@
|
|||||||
* Copyright (c) 2017 Machine Zone. All rights reserved.
|
* Copyright (c) 2017 Machine Zone. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Simple chat program that talks to the node.js server at
|
||||||
|
// websocket_chat_server/broacast-server.js
|
||||||
|
//
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <queue>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
#include "msgpack11.hpp"
|
#include "msgpack11.hpp"
|
||||||
@ -24,7 +30,8 @@ namespace
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WebSocketChat(const std::string& user,
|
WebSocketChat(const std::string& user,
|
||||||
const std::string& session);
|
const std::string& session,
|
||||||
|
int port);
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
void subscribe(const std::string& channel);
|
||||||
void start();
|
void start();
|
||||||
@ -33,30 +40,49 @@ namespace
|
|||||||
|
|
||||||
void sendMessage(const std::string& text);
|
void sendMessage(const std::string& text);
|
||||||
size_t getReceivedMessagesCount() const;
|
size_t getReceivedMessagesCount() const;
|
||||||
|
const std::vector<std::string>& getReceivedMessages() const;
|
||||||
|
|
||||||
std::string encodeMessage(const std::string& text);
|
std::string encodeMessage(const std::string& text);
|
||||||
std::pair<std::string, std::string> decodeMessage(const std::string& str);
|
std::pair<std::string, std::string> decodeMessage(const std::string& str);
|
||||||
|
void appendMessage(const std::string& message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _user;
|
std::string _user;
|
||||||
std::string _session;
|
std::string _session;
|
||||||
|
int _port;
|
||||||
|
|
||||||
ix::WebSocket _webSocket;
|
ix::WebSocket _webSocket;
|
||||||
|
|
||||||
std::queue<std::string> _receivedQueue;
|
std::vector<std::string> _receivedMessages;
|
||||||
|
mutable std::mutex _mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
WebSocketChat::WebSocketChat(const std::string& user,
|
WebSocketChat::WebSocketChat(const std::string& user,
|
||||||
const std::string& session) :
|
const std::string& session,
|
||||||
|
int port) :
|
||||||
_user(user),
|
_user(user),
|
||||||
_session(session)
|
_session(session),
|
||||||
|
_port(port)
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WebSocketChat::getReceivedMessagesCount() const
|
size_t WebSocketChat::getReceivedMessagesCount() const
|
||||||
{
|
{
|
||||||
return _receivedQueue.size();
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
return _receivedMessages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& WebSocketChat::getReceivedMessages() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
return _receivedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketChat::appendMessage(const std::string& message)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
_receivedMessages.push_back(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketChat::isReady() const
|
bool WebSocketChat::isReady() const
|
||||||
@ -71,7 +97,17 @@ namespace
|
|||||||
|
|
||||||
void WebSocketChat::start()
|
void WebSocketChat::start()
|
||||||
{
|
{
|
||||||
std::string url("ws://localhost:8090/");
|
std::string url;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "ws://localhost:"
|
||||||
|
<< _port
|
||||||
|
<< "/"
|
||||||
|
<< _user;
|
||||||
|
|
||||||
|
url = ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
_webSocket.setUrl(url);
|
_webSocket.setUrl(url);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@ -109,10 +145,16 @@ namespace
|
|||||||
// as we do for the satori chat example.
|
// as we do for the satori chat example.
|
||||||
|
|
||||||
// store text
|
// store text
|
||||||
_receivedQueue.push(result.second);
|
appendMessage(result.second);
|
||||||
|
|
||||||
|
std::string payload = result.second;
|
||||||
|
if (payload.size() > 2000)
|
||||||
|
{
|
||||||
|
payload = "<message too large>";
|
||||||
|
}
|
||||||
|
|
||||||
ss << std::endl
|
ss << std::endl
|
||||||
<< result.first << " > " << result.second
|
<< result.first << " > " << payload
|
||||||
<< std::endl
|
<< std::endl
|
||||||
<< _user << " > ";
|
<< _user << " > ";
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
@ -226,8 +268,8 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
|
|||||||
REQUIRE(startServer(server));
|
REQUIRE(startServer(server));
|
||||||
|
|
||||||
std::string session = ix::generateSessionId();
|
std::string session = ix::generateSessionId();
|
||||||
WebSocketChat chatA("jean", session);
|
WebSocketChat chatA("jean", session, port);
|
||||||
WebSocketChat chatB("paul", session);
|
WebSocketChat chatB("paul", session, port);
|
||||||
|
|
||||||
chatA.start();
|
chatA.start();
|
||||||
chatB.start();
|
chatB.start();
|
||||||
@ -251,15 +293,36 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
|
|||||||
chatB.sendMessage("from B1");
|
chatB.sendMessage("from B1");
|
||||||
chatB.sendMessage("from B2");
|
chatB.sendMessage("from B2");
|
||||||
|
|
||||||
// Give us 1s for all messages to be received
|
// Test large messages that needs to be broken into small fragments
|
||||||
ix::msleep(1000);
|
size_t size = 1 * 1024 * 1024; // ~1Mb
|
||||||
|
std::string bigMessage(size, 'a');
|
||||||
|
chatB.sendMessage(bigMessage);
|
||||||
|
|
||||||
|
log("Sent all messages");
|
||||||
|
|
||||||
|
// Wait until all messages are received. 10s timeout
|
||||||
|
int attempts = 0;
|
||||||
|
while (chatA.getReceivedMessagesCount() != 3 ||
|
||||||
|
chatB.getReceivedMessagesCount() != 3)
|
||||||
|
{
|
||||||
|
REQUIRE(attempts++ < 10);
|
||||||
|
ix::msleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
chatA.stop();
|
chatA.stop();
|
||||||
chatB.stop();
|
chatB.stop();
|
||||||
|
|
||||||
REQUIRE(chatA.getReceivedMessagesCount() == 2);
|
REQUIRE(chatA.getReceivedMessagesCount() == 3);
|
||||||
REQUIRE(chatB.getReceivedMessagesCount() == 3);
|
REQUIRE(chatB.getReceivedMessagesCount() == 3);
|
||||||
|
|
||||||
|
REQUIRE(chatB.getReceivedMessages()[0] == "from A1");
|
||||||
|
REQUIRE(chatB.getReceivedMessages()[1] == "from A2");
|
||||||
|
REQUIRE(chatB.getReceivedMessages()[2] == "from A3");
|
||||||
|
|
||||||
|
REQUIRE(chatA.getReceivedMessages()[0] == "from B1");
|
||||||
|
REQUIRE(chatA.getReceivedMessages()[1] == "from B2");
|
||||||
|
REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size());
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 500ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(500);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
@ -30,10 +30,14 @@ sanitizersFlags = {
|
|||||||
}
|
}
|
||||||
sanitizer = 'tsan'
|
sanitizer = 'tsan'
|
||||||
if osName == 'Linux':
|
if osName == 'Linux':
|
||||||
sanitizer = 'asan'
|
sanitizer = 'none'
|
||||||
|
|
||||||
sanitizerFlags = sanitizersFlags[sanitizer]
|
sanitizerFlags = sanitizersFlags[sanitizer]
|
||||||
|
|
||||||
|
# if osName == 'Windows':
|
||||||
|
# os.environ['CC'] = 'clang-cl'
|
||||||
|
# os.environ['CXX'] = 'clang-cl'
|
||||||
|
|
||||||
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ..'.format(generator, sanitizerFlags)
|
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ..'.format(generator, sanitizerFlags)
|
||||||
print(cmakeCmd)
|
print(cmakeCmd)
|
||||||
ret = os.system(cmakeCmd)
|
ret = os.system(cmakeCmd)
|
||||||
|
4641
third_party/cli11/CLI11.hpp
vendored
Normal file
4641
third_party/cli11/CLI11.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
333
third_party/jsoncpp/json/json-forwards.h
vendored
Normal file
333
third_party/jsoncpp/json/json-forwards.h
vendored
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).
|
||||||
|
/// It is intended to be used with #include "json/json-forwards.h"
|
||||||
|
/// This header provides forward declaration for all JsonCpp types.
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// Beginning of content of file: LICENSE
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
The JsonCpp library's source code, including accompanying documentation,
|
||||||
|
tests and demonstration applications, are licensed under the following
|
||||||
|
conditions...
|
||||||
|
|
||||||
|
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
|
||||||
|
jurisdictions which recognize such a disclaimer. In such jurisdictions,
|
||||||
|
this software is released into the Public Domain.
|
||||||
|
|
||||||
|
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
|
||||||
|
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and
|
||||||
|
The JsonCpp Authors, and is released under the terms of the MIT License (see below).
|
||||||
|
|
||||||
|
In jurisdictions which recognize Public Domain property, the user of this
|
||||||
|
software may choose to accept it either as 1) Public Domain, 2) under the
|
||||||
|
conditions of the MIT License (see below), or 3) under the terms of dual
|
||||||
|
Public Domain/MIT License conditions described here, as they choose.
|
||||||
|
|
||||||
|
The MIT License is about as close to Public Domain as a license can get, and is
|
||||||
|
described in clear, concise terms at:
|
||||||
|
|
||||||
|
http://en.wikipedia.org/wiki/MIT_License
|
||||||
|
|
||||||
|
The full text of the MIT License follows:
|
||||||
|
|
||||||
|
========================================================================
|
||||||
|
Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use, copy,
|
||||||
|
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
========================================================================
|
||||||
|
(END LICENSE TEXT)
|
||||||
|
|
||||||
|
The MIT license is compatible with both the GPL and commercial
|
||||||
|
software, affording one all of the rights of Public Domain with the
|
||||||
|
minor nuisance of being required to keep the above copyright notice
|
||||||
|
and license text in the source code. Note also that by accepting the
|
||||||
|
Public Domain "license" you can re-license your copy using whatever
|
||||||
|
license you like.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// End of content of file: LICENSE
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
||||||
|
# define JSON_FORWARD_AMALGATED_H_INCLUDED
|
||||||
|
/// If defined, indicates that the source file is amalgated
|
||||||
|
/// to prevent private header inclusion.
|
||||||
|
#define JSON_IS_AMALGAMATION
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// Beginning of content of file: include/json/config.h
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
||||||
|
// Distributed under MIT license, or public domain if desired and
|
||||||
|
// recognized in your jurisdiction.
|
||||||
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
||||||
|
|
||||||
|
#ifndef JSON_CONFIG_H_INCLUDED
|
||||||
|
#define JSON_CONFIG_H_INCLUDED
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string> //typedef String
|
||||||
|
#include <stdint.h> //typedef int64_t, uint64_t
|
||||||
|
|
||||||
|
/// If defined, indicates that json library is embedded in CppTL library.
|
||||||
|
//# define JSON_IN_CPPTL 1
|
||||||
|
|
||||||
|
/// If defined, indicates that json may leverage CppTL library
|
||||||
|
//# define JSON_USE_CPPTL 1
|
||||||
|
/// If defined, indicates that cpptl vector based map should be used instead of
|
||||||
|
/// std::map
|
||||||
|
/// as Value container.
|
||||||
|
//# define JSON_USE_CPPTL_SMALLMAP 1
|
||||||
|
|
||||||
|
// If non-zero, the library uses exceptions to report bad input instead of C
|
||||||
|
// assertion macros. The default is to use exceptions.
|
||||||
|
#ifndef JSON_USE_EXCEPTION
|
||||||
|
#define JSON_USE_EXCEPTION 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// If defined, indicates that the source file is amalgated
|
||||||
|
/// to prevent private header inclusion.
|
||||||
|
/// Remarks: it is automatically defined in the generated amalgated header.
|
||||||
|
// #define JSON_IS_AMALGAMATION
|
||||||
|
|
||||||
|
#ifdef JSON_IN_CPPTL
|
||||||
|
#include <cpptl/config.h>
|
||||||
|
#ifndef JSON_USE_CPPTL
|
||||||
|
#define JSON_USE_CPPTL 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_IN_CPPTL
|
||||||
|
#define JSON_API CPPTL_API
|
||||||
|
#elif defined(JSON_DLL_BUILD)
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
#define JSON_API __declspec(dllexport)
|
||||||
|
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||||
|
#endif // if defined(_MSC_VER)
|
||||||
|
#elif defined(JSON_DLL)
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
#define JSON_API __declspec(dllimport)
|
||||||
|
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||||
|
#endif // if defined(_MSC_VER)
|
||||||
|
#endif // ifdef JSON_IN_CPPTL
|
||||||
|
#if !defined(JSON_API)
|
||||||
|
#define JSON_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
||||||
|
// integer
|
||||||
|
// Storages, and 64 bits integer support is disabled.
|
||||||
|
// #define JSON_NO_INT64 1
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) // MSVC
|
||||||
|
# if _MSC_VER <= 1200 // MSVC 6
|
||||||
|
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
||||||
|
// (no conversion from unsigned __int64).
|
||||||
|
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||||
|
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
||||||
|
// characters in the debug information)
|
||||||
|
// All projects I've ever seen with VS6 were using this globally (not bothering
|
||||||
|
// with pragma push/pop).
|
||||||
|
# pragma warning(disable : 4786)
|
||||||
|
# endif // MSVC 6
|
||||||
|
|
||||||
|
# if _MSC_VER >= 1500 // MSVC 2008
|
||||||
|
/// Indicates that the following function is deprecated.
|
||||||
|
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#endif // defined(_MSC_VER)
|
||||||
|
|
||||||
|
// In c++11 the override keyword allows you to explicity define that a function
|
||||||
|
// is intended to override the base-class version. This makes the code more
|
||||||
|
// managable and fixes a set of common hard-to-find bugs.
|
||||||
|
#if __cplusplus >= 201103L
|
||||||
|
# define JSONCPP_OVERRIDE override
|
||||||
|
# define JSONCPP_NOEXCEPT noexcept
|
||||||
|
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
|
||||||
|
# define JSONCPP_OVERRIDE override
|
||||||
|
# define JSONCPP_NOEXCEPT throw()
|
||||||
|
#elif defined(_MSC_VER) && _MSC_VER >= 1900
|
||||||
|
# define JSONCPP_OVERRIDE override
|
||||||
|
# define JSONCPP_NOEXCEPT noexcept
|
||||||
|
#else
|
||||||
|
# define JSONCPP_OVERRIDE
|
||||||
|
# define JSONCPP_NOEXCEPT throw()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_HAS_RVALUE_REFERENCES
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
|
||||||
|
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||||
|
#endif // MSVC >= 2010
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#if __has_feature(cxx_rvalue_references)
|
||||||
|
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||||
|
#endif // has_feature
|
||||||
|
|
||||||
|
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
||||||
|
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
|
||||||
|
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||||
|
#endif // GXX_EXPERIMENTAL
|
||||||
|
|
||||||
|
#endif // __clang__ || __GNUC__
|
||||||
|
|
||||||
|
#endif // not defined JSON_HAS_RVALUE_REFERENCES
|
||||||
|
|
||||||
|
#ifndef JSON_HAS_RVALUE_REFERENCES
|
||||||
|
#define JSON_HAS_RVALUE_REFERENCES 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
# if __has_extension(attribute_deprecated_with_message)
|
||||||
|
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||||
|
# endif
|
||||||
|
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
||||||
|
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||||
|
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||||
|
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
||||||
|
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
||||||
|
# endif // GNUC version
|
||||||
|
#endif // __clang__ || __GNUC__
|
||||||
|
|
||||||
|
#if !defined(JSONCPP_DEPRECATED)
|
||||||
|
#define JSONCPP_DEPRECATED(message)
|
||||||
|
#endif // if !defined(JSONCPP_DEPRECATED)
|
||||||
|
|
||||||
|
#if __GNUC__ >= 6
|
||||||
|
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_IS_AMALGAMATION)
|
||||||
|
|
||||||
|
# include "version.h"
|
||||||
|
|
||||||
|
# if JSONCPP_USING_SECURE_MEMORY
|
||||||
|
# include "allocator.h" //typedef Allocator
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#endif // if !defined(JSON_IS_AMALGAMATION)
|
||||||
|
|
||||||
|
namespace Json {
|
||||||
|
typedef int Int;
|
||||||
|
typedef unsigned int UInt;
|
||||||
|
#if defined(JSON_NO_INT64)
|
||||||
|
typedef int LargestInt;
|
||||||
|
typedef unsigned int LargestUInt;
|
||||||
|
#undef JSON_HAS_INT64
|
||||||
|
#else // if defined(JSON_NO_INT64)
|
||||||
|
// For Microsoft Visual use specific types as long long is not supported
|
||||||
|
#if defined(_MSC_VER) // Microsoft Visual Studio
|
||||||
|
typedef __int64 Int64;
|
||||||
|
typedef unsigned __int64 UInt64;
|
||||||
|
#else // if defined(_MSC_VER) // Other platforms, use long long
|
||||||
|
typedef int64_t Int64;
|
||||||
|
typedef uint64_t UInt64;
|
||||||
|
#endif // if defined(_MSC_VER)
|
||||||
|
typedef Int64 LargestInt;
|
||||||
|
typedef UInt64 LargestUInt;
|
||||||
|
#define JSON_HAS_INT64
|
||||||
|
#endif // if defined(JSON_NO_INT64)
|
||||||
|
#if JSONCPP_USING_SECURE_MEMORY
|
||||||
|
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||||
|
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||||
|
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
|
||||||
|
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||||
|
#define JSONCPP_ISTREAM std::istream
|
||||||
|
#else
|
||||||
|
#define JSONCPP_STRING std::string
|
||||||
|
#define JSONCPP_OSTRINGSTREAM std::ostringstream
|
||||||
|
#define JSONCPP_OSTREAM std::ostream
|
||||||
|
#define JSONCPP_ISTRINGSTREAM std::istringstream
|
||||||
|
#define JSONCPP_ISTREAM std::istream
|
||||||
|
#endif // if JSONCPP_USING_SECURE_MEMORY
|
||||||
|
} // end namespace Json
|
||||||
|
|
||||||
|
#endif // JSON_CONFIG_H_INCLUDED
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// End of content of file: include/json/config.h
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// Beginning of content of file: include/json/forwards.h
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
||||||
|
// Distributed under MIT license, or public domain if desired and
|
||||||
|
// recognized in your jurisdiction.
|
||||||
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
||||||
|
|
||||||
|
#ifndef JSON_FORWARDS_H_INCLUDED
|
||||||
|
#define JSON_FORWARDS_H_INCLUDED
|
||||||
|
|
||||||
|
#if !defined(JSON_IS_AMALGAMATION)
|
||||||
|
#include "config.h"
|
||||||
|
#endif // if !defined(JSON_IS_AMALGAMATION)
|
||||||
|
|
||||||
|
namespace Json {
|
||||||
|
|
||||||
|
// writer.h
|
||||||
|
class FastWriter;
|
||||||
|
class StyledWriter;
|
||||||
|
|
||||||
|
// reader.h
|
||||||
|
class Reader;
|
||||||
|
|
||||||
|
// features.h
|
||||||
|
class Features;
|
||||||
|
|
||||||
|
// value.h
|
||||||
|
typedef unsigned int ArrayIndex;
|
||||||
|
class StaticString;
|
||||||
|
class Path;
|
||||||
|
class PathArgument;
|
||||||
|
class Value;
|
||||||
|
class ValueIteratorBase;
|
||||||
|
class ValueIterator;
|
||||||
|
class ValueConstIterator;
|
||||||
|
|
||||||
|
} // namespace Json
|
||||||
|
|
||||||
|
#endif // JSON_FORWARDS_H_INCLUDED
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
// End of content of file: include/json/forwards.h
|
||||||
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
2186
third_party/jsoncpp/json/json.h
vendored
Normal file
2186
third_party/jsoncpp/json/json.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5386
third_party/jsoncpp/jsoncpp.cpp
vendored
Normal file
5386
third_party/jsoncpp/jsoncpp.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
third_party/remove_trailing_whitespaces.sh
vendored
Normal file
2
third_party/remove_trailing_whitespaces.sh
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
|
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
1
ws/.gitignore
vendored
Normal file
1
ws/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
37
ws/CMakeLists.txt
Normal file
37
ws/CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#
|
||||||
|
# Author: Benjamin Sergeant
|
||||||
|
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
cmake_minimum_required (VERSION 3.4.1)
|
||||||
|
project (ws)
|
||||||
|
|
||||||
|
# There's -Weverything too for clang
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
|
||||||
|
|
||||||
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
|
option(USE_TLS "Add TLS support" ON)
|
||||||
|
|
||||||
|
add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
|
||||||
|
|
||||||
|
include_directories(ws .)
|
||||||
|
include_directories(ws ../third_party)
|
||||||
|
|
||||||
|
add_executable(ws
|
||||||
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
|
ixcrypto/IXBase64.cpp
|
||||||
|
ixcrypto/IXHash.cpp
|
||||||
|
ixcrypto/IXUuid.cpp
|
||||||
|
|
||||||
|
ws_transfer.cpp
|
||||||
|
ws_send.cpp
|
||||||
|
ws_receive.cpp
|
||||||
|
ws.cpp)
|
||||||
|
|
||||||
|
if (APPLE AND USE_TLS)
|
||||||
|
target_link_libraries(ws "-framework foundation" "-framework security")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(ws ixwebsocket)
|
||||||
|
install(TARGETS ws RUNTIME DESTINATION bin)
|
10
ws/README.md
Normal file
10
ws/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
```
|
||||||
|
# Start receiver first
|
||||||
|
./ws receive ws://localhost:8080
|
||||||
|
|
||||||
|
# Sender
|
||||||
|
./ws send ws://localhost:8080 /file/to/path
|
||||||
|
|
||||||
|
# Server
|
||||||
|
./ws transfer # running on port 8080.
|
||||||
|
```
|
39
ws/docker_build.sh
Normal file
39
ws/docker_build.sh
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Author: Benjamin Sergeant
|
||||||
|
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
# 'manual' way of building. I cannot get CMake to work to build in a container.
|
||||||
|
|
||||||
|
g++ --std=c++14 \
|
||||||
|
-DIXWEBSOCKET_USE_TLS \
|
||||||
|
-g \
|
||||||
|
../ixwebsocket/IXEventFd.cpp \
|
||||||
|
../ixwebsocket/IXSocket.cpp \
|
||||||
|
../ixwebsocket/IXSocketServer.cpp \
|
||||||
|
../ixwebsocket/IXSocketConnect.cpp \
|
||||||
|
../ixwebsocket/IXDNSLookup.cpp \
|
||||||
|
../ixwebsocket/IXCancellationRequest.cpp \
|
||||||
|
../ixwebsocket/IXWebSocket.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketServer.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketTransport.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketHandshake.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \
|
||||||
|
../ixwebsocket/IXSocketOpenSSL.cpp \
|
||||||
|
../ixwebsocket/linux/IXSetThreadName_linux.cpp \
|
||||||
|
../third_party/jsoncpp/jsoncpp.cpp \
|
||||||
|
ixcrypto/IXBase64.cpp \
|
||||||
|
ixcrypto/IXHash.cpp \
|
||||||
|
ixcrypto/IXUuid.cpp \
|
||||||
|
ws_transfer.cpp \
|
||||||
|
ws_send.cpp \
|
||||||
|
ws_receive.cpp \
|
||||||
|
ws.cpp \
|
||||||
|
-I . \
|
||||||
|
-I .. \
|
||||||
|
-I ../third_party \
|
||||||
|
-o ws \
|
||||||
|
-lcrypto -lssl -lz -lpthread
|
@ -81,4 +81,55 @@ namespace ix
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool is_base64(unsigned char c)
|
||||||
|
{
|
||||||
|
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base64_decode(const std::string& encoded_string)
|
||||||
|
{
|
||||||
|
int in_len = (int)encoded_string.size();
|
||||||
|
int i = 0;
|
||||||
|
int j = 0;
|
||||||
|
int in_ = 0;
|
||||||
|
unsigned char char_array_4[4], char_array_3[3];
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||||
|
{
|
||||||
|
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||||
|
if(i ==4)
|
||||||
|
{
|
||||||
|
for(i = 0; i <4; i++)
|
||||||
|
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||||
|
|
||||||
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
|
for(i = 0; (i < 3); i++)
|
||||||
|
ret += char_array_3[i];
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i)
|
||||||
|
{
|
||||||
|
for(j = i; j <4; j++)
|
||||||
|
char_array_4[j] = 0;
|
||||||
|
|
||||||
|
for(j = 0; j <4; j++)
|
||||||
|
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||||
|
|
||||||
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
|
for(j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,4 +11,5 @@
|
|||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
std::string base64_encode(const std::string& data, size_t len);
|
std::string base64_encode(const std::string& data, size_t len);
|
||||||
|
std::string base64_decode(const std::string& encoded_string);
|
||||||
}
|
}
|
22
ws/ixcrypto/IXHash.cpp
Normal file
22
ws/ixcrypto/IXHash.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* IXHash.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXHash.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
uint64_t djb2Hash(const std::vector<uint8_t>& data)
|
||||||
|
{
|
||||||
|
uint64_t hashAddress = 5381;
|
||||||
|
|
||||||
|
for (auto&& c : data)
|
||||||
|
{
|
||||||
|
hashAddress = ((hashAddress << 5) + hashAddress) + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashAddress;
|
||||||
|
}
|
||||||
|
}
|
15
ws/ixcrypto/IXHash.h
Normal file
15
ws/ixcrypto/IXHash.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* IXHash.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
uint64_t djb2Hash(const std::vector<uint8_t>& data);
|
||||||
|
}
|
||||||
|
|
75
ws/ixcrypto/IXUuid.cpp
Normal file
75
ws/ixcrypto/IXUuid.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* IXUuid.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random uuid similar to the uuid python module
|
||||||
|
*
|
||||||
|
* >>> import uuid
|
||||||
|
* >>> uuid.uuid4().hex
|
||||||
|
* 'bec08155b37d4050a1f3c3fa0276bf12'
|
||||||
|
*
|
||||||
|
* Code adapted from https://github.com/r-lyeh-archived/sole
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXUuid.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class Uuid
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Uuid();
|
||||||
|
std::string toString() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t _ab;
|
||||||
|
uint64_t _cd;
|
||||||
|
};
|
||||||
|
|
||||||
|
Uuid::Uuid()
|
||||||
|
{
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
|
||||||
|
|
||||||
|
_ab = dist(rd);
|
||||||
|
_cd = dist(rd);
|
||||||
|
|
||||||
|
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
|
||||||
|
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Uuid::toString() const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::nouppercase << std::setfill('0');
|
||||||
|
|
||||||
|
uint32_t a = (_ab >> 32);
|
||||||
|
uint32_t b = (_ab & 0xFFFFFFFF);
|
||||||
|
uint32_t c = (_cd >> 32);
|
||||||
|
uint32_t d = (_cd & 0xFFFFFFFF);
|
||||||
|
|
||||||
|
ss << std::setw(8) << (a);
|
||||||
|
ss << std::setw(4) << (b >> 16);
|
||||||
|
ss << std::setw(4) << (b & 0xFFFF);
|
||||||
|
ss << std::setw(4) << (c >> 16 );
|
||||||
|
ss << std::setw(4) << (c & 0xFFFF);
|
||||||
|
ss << std::setw(8) << d;
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string uuid4()
|
||||||
|
{
|
||||||
|
Uuid id;
|
||||||
|
return id.toString();
|
||||||
|
}
|
||||||
|
}
|
17
ws/ixcrypto/IXUuid.h
Normal file
17
ws/ixcrypto/IXUuid.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* IXUuid.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2017 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Generate a random uuid
|
||||||
|
*/
|
||||||
|
std::string uuid4();
|
||||||
|
|
||||||
|
}
|
68
ws/ws.cpp
Normal file
68
ws/ws.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* ws.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Main drive for websocket utilities
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <cli11/CLI11.hpp>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_receive_main(const std::string& url,
|
||||||
|
bool enablePerMessageDeflate);
|
||||||
|
|
||||||
|
extern int ws_transfer_main(int port);
|
||||||
|
|
||||||
|
extern int ws_send_main(const std::string& url,
|
||||||
|
const std::string& path);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
CLI::App app{"ws is a websocket tool"};
|
||||||
|
app.require_subcommand();
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
std::string path;
|
||||||
|
int port = 8080;
|
||||||
|
|
||||||
|
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
||||||
|
sendApp->add_option("url", url, "Connection url")->required();
|
||||||
|
sendApp->add_option("path", path, "Path to the file to send")->required();
|
||||||
|
|
||||||
|
CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file");
|
||||||
|
receiveApp->add_option("url", url, "Connection url")->required();
|
||||||
|
|
||||||
|
CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server");
|
||||||
|
transferApp->add_option("--port", port, "Connection url");
|
||||||
|
|
||||||
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
|
if (app.got_subcommand("transfer"))
|
||||||
|
{
|
||||||
|
return ix::ws_transfer_main(port);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("send"))
|
||||||
|
{
|
||||||
|
return ix::ws_send_main(url, path);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("receive"))
|
||||||
|
{
|
||||||
|
bool enablePerMessageDeflate = false;
|
||||||
|
return ix::ws_receive_main(url, enablePerMessageDeflate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
251
ws/ws_receive.cpp
Normal file
251
ws/ws_receive.cpp
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* ws_receiver.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixcrypto/IXUuid.h>
|
||||||
|
#include <ixcrypto/IXBase64.h>
|
||||||
|
#include <ixcrypto/IXHash.h>
|
||||||
|
#include <msgpack11/msgpack11.hpp>
|
||||||
|
|
||||||
|
using msgpack11::MsgPack;
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class WebSocketReceiver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebSocketReceiver(const std::string& _url,
|
||||||
|
bool enablePerMessageDeflate);
|
||||||
|
|
||||||
|
void subscribe(const std::string& channel);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void waitForConnection();
|
||||||
|
void waitForMessage();
|
||||||
|
void handleMessage(const std::string& str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _url;
|
||||||
|
std::string _id;
|
||||||
|
ix::WebSocket _webSocket;
|
||||||
|
bool _enablePerMessageDeflate;
|
||||||
|
|
||||||
|
std::mutex _conditionVariableMutex;
|
||||||
|
std::condition_variable _condition;
|
||||||
|
|
||||||
|
std::string extractFilename(const std::string& path);
|
||||||
|
void handleError(const std::string& errMsg, const std::string& id);
|
||||||
|
void log(const std::string& msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketReceiver::WebSocketReceiver(const std::string& url,
|
||||||
|
bool enablePerMessageDeflate) :
|
||||||
|
_url(url),
|
||||||
|
_enablePerMessageDeflate(enablePerMessageDeflate)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::stop()
|
||||||
|
{
|
||||||
|
_webSocket.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::log(const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::waitForConnection()
|
||||||
|
{
|
||||||
|
std::cout << "Connecting..." << std::endl;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
|
_condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::waitForMessage()
|
||||||
|
{
|
||||||
|
std::cout << "Waiting for message..." << std::endl;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
|
_condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should cleanup the file name and full path further to remove .. as well
|
||||||
|
std::string WebSocketReceiver::extractFilename(const std::string& path)
|
||||||
|
{
|
||||||
|
std::string::size_type idx;
|
||||||
|
|
||||||
|
idx = path.rfind('/');
|
||||||
|
if (idx != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string filename = path.substr(idx+1);
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::handleError(const std::string& errMsg,
|
||||||
|
const std::string& id)
|
||||||
|
{
|
||||||
|
std::map<MsgPack, MsgPack> pdu;
|
||||||
|
pdu["kind"] = "error";
|
||||||
|
pdu["id"] = id;
|
||||||
|
pdu["message"] = errMsg;
|
||||||
|
|
||||||
|
MsgPack msg(pdu);
|
||||||
|
_webSocket.send(msg.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::handleMessage(const std::string& str)
|
||||||
|
{
|
||||||
|
std::cerr << "Received message: " << str.size() << std::endl;
|
||||||
|
|
||||||
|
std::string errMsg;
|
||||||
|
MsgPack data = MsgPack::parse(str, errMsg);
|
||||||
|
if (!errMsg.empty())
|
||||||
|
{
|
||||||
|
handleError("Invalid MsgPack", std::string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "id: " << data["id"].string_value() << std::endl;
|
||||||
|
|
||||||
|
std::vector<uint8_t> content = data["content"].binary_items();
|
||||||
|
std::cout << "Content size: " << content.size() << std::endl;
|
||||||
|
|
||||||
|
// Validate checksum
|
||||||
|
uint64_t cksum = ix::djb2Hash(content);
|
||||||
|
auto cksumRef = data["djb2_hash"].string_value();
|
||||||
|
|
||||||
|
std::cout << "Computed hash: " << cksum << std::endl;
|
||||||
|
std::cout << "Reference hash: " << cksumRef << std::endl;
|
||||||
|
|
||||||
|
if (std::to_string(cksum) != cksumRef)
|
||||||
|
{
|
||||||
|
handleError("Hash mismatch.", std::string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string filename = data["filename"].string_value();
|
||||||
|
filename = extractFilename(filename);
|
||||||
|
|
||||||
|
std::cout << "Writing to disk: " << filename << std::endl;
|
||||||
|
std::ofstream out(filename);
|
||||||
|
out.write((char*)&content.front(), content.size());
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
std::map<MsgPack, MsgPack> pdu;
|
||||||
|
pdu["ack"] = true;
|
||||||
|
pdu["id"] = data["id"];
|
||||||
|
pdu["filename"] = data["filename"];
|
||||||
|
|
||||||
|
MsgPack msg(pdu);
|
||||||
|
_webSocket.send(msg.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketReceiver::start()
|
||||||
|
{
|
||||||
|
_webSocket.setUrl(_url);
|
||||||
|
|
||||||
|
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
|
||||||
|
_enablePerMessageDeflate, false, false, 15, 15);
|
||||||
|
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
log(std::string("Connecting to url: ") + _url);
|
||||||
|
|
||||||
|
_webSocket.setOnMessageCallback(
|
||||||
|
[this](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
|
{
|
||||||
|
_condition.notify_one();
|
||||||
|
|
||||||
|
log("ws_receive: connected");
|
||||||
|
std::cout << "Uri: " << openInfo.uri << std::endl;
|
||||||
|
std::cout << "Handshake Headers:" << std::endl;
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
|
{
|
||||||
|
ss << "ws_receive: connection closed:";
|
||||||
|
ss << " code " << closeInfo.code;
|
||||||
|
ss << " reason " << closeInfo.reason << std::endl;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||||
|
{
|
||||||
|
ss << "ws_receive: transfered " << wireSize << " bytes";
|
||||||
|
log(ss.str());
|
||||||
|
handleMessage(str);
|
||||||
|
_condition.notify_one();
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||||
|
{
|
||||||
|
ss << "Connection error: " << error.reason << std::endl;
|
||||||
|
ss << "#retries: " << error.retries << std::endl;
|
||||||
|
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
||||||
|
ss << "HTTP Status: " << error.http_status << std::endl;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "Invalid ix::WebSocketMessageType";
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_webSocket.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wsReceive(const std::string& url,
|
||||||
|
bool enablePerMessageDeflate)
|
||||||
|
{
|
||||||
|
WebSocketReceiver webSocketReceiver(url, enablePerMessageDeflate);
|
||||||
|
webSocketReceiver.start();
|
||||||
|
|
||||||
|
webSocketReceiver.waitForConnection();
|
||||||
|
|
||||||
|
webSocketReceiver.waitForMessage();
|
||||||
|
|
||||||
|
std::chrono::duration<double, std::milli> duration(1000);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
|
||||||
|
std::cout << "Done !" << std::endl;
|
||||||
|
webSocketReceiver.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_receive_main(const std::string& url,
|
||||||
|
bool enablePerMessageDeflate)
|
||||||
|
{
|
||||||
|
Socket::init();
|
||||||
|
wsReceive(url, enablePerMessageDeflate);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
296
ws/ws_send.cpp
Normal file
296
ws/ws_send.cpp
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* ws_send.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixcrypto/IXUuid.h>
|
||||||
|
#include <ixcrypto/IXBase64.h>
|
||||||
|
#include <ixcrypto/IXHash.h>
|
||||||
|
#include <msgpack11/msgpack11.hpp>
|
||||||
|
|
||||||
|
using msgpack11::MsgPack;
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class WebSocketSender
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebSocketSender(const std::string& _url,
|
||||||
|
bool enablePerMessageDeflate);
|
||||||
|
|
||||||
|
void subscribe(const std::string& channel);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void waitForConnection();
|
||||||
|
void waitForAck();
|
||||||
|
|
||||||
|
void sendMessage(const std::string& filename, bool throttle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _url;
|
||||||
|
std::string _id;
|
||||||
|
ix::WebSocket _webSocket;
|
||||||
|
bool _enablePerMessageDeflate;
|
||||||
|
|
||||||
|
std::mutex _conditionVariableMutex;
|
||||||
|
std::condition_variable _condition;
|
||||||
|
|
||||||
|
void log(const std::string& msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketSender::WebSocketSender(const std::string& url,
|
||||||
|
bool enablePerMessageDeflate) :
|
||||||
|
_url(url),
|
||||||
|
_enablePerMessageDeflate(enablePerMessageDeflate)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketSender::stop()
|
||||||
|
{
|
||||||
|
_webSocket.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketSender::log(const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketSender::waitForConnection()
|
||||||
|
{
|
||||||
|
std::cout << "Connecting..." << std::endl;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
|
_condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketSender::waitForAck()
|
||||||
|
{
|
||||||
|
std::cout << "Waiting for ack..." << std::endl;
|
||||||
|
|
||||||
|
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);
|
||||||
|
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("Connecting to url: ") + _url);
|
||||||
|
|
||||||
|
_webSocket.setOnMessageCallback(
|
||||||
|
[this](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
|
{
|
||||||
|
_condition.notify_one();
|
||||||
|
|
||||||
|
log("ws_send: connected");
|
||||||
|
std::cout << "Uri: " << openInfo.uri << std::endl;
|
||||||
|
std::cout << "Handshake Headers:" << std::endl;
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
|
{
|
||||||
|
ss << "ws_send: connection closed:";
|
||||||
|
ss << " code " << closeInfo.code;
|
||||||
|
ss << " reason " << closeInfo.reason << std::endl;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||||
|
{
|
||||||
|
_condition.notify_one();
|
||||||
|
|
||||||
|
ss << "ws_send: received message (" << wireSize << " bytes)";
|
||||||
|
log(ss.str());
|
||||||
|
|
||||||
|
std::string errMsg;
|
||||||
|
MsgPack data = MsgPack::parse(str, errMsg);
|
||||||
|
if (!errMsg.empty())
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid MsgPack response" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string id = data["id"].string_value();
|
||||||
|
if (_id != id)
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid id" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||||
|
{
|
||||||
|
ss << "Connection error: " << error.reason << std::endl;
|
||||||
|
ss << "#retries: " << error.retries << std::endl;
|
||||||
|
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
||||||
|
ss << "HTTP Status: " << error.http_status << std::endl;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "Invalid ix::WebSocketMessageType";
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_webSocket.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bench
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Bench(const std::string& description) :
|
||||||
|
_description(description),
|
||||||
|
_start(std::chrono::system_clock::now()),
|
||||||
|
_reported(false)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Bench()
|
||||||
|
{
|
||||||
|
if (!_reported)
|
||||||
|
{
|
||||||
|
report();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void report()
|
||||||
|
{
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
|
||||||
|
|
||||||
|
_ms = milliseconds.count();
|
||||||
|
std::cout << _description << " completed in "
|
||||||
|
<< _ms << "ms" << std::endl;
|
||||||
|
|
||||||
|
_reported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t getDuration() const
|
||||||
|
{
|
||||||
|
return _ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _description;
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> _start;
|
||||||
|
uint64_t _ms;
|
||||||
|
bool _reported;
|
||||||
|
};
|
||||||
|
|
||||||
|
void 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");
|
||||||
|
_webSocket.send(msg.dump(),
|
||||||
|
[throttle](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cout << "Step " << current << " out of " << total << std::endl;
|
||||||
|
|
||||||
|
if (throttle)
|
||||||
|
{
|
||||||
|
std::chrono::duration<double, std::milli> duration(10);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
bench.report();
|
||||||
|
auto duration = bench.getDuration();
|
||||||
|
auto transferRate = 1000 * content.size() / duration;
|
||||||
|
transferRate /= (1024 * 1024);
|
||||||
|
std::cout << "Send transfer rate: " << transferRate << "MB/s" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wsSend(const std::string& url,
|
||||||
|
const std::string& path,
|
||||||
|
bool enablePerMessageDeflate,
|
||||||
|
bool throttle)
|
||||||
|
{
|
||||||
|
WebSocketSender webSocketSender(url, enablePerMessageDeflate);
|
||||||
|
webSocketSender.start();
|
||||||
|
|
||||||
|
webSocketSender.waitForConnection();
|
||||||
|
|
||||||
|
std::cout << "Sending..." << std::endl;
|
||||||
|
webSocketSender.sendMessage(path, throttle);
|
||||||
|
|
||||||
|
webSocketSender.waitForAck();
|
||||||
|
|
||||||
|
std::cout << "Done !" << std::endl;
|
||||||
|
webSocketSender.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_send_main(const std::string& url,
|
||||||
|
const std::string& path)
|
||||||
|
{
|
||||||
|
bool throttle = false;
|
||||||
|
bool enablePerMessageDeflate = false;
|
||||||
|
|
||||||
|
Socket::init();
|
||||||
|
wsSend(url, path, enablePerMessageDeflate, throttle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
72
ws/ws_transfer.cpp
Normal file
72
ws/ws_transfer.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* ws_transfer.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_transfer_main(int port)
|
||||||
|
{
|
||||||
|
std::cout << "Listening on port " << port << std::endl;
|
||||||
|
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
server.setOnConnectionCallback(
|
||||||
|
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
||||||
|
{
|
||||||
|
webSocket->setOnMessageCallback(
|
||||||
|
[webSocket, &server](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
|
{
|
||||||
|
std::cerr << "New connection" << std::endl;
|
||||||
|
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
||||||
|
std::cerr << "Headers:" << std::endl;
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
|
{
|
||||||
|
std::cerr << "Closed connection" << std::endl;
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||||
|
{
|
||||||
|
std::cerr << "Received " << wireSize << " bytes" << std::endl;
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
|
{
|
||||||
|
if (client != webSocket)
|
||||||
|
{
|
||||||
|
client->send(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
std::cerr << res.second << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.wait();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user