mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
Core: WebSocket refactored
git-svn-id: svn://ultimatepp.org/upp/trunk@11327 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
e8fa5069ff
commit
03912e5ded
4 changed files with 625 additions and 272 deletions
|
|
@ -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(); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;%% ]]
|
||||
Loading…
Add table
Add a link
Reference in a new issue