]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Drop support for the draft76 version of WebSockets.
authorBen Darnell <ben@bendarnell.com>
Sat, 21 Jun 2014 17:50:56 +0000 (13:50 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 21 Jun 2014 18:06:59 +0000 (14:06 -0400)
Browsers that only support draft76 are now less common than those
that do not support websockets at all, so applications should
use their non-websocket workarounds for these browsers.

demos/websocket/chatdemo.py
docs/releases/next.rst
docs/releases/v2.2.0.rst
tornado/websocket.py

index 92a0e14525d1f0e43039531f9d8126d84a7682b1..c1067e9e97572f6d7a2b84bfb9a1e704db8477d1 100755 (executable)
@@ -56,10 +56,6 @@ class ChatSocketHandler(tornado.websocket.WebSocketHandler):
     cache = []
     cache_size = 200
 
-    def allow_draft76(self):
-        # for iOS 5.0 Safari
-        return True
-
     def open(self):
         ChatSocketHandler.waiters.add(self)
 
index 76cbe3c63d3e7d081370815e9700550de1891f01..445728d584653c3154443ea335d656b4a8387db9 100644 (file)
@@ -28,6 +28,9 @@ Backwards-compatibility notes
   pycurl 7.18.2.
 * Support for ``RequestHandler.get_error_html`` has been removed;
   override `.RequestHandler.write_error` instead.
+* `.WebSocketHandler` no longer supports the old ``draft 76`` protocol
+  (this mainly affects Safari 5.x browsers).  Applications should use
+  non-websocket workarounds for these browsers.
 
 
 Other notes
index bffa1fd5b66130bd27dcd14e1d0eb37838d5ee5c..93df0f880794002dfb1d968a877377eb43b75a9b 100644 (file)
@@ -31,7 +31,7 @@ Backwards-incompatible changes
   without also supporting the insecure SSLv2.
 * `tornado.websocket` no longer supports the older "draft 76" version
   of the websocket protocol by default, although this version can
-  be enabled by overriding `tornado.websocket.WebSocketHandler.allow_draft76`.
+  be enabled by overriding ``tornado.websocket.WebSocketHandler.allow_draft76``.
 
 ``tornado.httpclient``
 ~~~~~~~~~~~~~~~~~~~~~~
index 19196b88b1109ee7ed631e901e22b6b6b530b37e..b334998857a7ae9342484ba4e635a3f22ce9f67b 100644 (file)
@@ -3,18 +3,17 @@
 `WebSockets <http://dev.w3.org/html5/websockets/>`_ allow for bidirectional
 communication between the browser and server.
 
-.. warning::
-
-   The WebSocket protocol was recently finalized as `RFC 6455
-   <http://tools.ietf.org/html/rfc6455>`_ and is not yet supported in
-   all browsers.  Refer to http://caniuse.com/websockets for details
-   on compatibility.  In addition, during development the protocol
-   went through several incompatible versions, and some browsers only
-   support older versions.  By default this module only supports the
-   latest version of the protocol, but optional support for an older
-   version (known as "draft 76" or "hixie-76") can be enabled by
-   overriding `WebSocketHandler.allow_draft76` (see that method's
-   documentation for caveats).
+WebSockets are supported in the current versions of all major browsers,
+although older versions that do not support WebSockets are still in use
+(refer to http://caniuse.com/websockets for details).
+
+This module implements the final version of the WebSocket protocol as
+defined in `RFC 6455 <http://tools.ietf.org/html/rfc6455>`_.  Certain
+browser versions (notably Safari 5.x) implemented an earlier draft of
+the protocol (known as "draft 76") and are not compatible with this module.
+
+.. versionchanged:: 4.0
+   Removed support for the draft 76 protocol version.
 """
 
 from __future__ import absolute_import, division, print_function, with_statement
@@ -22,11 +21,9 @@ from __future__ import absolute_import, division, print_function, with_statement
 
 import base64
 import collections
-import functools
 import hashlib
 import os
 import struct
-import time
 import tornado.escape
 import tornado.web
 
@@ -38,7 +35,7 @@ from tornado.iostream import StreamClosedError
 from tornado.log import gen_log, app_log
 from tornado import simple_httpclient
 from tornado.tcpclient import TCPClient
-from tornado.util import bytes_type, unicode_type, _websocket_mask
+from tornado.util import bytes_type, _websocket_mask
 
 try:
     from urllib.parse import urlparse # py2
@@ -161,10 +158,6 @@ class WebSocketHandler(tornado.web.RequestHandler):
         if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
             self.ws_connection = WebSocketProtocol13(self)
             self.ws_connection.accept_connection()
-        elif (self.allow_draft76() and
-              "Sec-WebSocket-Version" not in self.request.headers):
-            self.ws_connection = WebSocketProtocol76(self)
-            self.ws_connection.accept_connection()
         else:
             self.stream.write(tornado.escape.utf8(
                 "HTTP/1.1 426 Upgrade Required\r\n"
@@ -256,9 +249,6 @@ class WebSocketHandler(tornado.web.RequestHandler):
         closing.  These values are made available to the client, but are
         not otherwise interpreted by the websocket protocol.
 
-        The ``code`` and ``reason`` arguments are ignored in the "draft76"
-        protocol version.
-
         .. versionchanged:: 4.0
 
            Added the ``code`` and ``reason`` arguments.
@@ -296,21 +286,6 @@ class WebSocketHandler(tornado.web.RequestHandler):
         # Check to see that origin matches host directly, including ports
         return origin == host
 
-    def allow_draft76(self):
-        """Override to enable support for the older "draft76" protocol.
-
-        The draft76 version of the websocket protocol is disabled by
-        default due to security concerns, but it can be enabled by
-        overriding this method to return True.
-
-        Connections using the draft76 protocol do not support the
-        ``binary=True`` flag to `write_message`.
-
-        Support for the draft76 protocol is deprecated and will be
-        removed in a future version of Tornado.
-        """
-        return False
-
     def set_nodelay(self, value):
         """Set the no-delay flag for this stream.
 
@@ -327,18 +302,6 @@ class WebSocketHandler(tornado.web.RequestHandler):
         """
         self.stream.set_nodelay(value)
 
-    def get_websocket_scheme(self):
-        """Return the url scheme used for this request, either "ws" or "wss".
-
-        This is normally decided by HTTPServer, but applications
-        may wish to override this if they are using an SSL proxy
-        that does not provide the X-Scheme header as understood
-        by HTTPServer.
-
-        Note that this is only used by the draft76 protocol.
-        """
-        return "wss" if self.request.protocol == "https" else "ws"
-
     def on_connection_close(self):
         if self.ws_connection:
             self.ws_connection.on_connection_close()
@@ -392,175 +355,6 @@ class WebSocketProtocol(object):
         self.close()  # let the subclass cleanup
 
 
-class WebSocketProtocol76(WebSocketProtocol):
-    """Implementation of the WebSockets protocol, version hixie-76.
-
-    This class provides basic functionality to process WebSockets requests as
-    specified in
-    http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
-    """
-    def __init__(self, handler):
-        WebSocketProtocol.__init__(self, handler)
-        self.challenge = None
-        self._waiting = None
-
-    def accept_connection(self):
-        try:
-            self._handle_websocket_headers()
-        except ValueError:
-            gen_log.debug("Malformed WebSocket request received")
-            self._abort()
-            return
-
-        scheme = self.handler.get_websocket_scheme()
-
-        # draft76 only allows a single subprotocol
-        subprotocol_header = ''
-        subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None)
-        if subprotocol:
-            selected = self.handler.select_subprotocol([subprotocol])
-            if selected:
-                assert selected == subprotocol
-                subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected
-
-        # Write the initial headers before attempting to read the challenge.
-        # This is necessary when using proxies (such as HAProxy), which
-        # need to see the Upgrade headers before passing through the
-        # non-HTTP traffic that follows.
-        self.stream.write(tornado.escape.utf8(
-            "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
-            "Upgrade: WebSocket\r\n"
-            "Connection: Upgrade\r\n"
-            "Server: TornadoServer/%(version)s\r\n"
-            "Sec-WebSocket-Origin: %(origin)s\r\n"
-            "Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n"
-            "%(subprotocol)s"
-            "\r\n" % (dict(
-                version=tornado.version,
-                origin=self.request.headers["Origin"],
-                scheme=scheme,
-                host=self.request.host,
-                uri=self.request.uri,
-                subprotocol=subprotocol_header))))
-        self.stream.read_bytes(8, self._handle_challenge)
-
-    def challenge_response(self, challenge):
-        """Generates the challenge response that's needed in the handshake
-
-        The challenge parameter should be the raw bytes as sent from the
-        client.
-        """
-        key_1 = self.request.headers.get("Sec-Websocket-Key1")
-        key_2 = self.request.headers.get("Sec-Websocket-Key2")
-        try:
-            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_challenge(self, challenge):
-        try:
-            challenge_response = self.challenge_response(challenge)
-        except ValueError:
-            gen_log.debug("Malformed key data in WebSocket request")
-            self._abort()
-            return
-        self._write_response(challenge_response)
-
-    def _write_response(self, challenge):
-        self.stream.write(challenge)
-        self._run_callback(self.handler.open, *self.handler.open_args,
-                           **self.handler.open_kwargs)
-        self._receive_message()
-
-    def _handle_websocket_headers(self):
-        """Verifies all invariant- and required headers
-
-        If a header is missing or have an incorrect value ValueError will be
-        raised
-        """
-        fields = ("Origin", "Host", "Sec-Websocket-Key1",
-                  "Sec-Websocket-Key2")
-        if 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."""
-        # pyflakes complains about variable reuse if both of these lines use 'c'
-        number = int(''.join(c for c in key if c.isdigit()))
-        spaces = len([c2 for c2 in key if c2.isspace()])
-        try:
-            key_number = number // 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()
-        m.update(part_1)
-        m.update(part_2)
-        m.update(part_3)
-        return m.digest()
-
-    def _receive_message(self):
-        self.stream.read_bytes(1, self._on_frame_type)
-
-    def _on_frame_type(self, byte):
-        frame_type = ord(byte)
-        if frame_type == 0x00:
-            self.stream.read_until(b"\xff", self._on_end_delimiter)
-        elif frame_type == 0xff:
-            self.stream.read_bytes(1, self._on_length_indicator)
-        else:
-            self._abort()
-
-    def _on_end_delimiter(self, frame):
-        if not self.client_terminated:
-            self._run_callback(self.handler.on_message,
-                               frame[:-1].decode("utf-8", "replace"))
-        if not self.client_terminated:
-            self._receive_message()
-
-    def _on_length_indicator(self, byte):
-        if ord(byte) != 0x00:
-            self._abort()
-            return
-        self.client_terminated = True
-        self.close()
-
-    def write_message(self, message, binary=False):
-        """Sends the given message to the client of this Web Socket."""
-        if binary:
-            raise ValueError(
-                "Binary messages not supported by this version of websockets")
-        if isinstance(message, unicode_type):
-            message = message.encode("utf-8")
-        assert isinstance(message, bytes_type)
-        self.stream.write(b"\x00" + message + b"\xff")
-
-    def write_ping(self, data):
-        """Send ping frame."""
-        raise ValueError("Ping messages not supported by this version of websockets")
-
-    def close(self, code=None, reason=None):
-        """Closes the WebSocket connection."""
-        if not self.server_terminated:
-            if not self.stream.closed():
-                self.stream.write("\xff\x00")
-            self.server_terminated = True
-        if self.client_terminated:
-            if self._waiting is not None:
-                self.stream.io_loop.remove_timeout(self._waiting)
-            self._waiting = None
-            self.stream.close()
-        elif self._waiting is None:
-            self._waiting = self.stream.io_loop.add_timeout(
-                time.time() + 5, self._abort)
-
-
 class WebSocketProtocol13(WebSocketProtocol):
     """Implementation of the WebSocket protocol from RFC 6455.