From 39082cb284c83c907cf199ded1e37435838d460a Mon Sep 17 00:00:00 2001 From: cxl Date: Sat, 28 Dec 2013 17:43:59 +0000 Subject: [PATCH] Developing WebSockets git-svn-id: svn://ultimatepp.org/upp/trunk@6701 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppdev/WebSockets/WebSockets.cpp | 142 ++++++++++++++++++++++++ uppdev/WebSockets/WebSockets.h | 56 ++++++++++ uppdev/WebSockets/WebSockets.upp | 13 +++ uppdev/WebSockets/h.java | 180 +++++++++++++++++++++++++++++++ uppdev/WebSockets/init | 4 + uppdev/WebSockets/main.cpp | 26 +++++ uppdev/WebSockets/test.html | 30 ++++++ 7 files changed, 451 insertions(+) create mode 100644 uppdev/WebSockets/WebSockets.cpp create mode 100644 uppdev/WebSockets/WebSockets.h create mode 100644 uppdev/WebSockets/WebSockets.upp create mode 100644 uppdev/WebSockets/h.java create mode 100644 uppdev/WebSockets/init create mode 100644 uppdev/WebSockets/main.cpp create mode 100644 uppdev/WebSockets/test.html diff --git a/uppdev/WebSockets/WebSockets.cpp b/uppdev/WebSockets/WebSockets.cpp new file mode 100644 index 000000000..79630307f --- /dev/null +++ b/uppdev/WebSockets/WebSockets.cpp @@ -0,0 +1,142 @@ +#include "WebSockets.h" + +bool WebSocket::Handshake() +{ + HttpHeader hdr; + if(!hdr.Read(*this)) { + SetSockError("websocket handshake", ERROR_NOHEADER, "Failed to read HTTP header"); + return false; + } + String key = hdr["sec-websocket-key"]; + if(IsNull(key)) { + 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( + "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" + ); +} + +int64 WebSocket::ReadLen(int n) +{ + int64 len = 0; + while(n-- > 0) + len = (len << 8) | (byte)Get(); + return len; +} + +bool WebSocket::RecieveRaw() +{ + if(IsError()) + return false; + + opcode = Get(); + int64 len = Get(); + bool mask = len & 128; + len &= 127; + if(len == 127) + len = ReadLen(8); + if(len == 126) + len = ReadLen(2); + + byte key[4]; + if(mask) + Get(key, 4); + + if(IsError()) { + SetSockError("websocket recieve", ERROR_INVALID_DATA, "Invalid data"); + return false; + } + + if(len > maxlen) { + SetSockError("websocket recieve", ERROR_SIZE_LIMIT, "Frame limit exceeded, size " + AsString(len)); + return false; + } + + StringBuffer frame(len); + char *buffer = ~frame; + if(!GetAll(buffer, len)) { + SetSockError("websocket recieve", ERROR_INVALID_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::Recieve() +{ + for(;;) { + if(!RecieveRaw()) + return String::GetVoid(); + if(GetOpCode() == PING) + SendRaw(PONG, ~data, data.GetLength()); + else + if(GetOpCode() == CLOSE) + SendRaw(CLOSE, ~data, data.GetLength()); + else + break; + } + return data; +} + +bool WebSocket::SendRaw(int hdr, const void *data, int64 len) +{ + if(IsError()) + return false; + + 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(len); + + if(IsError() || !PutAll(~b, b.GetLength()) || !PutAll(data, len)) { + SetSockError("websocket send", ERROR_SEND, "Failed to send data"); + return false; + } + + return true; +} + +void WebSocket::Reset() +{ + opcode = 0; + data.Clear(); + maxlen = 10 * 1024 * 1024; +} + diff --git a/uppdev/WebSockets/WebSockets.h b/uppdev/WebSockets/WebSockets.h new file mode 100644 index 000000000..fdda84c62 --- /dev/null +++ b/uppdev/WebSockets/WebSockets.h @@ -0,0 +1,56 @@ +#ifndef _WebSockets_WebSockets_h_ +#define _WebSockets_WebSockets_h_ + +#include + +using namespace Upp; + +class WebSocket : public TcpSocket { + int64 ReadLen(int n); + + int opcode; + String data; + int64 maxlen; + +public: + enum { + ERROR_NOHEADER = TcpSocket::ERROR_LAST, ERROR_NOKEY, ERROR_INVALID_DATA, ERROR_SEND, ERROR_SIZE_LIMIT + }; + enum { + FIN = 0x80, + CONTINUE = 0x0, + TEXT = 0x1, + BINARY = 0x2, + CLOSE = 0x8, + PING = 0x9, + PONG = 0xa, + }; + + bool Handshake(); + + bool RecieveRaw(); + String Recieve(); + + 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 SendRaw(int hdr, const void *data, int64 len); + + bool SendText(const void *data, int64 len, bool fin = true) { SendRaw((fin ? 0x80 : 0)|TEXT, data, len); } + bool SendText(const String& data, bool fin = true) { SendText(~data, data.GetCount(), fin); } + + bool SendBinary(const void *data, int64 len, bool fin = true) { SendRaw((fin ? 0x80 : 0)|BINARY, data, len); } + bool SendBinary(const String& data, bool fin = true) { SendBinary(~data, data.GetCount(), fin); } + + void Reset(); + + WebSocket& MaxLen(int64 maxlen_) { maxlen = maxlen_; return *this; } + + WebSocket() { Reset(); } +}; + +#endif diff --git a/uppdev/WebSockets/WebSockets.upp b/uppdev/WebSockets/WebSockets.upp new file mode 100644 index 000000000..cc294820f --- /dev/null +++ b/uppdev/WebSockets/WebSockets.upp @@ -0,0 +1,13 @@ +uses + Core; + +file + WebSockets.h, + WebSockets.cpp, + main.cpp, + test.html, + h.java; + +mainconfig + "" = "SSE2"; + diff --git a/uppdev/WebSockets/h.java b/uppdev/WebSockets/h.java new file mode 100644 index 000000000..e8fa03939 --- /dev/null +++ b/uppdev/WebSockets/h.java @@ -0,0 +1,180 @@ +#include "WebSockets.h" + +import java.io.*; +import java.net.*; +import java.security.*; +import javax.xml.bind.*; + +public class WebSocket { + + private ServerSocket server; + private Socket sock; + private InputStream in; + private OutputStream out; + + public WebSocket() { + } + + public void listen(int port) throws IOException { + + server = new ServerSocket(port); + sock = server.accept(); + server.close(); + + in = sock.getInputStream(); + out = sock.getOutputStream(); + } + + private void handshake() throws Exception { + + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF8")); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, "UTF8")); + + + // the first line of HTTP headers + String line = br.readLine(); + + if(!line.startsWith("GET")) + throw new IOException("Wrong header: " + line); + + // we read header fields + String key = null; + + // read line by line until we get empty line + while( !(line=br.readLine()).isEmpty() ) { + + if(line.toLowerCase().contains("sec-websocket-key")) { + key = line.substring(line.indexOf(":")+1).trim(); + } + } + + if(key==null) + throw new IOException("No Websocket key specified"); + + System.out.println(key); + + // add key and magic value + String accept = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // sha1 + byte[] digest = MessageDigest.getInstance("SHA-1") + .digest(accept.getBytes("UTF8")); + // and base64 + accept = DatatypeConverter.printBase64Binary(digest); + + // send http headers + pw.println("HTTP/1.1 101 Switching Protocols"); + pw.println("Upgrade: websocket"); + pw.println("Connection: Upgrade"); + pw.println("Sec-WebSocket-Accept: " + accept); + pw.println(); + pw.flush(); + + } + + private void readFully(byte[] b) throws IOException { + + int readen = 0; + while(readen= 128); + + if(encoded) + len -= 128; + + if(len == 127) { + len = (in.read() << 16) | (in.read() << 8) | in.read(); + } + else if(len == 126) { + len = (in.read() << 8) | in.read(); + } + + byte[] key = null; + + if(encoded) { + key = new byte[4]; + readFully(key); + } + + byte[] frame = new byte[len]; + + readFully(frame); + + if(encoded) { + for(int i=0; i 65535) { + out.write(127); + out.write(utf.length >> 16); + out.write(utf.length >> 8); + out.write(utf.length); + } + else if(utf.length>125) { + out.write(126); + out.write(utf.length >> 8); + out.write(utf.length); + } + else { + out.write(utf.length); + } + + out.write(utf); + } + + private void close() { + try { + sock.close(); + } catch (IOException e) { + System.err.println(e); + } + } + + /** throws Exception, because we don't really care much in this example */ + public static void main(String[] args) throws Exception { + WebSocket ws = new WebSocket(); + + System.out.println("Listening..."); + ws.listen(9998); + + System.out.println("Handshake"); + ws.handshake(); + + System.out.println("Handshake complete!"); + + String message = ws.read(); + System.out.println("Message: " + message); + + ws.send("I got your message! It's length was: " + message.length()); + + ws.close(); + } +} diff --git a/uppdev/WebSockets/init b/uppdev/WebSockets/init new file mode 100644 index 000000000..68f7cc57d --- /dev/null +++ b/uppdev/WebSockets/init @@ -0,0 +1,4 @@ +#ifndef _WebSockets_icpp_init_stub +#define _WebSockets_icpp_init_stub +#include "Core/init" +#endif diff --git a/uppdev/WebSockets/main.cpp b/uppdev/WebSockets/main.cpp new file mode 100644 index 000000000..bf99f1e8f --- /dev/null +++ b/uppdev/WebSockets/main.cpp @@ -0,0 +1,26 @@ +#include "WebSockets.h" + +CONSOLE_APP_MAIN +{ + StdLogSetup(LOG_COUT|LOG_FILE); + + TcpSocket server; + if(!server.Listen(9998)) { + LOG("Failed to listen.."); + return; + } + + for(;;) { + WebSocket ws; + if(ws.Accept(server)) { + LOG("Accepted, trying to handshake"); + if(ws.Handshake()) { + LOG("Handshake successfull, trying to recieve"); + LOG(ws.Recieve()); + ws.SendText("This is some text..."); + } + } + if(ws.IsError()) + LOG("ERROR: " << ws.GetErrorDesc()); + } +} diff --git a/uppdev/WebSockets/test.html b/uppdev/WebSockets/test.html new file mode 100644 index 000000000..ed6ce114f --- /dev/null +++ b/uppdev/WebSockets/test.html @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file