Skip to content

@geoffb /simple_websocket_client.html
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Super simple websockets client/server using Python. Compatible with the draft 76 challenge/response.
<!DOCTYPE html>
<html lang="en">
<head>
<title>WebSocket Client</title>
<style>
#output {
border: solid 1px #000;
}
</style>
</head>
<body>
<form id="form">
<input type="text" id="message">
<button type="submit">Send</button>
</form>
<hr>
<div id="output"></div>
<script>
var inputBox = document.getElementById("message");
var output = document.getElementById("output");
var form = document.getElementById("form");
try {
var host = "ws://" + window.location.hostname + ":9876/stuff";
console.log("Host:", host);
var s = new WebSocket(host);
s.onopen = function (e) {
console.log("Socket opened.");
};
s.onclose = function (e) {
console.log("Socket closed.");
};
s.onmessage = function (e) {
console.log("Socket message:", e.data);
var p = document.createElement("p");
p.innerHTML = e.data;
output.appendChild(p);
};
s.onerror = function (e) {
console.log("Socket error:", e);
};
} catch (ex) {
console.log("Socket exception:", ex);
}
form.addEventListener("submit", function (e) {
e.preventDefault();
s.send(inputBox.value);
inputBox.value = "";
}, false)
</script>
</body>
</html>
#!/usr/bin/env python
import socket, struct, hashlib, threading, cgi
def decode_key (key):
num = ""
spaces = 0
for c in key:
if c.isdigit():
num += c
if c.isspace():
spaces += 1
return int(num) / spaces
def create_hash (key1, key2, code):
a = struct.pack(">L", decode_key(key1))
b = struct.pack(">L", decode_key(key2))
md5 = hashlib.md5(a + b + code)
return md5.digest()
def recv_data (client, length):
data = client.recv(length)
if not data: return data
return data.decode('utf-8', 'ignore')
def send_data (client, data):
message = "\x00%s\xFF" % data.encode('utf-8')
return client.send(message)
def parse_headers (data):
headers = {}
lines = data.splitlines()
for l in lines:
parts = l.split(": ", 1)
if len(parts) == 2:
headers[parts[0]] = parts[1]
headers['code'] = lines[len(lines) - 1]
return headers
def handshake (client):
print 'Handshaking...'
data = client.recv(1024)
headers = parse_headers(data)
print 'Got headers:'
for k, v in headers.iteritems():
print k, ':', v
digest = create_hash(
headers['Sec-WebSocket-Key1'],
headers['Sec-WebSocket-Key2'],
headers['code']
)
shake = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
shake += "Upgrade: WebSocket\r\n"
shake += "Connection: Upgrade\r\n"
shake += "Sec-WebSocket-Origin: %s\r\n" % (headers['Origin'])
shake += "Sec-WebSocket-Location: ws://%s/stuff\r\n" % (headers['Host'])
shake += "Sec-WebSocket-Protocol: sample\r\n\r\n"
shake += digest
return client.send(shake)
def handle (client, addr):
handshake(client)
lock = threading.Lock()
while 1:
data = recv_data(client, 1024)
if not data: break
data = cgi.escape(data)
lock.acquire()
[send_data(c, data) for c in clients]
lock.release()
print 'Client closed:', addr
lock.acquire()
clients.remove(client)
lock.release()
client.close()
def start_server ():
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 9876))
s.listen(5)
while 1:
conn, addr = s.accept()
print 'Connection from:', addr
clients.append(conn)
threading.Thread(target = handle, args = (conn, addr)).start()
clients = []
start_server()
@ccheaton

This is helpful for me because I'm trying to learn websockets. A few comments at the top of each file also would help (ports, etc.).

@brhsiao

What does that lock in handle(client, addr) do? Isn't the point of a lock to synchronize several threads? Is that a bug for each thread to have its own lock?

@dawsonlp

Running Ubuntu/Chromium/31.0.1650.63, the headers that come from the browser when I load the webpage don't include Sec-WebSocket-Key1 and Sec-WebSocket-Key2, they only have Sec-WebSocket-Key, so the python code throws a KeyError exception in handshake(client) when it tries to build a digest.
Is this because of a recent change in Chrome? Has the websocket protocol been updated since this code was published?

@dawsonlp

Explanation - the draft 76 challenge/response that this implements has been replaced with RFC-6455. Modern browsers don't work with this code as is.

@Paraintom

Thank you DawsonIp, I have the same error.
Did you find a workaround?

@jclounge

@brandonhsiao, yeah, it looks like there should instead only be a single lock. Then the threads would all use that one, so that only one thread at a time can do the send_data() calls.

@jamesbaber

Nice example, but I'm getting this error:
Exception in thread Thread-2:
Traceback (most recent call last):
File "E:\Program Files (x86)\Python27\lib\threading.py", line 810, in __bootstrap_inner
self.run()
File "E:\Program Files (x86)\Python27\lib\threading.py", line 763, in run
self.__target(self.__args, *self.__kwargs)
File "E:\James\EclipseWorkspace - Kepler\ProjectDungeon\WebSocketServe\main.py", line 62, in handle
handshake(client)
File "E:\main.py", line 48, in handshake
headers['Sec-WebSocket-Key1'],
KeyError: 'Sec-WebSocket-Key1'

@sadatanwer

A lot of the users complained that the code was throwing errors I needed it and too found that it was broken on this day (July 28, 2015) so I did my best to make it working again. Obviously all original credits go to https://gist.github.com/ccheaton for making it possible.

My working code is available at https://gist.github.com/sadatanwer/222d4643c25f72293461

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.