diff --git a/uppsrc/Core/Inet.h b/uppsrc/Core/Inet.h index b543e8d21..18b06f170 100644 --- a/uppsrc/Core/Inet.h +++ b/uppsrc/Core/Inet.h @@ -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 + +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 { + dword opcode; + String data; }; + + BiVector in_queue; + + BiVector 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(); } }; diff --git a/uppsrc/Core/Socket.cpp b/uppsrc/Core/Socket.cpp index 31140735d..4b9e3c067 100644 --- a/uppsrc/Core/Socket.cpp +++ b/uppsrc/Core/Socket.cpp @@ -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; diff --git a/uppsrc/Core/WebSocket.cpp b/uppsrc/Core/WebSocket.cpp index 816db9956..fa2c47fe6 100644 --- a/uppsrc/Core/WebSocket.cpp +++ b/uppsrc/Core/WebSocket.cpp @@ -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 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; - } -} - - } diff --git a/uppsrc/Core/src.tpp/WebSocket$en-us.tpp b/uppsrc/Core/src.tpp/WebSocket$en-us.tpp index c7ed7bf91..f1ffedc77 100644 --- a/uppsrc/Core/src.tpp/WebSocket$en-us.tpp +++ b/uppsrc/Core/src.tpp/WebSocket$en-us.tpp @@ -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;%% ]] \ No newline at end of file