mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
Basic web-socket client implementation.
This commit is contained in:
parent
d787803fd8
commit
f3d4dab454
@ -1,18 +1,91 @@
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
#include "PocoLib/Net.hpp"
|
#include "PocoLib/Net.hpp"
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
#include <sqratConst.h>
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
namespace SqMod {
|
namespace SqMod {
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
SQMOD_DECL_TYPENAME(SqWsClient, _SC("SqWsClient"))
|
||||||
|
|
||||||
// ================================================================================================
|
// ================================================================================================
|
||||||
void Register_POCO_Net(HSQUIRRELVM vm, Table &)
|
void Register_POCO_Net(HSQUIRRELVM vm, Table &)
|
||||||
{
|
{
|
||||||
//Table ns(vm);
|
Table ns(vm);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
//RootTable(vm).Bind(_SC("SqNet"), ns);
|
ns.Bind(_SC("WsClient"),
|
||||||
|
Class< WsClient, NoCopy< WsClient > >(ns.GetVM(), SqWsClient::Str)
|
||||||
|
// Constructors
|
||||||
|
.Ctor< StackStrF &, uint16_t, StackStrF & >()
|
||||||
|
// Meta-methods
|
||||||
|
.SquirrelFunc(_SC("_typename"), &SqWsClient::Fn)
|
||||||
|
// Member Variables
|
||||||
|
.Var(_SC("Flags"), &WsClient::mFlags)
|
||||||
|
.Var(_SC("State"), &WsClient::mState)
|
||||||
|
// Properties
|
||||||
|
.Prop(_SC("MaxPayloadSize"), &WsClient::GetMaxPayloadSize, &WsClient::SetMaxPayloadSize)
|
||||||
|
// Member Methods
|
||||||
|
.Func(_SC("Shutdown"), &WsClient::Shutdown)
|
||||||
|
.FmtFunc(_SC("ShutdownWith"), &WsClient::ShutdownWith)
|
||||||
|
.Func(_SC("SendFrame"), &WsClient::SendFrame)
|
||||||
|
.FmtFunc(_SC("SendStringFrame"), &WsClient::SendStringFrame)
|
||||||
|
.Func(_SC("RecvFrame"), &WsClient::RecvFrame)
|
||||||
|
.Func(_SC("RecvStringFrame"), &WsClient::RecvStringFrame)
|
||||||
|
);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
RootTable(vm).Bind(_SC("SqNet"), ns);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
ConstTable(vm).Enum(_SC("SqWsFrameFlags"), Enumeration(vm)
|
||||||
|
.Const(_SC("FIN"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_FLAG_FIN))
|
||||||
|
.Const(_SC("RSV1"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_FLAG_RSV1))
|
||||||
|
.Const(_SC("RSV2"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_FLAG_RSV2))
|
||||||
|
.Const(_SC("RSV3"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_FLAG_RSV3))
|
||||||
|
);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
ConstTable(vm).Enum(_SC("SqWsFrameOpcodes"), Enumeration(vm)
|
||||||
|
.Const(_SC("CONT"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_CONT))
|
||||||
|
.Const(_SC("TEXT"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_TEXT))
|
||||||
|
.Const(_SC("BINARY"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_BINARY))
|
||||||
|
.Const(_SC("CLOSE"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_CLOSE))
|
||||||
|
.Const(_SC("PING"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_PING))
|
||||||
|
.Const(_SC("PONG"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_PONG))
|
||||||
|
.Const(_SC("BITMASK"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_BITMASK))
|
||||||
|
.Const(_SC("SETRAW"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_OP_SETRAW))
|
||||||
|
);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
ConstTable(vm).Enum(_SC("SqWsSendFlags"), Enumeration(vm)
|
||||||
|
.Const(_SC("TEXT"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_TEXT))
|
||||||
|
.Const(_SC("BINARY"), static_cast< SQInteger >(Poco::Net::WebSocket::FRAME_BINARY))
|
||||||
|
);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
ConstTable(vm).Enum(_SC("SqWsStatusCodes"), Enumeration(vm)
|
||||||
|
.Const(_SC("NormalClose"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_NORMAL_CLOSE))
|
||||||
|
.Const(_SC("EndpointGoingAway"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ENDPOINT_GOING_AWAY))
|
||||||
|
.Const(_SC("ProtocolError"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_PROTOCOL_ERROR))
|
||||||
|
.Const(_SC("PayloadNotAcceptable"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_PAYLOAD_NOT_ACCEPTABLE))
|
||||||
|
.Const(_SC("Reserved"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_RESERVED))
|
||||||
|
.Const(_SC("ReservedNoStatusCode"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_RESERVED_NO_STATUS_CODE))
|
||||||
|
.Const(_SC("ReservedAbnormalClose"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_RESERVED_ABNORMAL_CLOSE))
|
||||||
|
.Const(_SC("MalformedPayload"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_MALFORMED_PAYLOAD))
|
||||||
|
.Const(_SC("PolicyViolation"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_POLICY_VIOLATION))
|
||||||
|
.Const(_SC("PayloadTooBig"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_PAYLOAD_TOO_BIG))
|
||||||
|
.Const(_SC("ExtensionRequired"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_EXTENSION_REQUIRED))
|
||||||
|
.Const(_SC("UnexpectedCondition"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_UNEXPECTED_CONDITION))
|
||||||
|
.Const(_SC("ReservedTlsFailure"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_RESERVED_TLS_FAILURE))
|
||||||
|
);
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
ConstTable(vm).Enum(_SC("SqWsSendFlags"), Enumeration(vm)
|
||||||
|
.Const(_SC("NoHandshake"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_NO_HANDSHAKE))
|
||||||
|
.Const(_SC("HandshakeNoVersion"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_HANDSHAKE_NO_VERSION))
|
||||||
|
.Const(_SC("HandshakeUnsupportedVersion"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION))
|
||||||
|
.Const(_SC("HandshakeNoKey"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_HANDSHAKE_NO_KEY))
|
||||||
|
.Const(_SC("HandshakeAccept"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_HANDSHAKE_ACCEPT))
|
||||||
|
.Const(_SC("Unauthorized"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_UNAUTHORIZED))
|
||||||
|
.Const(_SC("PayloadTooBig"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_PAYLOAD_TOO_BIG))
|
||||||
|
.Const(_SC("IncompleteFrame"), static_cast< SQInteger >(Poco::Net::WebSocket::WS_ERR_INCOMPLETE_FRAME))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Namespace:: SqMod
|
} // Namespace:: SqMod
|
||||||
|
@ -2,10 +2,168 @@
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
#include "Core/Common.hpp"
|
#include "Core/Common.hpp"
|
||||||
|
#include "Library/IO/Buffer.hpp"
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
#include <Poco/Net/HTTPRequest.h>
|
||||||
|
#include <Poco/Net/HTTPResponse.h>
|
||||||
|
#include <Poco/Net/HTTPMessage.h>
|
||||||
|
#include <Poco/Net/HTTPClientSession.h>
|
||||||
|
#include <Poco/Net/WebSocket.h>
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
namespace SqMod {
|
namespace SqMod {
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------
|
||||||
|
* Implements a WebSocket according to the WebSocket protocol specification in RFC 6455.
|
||||||
|
*/
|
||||||
|
struct WsClient
|
||||||
|
{
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* HTTP client session instance.
|
||||||
|
*/
|
||||||
|
Poco::Net::HTTPClientSession mClient;
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* HTTP request instance.
|
||||||
|
*/
|
||||||
|
Poco::Net::HTTPRequest mRequest;
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* HTTP response instance.
|
||||||
|
*/
|
||||||
|
Poco::Net::HTTPResponse mResponse;
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* WebSocket instance.
|
||||||
|
*/
|
||||||
|
Poco::Net::WebSocket mWebSocket;
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Receiving buffer instance.
|
||||||
|
*/
|
||||||
|
Poco::Buffer< char > mBuffer;
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* 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};
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Base constructor.
|
||||||
|
*/
|
||||||
|
WsClient(StackStrF &host, uint16_t port, StackStrF &uri)
|
||||||
|
: mClient(host.ToStr(), port),
|
||||||
|
mRequest(Poco::Net::HTTPRequest::HTTP_GET, uri.ToStr(), Poco::Net::HTTPRequest::HTTP_1_1), mResponse(),
|
||||||
|
mWebSocket(mClient, mRequest, mResponse), mBuffer(0), mFlags(0), mState(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Sends a Close control frame to the server end of the connection to initiate an orderly shutdown of the connection.
|
||||||
|
*/
|
||||||
|
void Shutdown() {
|
||||||
|
mWebSocket.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Sends a Close control frame to the server end of the connection to initiate an orderly shutdown of the connection.
|
||||||
|
*/
|
||||||
|
void ShutdownWith(SQInteger code, StackStrF &msg) {
|
||||||
|
mWebSocket.shutdown(static_cast< uint16_t >(code), msg.ToStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
SQInteger SendFrame(SqBuffer & buf, SQInteger flags) {
|
||||||
|
return mWebSocket.sendFrame(buf.Valid().Data(), static_cast< int >(buf.Valid().Position()), static_cast< int >(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
SQInteger SendStringFrame(SQInteger flags, StackStrF &str) {
|
||||||
|
return mWebSocket.sendFrame(str.mPtr, static_cast< int >(str.mLen), static_cast< int >(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* 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
|
||||||
|
mState = mWebSocket.receiveFrame(mBuffer, mFlags);
|
||||||
|
// 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
|
||||||
|
mState = mWebSocket.receiveFrame(mBuffer, mFlags);
|
||||||
|
// If something was returned
|
||||||
|
if (mState != 0)
|
||||||
|
{
|
||||||
|
// Create a string with buffer contents
|
||||||
|
LightObj obj(SqTypeIdentity< SqBuffer >{}, SqVM(),
|
||||||
|
const_cast< const SQChar * >(mBuffer.begin()),
|
||||||
|
static_cast< SQInteger >(mBuffer.sizeBytes()));
|
||||||
|
// Discard buffer contents for next request
|
||||||
|
mBuffer.resize(0);
|
||||||
|
// Return the string object
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
// Default to null
|
||||||
|
return LightObj{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Sets the maximum payload size for RecvFrame(). The default is std::numeric_limits<int>::max().
|
||||||
|
*/
|
||||||
|
WsClient & SetMaxPayloadSize(SQInteger size)
|
||||||
|
{
|
||||||
|
mWebSocket.setMaxPayloadSize(static_cast< int >(size));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Returns the maximum payload size for RecvFrame(). The default is std::numeric_limits<int>::max().
|
||||||
|
*/
|
||||||
|
SQMOD_NODISCARD SQInteger GetMaxPayloadSize() const
|
||||||
|
{
|
||||||
|
return mWebSocket.getMaxPayloadSize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // Namespace:: SqMod
|
} // Namespace:: SqMod
|
||||||
|
Loading…
Reference in New Issue
Block a user