项目班 08 WebSocket
app.py 更新 添加两个路由
handlers = [ ('/', main.IndexHandler), ('/explore', main.ExploreHandler), ('/post/(?P<post_id>[0-9]+)', main.PostHandler), ('/upload', main.UploadHandler), ('/login', auth.LoginHandler), ('/logout', auth.LogoutHandler), ('/signup', auth.SignupHandler), ('/room', chat.RoomHandler), ('/ws', chat.ChatSocketHandler), ]
base.html 更新
{% block extra_scripts %}{% end %} #在body最后添加这一条
templates/message.html 添加HTML文件
<div class="message" id="m{{ message["id"] }}">{% module linkify(message["body"]) %}</div>
templates/room.html 添加聊天室html
{% extends 'base.html' %} {% block title %}room page{% end %} {% block content %} <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="500px"></td> <td style="padding-left:5px"> <input type="submit" value="提交"> <input type="hidden" name="next" value="{{ request.path }}"> </td> </tr> </table> </form> </div> </div> {% end %} {% block extra_scripts %} <script src="{{ static_url("js/chat.js") }}" type="text/javascript"></script> {% end %}
handlers/chat.py 添加聊天的handlers
import logging import tornado.escape import tornado.ioloop import tornado.options import tornado.web import tornado.websocket import uuid from .main import AuthBaseHandler class RoomHandler(AuthBaseHandler): """ 聊天室页面 """ def get(self): self.render("room.html", messages=ChatSocketHandler.cache) class ChatSocketHandler(tornado.websocket.WebSocketHandler): waiters = set() # 等待接收信息的用户 cache = [] # 存放消息 cache_size = 200 # 消息列表的大小 def get_compression_options(self): """ 非 None 的返回值开启压缩 """ return {} def open(self): """ 新的WebSocket连接打开 """ logging.info("new connection %s" % self) ChatSocketHandler.waiters.add(self) #在集合中添加用户,出现相同用户会去重 def on_close(self): """ WebSocket连接断开 """ 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:] #如果列表长度大于200个元素,只显示最后200个元素 [-200:-1] @classmethod def send_updates(cls, chat): """给每个等待接收的用户发新的消息""" logging.info("sending message to %d waiters", len(cls.waiters)) #logging类似于print,但又比print高级 for waiter in cls.waiters: try: waiter.write_message(chat) #给每个waiter发送消息 except: logging.error("Error sending message", exc_info=True) def on_message(self, message): """ WebSocket 服务端接收到消息 """ logging.info("got message %r", message) parsed = tornado.escape.json_decode(message) #通过json解码message chat = { #创建一个chat字典,id为不重复的uuid字符串,body为上面json解码后的一个body "id": str(uuid.uuid4()), "body": parsed["body"], } chat["html"] = tornado.escape.to_basestring(self.render_string("message.html", message=chat)) #将chat赋给message,放入message.html里面渲染后变成一个html代码,然后通过tornado自带to_basestring方法 #转化为chat字典中html键的值;render_string只会返回字节流,需要用to_basestring来转化 ChatSocketHandler.update_cache(chat) #执行更新消息列表函数 ChatSocketHandler.send_updates(chat) #执行发送消息函数
static/js/chat.js 添加chat.js文件
$(document).ready(function() { if (!window.console) window.console = {}; if (!window.console.log) window.console.log = function() {}; $("#messageform").on("submit", function() { // 点击提交时执行 newMessage($(this)); return false; }); $("#messageform").on("keypress", function(e) { // 回车提交时执行 if (e.keyCode == 13) { newMessage($(this)); return false; } }); $("#message").select(); updater.start(); // 开始 WebSocket }); 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() { var url = "ws://" + location.host + "/ws"; updater.socket = new WebSocket(url); // 初始化 WebSocket 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); // 添加消息 DIV 到页面 // node.toggle(); } };