]> arthur.barton.de Git - bup.git/blob - lib/tornado/websocket.py
Always publish (l)utimes in helpers when available and fix type conversions.
[bup.git] / lib / tornado / websocket.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2009 Facebook
4 #
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
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
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
15 # under the License.
16
17 import functools
18 import logging
19 import tornado.escape
20 import tornado.web
21
22 class WebSocketHandler(tornado.web.RequestHandler):
23     """A request handler for HTML 5 Web Sockets.
24
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.
28
29     Here is an example Web Socket handler that echos back all received messages
30     back to the client:
31
32       class EchoWebSocket(websocket.WebSocketHandler):
33           def open(self):
34               self.receive_message(self.on_message)
35
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)
41
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().
48
49     If you map the handler above to "/websocket" in your application, you can
50     invoke it in JavaScript with:
51
52       var ws = new WebSocket("ws://localhost:8888/websocket");
53       ws.onopen = function() {
54          ws.send("Hello, world");
55       };
56       ws.onmessage = function (evt) {
57          alert(evt.data);
58       };
59
60     This script pops up an alert box that says "You said: Hello, world".
61     """
62     def __init__(self, application, request):
63         tornado.web.RequestHandler.__init__(self, application, request)
64         self.stream = request.connection.stream
65
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"
71             self.stream.write(
72                 "HTTP/1.1 403 Forbidden\r\nContent-Length: " +
73                 str(len(message)) + "\r\n\r\n" + message)
74             return
75         self.stream.write(
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)
84
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")
93
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))
99
100     def close(self):
101         """Closes this Web Socket.
102
103         The browser will receive the onclose event for the open web socket
104         when this method is called.
105         """
106         self.stream.close()
107
108     def async_callback(self, callback, *args, **kwargs):
109         """Wrap callbacks with this if they are used on asynchronous requests.
110
111         Catches exceptions properly and closes this Web Socket if an exception
112         is uncaught.
113         """
114         if args or kwargs:
115             callback = functools.partial(callback, *args, **kwargs)
116         def wrapper(*args, **kwargs):
117             try:
118                 return callback(*args, **kwargs)
119             except Exception, e:
120                 logging.error("Uncaught exception in %s",
121                               self.request.path, exc_info=True)
122                 self.stream.close()
123         return wrapper
124
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))
130
131     def _on_end_delimiter(self, callback, frame):
132         callback(frame[:-1].decode("utf-8", "replace"))
133
134     def _not_supported(self, *args, **kwargs):
135         raise Exception("Method not supported for Web Sockets")
136
137 for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
138                "set_status", "flush", "finish"]:
139     setattr(WebSocketHandler, method, WebSocketHandler._not_supported)