Developing WebSockets

git-svn-id: svn://ultimatepp.org/upp/trunk@6701 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
cxl 2013-12-28 17:43:59 +00:00
parent 4f87cc5e86
commit 39082cb284
7 changed files with 451 additions and 0 deletions

View file

@ -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;
}

View file

@ -0,0 +1,56 @@
#ifndef _WebSockets_WebSockets_h_
#define _WebSockets_WebSockets_h_
#include <Core/Core.h>
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

View file

@ -0,0 +1,13 @@
uses
Core;
file
WebSockets.h,
WebSockets.cpp,
main.cpp,
test.html,
h.java;
mainconfig
"" = "SSE2";

180
uppdev/WebSockets/h.java Normal file
View file

@ -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<b.length)
{
int r = in.read(b, readen, b.length-readen);
if(r==-1)
break;
readen+=r;
}
}
private String read() throws Exception {
int opcode = in.read();
boolean whole = (opcode & 0b10000000) !=0;
opcode = opcode & 0xF;
if(opcode!=1)
throw new IOException("Wrong opcode: " + opcode);
int len = in.read();
boolean encoded = (len >= 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<frame.length; i++) {
frame[i] = (byte) (frame[i] ^ key[i%4]);
}
}
return new String(frame, "UTF8");
}
private void send(String message) throws Exception {
byte[] utf = message.getBytes("UTF8");
out.write(129);
if(utf.length > 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();
}
}

4
uppdev/WebSockets/init Normal file
View file

@ -0,0 +1,4 @@
#ifndef _WebSockets_icpp_init_stub
#define _WebSockets_icpp_init_stub
#include "Core/init"
#endif

View file

@ -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());
}
}

View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head>
<body>
<button onclick="webs()">WebSocket message</button>
<script>
function webs() {
if("WebSocket" in window) {
var ws = new WebSocket("ws://127.0.0.1:9998/echo");
ws.onopen = function(){
ws.send("A message that consist of 39 characters");
};
ws.onmessage = function(evt){
alert(evt.data);
};
ws.onclose = function(ev){
};
ws.onerror = function(ev){
alert("Connection error: " + ev.reason);
};
}
}
</script>
</body>
</html>