-
-
Save mumrah/512987 to your computer and use it in GitHub Desktop.
import time | |
import struct | |
import socket | |
import hashlib | |
import sys | |
from select import select | |
import re | |
import logging | |
from threading import Thread | |
import signal | |
class WebSocket(object): | |
handshake = ( | |
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n" | |
"Upgrade: WebSocket\r\n" | |
"Connection: Upgrade\r\n" | |
"WebSocket-Origin: %(origin)s\r\n" | |
"WebSocket-Location: ws://%(bind)s:%(port)s/\r\n" | |
"Sec-Websocket-Origin: %(origin)s\r\n" | |
"Sec-Websocket-Location: ws://%(bind)s:%(port)s/\r\n" | |
"\r\n" | |
) | |
def __init__(self, client, server): | |
self.client = client | |
self.server = server | |
self.handshaken = False | |
self.header = "" | |
self.data = "" | |
def feed(self, data): | |
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]): | |
logging.info("Handshake successful") | |
self.handshaken = True | |
else: | |
self.data += data | |
msgs = self.data.split('\xff') | |
self.data = msgs.pop() | |
for msg in msgs: | |
if msg[0] == '\x00': | |
self.onmessage(msg[1:]) | |
def dohandshake(self, header, key=None): | |
logging.debug("Begin handshake: %s" % header) | |
digitRe = re.compile(r'[^0-9]') | |
spacesRe = re.compile(r'\s') | |
part_1 = part_2 = origin = None | |
for line in header.split('\r\n')[1:]: | |
name, value = line.split(': ', 1) | |
if name.lower() == "sec-websocket-key1": | |
key_number_1 = int(digitRe.sub('', value)) | |
spaces_1 = len(spacesRe.findall(value)) | |
if spaces_1 == 0: | |
return False | |
if key_number_1 % spaces_1 != 0: | |
return False | |
part_1 = key_number_1 / spaces_1 | |
elif name.lower() == "sec-websocket-key2": | |
key_number_2 = int(digitRe.sub('', value)) | |
spaces_2 = len(spacesRe.findall(value)) | |
if spaces_2 == 0: | |
return False | |
if key_number_2 % spaces_2 != 0: | |
return False | |
part_2 = key_number_2 / spaces_2 | |
elif name.lower() == "origin": | |
origin = value | |
if part_1 and part_2: | |
logging.debug("Using challenge + response") | |
challenge = struct.pack('!I', part_1) + struct.pack('!I', part_2) + key | |
response = hashlib.md5(challenge).digest() | |
handshake = WebSocket.handshake % { | |
'origin': origin, | |
'port': self.server.port, | |
'bind': self.server.bind | |
} | |
handshake += response | |
else: | |
logging.warning("Not using challenge + response") | |
handshake = WebSocket.handshake % { | |
'origin': origin, | |
'port': self.server.port, | |
'bind': self.server.bind | |
} | |
logging.debug("Sending handshake %s" % handshake) | |
self.client.send(handshake) | |
return True | |
def onmessage(self, data): | |
logging.info("Got message: %s" % data) | |
def send(self, data): | |
logging.info("Sent message: %s" % data) | |
self.client.send("\x00%s\xff" % data) | |
def close(self): | |
self.client.close() | |
class WebSocketServer(object): | |
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] | |
def listen(self, backlog=5): | |
self.socket.listen(backlog) | |
logging.info("Listening on %s" % self.port) | |
self.running = True | |
while self.running: | |
rList, wList, xList = select(self.listeners, [], self.listeners, 1) | |
for ready in rList: | |
if ready == self.socket: | |
logging.debug("New client connection") | |
client, address = self.socket.accept() | |
fileno = client.fileno() | |
self.listeners.append(fileno) | |
self.connections[fileno] = self.cls(client, self) | |
else: | |
logging.debug("Client ready for reading %s" % ready) | |
client = self.connections[ready].client | |
data = client.recv(1024) | |
fileno = client.fileno() | |
if data: | |
self.connections[fileno].feed(data) | |
else: | |
logging.debug("Closing client %s" % ready) | |
self.connections[fileno].close() | |
del self.connections[fileno] | |
self.listeners.remove(ready) | |
for failed in xList: | |
if failed == self.socket: | |
logging.error("Socket broke") | |
for fileno, conn in self.connections: | |
conn.close() | |
self.running = False | |
if __name__ == "__main__": | |
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") | |
server = WebSocketServer("localhost", 9999, WebSocket) | |
server_thread = Thread(target=server.listen, args=[5]) | |
server_thread.start() | |
# Add SIGINT handler for killing the threads | |
def signal_handler(signal, frame): | |
logging.info("Caught Ctrl+C, shutting down...") | |
server.running = False | |
sys.exit() | |
signal.signal(signal.SIGINT, signal_handler) | |
while True: | |
time.sleep(100) |
Hi. Great work. What version of the protocol currently used?
hixie-76 (hybi-00) . As of Oct. 2011 only Opera and Safari support this old version. Doesn't work with Chrome and Firefox anymore :(
You might want to check out Tavendo Autobahn or pywebsocket as an alternative (hybi-10). Though I'd really like to see this server getting upgraded sometime.
This was really just a prototype to see WebSockets working with Python. For an up-to-date (and more complete) implementation check out Tornado https://github.com/facebook/tornado/blob/master/tornado/websocket.py
Thanks. Tornado seems is that I need :)
I spent literally hours looking around for a really simple WebSocket implemention in Python over the past couple of days. There are lots of frameworks and helpers, though I wanted something that was self contained and didn't have a list of dependencies as long as my arm. In the end I adapted this so that it works with the RFC version of WebSockets. It's slightly hacky at the moment, though hopefully will help somebody. See here for the code: https://gist.github.com/4190745
Sorry, the URL above should have been https://gist.github.com/4190781
How would you close a connection from client correctly
This piece of code saved Zeokat lots of hours. Deal with HTTP protocol is always painfull and the rfc2616 grgrgr
@danielfaust, @remcoder, thanks updated gist