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
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
00042 except ImportError:
00043 from urlparse import urlparse
00044
00045 try:
00046 xrange
00047 except NameError:
00048 xrange = range
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
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
00144
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
00153
00154
00155
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
00163
00164
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
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()
00383 self.close()
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")
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
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
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
00569
00570
00571 if not self._final_frame:
00572
00573 self._abort()
00574 return
00575 opcode = self._frame_opcode
00576 elif self._frame_opcode == 0:
00577 if self._fragmented_message_buffer is None:
00578
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:
00587 if self._fragmented_message_buffer is not None:
00588
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
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
00617 self._run_callback(self.handler.on_message, data)
00618 elif opcode == 0x8:
00619
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
00628 self._write_frame(True, 0xA, data)
00629 elif opcode == 0xA:
00630
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
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
00656
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
00740
00741
00742
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
00795
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