Skip to content

Commit 1e69faa

Browse files
committed
Make the websocket_connect error handling more comprehensive.
Now covers successful http responses that return a non-websocket body and network errors that prevent a body from being returned. Added a connect_timeout parameter to websocket_connect.
1 parent cc74578 commit 1e69faa

File tree

3 files changed

+51
-17
lines changed

3 files changed

+51
-17
lines changed

docs/releases/v3.0.1.rst

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Apr 8, 2013
1212
as a (broken) test by ``nose``.
1313
* Work around a bug in Ubuntu 13.04 betas involving an incomplete backport
1414
of the `ssl.match_hostname` function.
15+
* `tornado.websocket.websocket_connect` now fails cleanly when it attempts
16+
to connect to a non-websocket url.
1517
* `tornado.testing.LogTrapTestCase` once again works with byte strings
1618
on Python 2.
1719
* The ``request`` attribute of `tornado.httpclient.HTTPResponse` is

tornado/test/websocket_test.py

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
from tornado.testing import AsyncHTTPTestCase, gen_test
2-
from tornado.web import Application
3-
from tornado.websocket import WebSocketHandler, websocket_connect
1+
from tornado.httpclient import HTTPError
2+
from tornado.log import gen_log
3+
from tornado.testing import AsyncHTTPTestCase, gen_test, bind_unused_port, ExpectLog
4+
from tornado.web import Application, RequestHandler
5+
from tornado.websocket import WebSocketHandler, websocket_connect, WebSocketError
46

57

68
class EchoHandler(WebSocketHandler):
79
def on_message(self, message):
810
self.write_message(message, isinstance(message, bytes))
911

12+
class NonWebSocketHandler(RequestHandler):
13+
def get(self):
14+
self.write('ok')
15+
1016

1117
class WebSocketTest(AsyncHTTPTestCase):
1218
def get_app(self):
1319
return Application([
1420
('/echo', EchoHandler),
21+
('/non_ws', NonWebSocketHandler),
1522
])
1623

1724
@gen_test
@@ -32,14 +39,30 @@ def test_websocket_callbacks(self):
3239
ws.read_message(self.stop)
3340
response = self.wait().result()
3441
self.assertEqual(response, 'hello')
35-
42+
3643
@gen_test
37-
def test_websocket_fail(self):
38-
try:
39-
ws = yield websocket_connect(
40-
'ws://localhost:%d/no_websock' % self.get_http_port(),
44+
def test_websocket_http_fail(self):
45+
with self.assertRaises(HTTPError) as cm:
46+
yield websocket_connect(
47+
'ws://localhost:%d/notfound' % self.get_http_port(),
4148
io_loop=self.io_loop)
42-
except:
43-
pass
44-
else:
45-
self.fail('Should\'ve caught an Exception')
49+
self.assertEqual(cm.exception.code, 404)
50+
51+
@gen_test
52+
def test_websocket_http_success(self):
53+
with self.assertRaises(WebSocketError):
54+
yield websocket_connect(
55+
'ws://localhost:%d/non_ws' % self.get_http_port(),
56+
io_loop=self.io_loop)
57+
58+
@gen_test
59+
def test_websocket_network_fail(self):
60+
sock, port = bind_unused_port()
61+
sock.close()
62+
with self.assertRaises(HTTPError) as cm:
63+
with ExpectLog(gen_log, ".*"):
64+
yield websocket_connect(
65+
'ws://localhost:%d/' % port,
66+
io_loop=self.io_loop,
67+
connect_timeout=0.01)
68+
self.assertEqual(cm.exception.code, 599)

tornado/websocket.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
xrange = range # py3
4747

4848

49+
class WebSocketError(Exception):
50+
pass
51+
52+
4953
class WebSocketHandler(tornado.web.RequestHandler):
5054
"""Subclass this class to create a basic WebSocket handler.
5155
@@ -740,14 +744,19 @@ def __init__(self, io_loop, request):
740744
})
741745

742746
super(WebSocketClientConnection, self).__init__(
743-
io_loop, None, request, lambda: None, lambda response: None,
747+
io_loop, None, request, lambda: None, self._on_http_response,
744748
104857600, Resolver(io_loop=io_loop))
745749

746750
def _on_close(self):
747751
self.on_message(None)
748752

749-
def _on_body(self, body):
750-
self.connect_future.set_exception(Exception('Could not connect.'))
753+
def _on_http_response(self, response):
754+
if not self.connect_future.done():
755+
if response.error:
756+
self.connect_future.set_exception(response.error)
757+
else:
758+
self.connect_future.set_exception(WebSocketError(
759+
"Non-websocket response"))
751760

752761
def _handle_1xx(self, code):
753762
assert code == 101
@@ -798,15 +807,15 @@ def on_pong(self, data):
798807
pass
799808

800809

801-
def websocket_connect(url, io_loop=None, callback=None):
810+
def websocket_connect(url, io_loop=None, callback=None, connect_timeout=None):
802811
"""Client-side websocket support.
803812
804813
Takes a url and returns a Future whose result is a
805814
`WebSocketClientConnection`.
806815
"""
807816
if io_loop is None:
808817
io_loop = IOLoop.current()
809-
request = httpclient.HTTPRequest(url)
818+
request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)
810819
request = httpclient._RequestProxy(
811820
request, httpclient.HTTPRequest._DEFAULTS)
812821
conn = WebSocketClientConnection(io_loop, request)

0 commit comments

Comments
 (0)