Skip to content

Instantly share code, notes, and snippets.

@yamatt
Created March 19, 2013 13:59
Show Gist options
  • Save yamatt/5196319 to your computer and use it in GitHub Desktop.
Save yamatt/5196319 to your computer and use it in GitHub Desktop.
Reference WebSocket server in Python. Works with WebSocket revision 13.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<style media="screen" type="text/css">
body {
vertical-align: middle;
}
</style>
</head>
<body>
<form id="send">
<h2>Send Message</h2>
<input name="value" type="text" value="Hello World!"/>
<button type="submit">send</button>
</form>
<h2>Responses</h2>
<ul id="responses">
</ul>
<script type="text/javascript">
var ws = new WebSocket("ws://localhost:8000 ");
var send = document.querySelector("#send");
var responses = document.querySelector("#responses");
ws.onopen = function() {
console.log("Connected.");
};
ws.onmessage = function (e) {
new_message = document.createElement("li");
new_message.innerText = e.data;
responses.appendChild(new_message);
};
window.onbeforeunload = function() {
ws.onclose = function () {
console.log("Disconnected.");
};
ws.close()
};
var send_message = function (message) {
ws.send(message);
};
send.addEventListener("submit", function (e) {
e.preventDefault();
message = e.srcElement.value.value;
send_message(message);
}, true);
</script>
</body>
</html>
#!/usr/bin/env python
import time
import struct
import socket
import hashlib
import base64
import sys
from select import select
import re
# Simple WebSocket server implementation. Handshakes with the client then echos back everything
# that is received. Has no dependencies (doesn't require Twisted etc) and works with the RFC6455
# version of WebSockets. Tested with FireFox 16, though should work with the latest versions of
# IE, Chrome etc.
#
# rich20b@gmail.com
# Adapted from https://gist.github.com/512987 with various functions stolen from other sites, see
# below for full details.
# Constants
MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
TEXT = 0x01
BINARY = 0x02
# represents client connection
class WebSocket(object):
handshake = (
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %(acceptstring)s\r\n"
"Server: TestTest\r\n"
"Access-Control-Allow-Origin: http://localhost\r\n"
"Access-Control-Allow-Credentials: true\r\n"
"\r\n"
)
# Constructor
def __init__(self, client, server):
self.client = client
self.server = server
self.handshaken = False
self.header = ""
self.data = ""
# Serve this client
def feed(self, data):
# If we haven't handshaken yet
if not self.handshaken:
self.header += data
if self.header.find('\r\n\r\n') != -1:
parts = self.header.split('\r\n\r\n', 1)
self.header = parts[0]
if self.dohandshake(self.header, parts[1]):
self.handshaken = True
print "Handshake complete."
# We have handshaken
else:
# Decode the data that we received according to section 5 of RFC6455
recv = self.decodeCharArrayToString(data)
print "Message found: {0}".format(recv)
# Send our reply
response = recv[::-1]
print "Sending response: {0}".format(response)
self.sendMessage(response);
# Stolen from http://www.cs.rpi.edu/~goldsd/docs/spring2012-csci4220/websocket-py.txt
def sendMessage(self, s):
"""
Encode and send a WebSocket message
"""
# Empty message to start with
message = ""
# always send an entire message as one frame (fin)
b1 = 0x80
# in Python 2, strs are bytes and unicodes are strings
if type(s) == unicode:
b1 |= TEXT
payload = s.encode("UTF8")
elif type(s) == str:
b1 |= TEXT
payload = s
# Append 'FIN' flag to the message
message += chr(b1)
# never mask frames from the server to the client
b2 = 0
# How long is our payload?
length = len(payload)
if length < 126:
b2 |= length
message += chr(b2)
elif length < (2 ** 16) - 1:
b2 |= 126
message += chr(b2)
l = struct.pack(">H", length)
message += l
else:
l = struct.pack(">Q", length)
b2 |= 127
message += chr(b2)
message += l
# Append payload to message
message += payload
# Send to the client
self.client.send(str(message))
def decodeCharArrayToString(self, stringStreamIn):
message = self.decodeCharArray(stringStreamIn)
return ''.join(message)
# Stolen from http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
def decodeCharArray(self, stringStreamIn):
# Turn string values into opererable numeric byte values
byteArray = [ord(character) for character in stringStreamIn]
datalength = byteArray[1] & 127
indexFirstMask = 2
if datalength == 126:
indexFirstMask = 4
elif datalength == 127:
indexFirstMask = 10
# Extract masks
masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]]
indexFirstDataByte = indexFirstMask + 4
# List of decoded characters
decodedChars = []
i = indexFirstDataByte
j = 0
# Loop through each byte that was received
while i < len(byteArray):
# Unmask this byte and add to the decoded buffer
decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) )
i += 1
j += 1
# Return the decoded string
return decodedChars
# Handshake with this client
def dohandshake(self, header, key=None):
# Get the handshake template
handshake = self.handshake
# Step through each header
for line in header.split('\r\n')[1:]:
name, value = line.split(': ', 1)
# If this is the key
if name.lower() == "sec-websocket-key":
# Append the standard GUID and get digest
combined = value + MAGIC_GUID
response = base64.b64encode(hashlib.sha1(combined).digest())
# Replace the placeholder in the handshake response
handshake = handshake % { 'acceptstring' : response }
self.client.send(handshake)
return True
def onmessage(self, data):
self.send(data)
def send(self, data):
self.client.send("\x00%s\xff" % data)
def close(self):
self.client.close()
# WebSocket server implementation
class WebSocketServer(object):
# Constructor
def __init__(self, bind, port, cls):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((bind, port))
self.bind = bind
self.port = port
self.cls = cls
self.connections = {}
self.listeners = [self.socket]
# Listen for requests
def listen(self, backlog=5):
self.socket.listen(backlog)
print "Waiting for connections."
# Keep serving requests
self.running = True
while self.running:
# Find clients that need servicing
rList, wList, xList = select(self.listeners, [], self.listeners, 1)
for ready in rList:
if ready == self.socket:
client, address = self.socket.accept()
fileno = client.fileno()
self.listeners.append(fileno)
self.connections[fileno] = self.cls(client, self)
print "Client connected."
else:
client = self.connections[ready].client
data = client.recv(4096)
fileno = client.fileno()
if data:
self.connections[fileno].feed(data)
else:
self.connections[fileno].close()
del self.connections[fileno]
self.listeners.remove(ready)
# Step though and delete broken connections
for failed in xList:
if failed == self.socket:
for fileno, conn in self.connections:
conn.close()
self.running = False
# Entry point
if __name__ == "__main__":
port = 8000
if len(sys.argv) > 1:
port = int(sys.argv[1])
server = WebSocketServer("", 8000, WebSocket)
try:
server.listen()
except KeyboardInterrupt:
print "Shutting down."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment