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