~0x44/nova/extdoc

« back to all changes in this revision

Viewing changes to vendor/tornado/tornado/websocket.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
_log = logging.getLogger('tornado.websocket')
 
23
 
 
24
class WebSocketHandler(tornado.web.RequestHandler):
 
25
    """A request handler for HTML 5 Web Sockets.
 
26
 
 
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.
 
30
 
 
31
    Here is an example Web Socket handler that echos back all received messages
 
32
    back to the client:
 
33
 
 
34
      class EchoWebSocket(websocket.WebSocketHandler):
 
35
          def open(self):
 
36
              self.receive_message(self.on_message)
 
37
 
 
38
          def on_message(self, message):
 
39
             self.write_message(u"You said: " + message)
 
40
 
 
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().
 
47
 
 
48
    If you map the handler above to "/websocket" in your application, you can
 
49
    invoke it in JavaScript with:
 
50
 
 
51
      var ws = new WebSocket("ws://localhost:8888/websocket");
 
52
      ws.onopen = function() {
 
53
         ws.send("Hello, world");
 
54
      };
 
55
      ws.onmessage = function (evt) {
 
56
         alert(evt.data);
 
57
      };
 
58
 
 
59
    This script pops up an alert box that says "You said: Hello, world".
 
60
    """
 
61
    def __init__(self, application, request):
 
62
        tornado.web.RequestHandler.__init__(self, application, request)
 
63
        self.stream = request.connection.stream
 
64
 
 
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"
 
70
            self.stream.write(
 
71
                "HTTP/1.1 403 Forbidden\r\nContent-Length: " +
 
72
                str(len(message)) + "\r\n\r\n" + message)
 
73
            return
 
74
        self.stream.write(
 
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)
 
83
 
 
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")
 
92
 
 
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))
 
98
 
 
99
    def close(self):
 
100
        """Closes this Web Socket.
 
101
 
 
102
        The browser will receive the onclose event for the open web socket
 
103
        when this method is called.
 
104
        """
 
105
        self.stream.close()
 
106
 
 
107
    def async_callback(self, callback, *args, **kwargs):
 
108
        """Wrap callbacks with this if they are used on asynchronous requests.
 
109
 
 
110
        Catches exceptions properly and closes this Web Socket if an exception
 
111
        is uncaught.
 
112
        """
 
113
        if args or kwargs:
 
114
            callback = functools.partial(callback, *args, **kwargs)
 
115
        def wrapper(*args, **kwargs):
 
116
            try:
 
117
                return callback(*args, **kwargs)
 
118
            except Exception, e:
 
119
                _log.error("Uncaught exception in %s",
 
120
                              self.request.path, exc_info=True)
 
121
                self.stream.close()
 
122
        return wrapper
 
123
 
 
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))
 
129
 
 
130
    def _on_end_delimiter(self, callback, frame):
 
131
        callback(frame[:-1].decode("utf-8", "replace"))
 
132
 
 
133
    def _not_supported(self, *args, **kwargs):
 
134
        raise Exception("Method not supported for Web Sockets")
 
135
 
 
136
for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
 
137
               "set_status", "flush", "finish"]:
 
138
    setattr(WebSocketHandler, method, WebSocketHandler._not_supported)