Compare commits

..

6 Commits

86 changed files with 285 additions and 14039 deletions

View File

@ -1 +0,0 @@
build

View File

@ -2,16 +2,10 @@ language: cpp
dist: xenial dist: xenial
compiler: compiler:
- gcc
- clang - clang
os: # - gcc
- linux
- osx
matrix:
exclude:
# GCC fails on recent Travis OSX images.
- compiler: gcc
os: osx
os: osx
# os: windows
# script: make test
script: python test/run.py script: python test/run.py

View File

@ -38,7 +38,6 @@ 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
@ -113,6 +112,3 @@ set( IXWEBSOCKET_INCLUDE_DIRS
. .
../../shared/OpenSSL/include) ../../shared/OpenSSL/include)
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} ) target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
add_subdirectory(ws)
add_subdirectory(examples)

View File

@ -134,10 +134,6 @@ 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.
@ -313,7 +309,6 @@ 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.

View File

@ -15,8 +15,5 @@ RUN apt-get -y install cmake
COPY . . COPY . .
WORKDIR ws WORKDIR test
RUN ["sh", "docker_build.sh"] RUN ["sh", "build_linux.sh"]
EXPOSE 8765
CMD ["/ws/ws", "transfer", "8765"]

View File

@ -1,7 +0,0 @@
add_subdirectory(broadcast_server)
add_subdirectory(ping_pong)
add_subdirectory(chat)
add_subdirectory(echo_server)
add_subdirectory(ws_connect)
# add_subdirectory(cobra_publisher)

View File

@ -15,6 +15,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(broadcast_server .) include_directories(broadcast_server .)
add_executable(broadcast_server add_executable(broadcast_server

View File

@ -47,7 +47,6 @@ 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)

View File

@ -11,6 +11,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(cmd_websocket_chat cmd_websocket_chat.cpp) add_executable(cmd_websocket_chat cmd_websocket_chat.cpp)
if (APPLE AND USE_TLS) if (APPLE AND USE_TLS)

View File

@ -115,7 +115,7 @@ namespace
// store text // store text
_receivedQueue.push(result.second); _receivedQueue.push(result.second);
ss << std::endl ss << std::endl
<< result.first << " > " << result.second << result.first << " > " << result.second
<< std::endl << std::endl
<< _user << " > "; << _user << " > ";

View File

@ -15,6 +15,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(cobra_publisher ${OPENSSL_PREFIX}/include) include_directories(cobra_publisher ${OPENSSL_PREFIX}/include)
include_directories(cobra_publisher .) include_directories(cobra_publisher .)

View File

@ -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;
@ -229,7 +229,7 @@ namespace ix
return _webSocket.send(serializedJson).success; return _webSocket.send(serializedJson).success;
} }
// //
// Extract the nonce from the handshake response // Extract the nonce from the handshake response
// use it to compute a hash during authentication // use it to compute a hash during authentication
// //
@ -297,7 +297,7 @@ namespace ix
if (!pdu.isMember("body")) return false; if (!pdu.isMember("body")) return false;
Json::Value body = pdu["body"]; Json::Value body = pdu["body"];
// Identify subscription_id, so that we can find // Identify subscription_id, so that we can find
// which callback to execute // which callback to execute
if (!body.isMember("subscription_id")) return false; if (!body.isMember("subscription_id")) return false;
Json::Value subscriptionId = body["subscription_id"]; Json::Value subscriptionId = body["subscription_id"];
@ -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;
@ -471,5 +471,5 @@ namespace ix
{ {
connect(); connect();
} }
} // namespace ix } // namespace ix

View File

@ -84,7 +84,7 @@ namespace ix
/// Returns true only if we're connected /// Returns true only if we're connected
bool isConnected() const; bool isConnected() const;
/// Flush the publish queue /// Flush the publish queue
bool flushQueue(); bool flushQueue();
@ -118,7 +118,7 @@ namespace ix
/// ///
/// Member variables /// Member variables
/// ///
WebSocket _webSocket; WebSocket _webSocket;
/// Configuration data /// Configuration data
@ -148,10 +148,10 @@ namespace ix
std::unordered_map<std::string, SubscriptionCallback> _cbs; std::unordered_map<std::string, SubscriptionCallback> _cbs;
mutable std::mutex _cbsMutex; mutable std::mutex _cbsMutex;
// Message Queue can be touched on control+background thread, // Message Queue can be touched on control+background thread,
// protecting with a mutex. // protecting with a mutex.
// //
// Message queue is used when there are problems sending messages so // Message queue is used when there are problems sending messages so
// that sending can be retried later. // that sending can be retried later.
std::deque<std::string> _messageQueue; std::deque<std::string> _messageQueue;
mutable std::mutex _queueMutex; mutable std::mutex _queueMutex;
@ -159,5 +159,5 @@ namespace ix
// Cap the queue size (100 elems so far -> ~100k) // Cap the queue size (100 elems so far -> ~100k)
static constexpr size_t kQueueMaxSize = 256; static constexpr size_t kQueueMaxSize = 256;
}; };
} // namespace ix } // namespace ix

View File

@ -1,39 +1,39 @@
/* /*
base64.cpp and base64.h base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages warranty. In no event will the author be held liable for any damages
arising from the use of this software. arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions: freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not 1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be in a product, an acknowledgment in the product documentation would be
appreciated but is not required. appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be 2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code. misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/ */
#include "IXBase64.h" #include "IXBase64.h"
namespace ix namespace ix
{ {
static const std::string base64_chars = static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
"0123456789+/"; "0123456789+/";
std::string base64_encode(const std::string& data, size_t len) std::string base64_encode(const std::string& data, size_t len)
{ {
std::string ret; std::string ret;
@ -41,9 +41,9 @@ namespace ix
int j = 0; int j = 0;
unsigned char char_array_3[3]; unsigned char char_array_3[3];
unsigned char char_array_4[4]; unsigned char char_array_4[4];
const char* bytes_to_encode = data.c_str(); const char* bytes_to_encode = data.c_str();
while(len--) while(len--)
{ {
char_array_3[i++] = *(bytes_to_encode++); char_array_3[i++] = *(bytes_to_encode++);
@ -53,83 +53,32 @@ namespace ix
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f; char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++) for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]]; ret += base64_chars[char_array_4[i]];
i = 0; i = 0;
} }
} }
if(i) if(i)
{ {
for(j = i; j < 3; j++) for(j = i; j < 3; j++)
char_array_3[j] = '\0'; char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f; char_array_4[3] = char_array_3[2] & 0x3f;
for(j = 0; (j < i + 1); j++) for(j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]]; ret += base64_chars[char_array_4[j]];
while((i++ < 3)) while((i++ < 3))
ret += '='; 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; return ret;
} }
} }

View File

@ -11,5 +11,4 @@
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);
} }

View File

@ -7,28 +7,28 @@
// ////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////
/* /*
The JsonCpp library's source code, including accompanying documentation, The JsonCpp library's source code, including accompanying documentation,
tests and demonstration applications, are licensed under the following tests and demonstration applications, are licensed under the following
conditions... conditions...
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
jurisdictions which recognize such a disclaimer. In such jurisdictions, jurisdictions which recognize such a disclaimer. In such jurisdictions,
this software is released into the Public Domain. this software is released into the Public Domain.
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 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 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). 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 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 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 conditions of the MIT License (see below), or 3) under the terms of dual
Public Domain/MIT License conditions described here, as they choose. 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 The MIT License is about as close to Public Domain as a license can get, and is
described in clear, concise terms at: described in clear, concise terms at:
http://en.wikipedia.org/wiki/MIT_License http://en.wikipedia.org/wiki/MIT_License
The full text of the MIT License follows: The full text of the MIT License follows:
======================================================================== ========================================================================

View File

@ -6,28 +6,28 @@
// ////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////
/* /*
The JsonCpp library's source code, including accompanying documentation, The JsonCpp library's source code, including accompanying documentation,
tests and demonstration applications, are licensed under the following tests and demonstration applications, are licensed under the following
conditions... conditions...
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
jurisdictions which recognize such a disclaimer. In such jurisdictions, jurisdictions which recognize such a disclaimer. In such jurisdictions,
this software is released into the Public Domain. this software is released into the Public Domain.
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 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 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). 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 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 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 conditions of the MIT License (see below), or 3) under the terms of dual
Public Domain/MIT License conditions described here, as they choose. 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 The MIT License is about as close to Public Domain as a license can get, and is
described in clear, concise terms at: described in clear, concise terms at:
http://en.wikipedia.org/wiki/MIT_License http://en.wikipedia.org/wiki/MIT_License
The full text of the MIT License follows: The full text of the MIT License follows:
======================================================================== ========================================================================
@ -1673,7 +1673,7 @@ public:
- `"rejectDupKeys": false or true` - `"rejectDupKeys": false or true`
- If true, `parse()` returns false when a key is duplicated within an object. - If true, `parse()` returns false when a key is duplicated within an object.
- `"allowSpecialFloats": false or true` - `"allowSpecialFloats": false or true`
- If true, special float values (NaNs and infinities) are allowed - If true, special float values (NaNs and infinities) are allowed
and their values are lossfree restorable. and their values are lossfree restorable.
You can examine 'settings_` yourself You can examine 'settings_` yourself

View File

@ -6,28 +6,28 @@
// ////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////
/* /*
The JsonCpp library's source code, including accompanying documentation, The JsonCpp library's source code, including accompanying documentation,
tests and demonstration applications, are licensed under the following tests and demonstration applications, are licensed under the following
conditions... conditions...
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
jurisdictions which recognize such a disclaimer. In such jurisdictions, jurisdictions which recognize such a disclaimer. In such jurisdictions,
this software is released into the Public Domain. this software is released into the Public Domain.
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 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 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). 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 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 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 conditions of the MIT License (see below), or 3) under the terms of dual
Public Domain/MIT License conditions described here, as they choose. 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 The MIT License is about as close to Public Domain as a license can get, and is
described in clear, concise terms at: described in clear, concise terms at:
http://en.wikipedia.org/wiki/MIT_License http://en.wikipedia.org/wiki/MIT_License
The full text of the MIT License follows: The full text of the MIT License follows:
======================================================================== ========================================================================
@ -238,7 +238,7 @@ static inline void fixNumericLocaleInput(char* begin, char* end) {
#include <limits> #include <limits>
#if defined(_MSC_VER) #if defined(_MSC_VER)
#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above #if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
#define snprintf sprintf_s #define snprintf sprintf_s
#elif _MSC_VER >= 1900 // VC++ 14.0 and above #elif _MSC_VER >= 1900 // VC++ 14.0 and above
#define snprintf std::snprintf #define snprintf std::snprintf
@ -383,7 +383,7 @@ bool Reader::parse(const char* beginDoc,
bool Reader::readValue() { bool Reader::readValue() {
// readValue() may call itself only if it calls readObject() or ReadArray(). // readValue() may call itself only if it calls readObject() or ReadArray().
// These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue().
// parse() executes one nodes_.push(), so > instead of >=. // parse() executes one nodes_.push(), so > instead of >=.
if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue().");
@ -4215,7 +4215,7 @@ Value& Path::make(Value& root) const {
#endif #endif
#endif #endif
#if defined(__BORLANDC__) #if defined(__BORLANDC__)
#include <float.h> #include <float.h>
#define isfinite _finite #define isfinite _finite
#define snprintf _snprintf #define snprintf _snprintf
@ -5290,7 +5290,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const
JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); JSONCPP_STRING cs_str = settings_["commentStyle"].asString();
bool eyc = settings_["enableYAMLCompatibility"].asBool(); bool eyc = settings_["enableYAMLCompatibility"].asBool();
bool dnp = settings_["dropNullPlaceholders"].asBool(); bool dnp = settings_["dropNullPlaceholders"].asBool();
bool usf = settings_["useSpecialFloats"].asBool(); bool usf = settings_["useSpecialFloats"].asBool();
unsigned int pre = settings_["precision"].asUInt(); unsigned int pre = settings_["precision"].asUInt();
CommentStyle::Enum cs = CommentStyle::All; CommentStyle::Enum cs = CommentStyle::All;
if (cs_str == "All") { if (cs_str == "All") {

View File

@ -15,6 +15,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(echo_server .) include_directories(echo_server .)
add_executable(echo_server add_executable(echo_server

View File

@ -10,6 +10,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ping_pong ping_pong.cpp) add_executable(ping_pong ping_pong.cpp)
if (APPLE AND USE_TLS) if (APPLE AND USE_TLS)

View File

@ -10,6 +10,8 @@ set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ws_connect ws_connect.cpp) add_executable(ws_connect ws_connect.cpp)
if (APPLE AND USE_TLS) if (APPLE AND USE_TLS)

View File

@ -8,7 +8,7 @@
#include <chrono> #include <chrono>
namespace ix namespace ix
{ {
CancellationRequest makeCancellationRequestWithTimeout(int secs, CancellationRequest makeCancellationRequestWithTimeout(int secs,
std::atomic<bool>& requestInitCancellation) std::atomic<bool>& requestInitCancellation)
@ -20,7 +20,7 @@ namespace ix
{ {
// Was an explicit cancellation requested ? // Was an explicit cancellation requested ?
if (requestInitCancellation) return true; if (requestInitCancellation) return true;
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
if ((now - start) > timeout) return true; if ((now - start) > timeout) return true;

View File

@ -9,7 +9,7 @@
#include <functional> #include <functional>
#include <atomic> #include <atomic>
namespace ix namespace ix
{ {
using CancellationRequest = std::function<bool()>; using CancellationRequest = std::function<bool()>;

View File

@ -10,7 +10,7 @@
#include <string.h> #include <string.h>
#include <chrono> #include <chrono>
namespace ix namespace ix
{ {
const int64_t DNSLookup::kDefaultWait = 10; // ms const int64_t DNSLookup::kDefaultWait = 10; // ms
@ -26,7 +26,7 @@ namespace ix
_done(false), _done(false),
_id(_nextId++) _id(_nextId++)
{ {
} }
DNSLookup::~DNSLookup() DNSLookup::~DNSLookup()
@ -36,7 +36,7 @@ namespace ix
_activeJobs.erase(_id); _activeJobs.erase(_id);
} }
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg) std::string& errMsg)
{ {
@ -49,7 +49,7 @@ namespace ix
std::string sport = std::to_string(port); std::string sport = std::to_string(port);
struct addrinfo* res; struct addrinfo* res;
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(),
&hints, &res); &hints, &res);
if (getaddrinfo_result) if (getaddrinfo_result)
{ {
@ -101,7 +101,7 @@ namespace ix
_activeJobs.insert(_id); _activeJobs.insert(_id);
} }
// //
// Good resource on thread forced termination // Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
// //
@ -141,7 +141,7 @@ namespace ix
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
{ {
// We don't want to read or write into members variables of an object that could be // We don't want to read or write into members variables of an object that could be
// gone, so we use temporary variables (res) or we pass in by copy everything that // gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work. // getAddrInfo needs to work.
std::string errMsg; std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg); struct addrinfo* res = getAddrInfo(hostname, port, errMsg);

View File

@ -3,7 +3,7 @@
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved. * Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
* *
* Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo * Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo
* Does this in a background thread so that it can be cancelled, since * Does this in a background thread so that it can be cancelled, since
* getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile. * getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile.
*/ */
@ -20,7 +20,7 @@
struct addrinfo; struct addrinfo;
namespace ix namespace ix
{ {
class DNSLookup { class DNSLookup {
public: public:
@ -39,7 +39,7 @@ namespace ix
struct addrinfo* resolveBlocking(std::string& errMsg, struct addrinfo* resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname, static struct addrinfo* getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg); std::string& errMsg);

View File

@ -14,7 +14,7 @@
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4) // eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
// is on Kernel 3.x // is on Kernel 3.x
// //
// cf Android/Kernel table here // cf Android/Kernel table here
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel // https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
// //
@ -28,9 +28,9 @@
#include <unistd.h> // for write #include <unistd.h> // for write
#endif #endif
namespace ix namespace ix
{ {
EventFd::EventFd() : EventFd::EventFd() :
_eventfd(-1) _eventfd(-1)
{ {
#ifdef __linux__ #ifdef __linux__
@ -65,7 +65,7 @@ namespace ix
#if defined(__linux__) #if defined(__linux__)
if (_eventfd == -1) return false; if (_eventfd == -1) return false;
// 0 is a special value ; select will not wake up // 0 is a special value ; select will not wake up
uint64_t value = 0; uint64_t value = 0;
// we should write 8 bytes for an uint64_t // we should write 8 bytes for an uint64_t

View File

@ -6,7 +6,7 @@
#pragma once #pragma once
namespace ix namespace ix
{ {
class EventFd { class EventFd {
public: public:

View File

@ -1,14 +0,0 @@
/*
* 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)>;
}

View File

@ -15,16 +15,17 @@
#include <stdint.h> #include <stdint.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <poll.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
namespace ix namespace ix
{ {
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout; const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
Socket::Socket(int fd) : Socket::Socket(int fd) :
_sockfd(fd) _sockfd(fd)
{ {
@ -43,22 +44,21 @@ namespace ix
return; return;
} }
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds);
#ifdef __linux__ #ifdef __linux__
FD_SET(_eventfd.getFd(), &rfds); constexpr int nfds = 2;
#else
constexpr int nfds = 1;
#endif #endif
struct timeval timeout; struct pollfd fds[nfds];
timeout.tv_sec = timeoutSecs; fds[0].fd = _sockfd;
timeout.tv_usec = 0; fds[0].events = POLLIN;
int sockfd = _sockfd; #ifdef __linux__
int nfds = (std::max)(sockfd, _eventfd.getFd()); fds[1].fd = _eventfd.getFd();
int ret = select(nfds + 1, &rfds, nullptr, nullptr, fds[1].events = POLLIN;
(timeoutSecs < 0) ? nullptr : &timeout); #endif
int ret = ::poll(fds, nfds, timeoutSecs * 1000);
PollResultType pollResult = PollResultType_ReadyForRead; PollResultType pollResult = PollResultType_ReadyForRead;
if (ret < 0) if (ret < 0)
@ -71,6 +71,7 @@ namespace ix
} }
onPollCallback(pollResult); onPollCallback(pollResult);
} }
void Socket::wakeUpFromPoll() void Socket::wakeUpFromPoll()
@ -150,7 +151,7 @@ namespace ix
#ifdef _WIN32 #ifdef _WIN32
INT rc; INT rc;
WSADATA wsaData; WSADATA wsaData;
rc = WSAStartup(MAKEWORD(2, 2), &wsaData); rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
return rc != 0; return rc != 0;
#else #else

View File

@ -19,7 +19,7 @@ typedef SSIZE_T ssize_t;
#include "IXEventFd.h" #include "IXEventFd.h"
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
namespace ix namespace ix
{ {
enum PollResultType enum PollResultType
{ {
@ -42,7 +42,7 @@ namespace ix
virtual void wakeUpFromPoll(); virtual void wakeUpFromPoll();
// Virtual methods // Virtual methods
virtual bool connect(const std::string& url, virtual bool connect(const std::string& url,
int port, int port,
std::string& errMsg, std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);

View File

@ -50,7 +50,7 @@ OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len)
else else
return noErr; return noErr;
} }
else if (0 == status) else if (0 == status)
{ {
*len = 0; *len = 0;
return errSSLClosedGraceful; return errSSLClosedGraceful;
@ -102,7 +102,7 @@ OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t *
else else
{ {
*len = 0; *len = 0;
if (EAGAIN == errno) if (EAGAIN == errno)
{ {
return errSSLWouldBlock; return errSSLWouldBlock;
} }
@ -141,7 +141,7 @@ std::string getSSLErrorDescription(OSStatus status)
} // anonymous namespace } // anonymous namespace
namespace ix namespace ix
{ {
SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd), SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
_sslContext(nullptr) _sslContext(nullptr)
@ -176,11 +176,11 @@ namespace ix
do { do {
status = SSLHandshake(_sslContext); status = SSLHandshake(_sslContext);
} while (errSSLWouldBlock == status || } while (errSSLWouldBlock == status ||
errSSLServerAuthCompleted == status); errSSLServerAuthCompleted == status);
} }
if (noErr != status) if (noErr != status)
{ {
errMsg = getSSLErrorDescription(status); errMsg = getSSLErrorDescription(status);
close(); close();
@ -230,7 +230,7 @@ namespace ix
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte) ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{ {
OSStatus status = errSSLWouldBlock; OSStatus status = errSSLWouldBlock;
while (errSSLWouldBlock == status) while (errSSLWouldBlock == status)
{ {
size_t processed = 0; size_t processed = 0;
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
@ -239,7 +239,7 @@ namespace ix
if (processed > 0) if (processed > 0)
return (ssize_t) processed; return (ssize_t) processed;
// The connection was reset, inform the caller that this // The connection was reset, inform the caller that this
// Socket should close // Socket should close
if (status == errSSLClosedGraceful || if (status == errSSLClosedGraceful ||
status == errSSLClosedNoNotify || status == errSSLClosedNoNotify ||

View File

@ -14,15 +14,15 @@
#include <mutex> #include <mutex>
namespace ix namespace ix
{ {
class SocketAppleSSL : public Socket class SocketAppleSSL : public Socket
{ {
public: public:
SocketAppleSSL(int fd = -1); SocketAppleSSL(int fd = -1);
~SocketAppleSSL(); ~SocketAppleSSL();
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
const CancellationRequest& isCancellationRequested) final; const CancellationRequest& isCancellationRequested) final;

View File

@ -30,7 +30,7 @@ namespace
} }
} }
namespace ix namespace ix
{ {
// //
// This function can be cancelled every 50 ms // This function can be cancelled every 50 ms
@ -42,7 +42,7 @@ namespace ix
const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
errMsg = "no error"; errMsg = "no error";
int fd = socket(address->ai_family, int fd = socket(address->ai_family,
address->ai_socktype, address->ai_socktype,
address->ai_protocol); address->ai_protocol);
@ -72,7 +72,7 @@ namespace ix
errMsg = "Cancelled"; errMsg = "Cancelled";
return -1; return -1;
} }
// Use select to check the status of the new connection // Use select to check the status of the new connection
struct timeval timeout; struct timeval timeout;
timeout.tv_sec = 0; timeout.tv_sec = 0;
@ -179,7 +179,7 @@ namespace ix
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
#ifdef SO_NOSIGPIPE #ifdef SO_NOSIGPIPE
int value = 1; int value = 1;
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
(void *)&value, sizeof(value)); (void *)&value, sizeof(value));
#endif #endif
} }

View File

@ -12,7 +12,7 @@
struct addrinfo; struct addrinfo;
namespace ix namespace ix
{ {
class SocketConnect { class SocketConnect {
public: public:

View File

@ -18,12 +18,12 @@
#include <errno.h> #include <errno.h>
#define socketerrno errno #define socketerrno errno
namespace ix namespace ix
{ {
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false); std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd), SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
_ssl_connection(nullptr), _ssl_connection(nullptr),
_ssl_context(nullptr) _ssl_context(nullptr)
{ {
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
@ -80,7 +80,7 @@ namespace ix
return "OpenSSL failed - underlying BIO reported an I/O error"; return "OpenSSL failed - underlying BIO reported an I/O error";
} }
} }
else if (err == SSL_ERROR_SSL) else if (err == SSL_ERROR_SSL)
{ {
e = ERR_get_error(); e = ERR_get_error();
std::string errMsg("OpenSSL failed - "); std::string errMsg("OpenSSL failed - ");
@ -149,7 +149,7 @@ namespace ix
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
// Check server name // Check server name
bool hostname_verifies_ok = false; bool hostname_verifies_ok = false;
STACK_OF(GENERAL_NAME) *san_names = STACK_OF(GENERAL_NAME) *san_names =
(STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert, (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert,
NID_subject_alt_name, NULL, NULL); NID_subject_alt_name, NULL, NULL);
if (san_names) if (san_names)
@ -160,8 +160,8 @@ namespace ix
if (sk_name->type == GEN_DNS) if (sk_name->type == GEN_DNS)
{ {
char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName); char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName);
if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
checkHost(hostname, name)) checkHost(hostname, name))
{ {
hostname_verifies_ok = true; hostname_verifies_ok = true;
break; break;
@ -185,8 +185,8 @@ namespace ix
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char *cn = (char *)ASN1_STRING_data(cn_asn1); char *cn = (char *)ASN1_STRING_data(cn_asn1);
if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) && if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) &&
checkHost(hostname, cn)) checkHost(hostname, cn))
{ {
hostname_verifies_ok = true; hostname_verifies_ok = true;
} }
@ -205,7 +205,7 @@ namespace ix
return true; return true;
} }
bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg) bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
{ {
while (true) while (true)
{ {

View File

@ -17,15 +17,15 @@
#include <mutex> #include <mutex>
namespace ix namespace ix
{ {
class SocketOpenSSL : public Socket class SocketOpenSSL : public Socket
{ {
public: public:
SocketOpenSSL(int fd = -1); SocketOpenSSL(int fd = -1);
~SocketOpenSSL(); ~SocketOpenSSL();
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
const CancellationRequest& isCancellationRequested) final; const CancellationRequest& isCancellationRequested) final;

View File

@ -47,7 +47,7 @@
// link with ntdsapi.lib for DsMakeSpn function // link with ntdsapi.lib for DsMakeSpn function
#pragma comment(lib, "ntdsapi.lib") #pragma comment(lib, "ntdsapi.lib")
// The following function assumes that Winsock // The following function assumes that Winsock
// has already been initialized // has already been initialized
@ -59,7 +59,7 @@
# error("This file should only be built on Windows") # error("This file should only be built on Windows")
#endif #endif
namespace ix namespace ix
{ {
SocketSChannel::SocketSChannel() SocketSChannel::SocketSChannel()
{ {
@ -68,7 +68,7 @@ namespace ix
SocketSChannel::~SocketSChannel() SocketSChannel::~SocketSChannel()
{ {
} }
bool SocketSChannel::connect(const std::string& host, bool SocketSChannel::connect(const std::string& host,
@ -78,7 +78,7 @@ namespace ix
return Socket::connect(host, port, errMsg); return Socket::connect(host, port, errMsg);
} }
void SocketSChannel::secureSocket() void SocketSChannel::secureSocket()
{ {
// there will be a lot to do here ... // there will be a lot to do here ...

View File

@ -8,15 +8,15 @@
#include "IXSocket.h" #include "IXSocket.h"
namespace ix namespace ix
{ {
class SocketSChannel : public Socket class SocketSChannel : public Socket
{ {
public: public:
SocketSChannel(); SocketSChannel();
~SocketSChannel(); ~SocketSChannel();
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg) final; std::string& errMsg) final;
virtual void close() final; virtual void close() final;

View File

@ -14,7 +14,7 @@
#include <future> #include <future>
#include <string.h> #include <string.h>
namespace ix namespace ix
{ {
const int SocketServer::kDefaultPort(8080); const int SocketServer::kDefaultPort(8080);
const std::string SocketServer::kDefaultHost("127.0.0.1"); const std::string SocketServer::kDefaultHost("127.0.0.1");
@ -83,7 +83,7 @@ namespace ix
server.sin_family = AF_INET; server.sin_family = AF_INET;
server.sin_port = htons(_port); server.sin_port = htons(_port);
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected // Using INADDR_ANY trigger a pop-up box as binding to any address is detected
// by the osx firewall. We need to codesign the binary with a self-signed cert // by the osx firewall. We need to codesign the binary with a self-signed cert
// to allow that, but this is a bit of a pain. (this is what node or python would do). // to allow that, but this is a bit of a pain. (this is what node or python would do).
// //
@ -216,7 +216,7 @@ namespace ix
// Launch the handleConnection work asynchronously in its own thread. // Launch the handleConnection work asynchronously in its own thread.
// //
// the destructor of a future returned by std::async blocks, // the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop // so we need to declare it outside of this loop
f = std::async(std::launch::async, f = std::async(std::launch::async,
&SocketServer::handleConnection, &SocketServer::handleConnection,

View File

@ -16,7 +16,7 @@
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
namespace ix namespace ix
{ {
class SocketServer { class SocketServer {
public: public:

View File

@ -50,7 +50,7 @@ namespace ix
); );
} }
WebSocket::~WebSocket() WebSocket::~WebSocket()
{ {
stop(); stop();
} }
@ -135,7 +135,7 @@ namespace ix
} }
_onMessageCallback(WebSocket_MessageType_Open, "", 0, _onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(), WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers), WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()); WebSocketCloseInfo());
return status; return status;
@ -155,7 +155,7 @@ namespace ix
} }
_onMessageCallback(WebSocket_MessageType_Open, "", 0, _onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(), WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers), WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()); WebSocketCloseInfo());
return status; return status;
@ -184,7 +184,7 @@ namespace ix
using millis = std::chrono::duration<double, std::milli>; using millis = std::chrono::duration<double, std::milli>;
millis duration; millis duration;
while (true) while (true)
{ {
if (isConnected() || isClosing() || _stop || !_automaticReconnection) if (isConnected() || isClosing() || _stop || !_automaticReconnection)
{ {
@ -214,7 +214,7 @@ namespace ix
{ {
setThreadName(_url); setThreadName(_url);
while (true) while (true)
{ {
if (_stop) return; if (_stop) return;
@ -223,7 +223,7 @@ namespace ix
if (_stop) return; if (_stop) return;
// 2. Poll to see if there's any new data available // 2. Poll to see if there's any new data available
_ws.poll(); _ws.poll();
if (_stop) return; if (_stop) return;
@ -273,7 +273,7 @@ namespace ix
void WebSocket::setOnMessageCallback(const OnMessageCallback& callback) void WebSocket::setOnMessageCallback(const OnMessageCallback& callback)
{ {
_onMessageCallback = callback; _onMessageCallback = callback;
} }
void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback) void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
@ -294,10 +294,9 @@ namespace ix
} }
} }
WebSocketSendInfo WebSocket::send(const std::string& text, WebSocketSendInfo WebSocket::send(const std::string& text)
const OnProgressCallback& onProgressCallback)
{ {
return sendMessage(text, false, onProgressCallback); return sendMessage(text, false);
} }
WebSocketSendInfo WebSocket::ping(const std::string& text) WebSocketSendInfo WebSocket::ping(const std::string& text)
@ -309,9 +308,7 @@ namespace ix
return sendMessage(text, true); return sendMessage(text, true);
} }
WebSocketSendInfo WebSocket::sendMessage(const std::string& text, WebSocketSendInfo WebSocket::sendMessage(const std::string& text, bool ping)
bool ping,
const OnProgressCallback& onProgressCallback)
{ {
if (!isConnected()) return WebSocketSendInfo(false); if (!isConnected()) return WebSocketSendInfo(false);
@ -333,7 +330,7 @@ namespace ix
} }
else else
{ {
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback); webSocketSendInfo = _ws.sendBinary(text);
} }
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false); WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
@ -343,7 +340,7 @@ namespace ix
ReadyState WebSocket::getReadyState() const ReadyState WebSocket::getReadyState() const
{ {
switch (_ws.getReadyState()) switch (_ws.getReadyState())
{ {
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open; case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting; case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;

View File

@ -19,12 +19,11 @@
#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
{ {
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
enum ReadyState enum ReadyState
{ {
WebSocket_ReadyState_Connecting = 0, WebSocket_ReadyState_Connecting = 0,
WebSocket_ReadyState_Open = 1, WebSocket_ReadyState_Open = 1,
@ -79,7 +78,7 @@ namespace ix
using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>; using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
class WebSocket class WebSocket
{ {
public: public:
WebSocket(); WebSocket();
@ -98,8 +97,7 @@ 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();
@ -117,9 +115,7 @@ namespace ix
private: private:
WebSocketSendInfo sendMessage(const std::string& text, WebSocketSendInfo sendMessage(const std::string& text, bool ping);
bool ping,
const OnProgressCallback& callback = nullptr);
bool isConnected() const; bool isConnected() const;
bool isClosing() const; bool isClosing() const;

View File

@ -8,7 +8,7 @@
#include <string> #include <string>
namespace ix namespace ix
{ {
struct WebSocketErrorInfo struct WebSocketErrorInfo
{ {

View File

@ -16,7 +16,7 @@
#include <algorithm> #include <algorithm>
namespace ix namespace ix
{ {
WebSocketHandshake::WebSocketHandshake(std::atomic<bool>& requestInitCancellation, WebSocketHandshake::WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
std::shared_ptr<Socket> socket, std::shared_ptr<Socket> socket,
@ -171,7 +171,7 @@ namespace ix
std::string WebSocketHandshake::genRandomString(const int len) std::string WebSocketHandshake::genRandomString(const int len)
{ {
std::string alphanum = std::string alphanum =
"0123456789" "0123456789"
"ABCDEFGH" "ABCDEFGH"
"abcdefgh"; "abcdefgh";
@ -201,7 +201,7 @@ namespace ix
char line[256]; char line[256];
int i; int i;
while (true) while (true)
{ {
int colon = 0; int colon = 0;
@ -277,7 +277,7 @@ namespace ix
{ {
_requestInitCancellation = false; _requestInitCancellation = false;
auto isCancellationRequested = auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string errMsg; std::string errMsg;
@ -372,7 +372,7 @@ namespace ix
} }
// Check the value of the connection field // Check the value of the connection field
// Some websocket servers (Go/Gorilla?) send lowercase values for the // Some websocket servers (Go/Gorilla?) send lowercase values for the
// connection header, so do a case insensitive comparison // connection header, so do a case insensitive comparison
if (!insensitiveStringCompare(headers["connection"], "Upgrade")) if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
{ {
@ -418,7 +418,7 @@ namespace ix
// Set the socket to non blocking mode + other tweaks // Set the socket to non blocking mode + other tweaks
SocketConnect::configure(fd); SocketConnect::configure(fd);
auto isCancellationRequested = auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string remote = std::string("remote fd ") + std::to_string(fd); std::string remote = std::string("remote fd ") + std::to_string(fd);
@ -432,7 +432,7 @@ namespace ix
{ {
return sendErrorResponse(400, "Error reading HTTP request line"); return sendErrorResponse(400, "Error reading HTTP request line");
} }
// Validate request line (GET /foo HTTP/1.1\r\n) // Validate request line (GET /foo HTTP/1.1\r\n)
auto requestLine = parseRequestLine(line); auto requestLine = parseRequestLine(line);
auto method = std::get<0>(requestLine); auto method = std::get<0>(requestLine);

View File

@ -18,7 +18,7 @@
#include <memory> #include <memory>
#include <tuple> #include <tuple>
namespace ix namespace ix
{ {
struct WebSocketInitResult struct WebSocketInitResult
{ {

View File

@ -9,7 +9,7 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
namespace ix namespace ix
{ {
using WebSocketHttpHeaders = std::unordered_map<std::string, std::string>; using WebSocketHttpHeaders = std::unordered_map<std::string, std::string>;
} }

View File

@ -34,7 +34,7 @@
* - Reused zlib compression + decompression bits. * - Reused zlib compression + decompression bits.
* - Refactored to have 2 class for compression and decompression, to allow multi-threading * - Refactored to have 2 class for compression and decompression, to allow multi-threading
* and make sure that _compressBuffer is not shared between threads. * and make sure that _compressBuffer is not shared between threads.
* - Original code wasn't working for some reason, I had to add checks * - Original code wasn't working for some reason, I had to add checks
* for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers * for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers
* would start accepting receiving/decoding compressed messages. Original code was probably * would start accepting receiving/decoding compressed messages. Original code was probably
* modifying the passed in buffers before processing in enabled.hpp ? * modifying the passed in buffers before processing in enabled.hpp ?
@ -65,13 +65,13 @@ namespace ix
bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions) bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{ {
bool clientNoContextTakeover = bool clientNoContextTakeover =
perMessageDeflateOptions.getClientNoContextTakeover(); perMessageDeflateOptions.getClientNoContextTakeover();
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits(); uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits(); uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
return _compressor->init(deflateBits, clientNoContextTakeover) && return _compressor->init(deflateBits, clientNoContextTakeover) &&
_decompressor->init(inflateBits, clientNoContextTakeover); _decompressor->init(inflateBits, clientNoContextTakeover);
} }

View File

@ -37,7 +37,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
namespace ix namespace ix
{ {
class WebSocketPerMessageDeflateOptions; class WebSocketPerMessageDeflateOptions;
class WebSocketPerMessageDeflateCompressor; class WebSocketPerMessageDeflateCompressor;

View File

@ -14,7 +14,7 @@
namespace namespace
{ {
// The passed in size (4) is important, without it the string litteral // The passed in size (4) is important, without it the string litteral
// is treated as a char* and the null termination (\x00) makes it // is treated as a char* and the null termination (\x00) makes it
// look like an empty string. // look like an empty string.
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4); const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
@ -76,16 +76,16 @@ namespace ix
{ {
// //
// 7.2.1. Compression // 7.2.1. Compression
// //
// An endpoint uses the following algorithm to compress a message. // An endpoint uses the following algorithm to compress a message.
// //
// 1. Compress all the octets of the payload of the message using // 1. Compress all the octets of the payload of the message using
// DEFLATE. // DEFLATE.
// //
// 2. If the resulting data does not end with an empty DEFLATE block // 2. If the resulting data does not end with an empty DEFLATE block
// with no compression (the "BTYPE" bits are set to 00), append an // with no compression (the "BTYPE" bits are set to 00), append an
// empty DEFLATE block with no compression to the tail end. // empty DEFLATE block with no compression to the tail end.
// //
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. // 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
// After this step, the last octet of the compressed data contains // After this step, the last octet of the compressed data contains
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits // (possibly part of) the DEFLATE header bits with the "BTYPE" bits
@ -168,14 +168,14 @@ namespace ix
{ {
// //
// 7.2.2. Decompression // 7.2.2. Decompression
// //
// An endpoint uses the following algorithm to decompress a message. // An endpoint uses the following algorithm to decompress a message.
// //
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
// payload of the message. // payload of the message.
// //
// 2. Decompress the resulting data using DEFLATE. // 2. Decompress the resulting data using DEFLATE.
// //
std::string inFixed(in); std::string inFixed(in);
inFixed += kEmptyUncompressedBlock; inFixed += kEmptyUncompressedBlock;

View File

@ -10,7 +10,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
namespace ix namespace ix
{ {
class WebSocketPerMessageDeflateCompressor class WebSocketPerMessageDeflateCompressor
{ {

View File

@ -36,7 +36,7 @@ namespace ix
_serverMaxWindowBits = serverMaxWindowBits; _serverMaxWindowBits = serverMaxWindowBits;
} }
// //
// Four extension parameters are defined for "permessage-deflate" to // Four extension parameters are defined for "permessage-deflate" to
// help endpoints manage per-connection resource usage. // help endpoints manage per-connection resource usage.
// //
@ -88,9 +88,9 @@ namespace ix
int x; int x;
ss >> x; ss >> x;
// Sanitize values to be in the proper range [8, 15] in // Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values // case a server would give us bogus values
_serverMaxWindowBits = _serverMaxWindowBits =
std::min(maxServerMaxWindowBits, std::min(maxServerMaxWindowBits,
std::max(x, minServerMaxWindowBits)); std::max(x, minServerMaxWindowBits));
} }
@ -103,9 +103,9 @@ namespace ix
int x; int x;
ss >> x; ss >> x;
// Sanitize values to be in the proper range [8, 15] in // Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values // case a server would give us bogus values
_clientMaxWindowBits = _clientMaxWindowBits =
std::min(maxClientMaxWindowBits, std::min(maxClientMaxWindowBits,
std::max(x, minClientMaxWindowBits)); std::max(x, minClientMaxWindowBits));
} }
@ -162,7 +162,7 @@ namespace ix
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str) std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
{ {
std::string out(str); std::string out(str);
out.erase(std::remove_if(out.begin(), out.erase(std::remove_if(out.begin(),
out.end(), out.end(),
[](unsigned char x){ return std::isspace(x); }), [](unsigned char x){ return std::isspace(x); }),
out.end()); out.end());

View File

@ -8,7 +8,7 @@
#include <string> #include <string>
namespace ix namespace ix
{ {
class WebSocketPerMessageDeflateOptions class WebSocketPerMessageDeflateOptions
{ {

View File

@ -9,7 +9,7 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
namespace ix namespace ix
{ {
struct WebSocketSendInfo struct WebSocketSendInfo
{ {

View File

@ -14,7 +14,7 @@
#include <future> #include <future>
#include <string.h> #include <string.h>
namespace ix namespace ix
{ {
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
@ -65,7 +65,7 @@ namespace ix
auto status = webSocket->connectToSocket(fd, _handshakeTimeoutSecs); auto status = webSocket->connectToSocket(fd, _handshakeTimeoutSecs);
if (status.success) if (status.success)
{ {
// Process incoming messages and execute callbacks // Process incoming messages and execute callbacks
// until the connection is closed // until the connection is closed
webSocket->run(); webSocket->run();
} }

View File

@ -18,7 +18,7 @@
#include "IXWebSocket.h" #include "IXWebSocket.h"
#include "IXSocketServer.h" #include "IXSocketServer.h"
namespace ix namespace ix
{ {
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>; using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;

View File

@ -29,15 +29,12 @@
#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),
@ -48,7 +45,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()
@ -132,7 +129,7 @@ namespace ix
return result; return result;
} }
WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const
{ {
return _readyState; return _readyState;
} }
@ -156,7 +153,7 @@ namespace ix
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback) void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
{ {
_onCloseCallback = onCloseCallback; _onCloseCallback = onCloseCallback;
} }
// Only consider send time points for that computation. // Only consider send time points for that computation.
@ -176,7 +173,7 @@ namespace ix
// If (1) heartbeat is enabled, and (2) no data was received or // If (1) heartbeat is enabled, and (2) no data was received or
// 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 &&
heartBeatPeriodExceeded()) heartBeatPeriodExceeded())
{ {
std::stringstream ss; std::stringstream ss;
@ -185,31 +182,33 @@ namespace ix
return; return;
} }
while (true) while (true)
{ {
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size()); int N = (int) _rxbuf.size();
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || _rxbuf.resize(N + 1500);
_socket->getErrno() == EAGAIN)) ssize_t ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
{
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN)) {
_rxbuf.resize(N);
break; break;
} }
else if (ret <= 0) else if (ret <= 0)
{ {
_rxbuf.clear(); _rxbuf.resize(N);
_socket->close(); _socket->close();
setReadyState(CLOSED); setReadyState(CLOSED);
break; break;
} }
else else
{ {
_rxbuf.insert(_rxbuf.end(), _rxbuf.resize(N + ret);
_readbuf.begin(),
_readbuf.begin() + ret);
} }
} }
if (isSendBufferEmpty() && _readyState == CLOSING) if (isSendBufferEmpty() && _readyState == CLOSING)
{ {
_socket->close(); _socket->close();
setReadyState(CLOSED); setReadyState(CLOSED);
@ -283,7 +282,7 @@ namespace ix
// //
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback) void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
{ {
while (true) while (true)
{ {
wsheader_type ws; wsheader_type ws;
if (_rxbuf.size() < 2) return; /* Need at least 2 */ if (_rxbuf.size() < 2) return; /* Need at least 2 */
@ -295,7 +294,7 @@ namespace ix
ws.N0 = (data[1] & 0x7f); ws.N0 = (data[1] & 0x7f);
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */ if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */
// //
// Calculate payload length: // Calculate payload length:
// 0-125 mean the payload is that long. // 0-125 mean the payload is that long.
@ -333,7 +332,7 @@ namespace ix
// invalid payload length according to the spec. bail out // invalid payload length according to the spec. bail out
return; return;
} }
if (ws.mask) if (ws.mask)
{ {
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
@ -356,40 +355,22 @@ namespace ix
// We got a whole message, now do something with it: // We got a whole message, now do something with it:
if ( if (
ws.opcode == wsheader_type::TEXT_FRAME ws.opcode == wsheader_type::TEXT_FRAME
|| ws.opcode == wsheader_type::BINARY_FRAME || ws.opcode == wsheader_type::BINARY_FRAME
|| 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);
// Usual case. Small unfragmented messages _receivedData.clear();
//
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)
@ -439,33 +420,12 @@ 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 void WebSocketTransport::emitMessage(MessageKind messageKind,
{
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,
const std::string& message, const std::string& message,
const wsheader_type& ws, const wsheader_type& ws,
const OnMessageCallback& onMessageCallback) const OnMessageCallback& onMessageCallback)
@ -488,17 +448,15 @@ namespace ix
unsigned WebSocketTransport::getRandomUnsigned() unsigned WebSocketTransport::getRandomUnsigned()
{ {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto seconds = auto seconds =
std::chrono::duration_cast<std::chrono::seconds>( std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count(); now.time_since_epoch()).count();
return static_cast<unsigned>(seconds); return static_cast<unsigned>(seconds);
} }
WebSocketSendInfo WebSocketTransport::sendData( WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
wsheader_type::opcode_type type, const std::string& message,
const std::string& message, bool compress)
bool compress,
const OnProgressCallback& onProgressCallback)
{ {
if (_readyState == CLOSING || _readyState == CLOSED) if (_readyState == CLOSING || _readyState == CLOSED)
{ {
@ -515,81 +473,15 @@ namespace ix
if (compress) if (compress)
{ {
if (!_perMessageDeflate.compress(message, compressedMessage)) bool success = _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();
} }
// Common case for most message. No fragmentation required. uint64_t message_size = wireSize;
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] = {};
@ -602,13 +494,7 @@ 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] = type; header[0] = 0x80 | 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)
@ -625,7 +511,7 @@ namespace ix
header[4] = masking_key[2]; header[4] = masking_key[2];
header[5] = masking_key[3]; header[5] = masking_key[3];
} }
else if (message_size < 65536) else if (message_size < 65536)
{ {
header[1] = 126 | 0x80; header[1] = 126 | 0x80;
header[2] = (message_size >> 8) & 0xff; header[2] = (message_size >> 8) & 0xff;
@ -660,6 +546,8 @@ 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)
@ -668,13 +556,9 @@ namespace ix
return sendData(wsheader_type::PING, message, compress); return sendData(wsheader_type::PING, message, compress);
} }
WebSocketSendInfo WebSocketTransport::sendBinary( WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message)
const std::string& message,
const OnProgressCallback& onProgressCallback)
{ {
return sendData(wsheader_type::BINARY_FRAME, message, return sendData(wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate);
_enablePerMessageDeflate, onProgressCallback);
} }
void WebSocketTransport::sendOnSocket() void WebSocketTransport::sendOnSocket()
@ -685,7 +569,7 @@ namespace ix
{ {
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN)) _socket->getErrno() == EAGAIN))
{ {
break; break;

View File

@ -16,7 +16,6 @@
#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"
@ -24,9 +23,8 @@
#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
{ {
class Socket; class Socket;
@ -68,8 +66,7 @@ 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;
@ -79,6 +76,7 @@ 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;
@ -98,31 +96,13 @@ 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;
@ -131,14 +111,13 @@ 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;
// Used to cancel dns lookup + socket connect + http upgrade // Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation; std::atomic<bool> _requestInitCancellation;
// Optional Heartbeat // Optional Heartbeat
int _heartBeatPeriod; int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod; static const int kDefaultHeartBeatPeriod;
@ -150,18 +129,11 @@ namespace ix
bool heartBeatPeriodExceeded(); 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, void emitMessage(MessageKind messageKind,
bool fin,
std::string::const_iterator begin,
std::string::const_iterator end,
bool compress);
void emitMessage(MessageKind messageKind,
const std::string& message, const std::string& message,
const wsheader_type& ws, const wsheader_type& ws,
const OnMessageCallback& onMessageCallback); const OnMessageCallback& onMessageCallback);
@ -176,7 +148,5 @@ namespace ix
unsigned getRandomUnsigned(); unsigned getRandomUnsigned();
void unmaskReceiveBuffer(const wsheader_type& ws); void unmaskReceiveBuffer(const wsheader_type& ws);
std::string getMergedChunks() const;
}; };
} }

View File

@ -3,19 +3,12 @@
# #
all: run all: run
brew:
mkdir -p ws/build && (cd ws/build ; cmake .. ; make)
.PHONY: docker .PHONY: docker
docker: docker:
docker build -t broadcast_server:latest . docker build -t ws_connect:latest .
run: run: docker
docker run --cap-add sys_ptrace -it broadcast_server:latest bash docker run --cap-add sys_ptrace -it ws_connect: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)
@ -31,7 +24,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_chat make test # env TEST=websocket_server make test
# env TEST=heartbeat make test # env TEST=heartbeat make test
test: test:
python test/run.py python test/run.py

View File

@ -18,14 +18,13 @@ 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
../third_party/msgpack11/msgpack11.cpp msgpack11.cpp
IXDNSLookupTest.cpp IXDNSLookupTest.cpp
IXSocketTest.cpp IXSocketTest.cpp

View File

@ -16,7 +16,7 @@
# endif # endif
#endif #endif
#include "IXTest.h" #include "IXTest.h"
#include "catch.hpp" #include "catch.hpp"
using namespace ix; using namespace ix;

View File

@ -57,7 +57,7 @@ namespace ix
std::string generateSessionId() std::string generateSessionId()
{ {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto seconds = auto seconds =
std::chrono::duration_cast<std::chrono::seconds>( std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count(); now.time_since_epoch()).count();
@ -69,11 +69,11 @@ namespace ix
Logger() << msg; Logger() << msg;
} }
int getAnyFreePort() int getFreePort()
{ {
int defaultPort = 8090; int defaultPort = 8090;
int sockfd; int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{ {
log("Cannot compute a free port. socket error."); log("Cannot compute a free port. socket error.");
@ -117,23 +117,4 @@ namespace ix
return port; 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;
}
} }

View File

@ -34,7 +34,7 @@ namespace
int _port; int _port;
}; };
WebSocketClient::WebSocketClient(int port) WebSocketClient::WebSocketClient(int port)
: _port(port) : _port(port)
{ {
; ;
@ -56,7 +56,7 @@ namespace
{ {
std::stringstream ss; std::stringstream ss;
ss << "ws://localhost:" ss << "ws://localhost:"
<< _port << _port
<< "/"; << "/";
url = ss.str(); url = ss.str();
@ -64,7 +64,7 @@ namespace
_webSocket.setUrl(url); _webSocket.setUrl(url);
// The important bit for this test. // The important bit for this test.
// Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second // Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second
// a ping message will be sent by the client. // a ping message will be sent by the client.
_webSocket.setHeartBeatPeriod(1); _webSocket.setHeartBeatPeriod(1);

View File

@ -11,8 +11,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <vector> #include <queue>
#include <mutex>
#include <ixwebsocket/IXWebSocket.h> #include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h> #include <ixwebsocket/IXWebSocketServer.h>
#include "msgpack11.hpp" #include "msgpack11.hpp"
@ -40,11 +39,9 @@ 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;
@ -53,8 +50,7 @@ namespace
ix::WebSocket _webSocket; ix::WebSocket _webSocket;
std::vector<std::string> _receivedMessages; std::queue<std::string> _receivedQueue;
mutable std::mutex _mutex;
}; };
WebSocketChat::WebSocketChat(const std::string& user, WebSocketChat::WebSocketChat(const std::string& user,
@ -69,20 +65,7 @@ namespace
size_t WebSocketChat::getReceivedMessagesCount() const size_t WebSocketChat::getReceivedMessagesCount() const
{ {
std::lock_guard<std::mutex> lock(_mutex); return _receivedQueue.size();
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
@ -101,9 +84,8 @@ namespace
{ {
std::stringstream ss; std::stringstream ss;
ss << "ws://localhost:" ss << "ws://localhost:"
<< _port << _port
<< "/" << "/";
<< _user;
url = ss.str(); url = ss.str();
} }
@ -145,16 +127,10 @@ namespace
// as we do for the satori chat example. // as we do for the satori chat example.
// store text // store text
appendMessage(result.second); _receivedQueue.push(result.second);
std::string payload = result.second; ss << std::endl
if (payload.size() > 2000) << result.first << " > " << result.second
{
payload = "<message too large>";
}
ss << std::endl
<< result.first << " > " << payload
<< std::endl << std::endl
<< _user << " > "; << _user << " > ";
log(ss.str()); log(ss.str());
@ -293,36 +269,15 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
chatB.sendMessage("from B1"); chatB.sendMessage("from B1");
chatB.sendMessage("from B2"); chatB.sendMessage("from B2");
// Test large messages that needs to be broken into small fragments // Give us 1s for all messages to be received
size_t size = 1 * 1024 * 1024; // ~1Mb ix::msleep(1000);
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() == 3); REQUIRE(chatA.getReceivedMessagesCount() == 2);
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);

View File

@ -20,7 +20,7 @@ if osName == 'Windows':
else: else:
generator = '' generator = ''
make = 'make -j6' make = 'make -j6'
testBinary ='./ixwebsocket_unittest' testBinary ='./ixwebsocket_unittest'
sanitizersFlags = { sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On', 'asan': '-DSANITIZE_ADDRESS=On',

View File

@ -9,7 +9,7 @@
#include <ixwebsocket/IXSocket.h> #include <ixwebsocket/IXSocket.h>
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
ix::Socket::init(); // for Windows ix::Socket::init(); // for Windows

File diff suppressed because it is too large Load Diff

View File

@ -1,333 +0,0 @@
/// 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+

1
ws/.gitignore vendored
View File

@ -1 +0,0 @@
build

View File

@ -1,35 +0,0 @@
#
# 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)
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)

View File

@ -1,10 +0,0 @@
```
# Start receiver first
./ws receive ws://localhost:8080
# Sender
./ws send ws://localhost:8080 /file/to/path
# Server
./ws transfer # running on port 8080.
```

View File

@ -1,39 +0,0 @@
#!/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

View File

@ -1,22 +0,0 @@
/*
* 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;
}
}

View File

@ -1,15 +0,0 @@
/*
* 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);
}

View File

@ -1,75 +0,0 @@
/*
* 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();
}
}

View File

@ -1,17 +0,0 @@
/*
* 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();
}

View File

@ -1,68 +0,0 @@
/*
* 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;
}

View File

@ -1,251 +0,0 @@
/*
* 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;
}
}

View File

@ -1,296 +0,0 @@
/*
* 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;
}
}

View File

@ -1,72 +0,0 @@
/*
* 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;
}
}