3
# Copyright 2009 Facebook
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
22
_log = logging.getLogger('tornado.websocket')
24
class WebSocketHandler(tornado.web.RequestHandler):
25
"""A request handler for HTML 5 Web Sockets.
27
See http://www.w3.org/TR/2009/WD-websockets-20091222/ for details on the
28
JavaScript interface. We implement the protocol as specified at
29
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55.
31
Here is an example Web Socket handler that echos back all received messages
34
class EchoWebSocket(websocket.WebSocketHandler):
36
self.receive_message(self.on_message)
38
def on_message(self, message):
39
self.write_message(u"You said: " + message)
41
Web Sockets are not standard HTTP connections. The "handshake" is HTTP,
42
but after the handshake, the protocol is message-based. Consequently,
43
most of the Tornado HTTP facilities are not available in handlers of this
44
type. The only communication methods available to you are send_message()
45
and receive_message(). Likewise, your request handler class should
46
implement open() method rather than get() or post().
48
If you map the handler above to "/websocket" in your application, you can
49
invoke it in JavaScript with:
51
var ws = new WebSocket("ws://localhost:8888/websocket");
52
ws.onopen = function() {
53
ws.send("Hello, world");
55
ws.onmessage = function (evt) {
59
This script pops up an alert box that says "You said: Hello, world".
61
def __init__(self, application, request):
62
tornado.web.RequestHandler.__init__(self, application, request)
63
self.stream = request.connection.stream
65
def _execute(self, transforms, *args, **kwargs):
66
if self.request.headers.get("Upgrade") != "WebSocket" or \
67
self.request.headers.get("Connection") != "Upgrade" or \
68
not self.request.headers.get("Origin"):
69
message = "Expected WebSocket headers"
71
"HTTP/1.1 403 Forbidden\r\nContent-Length: " +
72
str(len(message)) + "\r\n\r\n" + message)
75
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
76
"Upgrade: WebSocket\r\n"
77
"Connection: Upgrade\r\n"
78
"Server: TornadoServer/0.1\r\n"
79
"WebSocket-Origin: " + self.request.headers["Origin"] + "\r\n"
80
"WebSocket-Location: ws://" + self.request.host +
81
self.request.path + "\r\n\r\n")
82
self.async_callback(self.open)(*args, **kwargs)
84
def write_message(self, message):
85
"""Sends the given message to the client of this Web Socket."""
86
if isinstance(message, dict):
87
message = tornado.escape.json_encode(message)
88
if isinstance(message, unicode):
89
message = message.encode("utf-8")
90
assert isinstance(message, str)
91
self.stream.write("\x00" + message + "\xff")
93
def receive_message(self, callback):
94
"""Calls callback when the browser calls send() on this Web Socket."""
95
callback = self.async_callback(callback)
96
self.stream.read_bytes(
97
1, functools.partial(self._on_frame_type, callback))
100
"""Closes this Web Socket.
102
The browser will receive the onclose event for the open web socket
103
when this method is called.
107
def async_callback(self, callback, *args, **kwargs):
108
"""Wrap callbacks with this if they are used on asynchronous requests.
110
Catches exceptions properly and closes this Web Socket if an exception
114
callback = functools.partial(callback, *args, **kwargs)
115
def wrapper(*args, **kwargs):
117
return callback(*args, **kwargs)
119
_log.error("Uncaught exception in %s",
120
self.request.path, exc_info=True)
124
def _on_frame_type(self, callback, byte):
125
if ord(byte) & 0x80 == 0x80:
126
raise Exception("Length-encoded format not yet supported")
127
self.stream.read_until(
128
"\xff", functools.partial(self._on_end_delimiter, callback))
130
def _on_end_delimiter(self, callback, frame):
131
callback(frame[:-1].decode("utf-8", "replace"))
133
def _not_supported(self, *args, **kwargs):
134
raise Exception("Method not supported for Web Sockets")
136
for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
137
"set_status", "flush", "finish"]:
138
setattr(WebSocketHandler, method, WebSocketHandler._not_supported)