]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add simple test application for websockets
authorBen Darnell <ben@bendarnell.com>
Wed, 18 May 2011 06:14:02 +0000 (23:14 -0700)
committerBen Darnell <ben@bendarnell.com>
Wed, 18 May 2011 06:14:02 +0000 (23:14 -0700)
demos/websocket/chatdemo.py [new file with mode: 0755]
demos/websocket/static/chat.css [new file with mode: 0644]
demos/websocket/static/chat.js [new file with mode: 0644]
demos/websocket/templates/index.html [new file with mode: 0644]
demos/websocket/templates/message.html [new file with mode: 0644]

diff --git a/demos/websocket/chatdemo.py b/demos/websocket/chatdemo.py
new file mode 100755 (executable)
index 0000000..dd5a48d
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Simplified chat demo for websockets.
+
+Authentication, error handling, etc are left as an exercise for the reader :)
+"""
+
+import logging
+import tornado.escape
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import tornado.websocket
+import os.path
+import uuid
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+
+
+class Application(tornado.web.Application):
+    def __init__(self):
+        handlers = [
+            (r"/", MainHandler),
+            (r"/chatsocket", ChatSocketHandler),
+        ]
+        settings = dict(
+            cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+            template_path=os.path.join(os.path.dirname(__file__), "templates"),
+            static_path=os.path.join(os.path.dirname(__file__), "static"),
+            xsrf_cookies=True,
+        )
+        tornado.web.Application.__init__(self, handlers, **settings)
+
+
+class MainHandler(tornado.web.RequestHandler):
+    def get(self):
+        self.render("index.html", messages=ChatSocketHandler.cache)
+
+class ChatSocketHandler(tornado.websocket.WebSocketHandler):
+    waiters = set()
+    cache = []
+    cache_size = 200
+
+    def open(self):
+        ChatSocketHandler.waiters.add(self)
+
+    def on_close(self):
+        ChatSocketHandler.waiters.remove(self)
+
+    @classmethod
+    def update_cache(cls, chat):
+        cls.cache.append(chat)
+        if len(cls.cache) > cls.cache_size:
+            cls.cache = cls.cache[-cls.cache_size:]
+
+    @classmethod
+    def send_updates(cls, chat):
+        logging.info("sending message to %d waiters", len(cls.waiters))
+        for waiter in cls.waiters:
+            try:
+                waiter.write_message(chat)
+            except:
+                logging.error("Error sending message", exc_info=True)
+
+    def on_message(self, message):
+        logging.info("got message %r", message)
+        parsed = tornado.escape.json_decode(message)
+        chat = {
+            "id": str(uuid.uuid4()),
+            "body": parsed["body"],
+            }
+        chat["html"] = self.render_string("message.html", message=chat)
+
+        ChatSocketHandler.update_cache(chat)
+        ChatSocketHandler.send_updates(chat)
+
+
+def main():
+    tornado.options.parse_command_line()
+    app = Application()
+    app.listen(options.port)
+    tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/demos/websocket/static/chat.css b/demos/websocket/static/chat.css
new file mode 100644 (file)
index 0000000..a400c32
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 FriendFeed
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+  background: white;
+  margin: 10px;
+}
+
+body,
+input {
+  font-family: sans-serif;
+  font-size: 10pt;
+  color: black;
+}
+
+table {
+  border-collapse: collapse;
+  border: 0;
+}
+
+td {
+  border: 0;
+  padding: 0;
+}
+
+#body {
+  position: absolute;
+  bottom: 10px;
+  left: 10px;
+}
+
+#input {
+  margin-top: 0.5em;
+}
+
+#inbox .message {
+  padding-top: 0.25em;
+}
+
+#nav {
+  float: right;
+  z-index: 99;
+}
diff --git a/demos/websocket/static/chat.js b/demos/websocket/static/chat.js
new file mode 100644 (file)
index 0000000..236cb0d
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright 2009 FriendFeed
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+$(document).ready(function() {
+    if (!window.console) window.console = {};
+    if (!window.console.log) window.console.log = function() {};
+
+    $("#messageform").live("submit", function() {
+        newMessage($(this));
+        return false;
+    });
+    $("#messageform").live("keypress", function(e) {
+        if (e.keyCode == 13) {
+            newMessage($(this));
+            return false;
+        }
+    });
+    $("#message").select();
+    updater.start();
+});
+
+function newMessage(form) {
+    var message = form.formToDict();
+    updater.socket.send(JSON.stringify(message));
+    form.find("input[type=text]").val("").select();
+}
+
+jQuery.fn.formToDict = function() {
+    var fields = this.serializeArray();
+    var json = {}
+    for (var i = 0; i < fields.length; i++) {
+        json[fields[i].name] = fields[i].value;
+    }
+    if (json.next) delete json.next;
+    return json;
+};
+
+var updater = {
+    socket: null,
+
+    start: function() {
+       updater.socket = new WebSocket("ws://localhost:8888/chatsocket");
+       updater.socket.onmessage = function(event) {
+           updater.showMessage(JSON.parse(event.data));
+       }
+    },
+
+    showMessage: function(message) {
+        var existing = $("#m" + message.id);
+        if (existing.length > 0) return;
+        var node = $(message.html);
+        node.hide();
+        $("#inbox").append(node);
+        node.slideDown();
+    }
+};
diff --git a/demos/websocket/templates/index.html b/demos/websocket/templates/index.html
new file mode 100644 (file)
index 0000000..68721e7
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
+    <title>Tornado Chat Demo</title>
+    <link rel="stylesheet" href="{{ static_url("chat.css") }}" type="text/css"/>
+  </head>
+  <body>
+    <div id="body">
+      <div id="inbox">
+        {% for message in messages %}
+          {% include "message.html" %}
+        {% end %}
+      </div>
+      <div id="input">
+        <form action="/a/message/new" method="post" id="messageform">
+          <table>
+            <tr>
+              <td><input name="body" id="message" style="width:500px"/></td>
+              <td style="padding-left:5px">
+                <input type="submit" value="{{ _("Post") }}"/>
+                <input type="hidden" name="next" value="{{ request.path }}"/>
+                {{ xsrf_form_html() }}
+              </td>
+            </tr>
+          </table>
+        </form>
+      </div>
+    </div>
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
+    <script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
+  </body>
+</html>
diff --git a/demos/websocket/templates/message.html b/demos/websocket/templates/message.html
new file mode 100644 (file)
index 0000000..612f4cf
--- /dev/null
@@ -0,0 +1,2 @@
+{% import tornado.escape %}
+<div class="message" id="m{{ message["id"] }}">{{ tornado.escape.linkify(message["body"]) }}</div>