mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-08-26 06:12:49 -06:00
Developing WebSockets
git-svn-id: svn://ultimatepp.org/upp/trunk@6701 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
4f87cc5e86
commit
39082cb284
7 changed files with 451 additions and 0 deletions
142
uppdev/WebSockets/WebSockets.cpp
Normal file
142
uppdev/WebSockets/WebSockets.cpp
Normal 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;
|
||||
}
|
||||
|
||||
56
uppdev/WebSockets/WebSockets.h
Normal file
56
uppdev/WebSockets/WebSockets.h
Normal 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
|
||||
13
uppdev/WebSockets/WebSockets.upp
Normal file
13
uppdev/WebSockets/WebSockets.upp
Normal 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
180
uppdev/WebSockets/h.java
Normal 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
4
uppdev/WebSockets/init
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#ifndef _WebSockets_icpp_init_stub
|
||||
#define _WebSockets_icpp_init_stub
|
||||
#include "Core/init"
|
||||
#endif
|
||||
26
uppdev/WebSockets/main.cpp
Normal file
26
uppdev/WebSockets/main.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
30
uppdev/WebSockets/test.html
Normal file
30
uppdev/WebSockets/test.html
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue