improve performance.
- Requests with invalid websocket headers now get a response with
status code 400 instead of a closed connection.
-- `.WebSocketHandler.write_message` now raises `.StreamClosedError` if
- called after the connection is closed.
- TODO(bdarnell): fix this up with WebSocketClosedError
+- `.WebSocketHandler.write_message` now raises `.WebSocketClosedError` if
+ the connection closes while the write is in progress.
- The ``io_loop`` argument to `.websocket_connect` has been removed.
from tornado.concurrent import Future
from tornado import gen
from tornado.httpclient import HTTPError, HTTPRequest
-from tornado.iostream import StreamClosedError
from tornado.log import gen_log, app_log
from tornado.template import DictLoader
from tornado.testing import AsyncHTTPTestCase, gen_test, bind_unused_port, ExpectLog
traceback.print_exc()
raise
-from tornado.websocket import WebSocketHandler, websocket_connect, WebSocketError
+from tornado.websocket import WebSocketHandler, websocket_connect, WebSocketError, WebSocketClosedError
try:
from tornado import speedups
class EchoHandler(TestWebSocketHandler):
+ @gen.coroutine
def on_message(self, message):
try:
- self.write_message(message, isinstance(message, bytes))
- except StreamClosedError:
+ yield self.write_message(message, isinstance(message, bytes))
+ except WebSocketClosedError:
pass
ws = yield self.ws_connect('/close_reason')
msg = yield ws.read_message()
self.assertIs(msg, None)
- with self.assertRaises(StreamClosedError):
+ with self.assertRaises(WebSocketClosedError):
ws.write_message('hello')
@gen_test
is allowed.
If the connection is already closed, raises `WebSocketClosedError`.
+ Returns a `.Future` which can be used for flow control.
.. versionchanged:: 3.2
`WebSocketClosedError` was added (previously a closed connection
.. versionchanged:: 4.3
Returns a `.Future` which can be used for flow control.
+
+ .. versionchanged:: 5.0
+ Consistently raises `WebSocketClosedError`. Previously could
+ sometimes raise `.StreamClosedError`.
"""
if self.ws_connection is None:
raise WebSocketClosedError()
if self._compressor:
message = self._compressor.compress(message)
flags |= self.RSV1
- return self._write_frame(True, opcode, message, flags=flags)
+ # For historical reasons, write methods in Tornado operate in a semi-synchronous
+ # mode in which awaiting the Future they return is optional (But errors can
+ # still be raised). This requires us to go through an awkward dance here
+ # to transform the errors that may be returned while presenting the same
+ # semi-synchronous interface.
+ try:
+ fut = self._write_frame(True, opcode, message, flags=flags)
+ except StreamClosedError:
+ raise WebSocketClosedError()
+
+ @gen.coroutine
+ def wrapper():
+ try:
+ yield fut
+ except StreamClosedError:
+ raise WebSocketClosedError()
+ return wrapper()
def write_ping(self, data):
"""Send ping frame."""
future_set_result_unless_cancelled(self.connect_future, self)
def write_message(self, message, binary=False):
- """Sends a message to the WebSocket server."""
- return self.protocol.write_message(message, binary)
+ """Sends a message to the WebSocket server.
+
+ If the stream is closed, raises `WebSocketClosedError`.
+ Returns a `.Future` which can be used for flow control.
+
+ .. versionchanged:: 5.0
+ Exception raised on a closed stream changed from `.StreamClosedError`
+ to `WebSocketClosedError`.
+ """
+ return self.protocol.write_message(message, binary=binary)
def read_message(self, callback=None):
"""Reads a message from the WebSocket server.