Core: WebSocket refactored

git-svn-id: svn://ultimatepp.org/upp/trunk@11327 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
cxl 2017-09-17 20:21:21 +00:00
parent e8fa5069ff
commit 03912e5ded
4 changed files with 625 additions and 272 deletions

View file

@ -592,24 +592,54 @@ bool HttpResponse(TcpSocket& socket, bool scgi, int code, const char *phrase,
const char *content_type = NULL, const String& data = Null,
const char *server = NULL, bool gzip = false);
#include <Core/Core.h>
using namespace Upp;
class WebSocket {
int64 ReadLen(int n);
String error;
TcpSocket std_socket;
TcpSocket *socket;
String uri;
String host;
IpAddrInfo addrinfo;
bool ssl;
String data;
int data_pos;
int max_chunk;
int opcode;
String data;
int64 maxlen;
TcpSocket *socket;
int64 length;
bool mask;
int key[4];
void Reset();
bool Handshake();
public:
enum {
ERROR_DATA = TcpSocket::ERROR_LAST, ERROR_SEND, ERROR_LEN_LIMIT
struct Input : Moveable<Input> {
dword opcode;
String data;
};
BiVector<Input> in_queue;
BiVector<String> out_queue;
int out_at;
bool close_sent;
bool close_received;
dword current_opcode;
enum {
HTTP_REQUEST_HEADER = -100,
HTTP_RESPONSE_HEADER = -101,
READING_FRAME_HEADER = -102,
DNS = -103,
SSL_HANDSHAKE = -104,
FIN = 0x80,
CONTINUE = 0x0,
TEXT = 0x1,
BINARY = 0x2,
CLOSE = 0x8,
@ -617,41 +647,75 @@ public:
PONG = 0xa,
};
void Clear();
void Error(const String& error);
void Out(const String& s);
void Output();
void StartConnect();
void Dns();
void SSLHandshake();
void SendRequest();
bool ReadHttpHeader();
void ResponseHeader();
void RequestHeader();
void FrameHeader();
void FrameData();
int GetFinIndex() const;
void SendRaw(int hdr, const String& data);
void Do0();
public:
WebSocket& NonBlocking(bool b = true) { socket->Timeout(b ? 0 : Null); return *this; }
bool IsBlocking() const { return IsNull(socket->GetTimeout()); }
bool IsError() const { return socket->IsError() || error.GetCount(); }
String GetError() const { return Nvl(socket->GetErrorDesc(), error); }
void Accept(TcpSocket& listener_socket);
void Connect(const String& url);
void Do();
String Receive();
bool IsFin() const { return current_opcode & FIN; }
bool IsText() const { return current_opcode & TEXT; }
bool IsBinary() const { return current_opcode & BINARY; }
void SendText(const String& data) { SendRaw(FIN|TEXT, data); }
void SendBinary(const String& data) { SendRaw(FIN|BINARY, data); }
void BeginText(const String& data) { SendRaw(TEXT, data); }
void BeginBinary(const String& data) { SendRaw(BINARY, data); }
void Continue(const String& data) { SendRaw(0, data); }
void Fin(const String& data) { SendRaw(FIN, data); }
void Close(const String& msg = Null);
bool IsOpen() const { return socket->IsOpen(); }
bool IsClosed() const { return !IsOpen(); }
WebSocket();
// backward compatibility:
bool WebAccept(TcpSocket& socket, HttpHeader& hdr);
bool WebAccept(TcpSocket& socket);
bool ReceiveRaw();
String Receive();
int GetOpCode() const { return current_opcode & 15; }
bool IsFin() { return opcode & FIN; }
int GetOpCode() const { return opcode & 15; }
bool IsText() const { return GetOpCode() == TEXT; }
bool IsBinary() const { return GetOpCode() == BINARY; }
bool IsClosed() const { return GetOpCode() == CLOSE; }
String GetData() const { return data; }
bool SendText(const String& data, bool fin) { SendRaw((fin ? 0x80 : 0)|TEXT, data); return !IsError(); }
bool SendText(const void *data, int len, bool fin = true) { return SendText(String((char *)data, len), fin); }
bool SendRaw(int hdr, const void *data, int64 len);
bool SendBinary(const String& data, bool fin) { SendRaw((fin ? 0x80 : 0)|BINARY, data); return !IsError(); }
bool SendBinary(const void *data, int len, bool fin = true) { return SendText(String((char *)data, len), fin); }
bool SendText(const void *data, int64 len, bool fin = true) { return SendRaw((fin ? 0x80 : 0)|TEXT, data, len); }
bool SendText(const String& data, bool fin = true) { return SendText(~data, data.GetCount(), fin); }
bool SendBinary(const void *data, int64 len, bool fin = true) { return SendRaw((fin ? 0x80 : 0)|BINARY, data, len); }
bool SendBinary(const String& data, bool fin = true) { return SendBinary(~data, data.GetCount(), fin); }
void Close();
bool IsOpen() const { return socket && socket->IsOpen(); }
bool IsError() const { return socket && socket->IsError(); }
void ClearError() { if(socket) socket->ClearError(); }
int GetError() const { return socket ? socket->GetError() : 0; }
String GetErrorDesc() const { return socket ? socket->GetErrorDesc() : String(); }
WebSocket& MaxLen(int64 maxlen_) { maxlen = maxlen_; return *this; }
WebSocket() { Reset(); }
String GetErrorDesc() const { return GetError(); }
// keep mispeled method names
bool RecieveRaw() { return ReceiveRaw(); }
String Recieve() { return Receive(); }
};

View file

@ -825,7 +825,7 @@ int TcpSocket::Get(void *buffer, int count)
return done;
}
String TcpSocket::Get(int count)
String TcpSocket:: Get(int count)
{
if(count == 0)
return Null;

View file

@ -1,9 +1,442 @@
#include "Core.h"
#define LLOG(x) // DLOG(x)
#define LLOG(x) // DLOG(x)
namespace Upp {
WebSocket::WebSocket()
{
max_chunk = 10 * 1024 * 1024;
Clear();
}
void WebSocket::Clear()
{
socket = &std_socket;
opcode = 0;
current_opcode = 0;
data.Clear();
data_pos = 0;
in_queue.Clear();
out_queue.Clear();
out_at = 0;
error.Clear();
socket->Clear();
close_sent = close_received = false;
}
void WebSocket::Error(const String& err)
{
LLOG("ERROR: " << err);
error = err;
}
void WebSocket::Accept(TcpSocket& listen_socket)
{
if(!socket->Accept(listen_socket)) {
Error("Accept has failed");
return;
}
Clear();
opcode = HTTP_REQUEST_HEADER;
}
void WebSocket::Connect(const String& url)
{
Clear();
uri = url;
const char *u = url;
ssl = memcmp(u, "wss", 3) == 0;
const char *t = u;
while(*t && *t != '?')
if(*t++ == '/' && *t == '/') {
u = ++t;
break;
}
t = u;
while(*u && *u != ':' && *u != '/' && *u != '?')
u++;
host = String(t, u);
int port = ssl ? 443 : 80;
if(*u == ':')
port = ScanInt(u + 1, &u);
if(socket->IsBlocking()) {
if(!addrinfo.Execute(host, port)) {
Error("Not found");
return;
}
LLOG("DNS resolved");
StartConnect();
}
else
opcode = DNS;
}
void WebSocket::SendRequest()
{
LLOG("Sending connection request");
String h;
for(int i = 0; i < 20; i++)
h.Cat(Random());
Out( // needs to be the first thing to sent after the connection is established
"GET " + uri + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
"Accept-Language: cs,en-US;q=0.7,en;q=0.3\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Extensions: permessage-deflate\r\n"
"Sec-WebSocket-Key: " + Base64Encode(h) + "\r\n"
"Connection: keep-alive, Upgrade\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"Upgrade: websocket\r\n\r\n"
);
opcode = HTTP_RESPONSE_HEADER;
}
void WebSocket::StartConnect()
{
if(!socket->Connect(addrinfo)) {
Error("Connect has failed");
return;
}
LLOG("Connect issued");
if(IsBlocking()) {
if(ssl) {
socket->StartSSL();
socket->SSLHandshake();
LLOG("Blocking SSL handshake finished");
}
SendRequest();
return;
}
if(ssl) {
if(!socket->StartSSL()) {
Error("Unable to start SSL handshake");
return;
}
LLOG("Started SSL handshake");
opcode = SSL_HANDSHAKE;
}
else
SendRequest();
}
void WebSocket::Dns()
{
if(addrinfo.InProgress())
return;
LLOG("DNS resolved");
StartConnect();
}
void WebSocket::SSLHandshake()
{
if(socket->SSLHandshake())
return;
LLOG("SSL handshake finished");
SendRequest();
}
bool WebSocket::ReadHttpHeader()
{
for(;;) {
int c = socket->Get();
if(c < 0)
return false;
else
data.Cat(c);
if(data.GetCount() == 2 && data[0] == '\r' && data[1] == '\n') { // header is empty
Error("Empty HTTP header");
return false;
}
if(data.GetCount() >= 3) {
const char *h = data.Last();
if(h[0] == '\n' && h[-1] == '\r' && h[-2] == '\n') { // empty ending line after non-empty header
LLOG("HTTP header received");
LLOG(data);
return true;
}
}
if(data.GetCount() > 100000) {
Error("HTTP header size exceeded");
return false;
}
}
}
void WebSocket::RequestHeader()
{
if(ReadHttpHeader()) {
LLOG(data);
HttpHeader hdr;
if(!hdr.Parse(data)) {
Error("Invalid HTTP header");
return;
}
String dummy;
hdr.Request(dummy, uri, dummy);
String key = hdr["sec-websocket-key"];
if(IsNull(key)) {
Error("Invalid HTTP header: missing sec-websocket-key");
return;
}
byte sha1[20];
SHA1(sha1, key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
Out(
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: " + Base64Encode((char *)sha1, 20) + "\r\n\r\n"
);
LLOG("HTTP request header received, sending response");
data.Clear();
opcode = READING_FRAME_HEADER;
}
}
void WebSocket::ResponseHeader()
{
if(ReadHttpHeader()) {
LLOG(data);
if(ToLower(data).Find("upgrade: websocket") < 0) {
Error("Invalid server response HTTP header");
return;
}
LLOG("HTTP response header received");
opcode = READING_FRAME_HEADER;
data.Clear();
}
}
void WebSocket::FrameHeader()
{
for(;;) {
int c = socket->Get();
if(c < 0)
return;
data.Cat(c);
LLOG("Receiving frame header, current header len: " << data.GetCount());
int ii = 0;
bool ok = true;
auto Get = [&]() -> byte {
if(ii < data.GetCount())
return data[ii++];
ok = false;
return 0;
};
auto GetLen = [&](int count) -> int64 {
int64 len = 0;
while(count--)
len = (len << 8) | Get();
return len;
};
int new_opcode = Get();
length = Get();
mask = length & 128;
length &= 127;
if(length == 127)
length = GetLen(8);
if(length == 126)
length = GetLen(2);
if(mask) {
key[0] = Get();
key[1] = Get();
key[2] = Get();
key[3] = Get();
}
if(ok) {
LLOG("Frame header received, len: " << length << ", code " << opcode);
opcode = new_opcode;
data.Clear();
data_pos = 0;
return;
}
}
}
void WebSocket::Close(const String& msg)
{
LLOG("Sending CLOSE");
SendRaw(CLOSE, msg);
close_sent = true;
if(IsBlocking())
while(!IsClosed() && !IsError() && socket->IsOpen())
Do0();
}
void WebSocket::FrameData()
{
Buffer<char> buffer(32768);
for(;;) {
int n = socket->Get(~buffer, (int)min(length - data_pos, (int64)32768));
if(n == 0)
return;
if(mask)
for(int i = 0; i < n; i++) // TODO: Optimize
buffer[i] ^= key[(i + data_pos) & 3];
data.Cat(~buffer, n); // TODO: Split long data
data_pos += n;
LLOG("Frame data chunk received, chunk len: " << n);
if(data_pos >= length) {
LLOG("Frame data received");
int op = opcode & 15;
switch(op) {
case PING:
LLOG("PING");
SendRaw(PONG, data);
break;
case CLOSE:
LLOG("CLOSE received");
close_received = true;
if(!close_sent)
Close(data);
socket->Close();
break;
default:
Input& m = in_queue.AddTail();
m.opcode = opcode;
m.data = data;
LLOG((m.opcode & TEXT ? "TEXT" : "BINARY") << ", input queue count is now " << in_queue.GetCount());
LOGHEXDUMP(data, min(data.GetCount(), 64));
break;
}
data.Clear();
opcode = READING_FRAME_HEADER;
return;
}
}
}
void WebSocket::Do0()
{
if(socket->IsEof() && !(close_sent || close_received))
Error("Socket has been closed unexpectedly");
if(IsError())
return;
if(findarg(opcode, DNS, SSL_HANDSHAKE) < 0)
Output();
int prev_opcode = opcode;
switch(opcode) {
case DNS:
Dns();
break;
case SSL_HANDSHAKE:
SSLHandshake();
break;
case HTTP_RESPONSE_HEADER:
ResponseHeader();
break;
case HTTP_REQUEST_HEADER:
RequestHeader();
break;
case READING_FRAME_HEADER:
FrameHeader();
break;
default:
FrameData();
break;
}
}
void WebSocket::Do()
{
ASSERT(!IsBlocking());
Do0();
}
String WebSocket::Receive()
{
current_opcode = 0;
do {
Do0();
if(in_queue.GetCount()) {
String s = in_queue.Head().data;
current_opcode = in_queue.Head().opcode;
in_queue.DropHead();
return s;
}
}
while(IsBlocking() && socket->IsOpen() && !IsError());
return String::GetVoid();
}
void WebSocket::Out(const String& s)
{
out_queue.AddTail(s);
while(IsBlocking() && socket->IsOpen() && !IsError() && out_queue.GetCount())
Output();
}
void WebSocket::Output()
{
if(socket->IsOpen()) {
while(out_queue.GetCount()) {
const String& s = out_queue.Head();
int n = socket->Put(~s + out_at, s.GetCount() - out_at);
if(n == 0)
break;
LLOG("Sent " << n << " bytes");
out_at += n;
if(out_at >= s.GetCount()) {
out_at = 0;
out_queue.DropHead();
LLOG("Block sent complete, " << out_queue.GetCount() << " remaining blocks in queue");
}
}
}
}
void WebSocket::SendRaw(int hdr, const String& data)
{
if(IsError())
return;
ASSERT(!close_sent);
LLOG("Send " << data.GetCount() << " bytes, hdr: " << hdr);
LOGHEXDUMP(data, min(data.GetCount(), 64));
String header;
header.Cat(hdr);
int len = data.GetCount();
if(len > 65535) {
header.Cat(127);
header.Cat(0);
header.Cat(0);
header.Cat(0);
header.Cat(0);
header.Cat(byte(len >> 24));
header.Cat(byte(len >> 16));
header.Cat(byte(len >> 8));
header.Cat(byte(len));
}
else
if(len > 125) {
header.Cat(126);
header.Cat(byte(len >> 8));
header.Cat(byte(len));
}
else
header.Cat((int)len);
Out(header);
if(data.GetCount() == 0)
return;
Out(data);
}
bool WebSocket::WebAccept(TcpSocket& socket_, HttpHeader& hdr)
{
socket = &socket_;
@ -14,12 +447,16 @@ bool WebSocket::WebAccept(TcpSocket& socket_, HttpHeader& hdr)
byte sha1[20];
SHA1(sha1, key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
return socket->PutAll(
Out(
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: " + Base64Encode((char *)sha1, 20) + "\r\n\r\n"
);
data.Clear();
opcode = READING_FRAME_HEADER;
return true;
}
bool WebSocket::WebAccept(TcpSocket& socket)
@ -30,137 +467,4 @@ bool WebSocket::WebAccept(TcpSocket& socket)
return WebAccept(socket, hdr);
}
int64 WebSocket::ReadLen(int n)
{
int64 len = 0;
while(n-- > 0)
len = (len << 8) | (byte)socket->Get();
return len;
}
bool WebSocket::ReceiveRaw()
{
if(IsError())
return false;
opcode = socket->Get();
if(opcode < 0)
return false;
int64 len = socket->Get();
bool mask = len & 128;
len &= 127;
if(len == 127)
len = ReadLen(8);
if(len == 126)
len = ReadLen(2);
byte key[4];
if(mask)
socket->Get(key, 4);
if(IsError()) {
socket->SetSockError("websocket receive", ERROR_DATA, "Invalid data");
return false;
}
if(len > maxlen || len < 0) {
socket->SetSockError("websocket receive", ERROR_LEN_LIMIT, "Frame limit exceeded, size " + AsString(len));
return false;
}
StringBuffer frame((int)len); // TODO int64
char *buffer = ~frame;
if(!socket->GetAll(buffer, (int)len)) {
socket->SetSockError("websocket receive", ERROR_DATA, "Invalid data");
return false;
}
if(mask)
for(int i = 0; i < len; i++)
buffer[i] ^= key[i & 3];
data = frame;
return true;
}
String WebSocket::Receive()
{
LLOG("WebSocket::Receive");
for(;;) {
if(!RecieveRaw()) {
LLOG("WebSocket::Recieve failed");
return String::GetVoid();
}
if(GetOpCode() == PING)
SendRaw(PONG, ~data, data.GetLength());
else {
if(GetOpCode() == CLOSE)
SendRaw(CLOSE, ~data, data.GetLength());
break;
}
}
LLOG("WebSocket::Receive len: " << data.GetLength());
return data;
}
bool WebSocket::SendRaw(int hdr, const void *data, int64 len)
{
if(IsError())
return false;
ASSERT(len < INT_MAX); // temporary, todo
String b;
b.Cat(hdr);
if(len > 65535) {
b.Cat(127);
b.Cat(byte(len >> 56));
b.Cat(byte(len >> 48));
b.Cat(byte(len >> 40));
b.Cat(byte(len >> 32));
b.Cat(byte(len >> 24));
b.Cat(byte(len >> 16));
b.Cat(byte(len >> 8));
b.Cat(byte(len));
}
else
if(len > 125) {
b.Cat(126);
b.Cat(byte(len >> 8));
b.Cat(byte(len));
}
else
b.Cat((int)len);
LLOG("WebSocket::SendRaw hdr: " << hdr << ", len: " << len);
if(IsError() || !socket->PutAll(~b, b.GetLength()) || !socket->PutAll(data, (int)len)) {
socket->SetSockError("websocket send", ERROR_SEND, "Failed to send data");
LLOG("WebSocket::SendRaw FAILED");
return false;
}
LLOG("WebSocket::SendRaw OK");
return true;
}
void WebSocket::Reset()
{
opcode = 0;
data.Clear();
maxlen = 10 * 1024 * 1024;
socket = NULL;
}
void WebSocket::Close()
{
if(socket) {
socket->Close();
opcode = 0;
data.Clear();
socket = NULL;
}
}
}

View file

@ -13,123 +13,108 @@ topic "WebSocket";
[ {{10000@(113.42.0) [s0;%% [*@7;4 WebSocket]]}}&]
[s3; &]
[s1;:WebSocket`:`:class: [@(0.0.255)3 class][3 _][*3 WebSocket]&]
[s2;%% Provides the WebSocket protocol support. WebSocket is a wrapper
over existing TcpSocket connection, which performs server handshake
by calling WebAccept method.&]
[s2;%% Provides the WebSocket protocol support. &]
[s0;i448;a25;kKO9;:noref:@(0.0.255) &]
[s3;%% &]
[ {{10000F(128)G(128)@1 [s0;%% [* Public Method List]]}}&]
[s3; &]
[s5;:WebSocket`:`:WebAccept`(TcpSocket`&`,HttpHeader`&`): [@(0.0.255) bool]_[* WebAccept](
[_^TcpSocket^ TcpSocket][@(0.0.255) `&]_[*@3 socket], [_^HttpHeader^ HttpHeader][@(0.0.255) `&
]_[*@3 hdr])&]
[s2;%% Attempts to open websocket connection with [%-*@3 socket], [%-*@3 hdr]
is HTTP header read from socket `- prereading allows to distinguish
between websocket and normal HTTP connection to server.&]
[s5;:Upp`:`:WebSocket`:`:NonBlocking`(bool`): [_^Upp`:`:WebSocket^ WebSocket][@(0.0.255) `&
]_[* NonBlocking]([@(0.0.255) bool]_[*@3 b]_`=_[@(0.0.255) true])&]
[s2;%% If [%-*@3 b] is true, activates non`-blocking mode. Default
is blocking mode.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:IsBlocking`(`)const: [@(0.0.255) bool]_[* IsBlocking]()_[@(0.0.255) c
onst]&]
[s2;%% Returns true if WebSocket is in the blocking mode.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:IsError`(`)const: [@(0.0.255) bool]_[* IsError]()_[@(0.0.255) cons
t]&]
[s2;%% Returns true if there was any error.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:GetError`(`)const: [_^Upp`:`:String^ String]_[* GetError]()_[@(0.0.255) c
onst]&]
[s2;%% Returns the description of error.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:Accept`(Upp`:`:TcpSocket`&`): [@(0.0.255) void]_[* Accept]([_^Upp`:`:TcpSocket^ T
cpSocket][@(0.0.255) `&]_[*@3 listen`_socket])&]
[s2;%% Accepts connection from [%-*@3 listen`_socket].&]
[s3;%% &]
[s4; &]
[s5;:WebSocket`:`:WebAccept`(TcpSocket`&`): [@(0.0.255) bool]_[* WebAccept]([_^TcpSocket^ T
cpSocket][@(0.0.255) `&]_[*@3 socket])&]
[s2;%% Reads HTTP header from [%-*@3 socket] and calls other WebAccept
variant.&]
[s5;:Upp`:`:WebSocket`:`:Connect`(const Upp`:`:String`&`): [@(0.0.255) void]_[* Connect](
[@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 url])&]
[s2;%% Initiates connection to [%-*@3 url].&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:ReceiveRaw`(`): [@(0.0.255) bool]_[* ReceiveRaw]()&]
[s2;%% Recieves the message. This variant does not automatically
handle PING and CLOSE messages. Returns true on success.&]
[s5;:Upp`:`:WebSocket`:`:Do`(`): [@(0.0.255) void]_[* Do]()&]
[s2;%% Manages socket operations in non`-blocking mode. Cannot be
called in blocking mode.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:Receive`(`): [_^Upp`:`:String^ String]_[* Receive]()&]
[s2;%% Recieves the message. PING and CLOSE messages are handled
and are not returned; PING message is replied with PONG and not
reported to client `- Recieve does not return and waits for the
next message, CLOSE is replied by CLOSE and Recieve returns.
Returns the content of message recieved or String`::GetVoid()
if there was none. Note: If WaitRead was used to wait for read
on socket before calling Recieve, returning String`::GetVoid()
means that connection was broken.&]
[s2;%% Receives the message. In blocking mode waits until the message
is available. In non`-blocking mode returns either message or
String`::GetVoid() if there is no message. In both cases also
returns String`::GetVoid() in case of exceptional situations (errors,
connection closed etc...).&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:IsFin`(`): [@(0.0.255) bool]_[* IsFin]()&]
[s2;%% Returns true if the last message was marked as final.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:GetOpCode`(`)const: [@(0.0.255) int]_[* GetOpCode]()_[@(0.0.255) const]&]
[s2;%% Returns the websocket opcode of the last recieved message.&]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:IsFin`(`)const: [@(0.0.255) bool]_[* IsFin]()_[@(0.0.255) const]&]
[s2;%% Returns true if the last received message had flag FIN set.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:IsText`(`)const: [@(0.0.255) bool]_[* IsText]()_[@(0.0.255) const]&]
[s2;%% Returns true if the last message recieved was text.&]
[s2;%% Returns true if the last received message was text.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:IsBinary`(`)const: [@(0.0.255) bool]_[* IsBinary]()_[@(0.0.255) const]&]
[s2;%% Returns true if the last message recieved was binary.&]
[s2;%% Returns true if the last received message was text.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:SendText`(const Upp`:`:String`&`): [@(0.0.255) void]_[* SendText
]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Sends a single frame (non`-fragmented) text message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:SendBinary`(const Upp`:`:String`&`): [@(0.0.255) void]_[* SendBi
nary]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Sends a single frame (non`-fragmented) binary message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:BeginText`(const Upp`:`:String`&`): [@(0.0.255) void]_[* BeginTe
xt]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Starts a multi`-frame (fragmented) text message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:BeginBinary`(const Upp`:`:String`&`): [@(0.0.255) void]_[* Begin
Binary]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Starts a multi`-frame (fragmented) binary message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:Continue`(const Upp`:`:String`&`): [@(0.0.255) void]_[* Continue
]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Sends another chunk of data in a multi`-frame (fragmented)
message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:Fin`(const Upp`:`:String`&`): [@(0.0.255) void]_[* Fin]([@(0.0.255) c
onst]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 data])&]
[s2;%% Sends the final chunk of data in a multi`-frame (fragmented)
message.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:Close`(const Upp`:`:String`&`): [@(0.0.255) void]_[* Close]([@(0.0.255) c
onst]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 msg])&]
[s2;%% Initiates standard close of connection, sending CLOSE [%-*@3 msg].&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:WebSocket`:`:IsOpen`(`)const: [@(0.0.255) bool]_[* IsOpen]()&]
[s2;%% Returns true if the socket is open.&]
[s3; &]
[s4;%% &]
[s5;:WebSocket`:`:IsClosed`(`)const: [@(0.0.255) bool]_[* IsClosed]()_[@(0.0.255) const]&]
[s2;%% Returns true if CLOSE message was recieved.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:GetData`(`)const: [_^String^ String]_[* GetData]()_[@(0.0.255) const]&]
[s2;%% Returns the content of the last message.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:SendRaw`(int`,const void`*`,int64`): [@(0.0.255) bool]_[* SendRaw]([@(0.0.255) i
nt]_[*@3 hdr], [@(0.0.255) const]_[@(0.0.255) void]_`*[*@3 data], [_^int64^ int64]_[*@3 len])
&]
[s2;%% Sends the message, [%-*@3 hdr] is used as the first byte of
websocket protocol. Returns true on success.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:SendText`(const void`*`,int64`,bool`): [@(0.0.255) bool]_[* SendText]([@(0.0.255) c
onst]_[@(0.0.255) void]_`*[*@3 data], [_^int64^ int64]_[*@3 len], [@(0.0.255) bool]_[*@3 fin]_
`=_[@(0.0.255) true])&]
[s5;:WebSocket`:`:SendText`(const String`&`,bool`): [@(0.0.255) bool]_[* SendText]([@(0.0.255) c
onst]_[_^String^ String][@(0.0.255) `&]_[*@3 data], [@(0.0.255) bool]_[*@3 fin]_`=_[@(0.0.255) t
rue])&]
[s2;%% Sends the text message. Returns true on success.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:SendBinary`(const void`*`,int64`,bool`): [@(0.0.255) bool]_[* SendBinar
y]([@(0.0.255) const]_[@(0.0.255) void]_`*[*@3 data], [_^int64^ int64]_[*@3 len],
[@(0.0.255) bool]_[*@3 fin]_`=_[@(0.0.255) true])&]
[s5;:WebSocket`:`:SendBinary`(const String`&`,bool`): [@(0.0.255) bool]_[* SendBinary]([@(0.0.255) c
onst]_[_^String^ String][@(0.0.255) `&]_[*@3 data], [@(0.0.255) bool]_[*@3 fin]_`=_[@(0.0.255) t
rue])&]
[s2;%% Sends the binary message. Returns true on success.&]
[s3;%% &]
[s4;%% &]
[s5;:WebSocket`:`:Close`(`): [@(0.0.255) void]_[* Close]()&]
[s2;%% Closes the websocket.&]
[s3;%% &]
[s4; &]
[s5;:WebSocket`:`:IsOpen`(`)const: [@(0.0.255) bool]_[* IsOpen]()_[@(0.0.255) const]&]
[s2;%% Returns true if WebSocket is associated with open socket.&]
[s3; &]
[s4; &]
[s5;:WebSocket`:`:IsError`(`)const: [@(0.0.255) bool]_[* IsError]()_[@(0.0.255) const]&]
[s2;%% Returns true if WebSocket is associated with socket and this
socket returns IsError.&]
[s3; &]
[s4; &]
[s5;:WebSocket`:`:ClearError`(`): [@(0.0.255) void]_[* ClearError]()&]
[s2;%% Clears error in associated socket (if any).&]
[s3; &]
[s4; &]
[s5;:WebSocket`:`:GetError`(`)const: [@(0.0.255) int]_[* GetError]()_[@(0.0.255) const]&]
[s2;%% Returns error`-code in associated socket, or zero if there
is none.&]
[s3; &]
[s4; &]
[s5;:WebSocket`:`:GetErrorDesc`(`)const: [_^String^ String]_[* GetErrorDesc]()_[@(0.0.255) c
onst]&]
[s2;%% Returns error descroption in associated socket, or empty string
if there is none.&]
[s3; &]
[s4;%% &]
[s5;:WebSocket`:`:MaxLen`(int64`): [_^WebSocket^ WebSocket][@(0.0.255) `&]_[* MaxLen]([_^int64^ i
nt64]_[*@3 maxlen`_])&]
[s2;%% Sets the maximum length of recieved message to [%-*@3 maxlen]
. Default is 10M.&]
[s3;%% &]
[s2;%% Returns true if the socket is closed.&]
[s0;%% ]]