2011年7月4日月曜日

C# からProtoRPCを呼び出すサンプル

といっても、単にJSONオブジェクトをhttpで送ってるだけです。
JSONのシリアライズには、DynamicJsonを使っています。
これは簡単だ

GettingStartGuideのはじめの方にある、
helloサービスに接続するサンプルです。

var obj = new {
my_name = "3sons"
};
var jsonStringFromObj = Codeplex.Data.DynamicJson.Serialize(obj);
byte[] postDataBytes = System.Text.Encoding.ASCII.GetBytes(jsonStringFromObj);

System.Text.Encoding enc = System.Text.Encoding.GetEncoding("UTF-8");
System.Net.WebRequest req = System.Net.WebRequest.Create("http://localhost:8080/hello.hello");
req.Method = "POST";
req.ContentType = "application/json";
//POST送信するデータの長さを指定
req.ContentLength = postDataBytes.Length; 

//データをPOST送信するためのStreamを取得 
System.IO.Stream reqStream = req.GetRequestStream();
//送信するデータを書き込む 
reqStream.Write(postDataBytes, 0, postDataBytes.Length);
reqStream.Close();

//サーバーからの応答を受信するためのWebResponseを取得 
System.Net.WebResponse res = req.GetResponse();
//応答データを受信するためのStreamを取得 
System.IO.Stream resStream = res.GetResponseStream();
//受信して表示
System.IO.StreamReader sr = new System.IO.StreamReader(resStream, enc);
string ret = sr.ReadToEnd();
//閉じる 
sr.Close();
MessageBox.Show(ret);

C#は.Net2.0のころ触って以来ですが、いろいろ便利になってますね。varとか
現在、これを使ってGAEをバックエンドに使うWindowsアプリを開発中です。

ローカルDBにSQLiteを使い、定期的/必要に応じて、
ProtoRPCを使ってGAEのデータストアとレプリケーションを行います。
同じことはAndroidでもiPhone/iPadでもできるはずですので、
端末のフル機能を使用するクラウドシステムが構築できます。

また、クラウドの弱点である、ネットワークの混雑による遅延や、
ネットワークトラブルによりシステムが使えない、
といった問題を防ぐことができます。

2011年7月3日日曜日

19.Generate postservice module from the server

サーバー(ゲストブックアプリケーション)で作成したpostserviceモジュールのコードは、
クライアントに対しては秘密であり、クライアントがその中身を知る必要はないはずです。
また、サーバーのコードはクライアントには必要のないライブラリに依存している場合もあります。
(つまり、クライアントアプリケーションを書く人が、サーバーのpostserviceモジュールを入手し、利用することは通常、ありえません。また、その必要もありません。)

サーバーは、RegistryServiceを公開していますので、
クライアントプログラムを書く際に必要になるモジュールを自動生成することができます。

これを行うために、ProtoRPCでは、「get_protorpc.py」というコマンドラインツールを用意しています。
コマンドシェルで、クライアントアプリケーションのディレクトリに移動し、
サーバーを指定してこのコマンドを実行することで、モジュールのコードを自動生成できます。
以下のコマンドを実行してみてください。

gen_protorpc.py registry localhost:8080 /postservice

クライアントアプリケーションのディレクトリを覗いてみると、
postservice.pyという名前のファイルがあるはずです。

そのファイルの中身は、サーバーで書いたpostservice.pyとよく似ていると思います。
しかし、クライアント側はPostServiceのメソッドは空で、何も書かれていないはずです。


Service stubs

サーバーとの通信は、クライアント側の持つスタブクラスによって行います。
個々のサービスは、スタブサブクラスを持ち、スタブはすべてのメソッドを含んでいます。

from protorpc import transport
import postservice

service = postservice.PostService.Stub(
    transport.HttpTransport(
        'http://localhost:8080/postservice'))


Invoke remote method

スタブを用いて、サーバーのget_notesメソッドを呼び出すリクエストハンドラを書いてみましょう。
(6行目に出てくる「service」は、さきほど定義したスタブです。
ですので、実行する際には、class MainHandlerの宣言の前に、上のservice=のコードを書いてください。)

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

class MainHandler(webapp.RequestHandler):
    def get(self):
        notes = service.get_notes().notes
        if notes is None:
            notes = []

        note_count = len(notes)
        note_lengths = [len(note.text) for note in notes]
        total_characters = sum(note_lengths)
        if note_count == 0:
            average_characters = 0
        else:
            average_characters = total_characters / note_count
        self.response.out.write('<br>Notes retrieved: %d\n' %
                                note_count)
        self.response.out.write('<br>Total characters: %d\n' %
                                total_characters)
        self.response.out.write('<br>Average characters: %d\n' %
                                average_characters)


def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()


dev_appserverで、ゲストブックアプリケーションとは異なるポート番号を指定して、
クライアントアプリケーションを立ち上げてください。
クライアントアプリケーションにアクセスすると、
ゲストブックアプリケーションの投稿に関する統計情報が表示されるはずです。


Sending Parameters

上記では、クライアントアプリケーションは、get_notesを引数無しで呼び出しました。
しかし、サーバーはリクエストとしてGetNotesRequest型を受け取ることを知っていますよね
当然、クライアント側では、GetNotesRequest型のフィールドで指定されたパラメータをサーバーに送信することができます。
例えば、max_notesパラメータを送信することで、サーバーが返すノートの件数の上限を指定できます。

実は、スタブメソッドは内部でGetNotesRequestのインスタンスを生成し、それをサーバーに送っています。
get_notesメソッドのキーワード引数を指定することで、
サーバーに送信するGetNotesRequestインスタンスのフィールドを設定することができます。
以下のように、get_notesの呼び出し部分を変更します。

max_notes = int(self.request.params.get('max_notes', '10'))
notes = service.get_notes(limit=max_notes).notes


また、キーワード引数ではなく、GetNotesRequestのインスタンスそのものを渡すこともできます。

request = postservice.GetNotesRequest(limit=max_notes)
notes = service.get_notes(request).notes

18.Creating a client program

さあ、いよいよ、リモートサービスを「利用する」アプリケーションを作成します。

ゲストブックアプリケーションのサービスを利用するこのアプリケーションを「gueststats」と呼ぶことにします。
このアプリケーションは、最近の投稿の情報を収集し、ユーザーに示します。


まず、新規のApp Engineアプリケーションを生成し、
サーバー(ゲストブックアプリケーション)で行ったのと同様に、
protorpcのライブラリファイルをコピーしてください。
(または、eclipseであれば外部参照に追加)

17.Viewing a method form

PostService.post_noteメソッドをフォーム上で見てみましょう。

先ほどのwebページで、PostService.post_noteのリンクをクリックしてみてください。


こんなページが表示されると思います。

フォームには、Noteクラスに定義したフィールドが表示されているはずです。
whenフィールドは必須ではありませんから、デフォルトでは送信しない、と表示されています。

whenのチェックボックスをチェックすることで、whenフィールドに値を設定することができるのですが、今はそれをせずに、
textフィールドだけ何か入力して、「Send Request」をクリックしてください。

リクエストが成功すれば、レスポンスが表示されます。
curlを用いた場合と同様、レスポンスは空のJSONオブジェクトのはずです。


次のテストに進む前に、ゲストブックにいくつかのノートを追加しておいてください。
それができたら、ページ上部の「Back to method selection」をクリックしてください。

戻ったページで、PostService.get_notesメソッドをクリックします。


こんなページが表示されます。
パラーメータを設定せずに「Send Request」ボタンをクリックすると、
先ほどのPostService.post_noteよりは面白いレスポンスが表示されます。


「Show Form」ボタンをクリックすると、リクエストの送信ページに戻りますので、
orderや、limitといったパラーメータを変更して結果が変わるかどうか試してみてください。

16.The forms interface

webappフレームワークを用いて、ProtoRPCのリモートメソッドをテストする方法があります。

「service_mapping」ファンクションが、実際にリモートサービスを登録して公開するのですが、
その際に、「フォームインターフェース」も自動登録し、使えるようにします。

「フォームインターフェース」とは、登録したリモートサービスと、そのメソッドを閲覧できるwebページで、
さらに、そのページを使って、パラメータを与えてメソッドを呼び出すことができます。

この便利なフォームインターフェースは、app.yamlに、以下のような行を追加するだけで使用できます。
- url: /protorpc.*
   script: services.py

ブラウザで以下のURLにアクセスすることで、フォームインターフェースを使えます。

http://localhost:8080/protorpc/form


こんな感じのページが表示されます。
あれ、サービスが2つありますね。
(RegistryServiceと、PostService)

PostServiceは、以前の章で作成したものですが、RegistryServiceとは何でしょう?

The RegistryService

「service_mapping」ファンクションは、特に指定しなくても、RegistryServiceという組み込みサービスを自動登録します。

RegistryServiceは、ユーザーが登録したサービスに関する情報で構成され、サーバーがどんなサービスを公開しているのかを調べるのに役立ちます。
また、フォームインターフェース自体も、RegistryServiceを使用しています。

他にもいろいろあるのですが、とりあえずここでの説明はこれぐらいにしておきます。

2011年7月1日金曜日

15.Implement get_notes

PostServiceクラスにget_notesメソッドを定義してみましょう。


@remote.method(GetNotesRequest, Notes)
def get_notes(self, request):
    query = guestbook.Greeting.all().order('-date')
    if request.on_or_before:
        when = datetime.datetime.utcfromtimestamp(request.on_or_before)
        query.filter('date <=', when)

    notes = []
    for note_model in query.fetch(request.limit):
    if note_model.date:
        when = int(time.mktime(note_model.date.utctimetuple()))
    else:
        when = None
    note = Note(text=note_model.content, when=when)
    notes.append(note)

    if request.order == GetNotesRequest.Order.TEXT:
        notes.sort(key=lambda note: note.text)

    return Notes(notes=notes)

モジュールの先頭でtimeをインポートするのを忘れずに。
import time

14.More complex messages

では、get_notesのレスポンスメッセージを定義してみましょう。

get_notesのレスポンスは、複数の投稿を返すため、
Noteメッセージのコレクションとなる必要があります。
これは以下のように定義します。

class Notes(messages.Message):
  notes = messages.MessageField(Note, 1, repeated=True)



Message Fields


メッセージは、他のメッセージを含むことができます。
上記のNotes.notesフィールドでNoteを最初のパラメータとしたように、
最初のパラメータとして指定します。



Repeated Fields


上記のNotes.notesフィールドはまた、repeatedキーワードが指定されています。
repeatedフィールドの値は、指定した型のリストでなければなりません。
上記の例では、Notes.notesフィールドの値は、Note型のインスタンスのリストでなければなりません。

例として、Notesインスタンスの作成方法を示してみましょう。

response = Notes(notes=[Note(text='This is note 1'),
                        Note(text='This is note 2')])
print 'First Note is:', response.notes[0].text
print 'Second Note is:', response.notes[1].text