websocket.py
Go to the documentation of this file.
00001 """Implementation of the WebSocket protocol.
00002 
00003 `WebSockets <http://dev.w3.org/html5/websockets/>`_ allow for bidirectional
00004 communication between the browser and server.
00005 
00006 WebSockets are supported in the current versions of all major browsers,
00007 although older versions that do not support WebSockets are still in use
00008 (refer to http://caniuse.com/websockets for details).
00009 
00010 This module implements the final version of the WebSocket protocol as
00011 defined in `RFC 6455 <http://tools.ietf.org/html/rfc6455>`_.  Certain
00012 browser versions (notably Safari 5.x) implemented an earlier draft of
00013 the protocol (known as "draft 76") and are not compatible with this module.
00014 
00015 .. versionchanged:: 4.0
00016    Removed support for the draft 76 protocol version.
00017 """
00018 
00019 from __future__ import absolute_import, division, print_function, with_statement
00020 # Author: Jacob Kristhammar, 2010
00021 
00022 import base64
00023 import collections
00024 import hashlib
00025 import os
00026 import struct
00027 import tornado.escape
00028 import tornado.web
00029 
00030 from tornado.concurrent import TracebackFuture
00031 from tornado.escape import utf8, native_str, to_unicode
00032 from tornado import httpclient, httputil
00033 from tornado.ioloop import IOLoop
00034 from tornado.iostream import StreamClosedError
00035 from tornado.log import gen_log, app_log
00036 from tornado import simple_httpclient
00037 from tornado.tcpclient import TCPClient
00038 from tornado.util import bytes_type, _websocket_mask
00039 
00040 try:
00041     from urllib.parse import urlparse # py2
00042 except ImportError:
00043     from urlparse import urlparse # py3
00044 
00045 try:
00046     xrange  # py2
00047 except NameError:
00048     xrange = range  # py3
00049 
00050 
00051 class WebSocketError(Exception):
00052     pass
00053 
00054 
00055 class WebSocketClosedError(WebSocketError):
00056     """Raised by operations on a closed connection.
00057 
00058     .. versionadded:: 3.2
00059     """
00060     pass
00061 
00062 
00063 class WebSocketHandler(tornado.web.RequestHandler):
00064     """Subclass this class to create a basic WebSocket handler.
00065 
00066     Override `on_message` to handle incoming messages, and use
00067     `write_message` to send messages to the client. You can also
00068     override `open` and `on_close` to handle opened and closed
00069     connections.
00070 
00071     See http://dev.w3.org/html5/websockets/ for details on the
00072     JavaScript interface.  The protocol is specified at
00073     http://tools.ietf.org/html/rfc6455.
00074 
00075     Here is an example WebSocket handler that echos back all received messages
00076     back to the client::
00077 
00078       class EchoWebSocket(websocket.WebSocketHandler):
00079           def open(self):
00080               print "WebSocket opened"
00081 
00082           def on_message(self, message):
00083               self.write_message(u"You said: " + message)
00084 
00085           def on_close(self):
00086               print "WebSocket closed"
00087 
00088     WebSockets are not standard HTTP connections. The "handshake" is
00089     HTTP, but after the handshake, the protocol is
00090     message-based. Consequently, most of the Tornado HTTP facilities
00091     are not available in handlers of this type. The only communication
00092     methods available to you are `write_message()`, `ping()`, and
00093     `close()`. Likewise, your request handler class should implement
00094     `open()` method rather than ``get()`` or ``post()``.
00095 
00096     If you map the handler above to ``/websocket`` in your application, you can
00097     invoke it in JavaScript with::
00098 
00099       var ws = new WebSocket("ws://localhost:8888/websocket");
00100       ws.onopen = function() {
00101          ws.send("Hello, world");
00102       };
00103       ws.onmessage = function (evt) {
00104          alert(evt.data);
00105       };
00106 
00107     This script pops up an alert box that says "You said: Hello, world".
00108 
00109     Web browsers allow any site to open a websocket connection to any other,
00110     instead of using the same-origin policy that governs other network
00111     access from javascript.  This can be surprising and is a potential
00112     security hole, so since Tornado 4.0 `WebSocketHandler` requires
00113     applications that wish to receive cross-origin websockets to opt in
00114     by overriding the `~WebSocketHandler.check_origin` method (see that
00115     method's docs for details).  Failure to do so is the most likely
00116     cause of 403 errors when making a websocket connection.
00117 
00118     When using a secure websocket connection (``wss://``) with a self-signed
00119     certificate, the connection from a browser may fail because it wants
00120     to show the "accept this certificate" dialog but has nowhere to show it.
00121     You must first visit a regular HTML page using the same certificate
00122     to accept it before the websocket connection will succeed.
00123     """
00124     def __init__(self, application, request, **kwargs):
00125         tornado.web.RequestHandler.__init__(self, application, request,
00126                                             **kwargs)
00127         self.ws_connection = None
00128         self.close_code = None
00129         self.close_reason = None
00130         self.stream = None
00131 
00132     @tornado.web.asynchronous
00133     def get(self, *args, **kwargs):
00134         self.open_args = args
00135         self.open_kwargs = kwargs
00136 
00137         # Upgrade header should be present and should be equal to WebSocket
00138         if self.request.headers.get("Upgrade", "").lower() != 'websocket':
00139             self.set_status(400)
00140             self.finish("Can \"Upgrade\" only to \"WebSocket\".")
00141             return
00142 
00143         # Connection header should be upgrade. Some proxy servers/load balancers
00144         # might mess with it.
00145         headers = self.request.headers
00146         connection = map(lambda s: s.strip().lower(), headers.get("Connection", "").split(","))
00147         if 'upgrade' not in connection:
00148             self.set_status(400)
00149             self.finish("\"Connection\" must be \"Upgrade\".")
00150             return
00151 
00152         # Handle WebSocket Origin naming convention differences
00153         # The difference between version 8 and 13 is that in 8 the
00154         # client sends a "Sec-Websocket-Origin" header and in 13 it's
00155         # simply "Origin".
00156         if "Origin" in self.request.headers:
00157             origin = self.request.headers.get("Origin")
00158         else:
00159             origin = self.request.headers.get("Sec-Websocket-Origin", None)
00160 
00161 
00162         # If there was an origin header, check to make sure it matches
00163         # according to check_origin. When the origin is None, we assume it
00164         # did not come from a browser and that it can be passed on.
00165         if origin is not None and not self.check_origin(origin):
00166             self.set_status(403)
00167             self.finish("Cross origin websockets not allowed")
00168             return
00169 
00170         self.stream = self.request.connection.detach()
00171         self.stream.set_close_callback(self.on_connection_close)
00172 
00173         if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
00174             self.ws_connection = WebSocketProtocol13(self)
00175             self.ws_connection.accept_connection()
00176         else:
00177             self.stream.write(tornado.escape.utf8(
00178                 "HTTP/1.1 426 Upgrade Required\r\n"
00179                 "Sec-WebSocket-Version: 8\r\n\r\n"))
00180             self.stream.close()
00181 
00182 
00183     def write_message(self, message, binary=False):
00184         """Sends the given message to the client of this Web Socket.
00185 
00186         The message may be either a string or a dict (which will be
00187         encoded as json).  If the ``binary`` argument is false, the
00188         message will be sent as utf8; in binary mode any byte string
00189         is allowed.
00190 
00191         If the connection is already closed, raises `WebSocketClosedError`.
00192 
00193         .. versionchanged:: 3.2
00194            `WebSocketClosedError` was added (previously a closed connection
00195            would raise an `AttributeError`)
00196         """
00197         if self.ws_connection is None:
00198             raise WebSocketClosedError()
00199         if isinstance(message, dict):
00200             message = tornado.escape.json_encode(message)
00201         self.ws_connection.write_message(message, binary=binary)
00202 
00203     def select_subprotocol(self, subprotocols):
00204         """Invoked when a new WebSocket requests specific subprotocols.
00205 
00206         ``subprotocols`` is a list of strings identifying the
00207         subprotocols proposed by the client.  This method may be
00208         overridden to return one of those strings to select it, or
00209         ``None`` to not select a subprotocol.  Failure to select a
00210         subprotocol does not automatically abort the connection,
00211         although clients may close the connection if none of their
00212         proposed subprotocols was selected.
00213         """
00214         return None
00215 
00216     def open(self):
00217         """Invoked when a new WebSocket is opened.
00218 
00219         The arguments to `open` are extracted from the `tornado.web.URLSpec`
00220         regular expression, just like the arguments to
00221         `tornado.web.RequestHandler.get`.
00222         """
00223         pass
00224 
00225     def on_message(self, message):
00226         """Handle incoming messages on the WebSocket
00227 
00228         This method must be overridden.
00229         """
00230         raise NotImplementedError
00231 
00232     def ping(self, data):
00233         """Send ping frame to the remote end."""
00234         if self.ws_connection is None:
00235             raise WebSocketClosedError()
00236         self.ws_connection.write_ping(data)
00237 
00238     def on_pong(self, data):
00239         """Invoked when the response to a ping frame is received."""
00240         pass
00241 
00242     def on_close(self):
00243         """Invoked when the WebSocket is closed.
00244 
00245         If the connection was closed cleanly and a status code or reason
00246         phrase was supplied, these values will be available as the attributes
00247         ``self.close_code`` and ``self.close_reason``.
00248 
00249         .. versionchanged:: 4.0
00250 
00251            Added ``close_code`` and ``close_reason`` attributes.
00252         """
00253         pass
00254 
00255     def close(self, code=None, reason=None):
00256         """Closes this Web Socket.
00257 
00258         Once the close handshake is successful the socket will be closed.
00259 
00260         ``code`` may be a numeric status code, taken from the values
00261         defined in `RFC 6455 section 7.4.1
00262         <https://tools.ietf.org/html/rfc6455#section-7.4.1>`_.
00263         ``reason`` may be a textual message about why the connection is
00264         closing.  These values are made available to the client, but are
00265         not otherwise interpreted by the websocket protocol.
00266 
00267         .. versionchanged:: 4.0
00268 
00269            Added the ``code`` and ``reason`` arguments.
00270         """
00271         if self.ws_connection:
00272             self.ws_connection.close(code, reason)
00273             self.ws_connection = None
00274 
00275     def check_origin(self, origin):
00276         """Override to enable support for allowing alternate origins.
00277 
00278         The ``origin`` argument is the value of the ``Origin`` HTTP
00279         header, the url responsible for initiating this request.  This
00280         method is not called for clients that do not send this header;
00281         such requests are always allowed (because all browsers that
00282         implement WebSockets support this header, and non-browser
00283         clients do not have the same cross-site security concerns).
00284 
00285         Should return True to accept the request or False to reject it.
00286         By default, rejects all requests with an origin on a host other
00287         than this one.
00288 
00289         This is a security protection against cross site scripting attacks on
00290         browsers, since WebSockets are allowed to bypass the usual same-origin
00291         policies and don't use CORS headers.
00292 
00293         To accept all cross-origin traffic (which was the default prior to
00294         Tornado 4.0), simply override this method to always return true::
00295 
00296             def check_origin(self, origin):
00297                 return True
00298 
00299         To allow connections from any subdomain of your site, you might
00300         do something like::
00301 
00302             def check_origin(self, origin):
00303                 parsed_origin = urllib.parse.urlparse(origin)
00304                 return parsed_origin.netloc.endswith(".mydomain.com")
00305 
00306         .. versionadded:: 4.0
00307         """
00308         parsed_origin = urlparse(origin)
00309         origin = parsed_origin.netloc
00310         origin = origin.lower()
00311 
00312         host = self.request.headers.get("Host")
00313 
00314         # Check to see that origin matches host directly, including ports
00315         return origin == host
00316 
00317     def set_nodelay(self, value):
00318         """Set the no-delay flag for this stream.
00319 
00320         By default, small messages may be delayed and/or combined to minimize
00321         the number of packets sent.  This can sometimes cause 200-500ms delays
00322         due to the interaction between Nagle's algorithm and TCP delayed
00323         ACKs.  To reduce this delay (at the expense of possibly increasing
00324         bandwidth usage), call ``self.set_nodelay(True)`` once the websocket
00325         connection is established.
00326 
00327         See `.BaseIOStream.set_nodelay` for additional details.
00328 
00329         .. versionadded:: 3.1
00330         """
00331         self.stream.set_nodelay(value)
00332 
00333     def on_connection_close(self):
00334         if self.ws_connection:
00335             self.ws_connection.on_connection_close()
00336             self.ws_connection = None
00337             self.on_close()
00338 
00339 
00340 def _wrap_method(method):
00341     def _disallow_for_websocket(self, *args, **kwargs):
00342         if self.stream is None:
00343             method(self, *args, **kwargs)
00344         else:
00345             raise RuntimeError("Method not supported for Web Sockets")
00346     return _disallow_for_websocket
00347 for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
00348                "set_status", "flush", "finish"]:
00349     setattr(WebSocketHandler, method,
00350             _wrap_method(getattr(WebSocketHandler, method)))
00351 
00352 
00353 class WebSocketProtocol(object):
00354     """Base class for WebSocket protocol versions.
00355     """
00356     def __init__(self, handler):
00357         self.handler = handler
00358         self.request = handler.request
00359         self.stream = handler.stream
00360         self.client_terminated = False
00361         self.server_terminated = False
00362 
00363     def _run_callback(self, callback, *args, **kwargs):
00364         """Runs the given callback with exception handling.
00365 
00366         On error, aborts the websocket connection and returns False.
00367         """
00368         try:
00369             callback(*args, **kwargs)
00370         except Exception:
00371             app_log.error("Uncaught exception in %s",
00372                           self.request.path, exc_info=True)
00373             self._abort()
00374 
00375     def on_connection_close(self):
00376         self._abort()
00377 
00378     def _abort(self):
00379         """Instantly aborts the WebSocket connection by closing the socket"""
00380         self.client_terminated = True
00381         self.server_terminated = True
00382         self.stream.close()  # forcibly tear down the connection
00383         self.close()  # let the subclass cleanup
00384 
00385 
00386 class WebSocketProtocol13(WebSocketProtocol):
00387     """Implementation of the WebSocket protocol from RFC 6455.
00388 
00389     This class supports versions 7 and 8 of the protocol in addition to the
00390     final version 13.
00391     """
00392     def __init__(self, handler, mask_outgoing=False):
00393         WebSocketProtocol.__init__(self, handler)
00394         self.mask_outgoing = mask_outgoing
00395         self._final_frame = False
00396         self._frame_opcode = None
00397         self._masked_frame = None
00398         self._frame_mask = None
00399         self._frame_length = None
00400         self._fragmented_message_buffer = None
00401         self._fragmented_message_opcode = None
00402         self._waiting = None
00403 
00404     def accept_connection(self):
00405         try:
00406             self._handle_websocket_headers()
00407             self._accept_connection()
00408         except ValueError:
00409             gen_log.debug("Malformed WebSocket request received", exc_info=True)
00410             self._abort()
00411             return
00412 
00413     def _handle_websocket_headers(self):
00414         """Verifies all invariant- and required headers
00415 
00416         If a header is missing or have an incorrect value ValueError will be
00417         raised
00418         """
00419         fields = ("Host", "Sec-Websocket-Key", "Sec-Websocket-Version")
00420         if not all(map(lambda f: self.request.headers.get(f), fields)):
00421             raise ValueError("Missing/Invalid WebSocket headers")
00422 
00423     @staticmethod
00424     def compute_accept_value(key):
00425         """Computes the value for the Sec-WebSocket-Accept header,
00426         given the value for Sec-WebSocket-Key.
00427         """
00428         sha1 = hashlib.sha1()
00429         sha1.update(utf8(key))
00430         sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")  # Magic value
00431         return native_str(base64.b64encode(sha1.digest()))
00432 
00433     def _challenge_response(self):
00434         return WebSocketProtocol13.compute_accept_value(
00435             self.request.headers.get("Sec-Websocket-Key"))
00436 
00437     def _accept_connection(self):
00438         subprotocol_header = ''
00439         subprotocols = self.request.headers.get("Sec-WebSocket-Protocol", '')
00440         subprotocols = [s.strip() for s in subprotocols.split(',')]
00441         if subprotocols:
00442             selected = self.handler.select_subprotocol(subprotocols)
00443             if selected:
00444                 assert selected in subprotocols
00445                 subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected
00446 
00447         self.stream.write(tornado.escape.utf8(
00448             "HTTP/1.1 101 Switching Protocols\r\n"
00449             "Upgrade: websocket\r\n"
00450             "Connection: Upgrade\r\n"
00451             "Sec-WebSocket-Accept: %s\r\n"
00452             "%s"
00453             "\r\n" % (self._challenge_response(), subprotocol_header)))
00454 
00455         self._run_callback(self.handler.open, *self.handler.open_args,
00456                            **self.handler.open_kwargs)
00457         self._receive_frame()
00458 
00459     def _write_frame(self, fin, opcode, data):
00460         if fin:
00461             finbit = 0x80
00462         else:
00463             finbit = 0
00464         frame = struct.pack("B", finbit | opcode)
00465         l = len(data)
00466         if self.mask_outgoing:
00467             mask_bit = 0x80
00468         else:
00469             mask_bit = 0
00470         if l < 126:
00471             frame += struct.pack("B", l | mask_bit)
00472         elif l <= 0xFFFF:
00473             frame += struct.pack("!BH", 126 | mask_bit, l)
00474         else:
00475             frame += struct.pack("!BQ", 127 | mask_bit, l)
00476         if self.mask_outgoing:
00477             mask = os.urandom(4)
00478             data = mask + _websocket_mask(mask, data)
00479         frame += data
00480         self.stream.write(frame)
00481 
00482     def write_message(self, message, binary=False):
00483         """Sends the given message to the client of this Web Socket."""
00484         if binary:
00485             opcode = 0x2
00486         else:
00487             opcode = 0x1
00488         message = tornado.escape.utf8(message)
00489         assert isinstance(message, bytes_type)
00490         try:
00491             self._write_frame(True, opcode, message)
00492         except StreamClosedError:
00493             self._abort()
00494 
00495     def write_ping(self, data):
00496         """Send ping frame."""
00497         assert isinstance(data, bytes_type)
00498         self._write_frame(True, 0x9, data)
00499 
00500     def _receive_frame(self):
00501         try:
00502             self.stream.read_bytes(2, self._on_frame_start)
00503         except StreamClosedError:
00504             self._abort()
00505 
00506     def _on_frame_start(self, data):
00507         header, payloadlen = struct.unpack("BB", data)
00508         self._final_frame = header & 0x80
00509         reserved_bits = header & 0x70
00510         self._frame_opcode = header & 0xf
00511         self._frame_opcode_is_control = self._frame_opcode & 0x8
00512         if reserved_bits:
00513             # client is using as-yet-undefined extensions; abort
00514             self._abort()
00515             return
00516         self._masked_frame = bool(payloadlen & 0x80)
00517         payloadlen = payloadlen & 0x7f
00518         if self._frame_opcode_is_control and payloadlen >= 126:
00519             # control frames must have payload < 126
00520             self._abort()
00521             return
00522         try:
00523             if payloadlen < 126:
00524                 self._frame_length = payloadlen
00525                 if self._masked_frame:
00526                     self.stream.read_bytes(4, self._on_masking_key)
00527                 else:
00528                     self.stream.read_bytes(self._frame_length, self._on_frame_data)
00529             elif payloadlen == 126:
00530                 self.stream.read_bytes(2, self._on_frame_length_16)
00531             elif payloadlen == 127:
00532                 self.stream.read_bytes(8, self._on_frame_length_64)
00533         except StreamClosedError:
00534             self._abort()
00535 
00536     def _on_frame_length_16(self, data):
00537         self._frame_length = struct.unpack("!H", data)[0]
00538         try:
00539             if self._masked_frame:
00540                 self.stream.read_bytes(4, self._on_masking_key)
00541             else:
00542                 self.stream.read_bytes(self._frame_length, self._on_frame_data)
00543         except StreamClosedError:
00544             self._abort()
00545 
00546     def _on_frame_length_64(self, data):
00547         self._frame_length = struct.unpack("!Q", data)[0]
00548         try:
00549             if self._masked_frame:
00550                 self.stream.read_bytes(4, self._on_masking_key)
00551             else:
00552                 self.stream.read_bytes(self._frame_length, self._on_frame_data)
00553         except StreamClosedError:
00554             self._abort()
00555 
00556     def _on_masking_key(self, data):
00557         self._frame_mask = data
00558         try:
00559             self.stream.read_bytes(self._frame_length, self._on_masked_frame_data)
00560         except StreamClosedError:
00561             self._abort()
00562 
00563     def _on_masked_frame_data(self, data):
00564         self._on_frame_data(_websocket_mask(self._frame_mask, data))
00565 
00566     def _on_frame_data(self, data):
00567         if self._frame_opcode_is_control:
00568             # control frames may be interleaved with a series of fragmented
00569             # data frames, so control frames must not interact with
00570             # self._fragmented_*
00571             if not self._final_frame:
00572                 # control frames must not be fragmented
00573                 self._abort()
00574                 return
00575             opcode = self._frame_opcode
00576         elif self._frame_opcode == 0:  # continuation frame
00577             if self._fragmented_message_buffer is None:
00578                 # nothing to continue
00579                 self._abort()
00580                 return
00581             self._fragmented_message_buffer += data
00582             if self._final_frame:
00583                 opcode = self._fragmented_message_opcode
00584                 data = self._fragmented_message_buffer
00585                 self._fragmented_message_buffer = None
00586         else:  # start of new data message
00587             if self._fragmented_message_buffer is not None:
00588                 # can't start new message until the old one is finished
00589                 self._abort()
00590                 return
00591             if self._final_frame:
00592                 opcode = self._frame_opcode
00593             else:
00594                 self._fragmented_message_opcode = self._frame_opcode
00595                 self._fragmented_message_buffer = data
00596 
00597         if self._final_frame:
00598             self._handle_message(opcode, data)
00599 
00600         if not self.client_terminated:
00601             self._receive_frame()
00602 
00603     def _handle_message(self, opcode, data):
00604         if self.client_terminated:
00605             return
00606 
00607         if opcode == 0x1:
00608             # UTF-8 data
00609             try:
00610                 decoded = data.decode("utf-8")
00611             except UnicodeDecodeError:
00612                 self._abort()
00613                 return
00614             self._run_callback(self.handler.on_message, decoded)
00615         elif opcode == 0x2:
00616             # Binary data
00617             self._run_callback(self.handler.on_message, data)
00618         elif opcode == 0x8:
00619             # Close
00620             self.client_terminated = True
00621             if len(data) >= 2:
00622                 self.handler.close_code = struct.unpack('>H', data[:2])[0]
00623             if len(data) > 2:
00624                 self.handler.close_reason = to_unicode(data[2:])
00625             self.close()
00626         elif opcode == 0x9:
00627             # Ping
00628             self._write_frame(True, 0xA, data)
00629         elif opcode == 0xA:
00630             # Pong
00631             self._run_callback(self.handler.on_pong, data)
00632         else:
00633             self._abort()
00634 
00635     def close(self, code=None, reason=None):
00636         """Closes the WebSocket connection."""
00637         if not self.server_terminated:
00638             if not self.stream.closed():
00639                 if code is None and reason is not None:
00640                     code = 1000  # "normal closure" status code
00641                 if code is None:
00642                     close_data = b''
00643                 else:
00644                     close_data = struct.pack('>H', code)
00645                 if reason is not None:
00646                     close_data += utf8(reason)
00647                 self._write_frame(True, 0x8, close_data)
00648             self.server_terminated = True
00649         if self.client_terminated:
00650             if self._waiting is not None:
00651                 self.stream.io_loop.remove_timeout(self._waiting)
00652                 self._waiting = None
00653             self.stream.close()
00654         elif self._waiting is None:
00655             # Give the client a few seconds to complete a clean shutdown,
00656             # otherwise just close the connection.
00657             self._waiting = self.stream.io_loop.add_timeout(
00658                 self.stream.io_loop.time() + 5, self._abort)
00659 
00660 
00661 class WebSocketClientConnection(simple_httpclient._HTTPConnection):
00662     """WebSocket client connection.
00663 
00664     This class should not be instantiated directly; use the
00665     `websocket_connect` function instead.
00666     """
00667     def __init__(self, io_loop, request):
00668         self.connect_future = TracebackFuture()
00669         self.read_future = None
00670         self.read_queue = collections.deque()
00671         self.key = base64.b64encode(os.urandom(16))
00672 
00673         scheme, sep, rest = request.url.partition(':')
00674         scheme = {'ws': 'http', 'wss': 'https'}[scheme]
00675         request.url = scheme + sep + rest
00676         request.headers.update({
00677             'Upgrade': 'websocket',
00678             'Connection': 'Upgrade',
00679             'Sec-WebSocket-Key': self.key,
00680             'Sec-WebSocket-Version': '13',
00681         })
00682 
00683         self.tcp_client = TCPClient(io_loop=io_loop)
00684         super(WebSocketClientConnection, self).__init__(
00685             io_loop, None, request, lambda: None, self._on_http_response,
00686             104857600, self.tcp_client, 65536)
00687 
00688     def close(self, code=None, reason=None):
00689         """Closes the websocket connection.
00690 
00691         ``code`` and ``reason`` are documented under
00692         `WebSocketHandler.close`.
00693 
00694         .. versionadded:: 3.2
00695 
00696         .. versionchanged:: 4.0
00697 
00698            Added the ``code`` and ``reason`` arguments.
00699         """
00700         if self.protocol is not None:
00701             self.protocol.close(code, reason)
00702             self.protocol = None
00703 
00704     def on_connection_close(self):
00705         if not self.connect_future.done():
00706             self.connect_future.set_exception(StreamClosedError())
00707         self.on_message(None)
00708         self.tcp_client.close()
00709         super(WebSocketClientConnection, self).on_connection_close()
00710 
00711     def _on_http_response(self, response):
00712         if not self.connect_future.done():
00713             if response.error:
00714                 self.connect_future.set_exception(response.error)
00715             else:
00716                 self.connect_future.set_exception(WebSocketError(
00717                     "Non-websocket response"))
00718 
00719     def headers_received(self, start_line, headers):
00720         if start_line.code != 101:
00721             return super(WebSocketClientConnection, self).headers_received(
00722                 start_line, headers)
00723 
00724         self.headers = headers
00725         assert self.headers['Upgrade'].lower() == 'websocket'
00726         assert self.headers['Connection'].lower() == 'upgrade'
00727         accept = WebSocketProtocol13.compute_accept_value(self.key)
00728         assert self.headers['Sec-Websocket-Accept'] == accept
00729 
00730         self.protocol = WebSocketProtocol13(self, mask_outgoing=True)
00731         self.protocol._receive_frame()
00732 
00733         if self._timeout is not None:
00734             self.io_loop.remove_timeout(self._timeout)
00735             self._timeout = None
00736 
00737         self.stream = self.connection.detach()
00738         self.stream.set_close_callback(self.on_connection_close)
00739         # Once we've taken over the connection, clear the final callback
00740         # we set on the http request.  This deactivates the error handling
00741         # in simple_httpclient that would otherwise interfere with our
00742         # ability to see exceptions.
00743         self.final_callback = None
00744 
00745         self.connect_future.set_result(self)
00746 
00747     def write_message(self, message, binary=False):
00748         """Sends a message to the WebSocket server."""
00749         self.protocol.write_message(message, binary)
00750 
00751     def read_message(self, callback=None):
00752         """Reads a message from the WebSocket server.
00753 
00754         Returns a future whose result is the message, or None
00755         if the connection is closed.  If a callback argument
00756         is given it will be called with the future when it is
00757         ready.
00758         """
00759         assert self.read_future is None
00760         future = TracebackFuture()
00761         if self.read_queue:
00762             future.set_result(self.read_queue.popleft())
00763         else:
00764             self.read_future = future
00765         if callback is not None:
00766             self.io_loop.add_future(future, callback)
00767         return future
00768 
00769     def on_message(self, message):
00770         if self.read_future is not None:
00771             self.read_future.set_result(message)
00772             self.read_future = None
00773         else:
00774             self.read_queue.append(message)
00775 
00776     def on_pong(self, data):
00777         pass
00778 
00779 
00780 def websocket_connect(url, io_loop=None, callback=None, connect_timeout=None):
00781     """Client-side websocket support.
00782 
00783     Takes a url and returns a Future whose result is a
00784     `WebSocketClientConnection`.
00785 
00786     .. versionchanged:: 3.2
00787        Also accepts ``HTTPRequest`` objects in place of urls.
00788     """
00789     if io_loop is None:
00790         io_loop = IOLoop.current()
00791     if isinstance(url, httpclient.HTTPRequest):
00792         assert connect_timeout is None
00793         request = url
00794         # Copy and convert the headers dict/object (see comments in
00795         # AsyncHTTPClient.fetch)
00796         request.headers = httputil.HTTPHeaders(request.headers)
00797     else:
00798         request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)
00799     request = httpclient._RequestProxy(
00800         request, httpclient.HTTPRequest._DEFAULTS)
00801     conn = WebSocketClientConnection(io_loop, request)
00802     if callback is not None:
00803         io_loop.add_future(conn.connect_future, callback)
00804     return conn.connect_future


rosbridge_tools
Author(s): Jonathan Mace
autogenerated on Sat Dec 27 2014 11:25:59