Core: WebSocket support refactored

git-svn-id: svn://ultimatepp.org/upp/trunk@6712 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
cxl 2014-01-01 16:14:50 +00:00
parent 95fa1d9126
commit 48e58356e6
3 changed files with 85 additions and 44 deletions

View file

@ -523,12 +523,13 @@ 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);
class WebSocket : public TcpSocket {
class WebSocket {
int64 ReadLen(int n);
int opcode;
String data;
int64 maxlen;
int opcode;
String data;
int64 maxlen;
TcpSocket *socket;
void Reset();
bool Handshake();
@ -547,7 +548,8 @@ public:
PONG = 0xa,
};
bool WebAccept(TcpSocket& server);
bool WebAccept(TcpSocket& socket, HttpHeader& hdr);
bool WebAccept(TcpSocket& socket);
bool RecieveRaw();
String Recieve();
@ -568,6 +570,12 @@ public:
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; }

View file

@ -1,36 +1,22 @@
#include "Core.h"
#define LLOG(x) // DLOG(x)
NAMESPACE_UPP
bool WebSocket::WebAccept(TcpSocket& server)
bool WebSocket::WebAccept(TcpSocket& socket_, HttpHeader& hdr)
{
return Accept(server) && Handshake();
}
bool WebSocket::Handshake()
{
HttpHeader hdr;
if(!hdr.Read(*this)) {
SetSockError("websocket handshake", ERROR_NOHEADER, "Failed to read HTTP header");
return false;
}
socket = &socket_;
String key = hdr["sec-websocket-key"];
if(IsNull(key)) {
SetSockError("websocket handshake", ERROR_NOKEY, "Missing sec-websocket-key");
socket->SetSockError("websocket handshake", ERROR_NOKEY, "Missing sec-websocket-key");
return false;
}
byte sha1[20];
SHA1(sha1, key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
DLOG(
"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"
);
return PutAll(
return socket->PutAll(
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
@ -38,11 +24,21 @@ bool WebSocket::Handshake()
);
}
bool WebSocket::WebAccept(TcpSocket& socket)
{
HttpHeader hdr;
if(!hdr.Read(socket)) {
socket.SetSockError("websocket handshake", ERROR_NOHEADER, "Failed to read HTTP header");
return false;
}
return WebAccept(socket, hdr);
}
int64 WebSocket::ReadLen(int n)
{
int64 len = 0;
while(n-- > 0)
len = (len << 8) | (byte)Get();
len = (len << 8) | (byte)socket->Get();
return len;
}
@ -51,8 +47,8 @@ bool WebSocket::RecieveRaw()
if(IsError())
return false;
opcode = Get();
int64 len = Get();
opcode = socket->Get();
int64 len = socket->Get();
bool mask = len & 128;
len &= 127;
if(len == 127)
@ -62,22 +58,22 @@ bool WebSocket::RecieveRaw()
byte key[4];
if(mask)
Get(key, 4);
socket->Get(key, 4);
if(IsError()) {
SetSockError("websocket recieve", ERROR_DATA, "Invalid data");
socket->SetSockError("websocket recieve", ERROR_DATA, "Invalid data");
return false;
}
if(len > maxlen) {
SetSockError("websocket recieve", ERROR_LEN_LIMIT, "Frame limit exceeded, size " + AsString(len));
socket->SetSockError("websocket recieve", ERROR_LEN_LIMIT, "Frame limit exceeded, size " + AsString(len));
return false;
}
StringBuffer frame((int)len); // TODO int64
char *buffer = ~frame;
if(!GetAll(buffer, (int)len)) {
SetSockError("websocket recieve", ERROR_DATA, "Invalid data");
if(!socket->GetAll(buffer, (int)len)) {
socket->SetSockError("websocket recieve", ERROR_DATA, "Invalid data");
return false;
}
@ -133,8 +129,8 @@ bool WebSocket::SendRaw(int hdr, const void *data, int64 len)
else
b.Cat((int)len);
if(IsError() || !PutAll(~b, b.GetLength()) || !PutAll(data, (int)len)) {
SetSockError("websocket send", ERROR_SEND, "Failed to send data");
if(IsError() || !socket->PutAll(~b, b.GetLength()) || !socket->PutAll(data, (int)len)) {
socket->SetSockError("websocket send", ERROR_SEND, "Failed to send data");
return false;
}
@ -146,14 +142,18 @@ void WebSocket::Reset()
opcode = 0;
data.Clear();
maxlen = 10 * 1024 * 1024;
socket = NULL;
}
void WebSocket::Close()
{
TcpSocket::Close();
opcode = 0;
data.Clear();
if(socket) {
socket->Close();
opcode = 0;
data.Clear();
socket = NULL;
}
}

View file

@ -12,16 +12,25 @@ topic "WebSocket";
[{_}%EN-US
[ {{10000@(113.42.0) [s0; [*@7;4 WebSocket]]}}&]
[s3;%- &]
[s1;:WebSocket`:`:class:%- [@(0.0.255)3 class][3 _][*3 WebSocket][3 _:_][@(0.0.255)3 public][3 _][*@3;3 T
cpSocket]&]
[s2; Provides the WebSocket protocol support.&]
[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.&]
[s0;i448;a25;kKO9;:noref:@(0.0.255)%- &]
[ {{10000F(128)G(128)@1 [s0; [* Public Method List]]}}&]
[s3;%- &]
[s5;:WebSocket`:`:WebAccept`(TcpSocket`&`,HttpHeader`&`):%- [@(0.0.255) bool]_[* WebAccep
t]([_^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.&]
[s3; &]
[s4;%- &]
[s5;:WebSocket`:`:WebAccept`(TcpSocket`&`):%- [@(0.0.255) bool]_[* WebAccept]([_^TcpSocket^ T
cpSocket][@(0.0.255) `&]_[*@3 server])&]
[s2; Accepts a websocekt connectiont from listening socket [%-*@3 server]
and performs a handshake. Returns true on success.&]
cpSocket][@(0.0.255) `&]_[*@3 socket])&]
[s2; Reads HTTP header from [%-*@3 socket] and calls other WebAccept
variant.&]
[s3; &]
[s4; &]
[s5;:WebSocket`:`:RecieveRaw`(`):%- [@(0.0.255) bool]_[* RecieveRaw]()&]
@ -89,6 +98,30 @@ ary]([@(0.0.255) const]_[@(0.0.255) void]_`*[*@3 data], [_^int64^ int64]_[*@3 le
[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`_])&]