Created
March 19, 2013 13:59
-
-
Save yamatt/5196319 to your computer and use it in GitHub Desktop.
Reference WebSocket server in Python. Works with WebSocket revision 13.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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. | |
# | |
# [email protected] | |
# 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