Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple WebSockets in Python
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)
@mumrah

This comment has been minimized.

Show comment
Hide comment
@mumrah

mumrah Aug 7, 2010

Added a JSON-RPC example

Owner

mumrah commented Aug 7, 2010

Added a JSON-RPC example

@mgielda

This comment has been minimized.

Show comment
Hide comment
@mgielda

mgielda Oct 17, 2010

Hey! Great work, I got it to run without any major problems, but I had to:

import signal
import threading
change Myserver->WebSocket in line 138.

Otherwise it was perfect, unlike all those other implementations on the Web which can't seem to work.

Thanks!
M.

mgielda commented Oct 17, 2010

Hey! Great work, I got it to run without any major problems, but I had to:

import signal
import threading
change Myserver->WebSocket in line 138.

Otherwise it was perfect, unlike all those other implementations on the Web which can't seem to work.

Thanks!
M.

@mumrah

This comment has been minimized.

Show comment
Hide comment
@mumrah

mumrah Oct 18, 2010

@thalain, thanks updated the gist

Owner

mumrah commented Oct 18, 2010

@thalain, thanks updated the gist

@remcoder

This comment has been minimized.

Show comment
Hide comment
@remcoder

remcoder Oct 31, 2010

I had to change line 141 into this to get it to work:
server_thread = threading.Thread(target=server.listen, args=[5])

Just like thalain said, this is the first working Python implementation I got to work (the other ones are outdated I think). Thanks for the great work!

I had to change line 141 into this to get it to work:
server_thread = threading.Thread(target=server.listen, args=[5])

Just like thalain said, this is the first working Python implementation I got to work (the other ones are outdated I think). Thanks for the great work!

@remcoder

This comment has been minimized.

Show comment
Hide comment
@remcoder

remcoder Oct 31, 2010

also, CTRL-C didn't work. I added these lines at the very end:
while 1:
time.sleep(100)

and don't forget to
import time

also, CTRL-C didn't work. I added these lines at the very end:
while 1:
time.sleep(100)

and don't forget to
import time

@mumrah

This comment has been minimized.

Show comment
Hide comment
@mumrah

mumrah Oct 31, 2010

If the SIGINT trap doesn't work, you could try using KeyboardInterrupt

http://docs.python.org/library/exceptions.html#exceptions.KeyboardInterrupt

Owner

mumrah commented Oct 31, 2010

If the SIGINT trap doesn't work, you could try using KeyboardInterrupt

http://docs.python.org/library/exceptions.html#exceptions.KeyboardInterrupt

@remcoder

This comment has been minimized.

Show comment
Hide comment
@remcoder

remcoder Oct 31, 2010

The SIGINT trap itself works but only if I keep the main thread alive with the while-loop. The same goes for the KeyboordInterrupt which I only seem to catch like this:

while 1:
  try:
    time.sleep(100)
  except KeyboardInterrupt:
    logging.info("KeyboardInterrupt, shutting down...")
    server.running = False
    sys.exit()

I'm on a Mac. Maybe these things vary per platform?... Anyway, I've had a lot of fun playing with your code. Thx again for making this available.

The SIGINT trap itself works but only if I keep the main thread alive with the while-loop. The same goes for the KeyboordInterrupt which I only seem to catch like this:

while 1:
  try:
    time.sleep(100)
  except KeyboardInterrupt:
    logging.info("KeyboardInterrupt, shutting down...")
    server.running = False
    sys.exit()

I'm on a Mac. Maybe these things vary per platform?... Anyway, I've had a lot of fun playing with your code. Thx again for making this available.

@remcoder

This comment has been minimized.

Show comment
Hide comment
@remcoder

remcoder Oct 31, 2010

Another issue I encountered was the 'Address already in use' error I got when quitting and immediately restarting the server. I fixed it by inserting this code just before line 99
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Another issue I encountered was the 'Address already in use' error I got when quitting and immediately restarting the server. I fixed it by inserting this code just before line 99
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

@gitdlam

This comment has been minimized.

Show comment
Hide comment
@gitdlam

gitdlam Feb 26, 2011

i found that line 80 occasionally fail, and i had to delay "adding the md5 response to the handshake (line 76)", to after the string substitution.

gitdlam commented Feb 26, 2011

i found that line 80 occasionally fail, and i had to delay "adding the md5 response to the handshake (line 76)", to after the string substitution.

@dmytty

This comment has been minimized.

Show comment
Hide comment
@dmytty

dmytty Mar 4, 2011

Under Python 3 'str' and 'bytes' are handled differently during transmission...strings must be converted to bytes for transmission and bytes must be converted to strings on receive

Line 39:
Original ---> self.header += data
Error ---> Can't convert 'bytes' object to str implicitly
Modification ---> self.header += bytes.decode(data, encoding='utf_8', errors='strict')

Line 83:
original ---> self.client.send(handshake)
error ---> TypeError: 'str' does not support the buffer interface
modification ---> self.client.send(str.encode(handshake, encoding='utf_8', errors='strict'))

With those modifications the websockets servery is working on python 3.2

dmytty commented Mar 4, 2011

Under Python 3 'str' and 'bytes' are handled differently during transmission...strings must be converted to bytes for transmission and bytes must be converted to strings on receive

Line 39:
Original ---> self.header += data
Error ---> Can't convert 'bytes' object to str implicitly
Modification ---> self.header += bytes.decode(data, encoding='utf_8', errors='strict')

Line 83:
original ---> self.client.send(handshake)
error ---> TypeError: 'str' does not support the buffer interface
modification ---> self.client.send(str.encode(handshake, encoding='utf_8', errors='strict'))

With those modifications the websockets servery is working on python 3.2

@danielfaust

This comment has been minimized.

Show comment
Hide comment
@danielfaust

danielfaust Apr 29, 2011

As gitdlam pointed out, line 80 occasionally fails, which leads to self.client.send(handshake) never getting called.

This occurs when the challenge response contains a %, which messes up the string substitution.

A solution is to comment out line 80, and

  1. change line 76 to:
    handshake = (WebSocket.handshake % {'origin': origin, 'port': self.server.port, 'address': self.server.address}) + response

  2. line 79 to:
    handshake = (WebSocket.handshake % {'origin': origin, 'port': self.server.port, 'address': self.server.address})

As gitdlam pointed out, line 80 occasionally fails, which leads to self.client.send(handshake) never getting called.

This occurs when the challenge response contains a %, which messes up the string substitution.

A solution is to comment out line 80, and

  1. change line 76 to:
    handshake = (WebSocket.handshake % {'origin': origin, 'port': self.server.port, 'address': self.server.address}) + response

  2. line 79 to:
    handshake = (WebSocket.handshake % {'origin': origin, 'port': self.server.port, 'address': self.server.address})

@mumrah

This comment has been minimized.

Show comment
Hide comment
@mumrah

mumrah Apr 29, 2011

@danielfaust, @remcoder, thanks updated gist

Owner

mumrah commented Apr 29, 2011

@danielfaust, @remcoder, thanks updated gist

@gleb-chipiga

This comment has been minimized.

Show comment
Hide comment
@gleb-chipiga

gleb-chipiga Oct 11, 2011

Hi. Great work. What version of the protocol currently used?

Hi. Great work. What version of the protocol currently used?

@danielfaust

This comment has been minimized.

Show comment
Hide comment
@danielfaust

danielfaust Oct 11, 2011

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.

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.

@mumrah

This comment has been minimized.

Show comment
Hide comment
@mumrah

mumrah Oct 11, 2011

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

Owner

mumrah commented Oct 11, 2011

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

@gleb-chipiga

This comment has been minimized.

Show comment
Hide comment
@gleb-chipiga

gleb-chipiga Oct 11, 2011

Thanks. Tornado seems is that I need :)

Thanks. Tornado seems is that I need :)

@rich20bb

This comment has been minimized.

Show comment
Hide comment
@rich20bb

rich20bb Dec 2, 2012

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

rich20bb commented Dec 2, 2012

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

@rich20bb

This comment has been minimized.

Show comment
Hide comment
@rich20bb

rich20bb Dec 2, 2012

Sorry, the URL above should have been https://gist.github.com/4190781

rich20bb commented Dec 2, 2012

Sorry, the URL above should have been https://gist.github.com/4190781

@mayket04

This comment has been minimized.

Show comment
Hide comment
@mayket04

mayket04 Feb 21, 2014

How would you close a connection from client correctly

How would you close a connection from client correctly

@Zeokat

This comment has been minimized.

Show comment
Hide comment
@Zeokat

Zeokat Mar 5, 2014

This piece of code saved Zeokat lots of hours. Deal with HTTP protocol is always painfull and the rfc2616 grgrgr

Zeokat commented Mar 5, 2014

This piece of code saved Zeokat lots of hours. Deal with HTTP protocol is always painfull and the rfc2616 grgrgr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment