気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
本日は、先日公開させて頂いた「Google App Engine」と「Silverlight」で
作ったゲストブックアプリの解説をします。

ゲストブックアプリのURL

ゲストブックアプリの仕様

サンプルアプリなので仕様はとてもシンプルにしています。機能は以下の2つです。
・ページを訪問したユーザがハンドル名とコメントを入力し、訪問の記録として残す。
・ユーザが残した記録を閲覧する。

PythonでDataStoreに保存する

「Google App Engine」のデータストア(データの入れ物)はRDBではありません。
テーブルの代わりとなるデータモデルクラスを定義し、そのインスタンスを登録します。
インスタンスがRDBでいうところの行(レコード)になります。

まず、ブックの記録1レコードを表すデータモデルクラスを以下のように定義しました。

class Book(db.Model):
  handle_name = db.StringProperty();
  comment = db.StringProperty();
  created = db.DateTimeProperty(auto_now_add=True);

このブログを訪問してくださるみなさまはおそらくPythonに不慣れな(筆者もそうなのです。)
方がいらっしゃると思いますので、少し文法を補足します。

1行目はクラス宣言でクラス名の横の()の中身は継承しているクラスを表します。
2行目以降は変数に値を代入しているのですが、Pythonでは変数宣言は有りませんので、
代入された値の型がその変数の型になります。

これ以降は「Google App Engine」特有の話になるのですが、それぞれの変数に代入している値は、
属性(RDBでいうところの列)の型を定義するためのクラスのインスタンスです。
(Pythonではコンストラクタ呼び出しはnewなどをつけずクラス名()で行います。)

以下のページに「Google App Engine」のデータストアで扱える型の一覧が記載されています。

型の一覧

[created]は登録時の日時を管理するための属性で、登録時の日時が自動登録されるように
[auto_now_add=True]としています。

次にレコードを登録する部分のコードです。

class RegistBook(webapp.RequestHandler):
  def post(self):
    book = Book()
    book.handle_name = self.request.get('handle_name')
    book.comment = self.request.get('comment')
    book.put()

defはメソッドを定義する際に使用するキーワードで、post()メソッドはPOSTリクエストを
処理するためのメソッドです。親クラスのRequestHandlerに定義されているメソッドで
それをオーバーライドしています。

put()の中では定義したBookクラスのインスタンスを生成し、クライアントから受け取ったパラメータを
その属性に設定しています。そしてput()をコールすると、データストアへの保存を行います。

SilverlightからPOSTする

HTTPのPOSTで「ハンドル名」と「コメント」を送信するようにします。
サーバ側で定義したBookクラスの属性、[handle_name]と[comment]の型に設定した
StringProperty()は500バイト以下の文字列なので、その制限を超えないように
まず、入力チェックを行っています。

  if (_handleName.Text.Length == 0 || _comment.Text.Length == 0)
  {
      MessageBox.Show("ハンドル名とコメントを入力してください。", "メッセージ", MessageBoxButton.OK);
      return;
  }

  if (Encoding.Unicode.GetBytes(_handleName.Text).Length > 500
      || Encoding.Unicode.GetBytes(_comment.Text).Length > 500)
  {
      MessageBox.Show("ハンドル名とコメントが長すぎます。", "メッセージ", MessageBoxButton.OK);
      return;
  }

POSTリクエストの送信は「WebRequest」クラスを使って行います。
まずは、POSTするデータを送信するためのストリームの生成をサーバに要求します。
非同期のBeginGetRequestStream()をコールしています。
Tipsでも紹介しましたが、Silverlightの「WebRequest」には同期のメソッドがありません。

  //Webリクエストを生成する
  WebRequest req = WebRequest.Create(new Uri(_registUrl));
  req.Method = "POST";
  req.ContentType = "application/x-www-form-urlencoded";

  //POSTするデータを送信するようのストリームを要求する
  req.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), req); 

リクエストが成功すると、POSTするデータをサーバに送信します。
この処理は、リクエスト送信時に指定したコールバックメソッドの中で行います。
POSTデータの送信が完了すると、POSTリクエストをこれまた非同期で発行し、
レスポンスを待ちます。

  //データ送信用Streamが生成されるとコールされるメソッド
  private void GetRequestStreamCallback(IAsyncResult ar)
  {
      Byte[] postData = null;

      Dispatcher.BeginInvoke(
          () =>
          {
              //POSTするデータを作成する
              String postString = String.Format("handle_name={0}&comment={1}",
                  HttpUtility.UrlEncode(_handleName.Text), HttpUtility.UrlEncode(_comment.Text));

              postData = Encoding.UTF8.GetBytes(postString);
          });

      //ストリームを生成する
      WebRequest req = ar.AsyncState as WebRequest;
      using (Stream writer = req.EndGetRequestStream(ar))
      {
          writer.Write(postData, 0, postData.Length);
      }

      //リクエストを発行する
      req.BeginGetResponse(new AsyncCallback(PostResponseCallback), req);
  }

そして、POSTのレスポンスが帰った時点で、入力用のテキストボックスをクリアし、
記録の一覧を更新します。

  //登録が完了するとコールされる
  private void PostResponseCallback(IAsyncResult ar)
  {
      Dispatcher.BeginInvoke(
          () =>
          {
              _handleName.Text = "";
              _comment.Text = "";
              MessageBox.Show("登録が完了しました。", "メッセージ", MessageBoxButton.OK);
          });

      //一覧を更新する
      GetBookList();
  }

コールバックメソッドは非同期処理なので、コントロールを操作する処理については、
Dispatcherを使用してメインスレッドで実行しています。

次回の記事に続く。




追記 09.03.27 Modelの属性について

Modelの属性の定義について少しだけ補足します。

class Book(db.Model):
  handle_name = db.StringProperty();

本文中でhandle_nameに設定しているdb.StringPropertyはRDBでいうところの列)の型を
定義するためのクラスのインスタンスと説明していますが、もう少し正確にいうと
これは、属性に設定される値のデータ型、初期値、入力規則やその他の挙動を定義するものです。

これはPython 2.2から導入された「Descriptors」という仕組みを使用して実現されています。
私もまだ、Descriptorsをしっかり理解できていませんので、これ以上は説明できないのですが、
Pythonに初めて触れている方は不思議に思われるかと思い、補足しました。

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://csfun.blog49.fc2.com/tb.php/38-69c1b695

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。