Google App Engineをお試し

最近iMacを買ったのだが、その静かさに驚いている。しかし、そうなると今度は隣で動いている自宅サーバーのうるささが気になってくる。というわけで自宅サーバを止めた。しかし、さくらのレンタルサーバーを再び管理するのは面倒だ・・。ということで、ここは一丁GAEにチャレンジしてみるかということで、以下のページを参考に触ってみる。
http://code.google.com/appengine/docs/gettingstarted/

こういうものが出来上がる

http://kenmaz.appspot.com/

特徴

GAEで現在サポートしているのはpythonのみ。Djangoをベースにした、webappフレームワークが利用可能。Ruby on RailsJavaは今のところ使えません。
アプリのファイル構成は以下のような感じ。

開発手順は以下の通り。

  1. SDKに含まれるローカルサーバーを建てる
  2. ローカルでコーディング、テスト
  3. 「appcfg update」コマンドでGoogleのサーバにアップロード

ハンドラ、モデルのソースコード

ハンドラ、モデルのソースコードは以下のような感じ。

[helloworld.py]

import cgi
import wsgiref.handlers
import os

from google.appengine.api import users 
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template 

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)

class MainPage(webapp.RequestHandler):
  def get(self):
    query = Greeting.all().order("-date")
    greetings = query.fetch(10)

    if users.get_current_user():
      url = users.create_logout_url(self.request.uri)
      url_linktext = 'Logout'
    else:
      url = users.create_login_url(self.request.uri)
      url_linktext = 'Login'

    template_values = {
      'greetings': greetings,
      '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 Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()

    author = users.get_current_user()
    if author:
      greeting.author = author

    greeting.content = self.request.get('content')
    greeting.put()
    self.redirect('/')

def main():
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        ('/sign', Guestbook)],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()

ここでは、Greetingというモデルと、Main, Guestbookというリクエストハンドラを定義している。main()でリクエストハンドラとパスのマッピングをやっている。あとはお約束かな。

コントローラ(リクエストハンドラ)は以下のような感じで定義。

  • リクエストハンドラクラスは、webapp.RequestHandlerを継承する。
  • 1アクション:1リクエストハンドラを定義するのが基本。
  • get,postというメソッドがそのままhttpのメソッドに対応
  • レスポンスの描画は、self.response.out.write("...")
  • templateを使って、self.response.out.write(teplate.render('index.html', template_values))でもOK. 普通はこっちを使う. template.renderメソッドの第一引数はテンプレートファイル名、第二引数はhtmlに埋め込む動的な文字列のためのマップです。
  • Googleアカウントを操作するためのgoogle.appengine.api.usersなどのAPIが利用可能。


モデルは以下のような感じで定義。

  • モデルクラスは、db.Modelを継承する。
  • モデルクラスを通じて、DatastoreというDBっぽいものを操作できる
  • ここで定義したクラス名が、そのままテーブル名になる
  • さらにクラス内でcontent = db.StringProperty(..)とか定義すると、これがそのままテーブルの列名になる(content,という列ができる)。
  • モデルのインスタンスを作って、プロパティ値をセットし、putメソッドを呼べばDatastoreにデータを格納できる。
  • データを取得するには、SQLに似たGQLを利用する。
  • Datastoreは普通のRDBとは感じがちがうっぽい。
  • Greeting.all().order('-date')という感じで、RailsのARぽくデータを取得することも可能。

ビュー

で、以下がビューのテンプレート。

[index.html]

<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
  </head>
  <body>
    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname }}</b> wrote:
      {% else %}
        An anonymous person wrote:
      {% endif %}
      <blockquote>{{ greeting.content|escape }}</blockquote>
    {% endfor %}

    <form action="/sign" method="post">
      <div><textarea name="content" row="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sing Guestbook"></div>
    </form>

    <a href="{{ url }}">{{ url_linktext }}</a>
  </body>
</html>

{{% .. %}}とか{{ .. }}に、リクエストハンドラで渡された文字列を埋め込む感じ。

ハマり事項

今回の程度だと、ほとんどハマることはない。ただ、リクエストハンドラにおいて以下のコードを

    query = Greeting.all().order("-date")
    greetings = query.fetch(10)

以下のように書くと、

    greetings = Greeting.all().order("-date").fetch(10)

時々エラーが発生する。なぜだろ。