注: Google Protocol RPC で認証を使用する場合、Google Cloud コンソールで App Engine アプリに対して現在利用できる認証を使用できます。また、app.yaml
ファイルで使用するログイン要件を指定する必要もあります。App Engine 内の Google Protocol RPC ライブラリでは現在、その他の認証方法がサポートされていません。
Google Protocol RPC ライブラリは、HTTP ベースのリモート プロシージャ コール(RPC)を実装するためのフレームワークです。RPC サービスはメッセージ タイプとリモート メソッドのコレクションで、外部アプリケーションとウェブ アプリケーションが対話するための構造化された方法を提供します。Python プログラミング言語でメッセージやサービスを定義できるため、Protocol RPC サービスの開発、それらのサービスのテスト、App Engine でのスケーリングが容易です。
どのような種類の HTTP ベースの RPC サービスでも Google Protocol RPC ライブラリを使用できますが、一般的な使用例は次のとおりです。
- サードパーティによって使用されるウェブ API を公開する
- 構造化された Ajax バックエンドを作成する
- 実行時間の長いサーバー通信にクローンを作成する
Google Protocol RPC サービスは、任意の数の宣言済みリモート メソッドを含む 1 つの Python クラスで定義できます。各リモート メソッドに特定のパラメータ セットをリクエストとして指定すると、特定のレスポンスが返されます。このようなリクエストとレスポンスのパラメータは、メッセージと呼ばれるユーザー定義のクラスです。
Google Protocol RPC の Hello World
このセクションでは、ごく簡単なサービス定義の例を示します。このサービスはリモート クライアントからメッセージを受け取ります。このメッセージにはユーザーの名前(HelloRequest.my_name
)が含まれており、そのユーザー宛てのメッセージ(HelloResponse.hello
)を送り返します。
from protorpc import messages from protorpc import remote from protorpc.wsgi import service package = 'hello' # Create the request string containing the user's name class HelloRequest(messages.Message): my_name = messages.StringField(1, required=True) # Create the response string class HelloResponse(messages.Message): hello = messages.StringField(1, required=True) # Create the RPC service to exchange messages class HelloService(remote.Service): @remote.method(HelloRequest, HelloResponse) def hello(self, request): return HelloResponse(hello='Hello there, %s!' % request.my_name) # Map the RPC service and path (/hello) app = service.service_mappings([('/hello.*', HelloService)])
Google Protocol RPC を使ってみる
このセクションでは、Python で開発されたゲストブック アプリケーションを使用して、Google Protocol RPC を使用する方法について説明します。ユーザーはゲストブック(Python SDK にもデモとして含まれています)をオンラインで表示し、エントリを書き込み、あらゆるユーザーの書き込んだエントリを見ることができます。ユーザーはインターフェースを直接操作しますが、ウェブ アプリケーションでその情報に簡単にアクセスすることはできません。
これこそが Protocol RPC に必要とされていることです。このチュートリアルでは、この基本的な機能を持つゲストブックに Google Protocol RPC を適用し、他のウェブ アプリケーションからゲストブックのデータにアクセスできるようにします。このチュートリアルでは、Google Protocol RPC を使用してゲストブック機能を拡張する方法についてのみ説明します。次に何をするかはあなた次第です。たとえば、ユーザーにより投稿されたメッセージを読み取って 1 日あたりの投稿数を時系列で示すグラフを作るために、ツールを作成します。Protocol RPC をどのように使用するかは、アプリケーションによって異なります。重要な点は、Google Protocol RPC がアプリケーションのデータでできることを大幅に拡張することです。
最初に、postservice.py
というファイルを作成します。このファイルで、ゲストブック アプリケーションのデータストアにあるデータにアクセスするためのリモート メソッドを実装します。
PostService モジュールの作成
Google Protocol RPC を開始するための最初の手順は、アプリケーション ディレクトリに postservice.py
というファイルを作成することです。このファイルを使って新しいサービスを定義し、2 つのメソッドを実装します。1 つはデータをリモートで投稿するメソッド、もう 1 つはデータをリモートで取得するメソッドです。
現時点では、このファイルに何も追加する必要はありませんが、これ以降のセクションで定義するコードはすべて、このファイルに追加することになります。次のセクションでは、ゲストブック アプリケーションのデータストアに投稿されたメモを表すメッセージを作成します。
メッセージの処理
メッセージは、Google Protocol RPC で使用する基本的なデータタイプです。メッセージを定義するには、Message 基本クラスから継承するクラスを宣言します。その後、メッセージの各フィールドに対応するクラス属性を指定します。
たとえば、ゲストブック サービスではユーザーがメモを投稿できます。アプリケーション ディレクトリに postservice.py
というファイルを作成します(まだ作成していない場合)。PostService では、データストアへの投稿の保存に Greeting クラスが使用されます。このようなメモを表すメッセージを定義してみましょう。
from protorpc import messages class Note(messages.Message): text = messages.StringField(1, required=True) when = messages.IntegerField(2)
このメモメッセージは、text
と when
という 2 つのフィールドで定義します。各フィールドが特定のタイプを持ちます。text フィールドは、ゲストブック ページにユーザーが投稿する内容を表す Unicode 文字列です。when
フィールドは、投稿のタイムスタンプを表す整数です。文字列の定義では、次のことも行います:
- 各フィールドに固有の数値(
text
には1
、when
には2
)を割り当てます。この数値は、基礎となるネットワーク プロトコルでフィールドを識別するために使用されます。 text
を必須フィールドにします。フィールドはデフォルトでは省略可能ですが、required=True
を設定すると、必須にすることができます。メッセージは、必須フィールドに値を設定することで初期化する必要があります。Google Protocol RPC サービス メソッドは、正しく初期化されたメッセージのみを受け入れます。
フィールドの値は、Note クラスのコンストラクタを使用して設定できます:
# Import the standard time Python library to handle the timestamp. import time note_instance = Note(text=u'Hello guestbook!', when=int(time.time()))
メッセージでは、標準の Python 属性値と同じように、値の読み取りや設定も可能です。たとえば、メッセージを次のように変更します:
print note_instance.text note_instance.text = u'Good-bye guestbook!' print note_instance.text
Hello guestbook! Good-bye guestbook!
サービスの定義
「サービス」は、Service 基本クラスから継承されるクラス定義です。サービスのリモート メソッドは、remote
デコレータを使用して指示します。サービスのメソッドごとに、パラメータとして 1 つのメッセージを指定すると、応答として 1 つのメッセージが返されます。
PostService の 1 つ目のメソッドを定義しましょう。次のコードを postservice.py
ファイルに追加します。
import datetime from protorpc import message_types from protorpc import remote import guestbook class PostService(remote.Service): # Add the remote decorator to indicate the service methods @remote.method(Note, message_types.VoidMessage) def post_note(self, request): # If the Note instance has a timestamp, use that timestamp if request.when is not None: when = datetime.datetime.utcfromtimestamp(request.when) # Else use the current time else: when = datetime.datetime.now() note = guestbook.Greeting(content=request.text, date=when, parent=guestbook.guestbook_key) note.put() return message_types.VoidMessage()
remote
デコレータは 2 つのパラメータを取ります。
- 想定されているリクエスト タイプ。post_note() メソッドには、リクエスト タイプとして Note インスタンスを指定します。
- 想定されているレスポンス タイプ。Google Protocol RPC には、VoidMessage という組み込みの型が(
protorpc.message_types
モジュール内に)用意されており、フィールドのないメッセージとして定義されています。つまり、post_note() メッセージからこの呼び出し元に有用な情報は返されません。エラーなく戻った場合、このメッセージは投稿されたと見なされます。
Note.when
はオプションのフィールドであるため、呼び出し元によって設定されていない場合もあります。その場合、when
の値は None に設定されます。Note.when
が None に設定されていると、post_note() は、メッセージを受け取った時刻を使用してタイムスタンプを作成します。
応答メッセージは、リモート メソッドによってインスタンス化され、リモート メソッドの戻り値になります。
サービスの登録
新しいサービスは、protorpc.wsgi.service
ライブラリを使用して WSGI アプリケーションとして公開できます。services.py
という新しいファイルをアプリケーション ディレクトリに作成し、次のコードを追加してサービスを作成します:
from protorpc.wsgi import service import postservice # Map the RPC service and path (/PostService) app = service.service_mappings([('/PostService', postservice.PostService)])
次に、既存の catch-all エントリの上の app.yaml
ファイルに次のハンドラを追加します。
- url: /PostService.* script: services.app - url: .* script: guestbook.app
コマンドラインからのサービスのテスト
これでサービスの作成が完了したので、curl
または同様のコマンドライン ツールを使用してサービスをテストできます。
# After starting the development web server: # NOTE: ProtoRPC always expect a POST. % curl -H \ 'content-type:application/json' \ -d '{"text": "Hello guestbook!"}'\ http://localhost:8080/PostService.post_note
空の JSON 応答は、メモが正常に投稿されたことを示します。ブラウザでゲストブック アプリケーション(http://localhost:8080/)を開くと、メモを表示できます。
メッセージ フィールドの追加
PostService にメッセージを投稿できるようになったので、PostService からメッセージを取得する新しいメソッドを追加してみましょう。まず、postservice.py
でリクエスト メッセージを定義します。このメッセージでは、応答内でメモを順序付ける方法をサーバーに指示する新しい enum フィールドと、いくつかのデフォルト値を定義します。リクエスト メッセージは、定義済みの PostService
クラスより上で定義します。
class GetNotesRequest(messages.Message): limit = messages.IntegerField(1, default=10) on_or_before = messages.IntegerField(2) class Order(messages.Enum): WHEN = 1 TEXT = 2 order = messages.EnumField(Order, 3, default=Order.WHEN)
このメッセージは、PostService に送信されると、特定の日付またはそれより前に投稿された複数のメモを特定の順序でリクエストします。limit
フィールドは、取得するメモの最大数を示します。明示的に設定されていない場合、limit
は(default=10
キーワード引数で指定されるとおり)デフォルトでメモ数 10 になります。
order フィールドでは EnumField クラスが使用されており、フィールドの値が、限られた数の既知のシンボリック値に制限されている場合に、enum
フィールド タイプが有効になります。この場合、enum
は応答内でメモを順序付ける方法をサーバーに指示します。enum 値を定義するには、Enum クラスのサブクラスを作成します。名前にはそれぞれ、タイプについて固有の番号を割り当てる必要があります。各番号は enum タイプのインスタンスに変換され、クラスからアクセスできるようになります。
print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN.name, GetNotesRequest.Order.WHEN.number)
各 enum
値には、名前や番号への変換を簡単に行える特性があります。名前や番号の属性にアクセスするのではなく、それぞれの値を文字列または整数に変換します。
print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN, GetNotesRequest.Order.WHEN)
enum フィールドは、他のフィールドと同様に宣言します。ただし、フィールド番号の前に第 1 パラメータとして enum タイプを指定する必要があります。enum フィールドにはデフォルト値を設定することもできます。
応答メッセージの定義
次に、get_notes() 応答メッセージを定義しましょう。応答は、Note メッセージのコレクションである必要があります。メッセージには他のメッセージを含めることができます。以下で定義する Notes.notes
フィールドの場合、messages.MessageField
コンストラクタ(フィールド番号の前)にメッセージの最初のパラメータとして Note
クラスを指定することによって、それがメッセージの集合であることを示します。
class Notes(messages.Message): notes = messages.MessageField(Note, 1, repeated=True)
また、Notes.notes
フィールドは、repeated=True
キーワード引数で指定されているとおり、反復フィールドです。反復フィールドの値は、宣言のフィールド タイプのリストとする必要があります。この場合、Notes.notes
は Note インスタンスのリストでなければなりません。リストは自動的に作成され、None に割り当てることはできません。
たとえば、Notes オブジェクトを作成する方法は次のとおりです:
response = Notes(notes=[Note(text='This is note 1'), Note(text='This is note 2')]) print 'The first note is:', response.notes[0].text print 'The second note is:', response.notes[1].text
get_notes の実装
次に、get_notes() メソッドを PostService クラスに追加できます。
import datetime import time from protorpc import remote class PostService(remote.Service): @remote.method(GetNotesRequest, Notes) def get_notes(self, request): query = guestbook.Greeting.query().order(-guestbook.Greeting.date) if request.on_or_before: when = datetime.datetime.utcfromtimestamp( request.on_or_before) query = query.filter(guestbook.Greeting.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)