Présentation de la bibliothèque RPC Google Protocol

Remarque : Si vous souhaitez utiliser l'authentification avec Google Protocol RPC, vous pouvez utiliser l'authentification actuellement disponible dans la console Google Cloud pour les applications App Engine. Vous devez également spécifier les conditions de connexion requises dans votre fichier app.yaml. Les autres méthodes d'authentification ne sont actuellement pas prises en charge par la bibliothèque RPC Google Protocol dans App Engine.

La bibliothèque Google Protocol RPC est un framework permettant la mise en œuvre de services dont les procédures peuvent être appelées à distance (RPC – Remote Procedure Call) via HTTP. Un service RPC est une collection de types de messages et de méthodes distantes qui fournit une méthode structurée aux applications externes pour interagir avec les applications Web. Comme vous pouvez définir des messages et des services dans le langage de programmation Python, il est facile de développer des services Protocol RPC, de tester ces services et de les faire évoluer sur App Engine.

Bien que vous puissiez utiliser la bibliothèque RPC du protocole Google pour tout type de service RPC basé sur HTTP, voici quelques cas d'utilisation courants :

  • Publication d'API Web utilisées par des tiers
  • Création de moteurs Ajax structurés
  • Clonage de la communication longue durée des serveurs

Vous pouvez définir un service RPC Google Protocol dans une seule classe Python contenant un nombre quelconque de méthodes distantes déclarées. Chaque méthode distante accepte un ensemble particulier de paramètres en tant que demande et renvoie une réponse particulière. Ces paramètres de demande et réponse sont des classes définies par l'utilisateur appelées messages.

Le Hello World du protocole RPC de Google

Cette section propose un exemple très simple de définition de service recevant un message d'un client distant. Le message comporte le nom d'un utilisateur (HelloRequest.my_name) et renvoie un message d'accueil à cette personne (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)])

Premiers pas avec Google Protocol RPC

Cette section explique comment démarrer avec Google Protocol RPC à l'aide de l'application livre d'or développée dans Python. Les utilisateurs peuvent consulter le livre d'or (également inclus sous forme de démo dans le SDK Python) en ligne, saisir du texte et afficher les commentaires des autres utilisateurs. Les utilisateurs interagissent directement avec l'interface, mais les applications Web n'ont aucun moyen d'accéder facilement à ces informations.

C'est là qu'intervient le protocole RPC. Dans ce didacticiel, nous allons appliquer le protocole Google Protocol RPC à ce livre d'or de base, ce qui permettra à d'autres applications Web d'accéder aux données de ce dernier. Ce didacticiel couvre uniquement l'utilisation de Google Protocol RPC pour étendre les fonctionnalités du livre d'or ; c'est à vous de voir que faire ensuite. Par exemple, vous pouvez créer un outil qui lit les messages publiés par les utilisateurs et génère un graphique chronologique des posts par jour. Votre utilisation du protocole RPC dépend de votre application. L'important est que le protocole RPC de Google élargisse considérablement les possibilités d'utilisation des données de votre application.

Pour commencer, vous allez créer un fichier, postservice.py, qui met en œuvre des méthodes distantes d'accès aux données du datastore de l'application de livre d'or.

Créer le module PostService

La première étape pour faire vos premiers pas avec Google Protocol RPC consiste à créer un fichier nommé postservice.py dans le répertoire de votre application. Vous utiliserez ce fichier pour définir le nouveau service, qui implémentera deux méthodes : une qui permet de publier des données à distance et une autre qui obtient des données à distance.

Vous n'avez pas besoin d'ajouter quoi que ce soit à ce fichier maintenant, mais c'est le fichier dans lequel vous allez mettre tout le code défini dans les sections suivantes. Dans la section suivante, vous allez créer un message représentant un commentaire posté dans le magasin de données de l'application livre d'or.

Utiliser les messages

Les messages sont le type de données fondamental utilisé dans Google Protocol RPC. Ils sont définis en déclarant une classe qui hérite de la classe de base Message. Vous spécifiez ensuite des attributs de classe qui correspondent à chacun des champs du message.

Par exemple, le service de livre d'or permet aux utilisateurs de poster un commentaire. Si vous ne l'avez pas déjà fait, créez un fichier nommé postservice.py dans le répertoire de votre application. PostService utilise la classe Greeting pour stocker un post dans le datastore. Définissons un message qui représente ce commentaire :

from protorpc import messages

class Note(messages.Message):

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

Le message de commentaire est défini par deux champs, text et when. Chaque champ comporte un type particulier. Le champ de texte est une chaîne unicode représentant le contenu du post d'un utilisateur dans la page du livre d'or. Le champ when est un entier représentant l'horodatage du post. Lors de la définition de la chaîne, nous effectuons également les opérations suivantes :

  • Attribuer une valeur numérique unique (1 pour text et 2 pour when) à chaque champ. Cette valeur est utilisée par le protocole réseau sous-jacent pour identifier le champ.
  • Rendre le champ text obligatoire. Par défaut, les champs sont facultatifs, mais vous pouvez les rendre obligatoires en définissant le paramètre required=True. Pour initialiser les messages, vous devez attribuer une valeur aux champs obligatoires. Les méthodes de service RPC Google Protocol n'acceptent que les messages correctement initialisés.

Vous pouvez définir les valeurs de ces champs à l'aide du constructeur de la classe Note :

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

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

Vous pouvez également lire et définir les valeurs sur un message, telles que les valeurs d'attribut normales Python. Par exemple, pour modifier le message suivant :

print note_instance.text
note_instance.text = u'Good-bye guestbook!'
print note_instance.text
qui produit ce qui suit
Hello guestbook!
Good-bye guestbook!

Définir un service

Un service est une définition de classe qui hérite de la classe de base Service. Les méthodes distantes d'un service sont indiquées à l'aide du décorateur remote. Chaque méthode de service accepte un seul message en tant que paramètre et renvoie un seul message en tant que réponse.

Définissons la première méthode du service PostService. Ajoutez le code ci-dessous à votre fichier 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()

Le décorateur remote prend deux paramètres :

  • Type de demande attendu. La méthode post_note() accepte une instance Note en tant que type de demande.
  • Type de réponse attendu. La bibliothèque Google Protocol RPC est fournie avec un type intégré appelé VoidMessage (dans le module protorpc.message_types), défini comme un message sans champ. Cela signifie que le message post_note() ne renvoie aucun élément utile à l'appelant. S'il est renvoyé sans erreur, le message est considéré comme étant posté.

Étant donné que Note.when est un champ facultatif, il n'a peut-être pas été défini par l'appelant. Dans ce cas, la valeur de when est définie sur "None" (Aucun). Lorsque Note.when est défini sur "None" (Aucun), post_note() crée l'horodatage en prenant l'heure de réception du message.

Le message de réponse est instancié par la méthode distante et devient la valeur de renvoi de cette dernière.

Enregistrer le service

Vous pouvez publier votre nouveau service en tant qu'application WSGI à l'aide de la bibliothèque protorpc.wsgi.service. Créez un fichier nommé services.py dans le répertoire de votre application et ajoutez le code suivant pour créer votre service :

from protorpc.wsgi import service

import postservice

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

Maintenant, ajoutez le gestionnaire suivant à votre fichier app.yaml au-dessus de l'entrée fourre-tout existante :

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

Tester le service à partir de la ligne de commande

Maintenant que vous avez créé le service, vous pouvez le tester à l'aide de curl ou d'un outil de ligne de commande similaire.

# 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

Une réponse JSON vide indique que le commentaire a été posté. Pour le lire, accédez à votre application de livre d'or dans le navigateur (http://localhost:8080/).

Ajout de champs de message

Maintenant qu'il est possible de poster des messages dans le service PostService, ajoutons une nouvelle méthode pour les en extraire. Dans un premier temps, nous allons définir un message de requête dans postservice.py qui définit certaines valeurs par défaut, et un nouveau champ enum indiquant au serveur comment classer les commentaires dans la réponse. Définissez-le au-dessus de la classe PostService que vous avez définie précédemment :

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)

Une fois envoyé à PostService, ce message demande un certain nombre de commentaires postés à une date précise ou avant, et dans un ordre particulier. Le champ limit indique le nombre maximal de commentaires à extraire. Si ce paramètre n'est pas défini explicitement, le champ limit prend la valeur 10 par défaut (comme indiqué par l'argument de mot clé default=10).

Le champ "order" (ordre) introduit la classe EnumField, qui active le type de champ enum lorsque la valeur d'un champ est limitée à un nombre restreint de valeurs symboliques connues. Dans ce cas, enum indique au serveur comment classer les commentaires dans la réponse. Pour définir les valeurs enum, créez une sous-classe de la classe Enum. À chaque nom doit correspondre un numéro unique pour le type. Chaque numéro est converti en instance du type enum et est accessible à partir de la classe.

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

Chaque valeur enum possède une caractéristique spéciale qui facilite la conversion en leur nom ou leur numéro. Au lieu d'accéder à l'attribut de nom et de numéro, convertissez simplement chaque valeur en chaîne ou en entier :

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

Les champs Enum sont déclarés de la même manière que les autres champs, à la différence qu'ils doivent comporter le type enum comme premier paramètre avant le numéro de champ. Les champs Enum peuvent également comporter des valeurs par défaut.

Définir le message de réponse

Définissons maintenant le message de réponse get_notes(). La réponse doit être une collection de messages Note. Les messages peuvent contenir d'autres messages. Dans le cas du champ Notes.notes défini ci-dessous, nous indiquons qu'il s'agit d'un ensemble de messages en fournissant la classe Note comme premier paramètre du constructeur messages.MessageField (avant le numéro du champ) :

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

Le champ Notes.notes est également un champ répété comme indiqué par l'argument de mot clé repeated=True. Les valeurs des champs répétés doivent être des listes du type de champ de leur déclaration. Dans ce cas, Notes.notes doit être une liste d'instances Note. Les listes sont créées automatiquement et ne peuvent pas avoir la valeur None.

Par exemple, voici comment créer un objet 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

Implémenter la méthode get_notes

Nous pouvons maintenant ajouter la méthode get_notes() à la classe 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)