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 class WebSocketHandler(tornado.web.RequestHandler):
23 """A request handler for HTML 5 Web Sockets.
25 See http://www.w3.org/TR/2009/WD-websockets-20091222/ for details on the
26 JavaScript interface. We implement the protocol as specified at
27 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55.
29 Here is an example Web Socket handler that echos back all received messages
32 class EchoWebSocket(websocket.WebSocketHandler):
34 self.receive_message(self.on_message)
36 def on_message(self, message):
37 self.write_message(u"You said: " + message)
38 # receive_message only reads a single message, so call it
39 # again to listen for the next one
40 self.receive_message(self.on_message)
42 Web Sockets are not standard HTTP connections. The "handshake" is HTTP,
43 but after the handshake, the protocol is message-based. Consequently,
44 most of the Tornado HTTP facilities are not available in handlers of this
45 type. The only communication methods available to you are send_message()
46 and receive_message(). Likewise, your request handler class should
47 implement open() method rather than get() or post().
49 If you map the handler above to "/websocket" in your application, you can
50 invoke it in JavaScript with:
52 var ws = new WebSocket("ws://localhost:8888/websocket");
53 ws.onopen = function() {
54 ws.send("Hello, world");
56 ws.onmessage = function (evt) {
60 This script pops up an alert box that says "You said: Hello, world".
62 def __init__(self, application, request):
63 tornado.web.RequestHandler.__init__(self, application, request)
64 self.stream = request.connection.stream
66 def _execute(self, transforms, *args, **kwargs):
67 if self.request.headers.get("Upgrade") != "WebSocket" or \
68 self.request.headers.get("Connection") != "Upgrade" or \
69 not self.request.headers.get("Origin"):
70 message = "Expected WebSocket headers"
72 "HTTP/1.1 403 Forbidden\r\nContent-Length: " +
73 str(len(message)) + "\r\n\r\n" + message)
76 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
77 "Upgrade: WebSocket\r\n"
78 "Connection: Upgrade\r\n"
79 "Server: TornadoServer/0.1\r\n"
80 "WebSocket-Origin: " + self.request.headers["Origin"] + "\r\n"
81 "WebSocket-Location: ws://" + self.request.host +
82 self.request.path + "\r\n\r\n")
83 self.async_callback(self.open)(*args, **kwargs)
85 def write_message(self, message):
86 """Sends the given message to the client of this Web Socket."""
87 if isinstance(message, dict):
88 message = tornado.escape.json_encode(message)
89 if isinstance(message, unicode):
90 message = message.encode("utf-8")
91 assert isinstance(message, str)
92 self.stream.write("\x00" + message + "\xff")
94 def receive_message(self, callback):
95 """Calls callback when the browser calls send() on this Web Socket."""
96 callback = self.async_callback(callback)
97 self.stream.read_bytes(
98 1, functools.partial(self._on_frame_type, callback))
101 """Closes this Web Socket.
103 The browser will receive the onclose event for the open web socket
104 when this method is called.
108 def async_callback(self, callback, *args, **kwargs):
109 """Wrap callbacks with this if they are used on asynchronous requests.
111 Catches exceptions properly and closes this Web Socket if an exception
115 callback = functools.partial(callback, *args, **kwargs)
116 def wrapper(*args, **kwargs):
118 return callback(*args, **kwargs)
120 logging.error("Uncaught exception in %s",
121 self.request.path, exc_info=True)
125 def _on_frame_type(self, callback, byte):
126 if ord(byte) & 0x80 == 0x80:
127 raise Exception("Length-encoded format not yet supported")
128 self.stream.read_until(
129 "\xff", functools.partial(self._on_end_delimiter, callback))
131 def _on_end_delimiter(self, callback, frame):
132 callback(frame[:-1].decode("utf-8", "replace"))
134 def _not_supported(self, *args, **kwargs):
135 raise Exception("Method not supported for Web Sockets")
137 for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
138 "set_status", "flush", "finish"]:
139 setattr(WebSocketHandler, method, WebSocketHandler._not_supported)