Google Protocol RPC-Bibliothek

Hinweis: Wenn Sie die Authentifizierung mit Google Protocol RPC verwenden möchten, können Sie die derzeit für App Engine-Anwendungen verfügbare Authentifizierung in der Google Cloud Console nutzen. Sie müssen außerdem in der Datei app.yaml festlegen, dass die Anmeldung erforderlich ist. Andere Authentifizierungsmethoden werden derzeit von der Google Protocol RPC-Bibliothek in App Engine nicht unterstützt.

Die Google Protocol RPC-Bibliothek ist ein Framework für die Implementierung von HTTP-basierten Diensten für den Remoteprozeduraufruf (Remote Procedure Call, RPC). Ein RPC-Dienst ist eine Sammlung von Nachrichtentypen und Remotemethoden, die eine Struktur für externe Anwendungen zum Interagieren mit Webanwendungen bietet. Nachrichten und Dienste lassen sich mit der Programmiersprache Python definieren. Deshalb können Sie ganz einfach Google Protocol RPC-Dienste entwickeln, testen und in App Engine skalieren.

Sie können die Google Protocol RPC-Bibliothek zwar für alle Arten von HTTP-basierten RPC-Diensten verwenden, einige der häufigsten Anwendungsfälle sind jedoch:

  • Web-APIs für die Nutzung durch Dritte veröffentlichen
  • Strukturierte Ajax-Backends erstellen
  • Auf Serverkommunikation für die langfristige Nutzung klonen

Sie können einen Google Protocol RPC-Dienst in einer einzelnen Python-Klasse definieren, die eine beliebige Anzahl deklarierter Remotemethoden enthält. Jede Remotemethode akzeptiert bestimmte Parameter als Anfrage und gibt eine bestimmte Antwort zurück. Diese Anfrage- und Antwortparameter sind benutzerdefinierte Klassen, die als Nachrichten bezeichnet werden.

Hello World von Google Protocol RPC

In diesem Abschnitt ist ein Beispiel einer sehr einfachen Dienstdefinition dargestellt, die eine Nachricht von einem Remote-Client empfängt. Die Nachricht enthält den Namen eines Nutzers (HelloRequest.my_name) und sendet eine Begrüßung für diese Person zurück (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)])

Erste Schritte mit Google Protocol RPC

In diesem Abschnitt erfahren Sie mehr über die ersten Schritte mit Google Protocol RPC bei Verwendung der in Python entwickelten Gästebuchanwendung. Nutzer können das Gästebuch (auch als Demoversion im Python-SDK enthalten) online aufrufen, Einträge schreiben und die Einträge aller Nutzer ansehen. Nutzer interagieren direkt mit der Schnittstelle, aber Webanwendungen können auf diese Informationen nicht einfach zugreifen.

Hier kommt Protocol RPC ins Spiel. In dieser Anleitung wird Google Protocol RPC auf dieses grundlegende Gästebuch angewendet, sodass andere Webanwendungen auf die Daten des Gästebuchs zugreifen können. Diese Anleitung behandelt nur, wie die Gästebuchfunktion mit Google Protocol RPC erweitert werden kann. Die nächsten Schritte liegen bei Ihnen. Beispielsweise möchten Sie vielleicht ein Tool schreiben, das die von Nutzern veröffentlichten Nachrichten liest und ein Zeitreihendiagramm der Beiträge pro Tag erstellt. Die Nutzung von Protocol RPC hängt von der jeweiligen Anwendung ab. Am wichtigsten ist, dass Google Protocol RPC die Einsatzmöglichkeiten der Daten Ihrer Anwendung erheblich erweitert.

Zu Beginn erstellen Sie eine Datei postservice.py, mit der Remotemethoden für den Zugriff auf Daten im Datenspeicher der Gästebuchanwendung implementiert werden.

PostService-Modul erstellen

Zur Einführung in Google Protocol RPC erstellen Sie im ersten Schritt eine Datei mit dem Namen postservice.py in Ihrem Anwendungsverzeichnis. Mit dieser Datei wird der neue Dienst definiert. Dieser implementiert zwei Methoden: eine für das Remote-Senden von Daten und eine andere für das Remote-Empfangen.

Sie müssen dieser Datei jetzt nichts hinzufügen. Sie geben in diese Datei aber den gesamten Code ein, der in den folgenden Abschnitten definiert ist. Im nächsten Abschnitt erstellen Sie eine Nachricht, die eine im Datenspeicher der Gästebuchanwendung veröffentlichte Notiz darstellt.

Mit Nachrichten arbeiten

Nachrichten sind der grundlegende Datentyp, der in Google Protocol RPC verwendet wird. Sie werden durch das Deklarieren einer Klasse definiert. Diese wird von der Basisklasse Message übernommen. Dann geben Sie Klassenattribute an, die den einzelnen Feldern der Nachricht entsprechen.

Mit dem Gästebuchdienst können Nutzer beispielsweise Notizen veröffentlichen. Erstellen Sie eine Datei mit dem Namen postservice.py in Ihrem Anwendungsverzeichnis, falls noch nicht geschehen. Die Greeting-Klasse wird von PostService zum Speichern eines Beitrags im Datenspeicher verwendet. Lassen Sie uns eine Nachricht definieren, die einer solchen Notiz entspricht:

from protorpc import messages

class Note(messages.Message):

    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

Die Notiz wird durch zwei Felder definiert: text und when. Jedes Feld entspricht einem bestimmten Typ. Beim Textfeld handelt es sich um einen Unicode-String. Dieser gibt den Inhalt eines Nutzerbeitrags auf der Gästebuchseite wieder. Das Feld when ist eine ganze Zahl, die den Zeitstempel des Beitrags darstellt. Durch das Definieren des Strings führen wir auch folgende Aktionen aus:

  • Weisen Sie jedem Feld einen eindeutigen numerischen Wert zu (1 für text und 2 für when). Dieser dient dem zugrunde liegenden Netzwerkprotokoll zur Identifikation des Feldes.
  • Legen Sie text als Pflichtfeld fest. Felder sind standardmäßig optional. Sie können sie aber durch Angabe von required=True als Pflichtfelder markieren. Nachrichten müssen initialisiert werden, indem Sie für erforderliche Felder einen Wert festlegen. Google Protocol RPC-Dienstmethoden akzeptieren nur ordnungsgemäß initialisierte Nachrichten.

Sie können mithilfe des Konstruktors der Note-Klasse Werte für die Felder festlegen:

# Import the standard time Python library to handle the timestamp.
import time

note_instance = Note(text=u'Hello guestbook!', when=int(time.time()))

Sie können Werte für eine Nachricht wie normale Python-Attributwerte lesen und festlegen, zum Beispiel, um die Nachricht zu ändern:

print note_instance.text
note_instance.text = u'Good-bye guestbook!'
print note_instance.text
Dadurch wird Folgendes ausgegeben:
Hello guestbook!
Good-bye guestbook!

Einen Dienst definieren

Ein Dienst ist eine Klassendefinition, die ihre Attribute aus der Basisklasse Service übernimmt. Remotemethoden eines Dienstes werden mithilfe des remote-Decorators angegeben. Jede Methode eines Dienstes akzeptiert eine einzelne Nachricht als ihren Parameter und gibt eine einzelne Nachricht als Antwort aus.

Lassen Sie uns die erste Methode von PostService definieren. Fügen Sie der Datei postservice.py Folgendes hinzu:

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()

Der remote-Decorator hat zwei Parameter:

  • Den erwarteten Anfragetyp: Die Methode "post_note()" akzeptiert eine Note-Instanz als ihren Anfragetyp.
  • Den erwarteten Antworttyp. Die Google Protocol RPC-Bibliothek enthält einen integrierten Typ mit dem Namen VoidMessage (im Modul protorpc.message_types), der als Nachricht ohne Felder definiert ist. Dies bedeutet, dass von der post_note()-Nachricht keine hilfreichen Informationen an den Aufrufenden zurückgegeben werden. Falls kein Fehler zurückgegeben wird, wird die Nachricht als veröffentlicht betrachtet.

Da Note.when ein optionales Feld ist, wurde es möglicherweise nicht vom Aufrufer angegeben. Ist dies der Fall, wird der Wert von when auf "None" festgelegt. Wenn Note.when auf "None" gesetzt ist, erstellt post_note() den Zeitstempel anhand der Uhrzeit, zu der die Nachricht empfangen wurde.

Die Antwort wird von der Remotemethode instanziiert und wird zum Rückgabewert der Remotemethode.

Dienst registrieren

Sie können Ihren neuen Dienst mit der protorpc.wsgi.service-Bibliothek als WSGI-Anwendung veröffentlichen. Erstellen Sie eine neue Datei mit dem Namen services.py in Ihrem Anwendungsverzeichnis und fügen Sie zum Erstellen Ihres Dienstes den folgenden Code hinzu:

from protorpc.wsgi import service

import postservice

# Map the RPC service and path (/PostService)
app = service.service_mappings([('/PostService', postservice.PostService)])

Fügen Sie Ihrer app.yaml-Datei nun den folgenden Handler über dem vorhandenen Eintrag "catch-all" hinzu:

- url: /PostService.*
  script: services.app
- url: .*
  script: guestbook.app

Dienst über die Befehlszeile testen

Sie haben den Dienst nun erstellt und können ihn mit curl oder mit einem vergleichbaren Befehlszeilentool testen.

# 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

Mit einer leeren JSON-Antwort wird angegeben, dass die Notiz veröffentlicht wurde. Sie können die Notiz ansehen, indem Sie in Ihrem Browser zur Gästebuchanwendung wechseln (http://localhost:8080/).

Nachrichtenfelder hinzufügen

Da wir nun in PostService Nachrichten veröffentlichen können, fügen wir eine neue Methode hinzu, um Nachrichten aus PostService abzurufen. Zuerst definieren wir in postservice.py eine Anfragenachricht, in der einige Standardeinstellungen sowie ein neues Enum-Feld festgelegt werden. Dieses Feld legt für den Server fest, wie in der Antwort Notizen angeordnet werden. Definieren Sie die Nachricht oberhalb der zuvor definierten PostService-Klasse:

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)

Beim Senden an PostService fordert diese Nachricht einige Notizen von oder vor einem bestimmten Datum und in einer bestimmten Reihenfolge an. Das Feld limit gibt die maximale Anzahl der Notizen an, die abgerufen werden sollen. Wenn er nicht explizit festgelegt ist, liegt der Standardwert für limit bei 10 Notizen und wird durch das default=10-Schlüsselwortargument angegeben.

Das Feld für die Sortierung führt die Klasse EnumField ein. Diese aktiviert den enum-Feldtyp, wenn der Wert eines Feldes auf eine begrenzte Anzahl bekannter symbolischer Werte beschränkt ist. In diesem Fall wird mit dem Feld enum für den Server angegeben, wie die Notizen in der Antwort angeordnet werden sollen. Für die Definition der Enum-Werte erstellen Sie eine abgeleitete Klasse der Enum-Klasse. Jedem Namen muss eine eindeutige Zahl für den Typ zugewiesen werden. Jede Zahl wird in eine Instanz des enum-Typs umgewandelt und auf sie kann über die Klasse zugegriffen werden.

print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN.name,
                                             GetNotesRequest.Order.WHEN.number)

Jeder enum-Wert hat ein bestimmtes Merkmal. Dies vereinfacht das Umwandeln in den Namen oder in die Zahl. Anstatt auf das Namens- und Zahlattribut zuzugreifen, wandeln Sie jeden Wert einfach in einen String oder eine Ganzzahl um:

print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN,
                                             GetNotesRequest.Order.WHEN)

Enum-Felder werden ähnlich wie andere Felder deklariert. Als erster Parameter vor der Feldzahl muss jedoch der enum-Typ verwendet werden. Enum-Felder können auch Standardwerte haben.

Die Antwort definieren

Lassen Sie uns nun die get_notes()-Antwort definieren. Bei der Antwort muss es sich um eine Sammlung von Notiznachrichten handeln. Nachrichten können andere Nachrichten enthalten. Für das unten definierte Feld Notes.notes geben wir an, dass es sich um eine Sammlung von Nachrichten handelt. Dazu wird die Klasse Note als erster Parameter für den Konstruktor messages.MessageField vor der Feldnummer festgelegt:

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

Das Feld Notes.notes ist außerdem ein wiederholtes Feld, wie durch das Schlüsselwortargument repeated=True angegeben. Werte von wiederholten Feldern müssen Listen des Feldtyps ihrer Deklaration sein. In diesem Fall muss Notes.notes eine Liste von Notizinstanzen sein. Listen werden automatisch erstellt und ihnen kann nicht "None" zugewiesen werden.

So erstellen Sie beispielsweise ein Notizobjekt:

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 implementieren

Jetzt können wir der Klasse "PostService" die Methode "get_notes()" hinzufügen:

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)