Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@geoffb
Created October 7, 2010 23:37
Show Gist options
  • Star 61 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save geoffb/616117 to your computer and use it in GitHub Desktop.
Save geoffb/616117 to your computer and use it in GitHub Desktop.
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()
@clayheaton
Copy link

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.).

@dawsonlp
Copy link

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
Copy link

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
Copy link

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

@jclounge
Copy link

@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
Copy link

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'

@SadatAnwar
Copy link

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

@ptinker56
Copy link

Thanks for the update. What version of python is your example built for?

@ptinker56
Copy link

One comment on the newer code. Although the incorrect "recv(4096)" has been replaced, it still issues statements like "recv(4)" and assumes that 4 bytes were read. This is not true! The socket may have any number of bytes ready for recv, and you need to check the return value from recv to see how many bytes were actually transferred. If only two bytes have been received by the socket when recv is called, recv(-- anything greater than 2 --) will only return 2 bytes. You need a loop that keeps reading until the anticipated number of bytes have been read.

@sivaa
Copy link

sivaa commented Jan 9, 2017

@SadatAnwar Your code is not available and receiving 404 now.

@Benargee
Copy link

@sivaa his code is available. url is slightly different https://gist.github.com/SadatAnwar/222d4643c25f72293461 . I think he misspelled his name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment