Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
# Author: Jacob Kristhammar, 2010
# Updated version of[1] that implements latest[2] stable version
# of the websocket protocol.
# NB. It's no longer possible to manually select which callback that should
# be invoked upon message reception. Instead you must override the
# on_message(message) method to handle incoming messsages.
# This also means that you don't have to explicitly invoke
# receive_message, in fact you shouldn't.
# [1]
# 2c89b89536bbfa081745336bb5ab5465c448cb8a/tornado/
# [2]
import functools
import hashlib
import logging
import re
import struct
import time
import tornado.escape
import tornado.web
class WebSocketHandler(tornado.web.RequestHandler):
"""Subclass this class to create a basic WebSocket handler.
Override on_message to handle incoming messages. You can also override
open and on_close to handle opened and closed connections.
See for details on the
JavaScript interface. This implement the protocol as specified at
Here is an example Web Socket handler that echos back all received messages
back to the client:
class EchoWebSocket(websocket.WebSocketHandler):
def open(self):
print "WebSocket opened"
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print "WebSocket closed"
Web Sockets are not standard HTTP connections. The "handshake" is HTTP,
but after the handshake, the protocol is message-based. Consequently,
most of the Tornado HTTP facilities are not available in handlers of this
type. The only communication methods available to you are send_message()
and close(). Likewise, your request handler class should
implement open() method rather than get() or post().
If you map the handler above to "/websocket" in your application, you can
invoke it in JavaScript with:
var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
ws.send("Hello, world");
ws.onmessage = function (evt) {
This script pops up an alert box that says "You said: Hello, world".
def __init__(self, application, request):
tornado.web.RequestHandler.__init__(self, application, request) =
self.client_terminated = False
self._waiting = None
def _execute(self, transforms, *args, **kwargs):
self.open_args = args
self.open_kwargs = kwargs
self.ws_request = WebSocketRequest(self.request)
except ValueError:
logging.debug("Malformed WebSocket request received")
return, self._handle_challenge)
def _handle_challenge(self, challenge):
challenge_response = self.ws_request.challenge_response(challenge)
except ValueError:
logging.debug("Malformed key data in WebSocket request")
def _write_response(self, challenge):
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Server: TornadoServer/0.1\r\n"
"Sec-WebSocket-Origin: %s\r\n"
"Sec-WebSocket-Location: ws://%s%s\r\n"
"\r\n%s" % (self.request.headers["Origin"],,
self.request.path, challenge))
self.async_callback(*self.open_args, **self.open_kwargs)
def write_message(self, message):
"""Sends the given message to the client of this Web Socket."""
if isinstance(message, dict):
message = tornado.escape.json_encode(message)
if isinstance(message, unicode):
message = message.encode("utf-8")
assert isinstance(message, str)"\x00" + message + "\xff")
def open(self, *args, **kwargs):
"""Invoked when a new WebSocket is opened."""
def on_message(self, message):
"""Handle incoming messages on the WebSocket
This method must be overloaded
raise NotImplementedError
def on_close(self):
"""Invoked when the WebSocket is closed."""
def close(self):
"""Closes this Web Socket.
Once the close handshake is successful the socket will be closed.
if self.client_terminated and self._waiting:
self._waiting = tornado.ioloop.IOLoop.instance().add_timeout(
time.time() + 5, self._abort)
def async_callback(self, callback, *args, **kwargs):
"""Wrap callbacks with this if they are used on asynchronous requests.
Catches exceptions properly and closes this Web Socket if an exception
is uncaught.
if args or kwargs:
callback = functools.partial(callback, *args, **kwargs)
def wrapper(*args, **kwargs):
return callback(*args, **kwargs)
except Exception, e:
logging.error("Uncaught exception in %s",
self.request.path, exc_info=True)
return wrapper
def _abort(self):
"""Instantly aborts the WebSocket connection by closing the socket"""
self.client_terminated = True
def _receive_message(self):, self._on_frame_type)
def _on_frame_type(self, byte):
frame_type = ord(byte)
if frame_type == 0x00:"\xff", self._on_end_delimiter)
elif frame_type == 0xff:, self._on_length_indicator)
def _on_end_delimiter(self, frame):
if not self.client_terminated:
frame[:-1].decode("utf-8", "replace"))
def _on_length_indicator(self, byte):
if ord(byte) != 0x00:
self.client_terminated = True
def on_connection_close(self):
self.client_terminated = True
def _not_supported(self, *args, **kwargs):
raise Exception("Method not supported for Web Sockets")
for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
"set_status", "flush", "finish"]:
setattr(WebSocketHandler, method, WebSocketHandler._not_supported)
class WebSocketRequest(object):
"""A single WebSocket request.
This class provides basic functionality to process WebSockets requests as
specified in
def __init__(self, request):
self.request = request
self.challenge = None
def challenge_response(self, challenge):
"""Generates the challange response that's needed in the handshake
The challenge parameter should be the raw bytes as sent from the
key_1 = self.request.headers.get("Sec-Websocket-Key1")
key_2 = self.request.headers.get("Sec-Websocket-Key2")
part_1 = self._calculate_part(key_1)
part_2 = self._calculate_part(key_2)
except ValueError:
raise ValueError("Invalid Keys/Challenge")
return self._generate_challenge_response(part_1, part_2, challenge)
def _handle_websocket_headers(self):
"""Verifies all invariant- and required headers
If a header is missing or have an incorrect value ValueError will be
headers = self.request.headers
fields = ("Origin", "Host", "Sec-Websocket-Key1",
if headers.get("Upgrade", '').lower() != "websocket" or \
headers.get("Connection", '').lower() != "upgrade" or \
not all(map(lambda f: self.request.headers.get(f), fields)):
raise ValueError("Missing/Invalid WebSocket headers")
def _calculate_part(self, key):
"""Processes the key headers and calculates their key value.
Raises ValueError when feed invalid key."""
number, spaces = filter(str.isdigit, key), filter(str.isspace, key)
key_number = int(number) / len(spaces)
except (ValueError, ZeroDivisionError):
raise ValueError
return struct.pack(">I", key_number)
def _generate_challenge_response(self, part_1, part_2, part_3):
m = hashlib.md5()
return m.digest()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment