Jeffrey Gelens avatar Jeffrey Gelens committed 24d2a16

Preparation for implementation of newest version of the Websocket Protocol

Comments (0)

Files changed (5)

         return []
 
 
-
 server = pywsgi.WSGIServer(('0.0.0.0', 8000), app,
         handler_class=WebSocketHandler)
 server.serve_forever()

geventwebsocket/__init__.py

 
-version_info = (0, 2, 3)
+version_info = (0, 3, 0, 'dev')
 __version__ =  ".".join(map(str, version_info))
 
 try:
-    from geventwebsocket.websocket import WebSocket
+    from geventwebsocket.websocket import WebSocket, WebSocketLegacy
 except ImportError:
     import traceback
     traceback.print_exc()

geventwebsocket/handler.py

 import re
 import struct
-from hashlib import md5
+from hashlib import md5, sha1
+from base64 import b64encode
 
 from gevent.pywsgi import WSGIHandler
-from geventwebsocket import WebSocket
+from geventwebsocket import WebSocket, WebSocketLegacy
 
 
+PROTOCOL_VERSIONS = (
+    "hixie-75",
+    "0",
+    "6",
+)
+MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
 class HandShakeError(ValueError):
     """ Hand shake challenge can't be parsed """
     pass
     def handle_one_response(self, call_wsgi_app=True):
         # In case the client doesn't want to initialize a WebSocket connection
         # we will proceed with the default PyWSGI functionality.
-        if self.environ.get("HTTP_CONNECTION") != "Upgrade" or \
-           self.environ.get("HTTP_UPGRADE") != "WebSocket" or \
-           not self.environ.get("HTTP_ORIGIN") or \
-           not self.accept_upgrade():
+
+        if "Upgrade" in self.environ.get("HTTP_CONNECTION", "").split(",") and \
+             "WebSocket" in self.environ.get("HTTP_UPGRADE") and \
+             self.upgrade_allowed():
+            self.websocket_connection = True
+        else:
             return super(WebSocketHandler, self).handle_one_response()
-        else:
-            self.websocket_connection = True
 
-        self.websocket = WebSocket(self.socket, self.rfile, self.environ)
+        self.init_websocket()
         self.environ['wsgi.websocket'] = self.websocket
 
-        # Detect the Websocket protocol
-        if "HTTP_SEC_WEBSOCKET_KEY1" in self.environ:
-            version = 76
-        else:
-            version = 75
-
-        if version == 75:
-            headers = [
-                ("Upgrade", "WebSocket"),
-                ("Connection", "Upgrade"),
-                ("WebSocket-Origin", self.websocket.origin),
-                ("WebSocket-Protocol", self.websocket.protocol),
-                ("WebSocket-Location", "ws://" + self.environ.get('HTTP_HOST') + self.websocket.path),
-            ]
-            self.start_response("101 Web Socket Protocol Handshake", headers)
-        elif version == 76:
-            challenge = self._get_challenge()
-            headers = [
-                ("Upgrade", "WebSocket"),
-                ("Connection", "Upgrade"),
-                ("Sec-WebSocket-Origin", self.websocket.origin),
-                ("Sec-WebSocket-Protocol", self.websocket.protocol),
-                ("Sec-WebSocket-Location", "ws://" + self.environ.get('HTTP_HOST') + self.websocket.path),
-            ]
-
-            self.start_response("101 Web Socket Protocol Handshake", headers)
-            self.write(challenge)
-        else:
-            raise Exception("Version not supported")
-
         if call_wsgi_app:
             return self.application(self.environ, self.start_response)
         else:
             return
 
-    def accept_upgrade(self):
+    def init_websocket(self):
+        version = self.environ.get("HTTP_SEC_WEBSOCKET_VERSION")
+
+        if self.environ.get("HTTP_ORIGIN"):
+            self.websocket = WebSocketLegacy(self.socket, self.rfile, self.environ)
+
+            if "HTTP_SEC_WEBSOCKET_KEY1" in self.environ:
+                self._handshake_hybi00()
+            else:
+                self._handshake_hixie75()
+        else:
+            self.websocket = WebSocket(self.socket, self.rfile, self.environ)
+
+            if version and int(version) in PROTOCOL_VERSIONS:
+                pass
+
+
+    def _handshake_hixie75(self):
+        headers = [
+            ("Upgrade", "WebSocket"),
+            ("Connection", "Upgrade"),
+            ("WebSocket-Origin", self.websocket.origin),
+            ("WebSocket-Protocol", self.websocket.protocol),
+            ("WebSocket-Location", "ws://" + self.environ.get('HTTP_HOST') + self.websocket.path),
+        ]
+        self.start_response("101 Web Socket Protocol Handshake", headers)
+
+    def _handshake_hybi00(self):
+        challenge = self._get_challenge_hybi00()
+
+        headers = [
+            ("Upgrade", "WebSocket"),
+            ("Connection", "Upgrade"),
+            ("Sec-WebSocket-Origin", self.websocket.origin),
+            ("Sec-WebSocket-Protocol", self.websocket.protocol),
+            ("Sec-WebSocket-Location", "ws://" + self.environ.get('HTTP_HOST') + self.websocket.path),
+        ]
+
+        self.start_response("101 Web Socket Protocol Handshake", headers)
+        self.write(challenge)
+
+    def handshake_hybi06(self):
+        raise Exception("Version not yet supported")
+        challenge = self._get_challange_hybi06()
+        headers = [
+            ("Upgrade", "WebSocket"),
+            ("Connection", "Upgrade"),
+            ("Sec-WebSocket-Accept", challenge),
+        ]
+        self.start_response("101 Switching Protocols", headers)
+        self.write(challenge)
+
+
+    def upgrade_allowed(self):
         """
         Returns True if request is allowed to be upgraded.
         If self.allowed_paths is non-empty, self.environ['PATH_INFO'] will
             return True
 
     def write(self, data):
-        if self.websocket_connection:
-            self.socket.sendall(data)
+        if data:
+            if self.websocket_connection:
+                self.socket.sendall(data)
+            else:
+                super(WebSocketHandler, self).write(data)
         else:
-            super(WebSocketHandler, self).write(data)
+            raise Exception("No data to send")
 
     def start_response(self, status, headers, exc_info=None):
         if self.websocket_connection:
 
         return key_number / spaces
 
-    def _get_challenge(self):
+    def _get_challenge_hybi00(self):
         key1 = self.environ.get('HTTP_SEC_WEBSOCKET_KEY1')
         key2 = self.environ.get('HTTP_SEC_WEBSOCKET_KEY2')
 
 
         return md5(challenge).digest()
 
+    def _get_challenge_hybi06(self):
+        key = self.environ.get("HTTP_SEC_WEBSOCKET_KEY")
+        return b64encode(sha1(key + MAGIC_STRING).digest())
+
     def wait(self):
         return self.websocket.wait()
 

geventwebsocket/websocket.py

-# This class implements the Websocket protocol draft version as of May 23, 2010
-# The version as of August 6, 2010 will be implementend once Firefox or
-# Webkit-trunk support this version.
+class WebSocket(object):
+    pass
 
-class WebSocket(object):
+
+class WebSocketLegacy(object):
     def __init__(self, sock, rfile, environ):
         self.rfile = rfile
         self.socket = sock
         self.origin = environ.get('HTTP_ORIGIN')
-        self.protocol = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL', 'unknown')
+        self.protocol = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL')
         self.path = environ.get('PATH_INFO')
         self.websocket_closed = False
 
         if isinstance(message, unicode):
             message = message.encode('utf-8')
         elif isinstance(message, str):
-            message = unicode(message).encode('utf-8')
+            message = unicode(message, 'utf-8').encode('utf-8')
         else:
             raise Exception("Invalid message encoding")
 
             return
 
     def _message_length(self):
-        # TODO: buildin security agains lengths greater than 2**31 or 2**32
         length = 0
 
         while True:
 
 setup(
     name="gevent-websocket",
-    version="0.2.3",
+    version="0.3.0-dev",
     description="Websocket handler for the gevent pywsgi server, a Python network library",
     long_description=open("README.rst").read(),
     author="Jeffrey Gelens",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.