Google Protocol RPC ライブラリの概要

注: 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)

このメモメッセージは、textwhen という 2 つのフィールドで定義します。各フィールドが特定のタイプを持ちます。text フィールドは、ゲストブック ページにユーザーが投稿する内容を表す Unicode 文字列です。when フィールドは、投稿のタイムスタンプを表す整数です。文字列の定義では、次のことも行います:

  • 各フィールドに固有の数値(text には 1when には 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)