2021-09-07 19:55:05 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#include "Library/IO/Buffer.hpp"
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#include <sqratFunction.h>
|
|
|
|
#include <concurrentqueue.h>
|
|
|
|
#include <civetweb.h>
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
namespace SqMod {
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
|
|
* WebSocket client implementation.
|
|
|
|
*/
|
|
|
|
struct WebSocketClient : public SqChainedInstances< WebSocketClient >
|
|
|
|
{
|
|
|
|
using Base = SqChainedInstances< WebSocketClient >;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* WebSocket frame.
|
|
|
|
*/
|
|
|
|
struct Frame
|
|
|
|
{
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Frame data.
|
|
|
|
*/
|
|
|
|
char * mData{nullptr};
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Frame data capacity.
|
|
|
|
*/
|
|
|
|
uint32_t mSize{0};
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Frame flags.
|
|
|
|
*/
|
|
|
|
int mFlags{0};
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Default constructor.
|
|
|
|
*/
|
|
|
|
Frame() = default;
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Explicit constructor.
|
|
|
|
*/
|
|
|
|
Frame(char * data, size_t size, int flags)
|
|
|
|
: mData(nullptr), mSize(static_cast< uint32_t >(size)), mFlags(flags)
|
|
|
|
{
|
|
|
|
// Do we need to allocate a buffer?
|
|
|
|
if (mSize != 0)
|
|
|
|
{
|
|
|
|
// Allocate the memory buffer.
|
|
|
|
mData = new char[mSize];
|
|
|
|
// Copy the data into the buffer we own
|
|
|
|
std::memcpy(mData, data, mSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Copy constructor (disabled).
|
|
|
|
*/
|
|
|
|
Frame(const Frame & o) = delete;
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Move constructor (disabled).
|
|
|
|
*/
|
|
|
|
Frame(Frame && o) noexcept = delete;
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Destructor.
|
|
|
|
*/
|
|
|
|
~Frame()
|
|
|
|
{
|
|
|
|
delete[] mData;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Copy assignment operator (disabled).
|
|
|
|
*/
|
|
|
|
Frame & operator = (const Frame & o) = delete;
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Move assignment operator (disabled).
|
|
|
|
*/
|
|
|
|
Frame & operator = (Frame && o) noexcept = delete;
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------------
|
|
|
|
* Forget about the associated memory buffer.
|
|
|
|
*/
|
|
|
|
void ForgetBuffer() noexcept
|
|
|
|
{
|
|
|
|
mData = nullptr;
|
|
|
|
mSize = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Smart frame pointer.
|
|
|
|
*/
|
|
|
|
using FramePtr = std::unique_ptr< Frame >;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Queue of frames written from other threads.
|
|
|
|
*/
|
|
|
|
using FrameQueue = moodycamel::ConcurrentQueue< FramePtr >;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Connection handle.
|
|
|
|
*/
|
|
|
|
struct mg_connection * mHandle{nullptr};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Queue of frames that must be processed.
|
|
|
|
*/
|
|
|
|
FrameQueue mQueue{1024};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Callback to invoke when receiving data.
|
|
|
|
*/
|
|
|
|
Function mOnData{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Callback to invoke when the socket is shutting down.
|
|
|
|
*/
|
|
|
|
Function mOnClose{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* User tag associated with this instance.
|
|
|
|
*/
|
|
|
|
String mTag{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* User data associated with this instance.
|
|
|
|
*/
|
|
|
|
LightObj mData{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Server port.
|
|
|
|
*/
|
|
|
|
int mPort{0};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Make a secure connection to server.
|
|
|
|
*/
|
|
|
|
bool mSecure{false};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Whether the connection is currently closing.
|
|
|
|
*/
|
|
|
|
std::atomic< bool > mClosing{false};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Server host to connect to, i.e. "echo.websocket.org" or "192.168.1.1" or "localhost".
|
|
|
|
*/
|
|
|
|
String mHost{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Server path you are trying to connect to, i.e. if connection to localhost/app, path should be "/app".
|
|
|
|
*/
|
|
|
|
String mPath{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Value of the Origin HTTP header.
|
|
|
|
*/
|
|
|
|
String mOrigin{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Extensions to include in the connection.
|
|
|
|
*/
|
|
|
|
String mExtensions{};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Default constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient()
|
|
|
|
: Base(), mHandle(nullptr), mQueue(1024), mOnData(), mOnClose(), mTag(), mData()
|
|
|
|
, mPort(0), mSecure(false), mClosing(false), mHost(), mPath(), mOrigin(), mExtensions()
|
|
|
|
{
|
|
|
|
ChainInstance(); // Remember this instance
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Explicit constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(StackStrF & host, uint16_t port, StackStrF & path)
|
|
|
|
: Base(), mHandle(nullptr), mQueue(1024), mOnData(), mOnClose(), mTag(), mData()
|
|
|
|
, mPort(port), mSecure(false), mClosing(false)
|
|
|
|
, mHost(host.mPtr, host.GetSize())
|
|
|
|
, mPath(path.mPtr, path.GetSize())
|
|
|
|
, mOrigin(), mExtensions()
|
|
|
|
{
|
|
|
|
ChainInstance(); // Remember this instance
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Explicit constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(StackStrF & host, uint16_t port, StackStrF & path, bool secure)
|
|
|
|
: Base(), mHandle(nullptr), mQueue(1024), mOnData(), mOnClose(), mTag(), mData()
|
|
|
|
, mPort(port), mSecure(secure), mClosing(false)
|
|
|
|
, mHost(host.mPtr, host.GetSize())
|
|
|
|
, mPath(path.mPtr, path.GetSize())
|
|
|
|
, mOrigin(), mExtensions()
|
|
|
|
{
|
|
|
|
ChainInstance(); // Remember this instance
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Explicit constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(StackStrF & host, uint16_t port, StackStrF & path, bool secure, StackStrF & origin)
|
|
|
|
: Base(), mHandle(nullptr), mQueue(1024), mOnData(), mOnClose(), mTag(), mData()
|
|
|
|
, mPort(port), mSecure(secure), mClosing(false)
|
|
|
|
, mHost(host.mPtr, host.GetSize())
|
|
|
|
, mPath(path.mPtr, path.GetSize())
|
|
|
|
, mOrigin(origin.mPtr, origin.GetSize())
|
|
|
|
, mExtensions()
|
|
|
|
{
|
|
|
|
ChainInstance(); // Remember this instance
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Explicit constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(StackStrF & host, uint16_t port, StackStrF & path, bool secure, StackStrF & origin, StackStrF & ext)
|
|
|
|
: Base(), mHandle(nullptr), mQueue(1024), mOnData(), mOnClose(), mTag(), mData()
|
|
|
|
, mPort(port), mSecure(secure), mClosing(false)
|
|
|
|
, mHost(host.mPtr, host.GetSize())
|
|
|
|
, mPath(path.mPtr, path.GetSize())
|
|
|
|
, mOrigin(origin.mPtr, origin.GetSize())
|
|
|
|
, mExtensions(ext.mPtr, ext.GetSize())
|
|
|
|
{
|
|
|
|
ChainInstance(); // Remember this instance
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Copy constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(const WebSocketClient &) = delete;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Move constructor.
|
|
|
|
*/
|
|
|
|
WebSocketClient(WebSocketClient &&) = delete;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Destructor. Closes the connection.
|
|
|
|
*/
|
|
|
|
~WebSocketClient()
|
|
|
|
{
|
|
|
|
// Is there a connection left to close?
|
|
|
|
if (mHandle != nullptr)
|
|
|
|
{
|
|
|
|
Close();
|
|
|
|
}
|
|
|
|
// Forget about this instance
|
|
|
|
UnchainInstance();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Copy assignment operator.
|
|
|
|
*/
|
|
|
|
WebSocketClient & operator = (const WebSocketClient &) = delete;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Move assignment operator.
|
|
|
|
*/
|
|
|
|
WebSocketClient & operator = (WebSocketClient &&) = delete;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Return whether the associated connection handle is valid.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD bool IsValid() const
|
|
|
|
{
|
|
|
|
return mHandle != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Return whether the associated connection is closing.
|
|
|
|
* This is only valid inside the OnClose callback.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD bool IsClosing() const
|
|
|
|
{
|
|
|
|
return mClosing.load();
|
|
|
|
}
|
|
|
|
|
2021-09-10 21:44:55 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated user tag.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD const String & GetTag() const
|
|
|
|
{
|
|
|
|
return mTag;
|
|
|
|
}
|
|
|
|
|
2021-09-07 19:55:05 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated user tag.
|
|
|
|
*/
|
|
|
|
void SetTag(StackStrF & tag)
|
|
|
|
{
|
|
|
|
if (tag.mLen > 0)
|
|
|
|
{
|
|
|
|
mTag.assign(tag.mPtr, static_cast< size_t >(tag.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mTag.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated user tag.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyTag(StackStrF & tag)
|
|
|
|
{
|
|
|
|
SetTag(tag);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated user data.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD LightObj & GetData()
|
|
|
|
{
|
|
|
|
return mData;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated user data.
|
|
|
|
*/
|
|
|
|
void SetData(LightObj & data)
|
|
|
|
{
|
|
|
|
mData = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated user data.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyData(LightObj & data)
|
|
|
|
{
|
|
|
|
mData = data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Make sure a connection exists.
|
|
|
|
*/
|
|
|
|
void Validate() const
|
|
|
|
{
|
|
|
|
if (mHandle == nullptr)
|
|
|
|
{
|
|
|
|
STHROWF("No connection was made to server ({}:{}{})", mHost, mPort, mPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Return a valid connection.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD struct mg_connection * Valid() const
|
|
|
|
{
|
|
|
|
Validate();
|
|
|
|
return mHandle;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Make sure a connection does not exist.
|
|
|
|
*/
|
|
|
|
void Invalid() const
|
|
|
|
{
|
|
|
|
if (mHandle != nullptr)
|
|
|
|
{
|
|
|
|
STHROWF("Connection already made to server ({}:{}{})", mHost, mPort, mPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated server host.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD const String & GetHost() const
|
|
|
|
{
|
|
|
|
return mHost;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server host.
|
|
|
|
*/
|
|
|
|
void SetHost(StackStrF & host)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
// Is there a valid host?
|
|
|
|
if (host.mLen > 0)
|
|
|
|
{
|
|
|
|
mHost.assign(host.mPtr, static_cast< size_t >(host.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mHost.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server host.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyHost(StackStrF & host)
|
|
|
|
{
|
|
|
|
SetHost(host);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated server path.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD const String & GetPath() const
|
|
|
|
{
|
|
|
|
return mPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server path.
|
|
|
|
*/
|
|
|
|
void SetPath(StackStrF & path)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
// Is there a valid path?
|
|
|
|
if (path.mLen > 0)
|
|
|
|
{
|
|
|
|
mPath.assign(path.mPtr, static_cast< size_t >(path.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mPath.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server path.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyPath(StackStrF & path)
|
|
|
|
{
|
|
|
|
SetPath(path);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated origin value.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD const String & GetOrigin() const
|
|
|
|
{
|
|
|
|
return mOrigin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated origin value.
|
|
|
|
*/
|
|
|
|
void SetOrigin(StackStrF & origin)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
// Is there a valid origin?
|
|
|
|
if (origin.mLen > 0)
|
|
|
|
{
|
|
|
|
mOrigin.assign(origin.mPtr, static_cast< size_t >(origin.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mOrigin.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated origin value.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyOrigin(StackStrF & origin)
|
|
|
|
{
|
|
|
|
SetOrigin(origin);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated connection extensions.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD const String & GetExtensions() const
|
|
|
|
{
|
|
|
|
return mExtensions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated connection extensions.
|
|
|
|
*/
|
|
|
|
void SetExtensions(StackStrF & ext)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
// Is there a valid extension?
|
|
|
|
if (ext.mLen > 0)
|
|
|
|
{
|
|
|
|
mExtensions.assign(ext.mPtr, static_cast< size_t >(ext.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mExtensions.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated connection extensions.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyExtensions(StackStrF & ext)
|
|
|
|
{
|
|
|
|
SetExtensions(ext);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated server port number.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD int GetPort() const
|
|
|
|
{
|
|
|
|
return mPort;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server port number.
|
|
|
|
*/
|
|
|
|
void SetPort(int port)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
// Is there a valid port?
|
|
|
|
if (port < 0 || port > 65535)
|
|
|
|
{
|
|
|
|
STHROWF("Invalid port number: {0} < 0 || {0} > 65535", port);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mPort = port;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated server port number.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplyPort(int port)
|
|
|
|
{
|
|
|
|
SetPort(port);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated SSL status.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD bool GetSecure() const
|
|
|
|
{
|
|
|
|
return mSecure;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated SSL status.
|
|
|
|
*/
|
|
|
|
void SetSecure(bool secure)
|
|
|
|
{
|
|
|
|
Invalid();
|
|
|
|
mSecure = secure;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated SSL status.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ApplySecure(bool secure)
|
|
|
|
{
|
|
|
|
SetSecure(secure);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated data callback.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD Function & GetOnData()
|
|
|
|
{
|
|
|
|
return mOnData;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated data callback.
|
|
|
|
*/
|
|
|
|
void SetOnData(Function & cb)
|
|
|
|
{
|
|
|
|
mOnData = cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated data callback.
|
|
|
|
*/
|
|
|
|
WebSocketClient & BindOnData(Function & cb)
|
|
|
|
{
|
|
|
|
mOnData = cb;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Retrieve the associated close callback.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD Function & GetOnClose()
|
|
|
|
{
|
|
|
|
return mOnClose;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated close callback.
|
|
|
|
*/
|
|
|
|
void SetOnClose(Function & cb)
|
|
|
|
{
|
|
|
|
mOnClose = cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Modify the associated close callback.
|
|
|
|
*/
|
|
|
|
WebSocketClient & BindOnClose(Function & cb)
|
|
|
|
{
|
|
|
|
mOnClose = cb;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Connect to a web-socket as a client.
|
|
|
|
*/
|
|
|
|
WebSocketClient & Connect();
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Connect to a web-socket as a client with specific extensions.
|
|
|
|
*/
|
|
|
|
WebSocketClient & ConnectExt();
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Close client connection.
|
|
|
|
*/
|
|
|
|
void Close()
|
|
|
|
{
|
|
|
|
mg_close_connection(Valid());
|
|
|
|
// Prevent further use
|
|
|
|
mHandle = nullptr;
|
|
|
|
// Process pending events
|
|
|
|
Process(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends the contents of the given buffer through the socket as a single frame.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD SQInteger SendOpCode(SqBuffer & buf, SQInteger opcode)
|
|
|
|
{
|
|
|
|
return mg_websocket_client_write(Valid(), static_cast< int >(opcode), nullptr, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends the contents of the given buffer through the socket as a single frame.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD SQInteger SendBuffer(SqBuffer & buf, SQInteger opcode)
|
|
|
|
{
|
|
|
|
return mg_websocket_client_write(Valid(), static_cast< int >(opcode), buf.Valid().Data(), buf.Valid().Position());
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends the contents of the given string through the socket as a single frame.
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD SQInteger SendString(SQInteger opcode, StackStrF & str)
|
|
|
|
{
|
|
|
|
return mg_websocket_client_write(Valid(), static_cast< int >(opcode), str.mPtr, static_cast< size_t >(str.mLen));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Process received data.
|
|
|
|
*/
|
|
|
|
void Process(bool force = false)
|
|
|
|
{
|
|
|
|
// Is there a valid connection?
|
|
|
|
if (mHandle == nullptr && !force)
|
|
|
|
{
|
|
|
|
return; // No point in going forward
|
|
|
|
}
|
|
|
|
FramePtr frame;
|
|
|
|
// See if connection is closing
|
|
|
|
const bool closing = mClosing.load();
|
|
|
|
// Is the connection closing?
|
|
|
|
if (closing)
|
|
|
|
{
|
|
|
|
mHandle = nullptr; // Prevent further use
|
|
|
|
}
|
|
|
|
// Retrieve each frame individually and process it
|
|
|
|
for (size_t count = mQueue.size_approx(), n = 0; n <= count; ++n)
|
|
|
|
{
|
|
|
|
// Try to get a frame from the queue
|
|
|
|
if (mQueue.try_dequeue(frame) && !mOnData.IsNull())
|
|
|
|
{
|
|
|
|
// Obtain a buffer from the frame
|
|
|
|
Buffer b(frame->mData, frame->mSize, frame->mSize, Buffer::OwnIt{});
|
|
|
|
// Backup the frame size before forgetting about it
|
|
|
|
const SQInteger size = static_cast< SQInteger >(frame->mSize);
|
|
|
|
// Take ownership of the memory
|
|
|
|
frame->ForgetBuffer();
|
|
|
|
// Transform the buffer into a script object
|
|
|
|
LightObj obj(SqTypeIdentity< SqBuffer >{}, SqVM(), std::move(b));
|
|
|
|
// Forward the event to the callback
|
|
|
|
mOnData.Execute(obj, size, frame->mFlags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Is the server closing the connection?
|
|
|
|
if (closing && !mOnClose.IsNull())
|
|
|
|
{
|
|
|
|
mOnClose.Execute(); // Let the user know
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Used internally to release script resources, if any. The VM is about to be closed.
|
|
|
|
* If you don't close the connection yourself don't care about what is received after this.
|
|
|
|
*/
|
|
|
|
void Terminate()
|
|
|
|
{
|
|
|
|
// Process pending data
|
|
|
|
Process(true);
|
|
|
|
// Release callbacks
|
|
|
|
mOnData.Release();
|
|
|
|
mOnClose.Release();
|
|
|
|
// Release user data
|
|
|
|
mData.Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Callback for handling data received from the server
|
|
|
|
*/
|
|
|
|
int DataHandler(int flags, char * data, size_t data_len) noexcept;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Callback for handling a close message received from the server.
|
|
|
|
*/
|
|
|
|
void CloseHandler() noexcept;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Proxy for DataHandler()
|
|
|
|
*/
|
|
|
|
static int DataHandler_(struct mg_connection * SQ_UNUSED_ARG(c), int f, char * d, size_t n, void * u) noexcept
|
|
|
|
{
|
|
|
|
return reinterpret_cast< WebSocketClient * >(u)->DataHandler(f, d, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Proxy for CloseHandler();
|
|
|
|
*/
|
|
|
|
static void CloseHandler_(const struct mg_connection * SQ_UNUSED_ARG(c), void * u) noexcept
|
|
|
|
{
|
|
|
|
reinterpret_cast< WebSocketClient * >(u)->CloseHandler();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // Namespace:: SqMod
|