forked from rosh/MQL5Book
214 lines
5.4 KiB
MQL5
214 lines
5.4 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| wsmessage.mqh |
| |||
//| Copyright 2020-2022, MetaQuotes Ltd. |
| |||
//| https://www.mql5.com |
| |||
//+------------------------------------------------------------------+
| |||
#include "wstools.mqh"
| |||
#include "wsinterfaces.mqh"
| |||
#include "wsframe.mqh"
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| WebSocket messaging according to Hixie RFC (back compatibility) |
| |||
//+------------------------------------------------------------------+
| |||
class WebSocketMessageHixie: public IWebSocketMessage
| |||
{
| |||
protected:
| |||
IWebSocketFrame *frame;
| |||
| |||
public:
| |||
WebSocketMessageHixie(IWebSocketFrame *f = NULL): frame(f) { }
| |||
WebSocketMessageHixie(const string &text): frame(NULL)
| |||
{
| |||
setString(text);
| |||
}
| |||
| |||
~WebSocketMessageHixie()
| |||
{
| |||
if(frame) delete frame;
| |||
}
| |||
| |||
void getFrames(IWebSocketFrame *&frames[]) override
| |||
{
| |||
WsTools::push(frames, frame);
| |||
}
| |||
| |||
bool setString(const string &d) override
| |||
{
| |||
if(frame != NULL) delete frame;
| |||
frame = WebSocketFrameHixie::create(WS_FRAME_OPCODE::WS_TEXT_FRAME, d);
| |||
return frame != NULL;
| |||
}
| |||
| |||
string getString() override
| |||
{
| |||
return isFinalised() ? frame.getData() : NULL;
| |||
}
| |||
| |||
bool setData(const uchar &[]) override
| |||
{
| |||
return false;
| |||
}
| |||
| |||
bool getData(uchar &[]) override
| |||
{
| |||
return false;
| |||
}
| |||
| |||
bool isFinalised() override
| |||
{
| |||
return frame != NULL;
| |||
}
| |||
| |||
virtual bool takeFrame(IWebSocketFrame *f) override
| |||
{
| |||
if(!frame)
| |||
{
| |||
frame = f;
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| WebSocket messaging according to Hybi RFC (current standard) |
| |||
//+------------------------------------------------------------------+
| |||
class WebSocketMessage: public IWebSocketMessage
| |||
{
| |||
protected:
| |||
IWebSocketFrame *frames[];
| |||
uchar _data[];
| |||
bool compressed;
| |||
bool binary;
| |||
| |||
bool compose()
| |||
{
| |||
if(compressed)
| |||
{
| |||
uchar key[] = {1, 0, 0, 0};
| |||
uchar load[], result[];
| |||
const int n = CryptEncode(CRYPT_ARCH_ZIP, _data, key, result);
| |||
if(n == 0)
| |||
{
| |||
Print("Can't compress: ", _LastError);
| |||
compressed = false; // fallback (need to clear deflate flag)
| |||
}
| |||
else
| |||
{
| |||
ArraySwap(_data, result);
| |||
}
| |||
}
| |||
WsTools::push(frames, WebSocketFrame::create(/*compressed || */binary ?
| |||
WS_FRAME_OPCODE::WS_BINARY_FRAME : WS_FRAME_OPCODE::WS_TEXT_FRAME, _data, compressed));
| |||
return true;
| |||
}
| |||
| |||
bool extract()
| |||
{
| |||
if(!isFinalised())
| |||
{
| |||
Print("Can't get data from NotFinalised message");
| |||
return false;
| |||
}
| |||
| |||
ArrayResize(_data, 0);
| |||
| |||
for(int i = 0; i < ArraySize(frames); i++)
| |||
{
| |||
uchar temp[];
| |||
frames[i].getData(temp);
| |||
ArrayCopy(_data, temp, ArraySize(_data));
| |||
}
| |||
| |||
if(compressed)
| |||
{
| |||
uchar key[] = {1, 0, 0, 0};
| |||
uchar result[];
| |||
if(CryptDecode(CRYPT_ARCH_ZIP, _data, key, result) == 0)
| |||
{
| |||
Print("Can't decompress: ", _LastError);
| |||
}
| |||
else
| |||
{
| |||
ArraySwap(_data, result);
| |||
}
| |||
}
| |||
return true;
| |||
}
| |||
| |||
public:
| |||
WebSocketMessage(const bool deflate = false): compressed(deflate), binary(false) { }
| |||
| |||
WebSocketMessage(const uchar &bytes[], bool deflate = false): compressed(deflate), binary(true)
| |||
{
| |||
setData(bytes);
| |||
}
| |||
| |||
WebSocketMessage(const string &text, bool deflate = false): compressed(deflate), binary(false)
| |||
{
| |||
setString(text);
| |||
}
| |||
| |||
~WebSocketMessage()
| |||
{
| |||
for(int i = 0; i < ArraySize(frames); i++)
| |||
{
| |||
if(CheckPointer(frames[i]) == POINTER_DYNAMIC) delete frames[i];
| |||
}
| |||
ArrayResize(frames, 0);
| |||
}
| |||
| |||
bool setData(const uchar &data[]) override
| |||
{
| |||
ArrayCopy(_data, data);
| |||
return compose();
| |||
}
| |||
| |||
bool setString(const string &d) override
| |||
{
| |||
StringToCharArray(d, _data, 0, WHOLE_ARRAY, CP_UTF8);
| |||
ArrayResize(_data, ArraySize(_data) - 1); // cut trailing '\0'
| |||
return compose();
| |||
}
| |||
| |||
bool getData(uchar &data[]) override
| |||
{
| |||
return extract() && ArrayCopy(data, _data);
| |||
}
| |||
| |||
string getString() override
| |||
{
| |||
if(extract())
| |||
{
| |||
// NB. return string even if it's binary data, just for logging,
| |||
// calling code should check applicability
| |||
return CharArrayToString(_data, 0, WHOLE_ARRAY, CP_UTF8);
| |||
}
| |||
return NULL;
| |||
}
| |||
| |||
void getFrames(IWebSocketFrame *&_frames[]) override
| |||
{
| |||
ArrayCopy(_frames, this.frames);
| |||
}
| |||
| |||
bool isFinalised() override
| |||
{
| |||
if(ArraySize(frames) == 0)
| |||
return false;
| |||
| |||
return frames[ArraySize(frames) - 1].isFinal();
| |||
}
| |||
| |||
// append frame to the message
| |||
virtual bool takeFrame(IWebSocketFrame *frame) override
| |||
{
| |||
if(CheckPointer(frame) != POINTER_INVALID)
| |||
{
| |||
WsTools::push(frames, frame);
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
};
| |||
//+------------------------------------------------------------------+
|