Puhser + Google App EngineでリアルタイムWebアプリ その2
「WebSocketで目指せ“リアルタイムWeb”!」で取り上げられているRetrospectiveappのサーバサイド(Heroku + Sinatra)をGoogle App Engine + Bottleで実装してみる。
クライアントサイドは元ネタとほぼ同じ。
デモ : http://myretrospective.appspot.com/
Model
from google.appengine.ext import db class Note(db.Model): x = db.IntegerProperty(required = True) y = db.IntegerProperty(required = True) w = db.IntegerProperty(required = True) h = db.IntegerProperty(required = True) angle = db.IntegerProperty(required = True) text = db.StringProperty(required = True, multiline = True) color = db.StringProperty(required = True)
App
import os import re from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app import bottle from bottle import route, response, request, error, static_file from django.utils import simplejson from pusher import Pusher, Channel import dbutils from model import Note bottle.debug(True) APP_ID = 'app_id' KEY = 'key' SERCRET = 'secret' CHANNEL_NAME = 'retrospectiveapp-test' pusher= Pusher(APP_ID, KEY, SERCRET) channel = Channel(CHANNEL_NAME, pusher) @route('/') def index(): return static_file('index.html', os.path.dirname(__file__)) @route('/notes', method = 'GET') def notes_get(): notes = [dbutils.to_dict(note) for note in Note.all()] if notes: return simplejson.dumps(notes) return {} @route('/notes', method = 'POST') def notes_post(): x = request.POST.get('note[x]', '') y = request.POST.get('note[y]', '') w = request.POST.get('note[w]', '') h = request.POST.get('note[h]', '') text = request.POST.get('note[text]', '') angle = request.POST.get('note[angle]', '') color = request.POST.get('note[color]', '') note = Note(x = int(x), y = int(y), w = int(w), h = int(h), text = text, angle = int(float(angle)), color = color ) note.put() channel.trigger('note-create', dbutils.to_dict(note)) return dbutils.to_dict(note) @route('/notes/:id/softupdate', method = 'PUT') def softupdate(id): text = request.POST.get('n[text]', '') socket_id = request.POST.get('socket_id', '') channel.trigger('note-softupdate', {'text' : text, 'socket_id' : socket_id}) return {'text' : text} @route('/notes/:id', method = 'PUT') def notes_id_post(id): n = Note.get_by_id(int(id)) x = request.POST.get('note[x]', '') y = request.POST.get('note[y]', '') w = request.POST.get('note[w]', '') h = request.POST.get('note[h]', '') text = request.POST.get('note[text]', '') if (text): n.text = unicode(text.decode('utf-8')) elif (x and y and w and h): n.x = int(x) n.y = int(y) n.w = int(w) n.h = int(h) n.put() channel.trigger('note-update', dbutils.to_dict(n)) return dbutils.to_dict(n) @route('/notes/:id', method = 'DELETE') def notes_id_delete(id, method = 'DELETE'): n = Note.get_by_id(int(id)) n.delete() channel.trigger('note-destroy', {'id' : id}) return {'id' : id} @error(404) @error(403) def mistake404(code): return 'error' if __name__ == '__main__': app = bottle.app() run_wsgi_app(app)
Modelをdictに変換するためにKay Frameworkのdbutilsをちょっと改造して使ってる。
from google.appengine.ext import db import datetime import time #SIMPLE_TYPES = (int, long, float, bool, dict, basestring, list) SIMPLE_TYPES = (int, long, float, bool, dict, list) def to_dict(model): output = {} #set id output['id'] = model.key().id() for key in model.properties().iterkeys(): value = getattr(model, key) if isinstance(value, basestring): output[key] = unicode(value) elif value is None or isinstance(value, SIMPLE_TYPES): output[key] = value elif isinstance(value, datetime.date): # Convert date/datetime to ms-since-epoch ("new Date()"). ms = time.mktime(value.utctimetuple()) * 1000 ms += getattr(value, 'microseconds', 0) / 1000 output[key] = int(ms) elif isinstance(value, db.Model): output[key] = to_dict(value) else: output[key] = str(value) return output