用GAE+jQuery打造无需数据库的AJAX聊天室

标签:Google App Engine, JavaScript, Python, jQuery

之前写过一篇用Google App Engine+jQuery打造AJAX聊天室,已经用了很多缓存来提高效率了。
不过如果无需长期保存聊天数据的话,实际上连数据库都不需要,直接用Handler Script缓存即可。

这次我没有用XML来做,而是换成了JSON,代码一下就简单了很多。
此外我用到了自己写的一个Queue模块,详情见用Python写的限制长度的队列。你也可以直接使用list,只不过Handler Script缓存占用量会大些。

服务器端:
# -*- coding: utf-8 -*-

import os
from datetime import datetime, timedelta, tzinfo
from urllib import quote
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from Queue import Queue

maxID = 0

messages = Queue()

class CST(tzinfo):
  def utcoffset(self, dt):
    return timedelta(hours=8)

  def dst(self, dt):
    return timedelta(0)

  def tzname(self, dt):
    return 'China Standard Time UT+8:00'

def newMessage(author=None, content=''):
  global maxID
  maxID += 1
  return {
            'id': maxID,
            'time': datetime.now(CST()).isoformat(' '),
            'author': quote(author.nickname() if author else '天朝匿名用户'),
            'content': quote(content.encode('utf8'))
          }

class MainPage(webapp.RequestHandler):
  def get(self):

    if users.get_current_user():
      url = users.create_logout_url(self.request.uri)
      url_linktext = '退出'
    else:
      url = users.create_login_url(self.request.uri)
      url_linktext = '登录'

    template_values = {
      'url': url,
      'url_linktext': url_linktext,
    }

    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, template_values))

class Postmsg(webapp.RequestHandler):
  def post(self):
    messages.append(newMessage(users.get_current_user(), self.request.get('content')))

class Getmsg(webapp.RequestHandler):
  def get(self):
    self.response.headers['Content-Type'] = 'application/json; charset=UTF-8'

    result = {}

    global maxID
    result['id'] = maxID
    id = int(self.request.get('id'))

    if maxID <= id:
      self.response.out.write(result)
      return

    result['messages'] = sorted([message for message in messages if message['id'] > id], key=lambda message: message['id'], reverse=True)

    self.response.out.write(str(result))


application = webapp.WSGIApplication([('/', MainPage),
                                      ('/getmsg', Getmsg),
                                      ('/postmsg', Postmsg)])

def main():
  run_wsgi_app(application)

if __name__ == '__main__':
  main()
客户端:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>keakon的恶搞聊天室</title>
	<style type="text/css">
		#input { position: absolute; width: 45%; left: 50%; }
		#content { width: 95%; }
		#msg { position: absolute; top: 0; width: 45%; height: 100%; word-wrap: break-word; word-break: break-all; }
		.top1em { margin-top: 1em }
		.top2em { margin-top: 2em }
	</style>
	<script src="http://www.google.com/jsapi"></script>
	<script type="text/javascript">
		google.load("jquery", "1.3.2");
	</script>
	<script type="text/javascript">
		var g_id = 0;
		function submit() {
			$.post(
				"/postmsg",
				{content: $("#content").val()},
				function() {check_msg(); $("#content").val("");}
			);
		}
		function check_msg() {
			$.getJSON(
				"/getmsg",
				(function () {
					if ("\v" == "v") return {id: g_id, rand: Math.random()}; // IE
					else return {id: g_id};
				})(),
				function(json) {
					if (json.id > g_id) {
						var msg = [];
						g_id = json.id;
						$.each(json.messages, function (index, value) {
							var content = decodeURIComponent(value.content).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
							msg.push("<b>");
							msg.push(decodeURIComponent(value.author));
							msg.push("</b> @ ");
							msg.push(value.time);
							msg.push(":<blockquote><pre>");
							msg.push(content);
							msg.push("</pre></blockquote>");
						});
						if (msg) {
							$('#msg').prepend(msg.join(""));
						}
						check_msg();
					}
				}
			);
		}
		$(document).ready(
			function() {
				check_msg();
				setInterval(check_msg, 3000);
				$("#content").keypress(function(e) {
					if (e.shiftKey && e.keyCode == 13) submit();
				});
				$("#submit_msg").click(submit);
			}
		);
	</script>
</head>
<body>
	<div id="input">
		<div><textarea id="content" rows="3" cols="60"></textarea></div>
		<div class="top1em"><input type="submit" value="submit" id="submit_msg" />
		<a href="{{url}}">{{url_linktext}}</a></div>
		<div class="top2em">注意:
			<blockquote><p>可以用Shift+回车键快速发送消息。</p>
			<p>本站使用Google App Engine打造,点<a href="http://www.keakon.cn/bbs/thread-1130-1-1.html" style="color: red">这里</a>查看源码。</p></blockquote>
		</div>
	</div>
	<div id="msg"></div>
</body>
</html>
代码基本上和之前差不多,不过有个地方要说下:
(function () {
	if ("\v" == "v") return {id: g_id, rand: Math.random()}; // IE
	else return {id: g_id};
})()
实际上IE浏览器需要加个随机参数,否则第2次AJAX GET是不会产生的,浏览器默认就从缓存里取数据了,就算服务器端写了self.response.headers['Cache-Control'] = 'no-cache,must-revalidate'也没用。
如果不怕影响性能,直接写{id: g_id, rand: Math.random()}也是可以的;我之所以用匿名函数,是为了不拖慢其他浏览器的速度。

12条评论 你不来一发么↓ 顺序排列 倒序排列

    向下滚动可载入更多评论,或者点这里禁止自动加载

    想说点什么呢?