2021-01-30 23:16:10 +01:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#include "Core/Common.hpp"
|
2021-09-05 11:05:38 +02:00
|
|
|
#include "Library/IO/Buffer.hpp"
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#include <vector>
|
2021-09-05 17:15:02 +02:00
|
|
|
#include <utility>
|
|
|
|
#include <algorithm>
|
2021-09-05 11:05:38 +02:00
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
2021-09-05 17:15:02 +02:00
|
|
|
#include <Poco/Thread.h>
|
|
|
|
#include <Poco/AutoPtr.h>
|
|
|
|
#include <Poco/Runnable.h>
|
|
|
|
#include <Poco/Observer.h>
|
|
|
|
#include <Poco/NObserver.h>
|
2021-09-05 11:05:38 +02:00
|
|
|
#include <Poco/Net/HTTPRequest.h>
|
|
|
|
#include <Poco/Net/HTTPResponse.h>
|
|
|
|
#include <Poco/Net/HTTPMessage.h>
|
|
|
|
#include <Poco/Net/HTTPClientSession.h>
|
2021-09-05 17:15:02 +02:00
|
|
|
#include <Poco/Net/SocketAcceptor.h>
|
|
|
|
#include <Poco/Net/SocketReactor.h>
|
2021-09-05 11:05:38 +02:00
|
|
|
#include <Poco/Net/WebSocket.h>
|
2021-01-30 23:16:10 +01:00
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
namespace SqMod {
|
|
|
|
|
2021-09-05 11:05:38 +02:00
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
|
|
* Implements a WebSocket according to the WebSocket protocol specification in RFC 6455.
|
|
|
|
*/
|
|
|
|
struct WsClient
|
|
|
|
{
|
2021-09-05 17:15:02 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Flags received in the last call to Recv[String]Frame() (will be overwritten on next call).
|
|
|
|
*/
|
|
|
|
int mFlags{0};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Return value from the last call to Recv[String]Frame() (will be overwritten on next call).
|
|
|
|
* A return value of 0, with flags also 0, means that the peer has shut down or closed the connection.
|
|
|
|
* A return value of 0, with non-zero flags, indicates an reception of an empty frame (e.g., in case of a PING).
|
|
|
|
*/
|
|
|
|
int mState{0};
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Receiving buffer instance.
|
|
|
|
*/
|
2021-09-07 16:08:58 +02:00
|
|
|
Poco::Buffer< char > mBuffer{0};
|
2021-09-05 17:15:02 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* User tag associated with this instance.
|
|
|
|
*/
|
2021-09-07 16:08:58 +02:00
|
|
|
String mTag{};
|
2021-09-05 17:15:02 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* User data associated with this instance.
|
|
|
|
*/
|
2021-09-07 16:08:58 +02:00
|
|
|
LightObj mData{};
|
2021-09-05 17:15:02 +02:00
|
|
|
|
2021-09-05 11:05:38 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* HTTP client session instance.
|
|
|
|
*/
|
|
|
|
Poco::Net::HTTPClientSession mClient;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* HTTP request instance.
|
|
|
|
*/
|
|
|
|
Poco::Net::HTTPRequest mRequest;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* HTTP response instance.
|
|
|
|
*/
|
|
|
|
Poco::Net::HTTPResponse mResponse;
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* WebSocket instance.
|
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
Poco::Net::WebSocket mSocket;
|
2021-09-05 11:05:38 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
2021-09-05 17:15:02 +02:00
|
|
|
* Base constructor.
|
2021-09-05 11:05:38 +02:00
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
WsClient(StackStrF & host, uint16_t port, StackStrF & uri)
|
|
|
|
: mFlags(0), mState(0), mBuffer(0), mTag(), mData()
|
|
|
|
, mClient(host.ToStr(), port)
|
|
|
|
, mRequest(Poco::Net::HTTPRequest::HTTP_GET, uri.ToStr(), Poco::Net::HTTPRequest::HTTP_1_1)
|
|
|
|
, mResponse()
|
|
|
|
, mSocket(mClient, mRequest, mResponse)
|
|
|
|
{
|
|
|
|
mSocket.setBlocking(false); // Disable blocking
|
|
|
|
}
|
2021-09-05 11:05:38 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
2021-09-05 17:15:02 +02:00
|
|
|
* Retrieve the associated user tag.
|
2021-09-05 11:05:38 +02:00
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
SQMOD_NODISCARD const String & GetTag() const
|
|
|
|
{
|
|
|
|
return mTag;
|
|
|
|
}
|
2021-09-05 11:05:38 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
2021-09-05 17:15:02 +02:00
|
|
|
* Modify the associated user tag.
|
2021-09-05 11:05:38 +02:00
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
void SetTag(StackStrF & tag)
|
|
|
|
{
|
|
|
|
if (tag.mLen > 0)
|
|
|
|
{
|
|
|
|
mTag.assign(tag.mPtr, static_cast< size_t >(tag.mLen));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mTag.clear();
|
|
|
|
}
|
|
|
|
}
|
2021-09-05 11:05:38 +02:00
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
2021-09-05 17:15:02 +02:00
|
|
|
* Modify the associated user tag.
|
2021-09-05 11:05:38 +02:00
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
WsClient & ApplyTag(StackStrF & tag)
|
2021-09-05 11:05:38 +02:00
|
|
|
{
|
2021-09-05 17:15:02 +02:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
WsClient & ApplyData(LightObj & data)
|
|
|
|
{
|
|
|
|
mData = data;
|
|
|
|
return *this;
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends a Close control frame to the server end of the connection to initiate an orderly shutdown of the connection.
|
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
void Shutdown()
|
|
|
|
{
|
|
|
|
mSocket.shutdown();
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends a Close control frame to the server end of the connection to initiate an orderly shutdown of the connection.
|
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
void ShutdownWith(SQInteger code, StackStrF & msg)
|
|
|
|
{
|
|
|
|
mSocket.shutdown(static_cast< uint16_t >(code), msg.ToStr());
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends the contents of the given buffer through the socket as a single frame.
|
|
|
|
* Returns the number of bytes sent, which may be less than the number of bytes specified.
|
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
SQInteger SendFrame(SqBuffer & buf, SQInteger flags)
|
|
|
|
{
|
|
|
|
return mSocket.sendFrame(buf.Valid().Data(), static_cast< int >(buf.Valid().Position()), static_cast< int >(flags));
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sends the contents of the given string through the socket as a single frame.
|
|
|
|
* Returns the number of bytes sent, which may be less than the number of bytes specified.
|
|
|
|
*/
|
2021-09-05 17:15:02 +02:00
|
|
|
SQInteger SendStringFrame(SQInteger flags, StackStrF & str)
|
|
|
|
{
|
|
|
|
return mSocket.sendFrame(str.mPtr, static_cast< int >(str.mLen), static_cast< int >(flags));
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Receives a frame from the socket and return it as a buffer.
|
|
|
|
* The frame's payload size must not exceed the maximum payload size set with SetMaxPayloadSize().
|
|
|
|
*/
|
|
|
|
LightObj RecvFrame()
|
|
|
|
{
|
|
|
|
// Attempt to receive data
|
2021-09-05 12:27:19 +02:00
|
|
|
try {
|
2021-09-05 17:15:02 +02:00
|
|
|
mState = mSocket.receiveFrame(mBuffer, mFlags);
|
2021-09-05 12:27:19 +02:00
|
|
|
} catch (const Poco::TimeoutException &) {
|
2021-09-05 12:40:21 +02:00
|
|
|
mState = mFlags = 0; // Make sure these don't indicate otherwise
|
2021-09-05 12:27:19 +02:00
|
|
|
return LightObj{}; // We handle timeout so we can be non blocking
|
|
|
|
}
|
2021-09-05 11:05:38 +02:00
|
|
|
// If something was returned
|
|
|
|
if (mState != 0)
|
|
|
|
{
|
|
|
|
// Fetch buffer information
|
|
|
|
const auto cap = static_cast< Buffer::SzType >(mBuffer.capacityBytes());
|
|
|
|
const auto len = static_cast< Buffer::SzType >(mBuffer.sizeBytes());
|
|
|
|
// Steal buffer memory
|
|
|
|
Buffer::Pointer ptr = mBuffer.steal();
|
|
|
|
// Construct our buffer
|
|
|
|
Buffer b(ptr, cap, len, Buffer::OwnIt{});
|
|
|
|
// Transform it into a script object and return it
|
|
|
|
return LightObj(SqTypeIdentity< SqBuffer >{}, SqVM(), std::move(b));
|
|
|
|
}
|
|
|
|
// Default to null
|
|
|
|
return LightObj{};
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Receives a frame from the socket and return it as a string.
|
|
|
|
* The frame's payload size must not exceed the maximum payload size set with SetMaxPayloadSize().
|
|
|
|
*/
|
|
|
|
LightObj RecvStringFrame()
|
|
|
|
{
|
|
|
|
// Attempt to receive data
|
2021-09-05 12:27:19 +02:00
|
|
|
try {
|
2021-09-05 17:15:02 +02:00
|
|
|
mState = mSocket.receiveFrame(mBuffer, mFlags);
|
2021-09-05 12:27:19 +02:00
|
|
|
} catch (const Poco::TimeoutException &) {
|
2021-09-05 12:40:21 +02:00
|
|
|
mState = mFlags = 0; // Make sure these don't indicate otherwise
|
2021-09-05 12:27:19 +02:00
|
|
|
return LightObj{}; // We handle timeout so we can be non blocking
|
|
|
|
}
|
2021-09-05 11:05:38 +02:00
|
|
|
// If something was returned
|
|
|
|
if (mState != 0)
|
|
|
|
{
|
|
|
|
// Create a string with buffer contents
|
2021-09-05 11:30:49 +02:00
|
|
|
LightObj obj(const_cast< const SQChar * >(mBuffer.begin()), static_cast< SQInteger >(mBuffer.sizeBytes()), SqVM());
|
2021-09-05 11:05:38 +02:00
|
|
|
// Discard buffer contents for next request
|
|
|
|
mBuffer.resize(0);
|
|
|
|
// Return the string object
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
// Default to null
|
|
|
|
return LightObj{};
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:27:19 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Receives a frame from the socket and return it as a buffer. Only invokes callback if response is valid.
|
|
|
|
* The frame's payload size must not exceed the maximum payload size set with SetMaxPayloadSize().
|
|
|
|
*/
|
|
|
|
SQInteger RecvFrameIn(Function & cb)
|
|
|
|
{
|
|
|
|
auto obj = RecvFrame();
|
|
|
|
// Only invoke the callback if we have a valid response
|
|
|
|
if (mState != 0 || mFlags != 0)
|
|
|
|
{
|
|
|
|
cb(obj, mState, mFlags);
|
|
|
|
}
|
|
|
|
// Return result
|
|
|
|
return mState;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Receives a frame from the socket and return it as a string. Only invokes callback if response is valid.
|
|
|
|
* The frame's payload size must not exceed the maximum payload size set with SetMaxPayloadSize().
|
|
|
|
*/
|
|
|
|
SQInteger RecvStringFrameIn(Function & cb)
|
|
|
|
{
|
|
|
|
auto obj = RecvStringFrame();
|
|
|
|
// Only invoke the callback if we have data response
|
|
|
|
if (mState != 0 || mFlags != 0)
|
|
|
|
{
|
|
|
|
cb(obj, mState, mFlags);
|
|
|
|
}
|
|
|
|
// Return result
|
|
|
|
return mState;
|
|
|
|
}
|
|
|
|
|
2021-09-05 11:05:38 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Sets the maximum payload size for RecvFrame(). The default is std::numeric_limits<int>::max().
|
|
|
|
*/
|
|
|
|
WsClient & SetMaxPayloadSize(SQInteger size)
|
|
|
|
{
|
2021-09-05 17:15:02 +02:00
|
|
|
mSocket.setMaxPayloadSize(static_cast< int >(size));
|
2021-09-05 11:05:38 +02:00
|
|
|
return *this;
|
|
|
|
}
|
2021-01-30 23:16:10 +01:00
|
|
|
|
2021-09-05 11:05:38 +02:00
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
|
|
* Returns the maximum payload size for RecvFrame(). The default is std::numeric_limits<int>::max().
|
|
|
|
*/
|
|
|
|
SQMOD_NODISCARD SQInteger GetMaxPayloadSize() const
|
|
|
|
{
|
2021-09-05 17:15:02 +02:00
|
|
|
return mSocket.getMaxPayloadSize();
|
2021-09-05 11:05:38 +02:00
|
|
|
}
|
|
|
|
};
|
2021-01-30 23:16:10 +01:00
|
|
|
|
|
|
|
} // Namespace:: SqMod
|