-
-
Save rich20bb/4190781 to your computer and use it in GitHub Desktop.
import time | |
import struct | |
import socket | |
import hashlib | |
import base64 | |
import sys | |
from select import select | |
import re | |
import logging | |
from threading import Thread | |
import signal | |
# 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 | |
# WebSocket implementation | |
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: | |
logging.debug("No handshake yet") | |
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 | |
# We have handshaken | |
else: | |
logging.debug("Handshake is complete") | |
# Decode the data that we received according to section 5 of RFC6455 | |
recv = self.decodeCharArray(data) | |
# Send our reply | |
self.sendMessage(''.join(recv).strip()); | |
# 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)) | |
# 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): | |
logging.debug("Begin handshake: %s" % header) | |
# 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 } | |
logging.debug("Sending handshake %s" % handshake) | |
self.client.send(handshake) | |
return True | |
def onmessage(self, data): | |
#logging.info("Got message: %s" % data) | |
self.send(data) | |
def send(self, data): | |
logging.info("Sent message: %s" % 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) | |
logging.info("Listening on %s" % self.port) | |
# 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: | |
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(4096) | |
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) | |
# Step though and delete broken connections | |
for failed in xList: | |
if failed == self.socket: | |
logging.error("Socket broke") | |
for fileno, conn in self.connections: | |
conn.close() | |
self.running = False | |
# Entry point | |
if __name__ == "__main__": | |
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") | |
server = WebSocketServer("", 8000, 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! After a tedious work looking for a small working Websocket server I finally found yours. Runs perfectly on a Linux 64 machine.
Gold!
Hey!
Thanks for the gist, it's awesome. Just one thing, it does not correctly handle RST packets; when one of this is passed down it throws up a "error: [Errno 54] Connection reset by peer" error that causes the thread to crash. You can experience this by refreshing a website that has a WebSocket connection open
You can fix this by changing line 237 from
data = client.recv(4096)
to
try:
data = client.recv(4096)
except socket.error, e:
logging.error("Connection error: %s" % e)
data = None
print "Connection error: %s" % e
Then it will throw the error but it won't cause the thread to crash. Thanks again!
Hi
It is awesome,but I can not get html files ,like this "localhost/test.html".
How should i do?
Thank you~
Can u send me the HTML files for this on [email protected]?
for someone who experience this error "SyntaxError: can't assign to operator", change MAGIC-GUID to MAGICGUID, remove strip from variable name.
doesn't work in python 3
If I understand correctly, this solution is unable to handle partially received websocket messages? Or receiving multiple websocket messages within the same TCP read?
Thank you
Hi, How Ican get IP address of client?
Ran this on OSX 10.7 without changes. Worked great. Good piece of work.