Descripción general de biblioteca de la RPC del protocolo de Google

Nota: Si deseas usar la autenticación con la RPC del protocolo de Google, puedes usar la autenticación que está actualmente disponible para las aplicaciones de App Engine en Google Cloud Console. También deberás especificar el requisito de acceso de usuario en el archivo app.yaml. La biblioteca de la RPC de protocolo de Google no es compatible con otras metodologías de autenticación dentro de App Engine.

La biblioteca de la RPC de protocolo de Google es un marco de trabajo para implementar servicios de llamada de procedimiento remoto (RPC) basados en HTTP. Un servicio de RPC es un grupo de tipos de mensajes y métodos remotos que proporcionan una forma estructurada para que las aplicaciones externas interactúen con las aplicaciones web. Dado que se pueden definir mensajes y servicios en el lenguaje de programación Python, es fácil desarrollar servicios de RPC de protocolo, probarlos y escalarlos en App Engine.

Si bien se puede usar la biblioteca de la RPC de protocolo de Google para cualquier tipo de servicio de RPC basado en HTTP, se incluyen algunos casos prácticos como estos:

  • Publicar API web para el uso de terceros
  • Crear backends de Ajax estructurados
  • Clonar para lograr la comunicación entre servidores durante largo tiempo

Puedes definir un servicio de RPC de protocolo de Google en una clase de Python que contenga cualquier cantidad de métodos remotos declarados. Cada método remoto acepta un conjunto específico de parámetros como una solicitud y muestra una respuesta específica. Estos parámetros de solicitud y respuesta son clases definidas por el usuario que se conocen como mensajes.

El “Hello World” de la RPC de protocolo de Google

En esta sección, se presenta un ejemplo de una definición de servicio muy simple que recibe un mensaje de un cliente remoto. El mensaje contiene el nombre de un usuario (HelloRequest.my_name) y devuelve un saludo para esa persona (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)])

Introducción al RPC de protocolo de Google

En esta sección, se muestra una introducción al RPC de protocolo de Google mediante la aplicación de libro de visitas desarrollada en la Guía de introducción de App Engine (Python). Los usuarios pueden visitar el libro (también incluye una demostración en el SDK de Python) en línea, escribir entradas y ver las entradas de todos los usuarios. Los usuarios interactúan con la interfaz de forma directa, pero no hay forma de que las aplicaciones web puedan acceder a esa información.

Ahí es donde la RPC de protocolo cobra importancia. En este instructivo, aplicaremos la RPC de protocolo de Google a este libro de visitas básico, para que otras aplicaciones web puedan acceder a los datos de este. En este instructivo, solo se abarca el uso de la RPC de protocolo de Google para extender la funcionalidad del libro de visitas. Lo que hagas después depende de ti. Por ejemplo, puede que desees escribir una herramienta que lea los mensajes publicados por los usuarios y cree un gráfico de serie temporal de publicaciones por día. La forma en que uses la RPC de protocolo depende de tu aplicación específica; lo importante es que la RPC de protocolo de Google expande en gran medida lo que puedes hacer con los datos de tu aplicación.

Primero, debes crear un archivo, postservice.py, que implemente métodos remotos para acceder a los datos en el almacén de datos de la aplicación de libro de visitas.

Cómo crear el módulo de PostService

El primer paso para comenzar a usar el RPC de protocolo de Google es crear un archivo llamado postservice.py en el directorio de tu aplicación. Este archivo se usará para definir el servicio nuevo, el cual implementa dos métodos: uno que publica datos de manera remota y otro que obtiene datos de forma remota.

No necesitas agregar nada a este archivo ahora, pero este es el archivo donde pondrás todo el código definido en las secciones subsiguientes. En la próxima sección, crearás un mensaje que represente una nota publicada en el almacén de datos de la aplicación de libro de visitas.

Cómo trabajar con mensajes

Los mensajes son el tipo de datos fundamental que se usa en la RPC del protocolo de Google. Los mensajes se definen mediante la declaración de una clase que se hereda de la clase base Message. Luego, se especifican los atributos de clase que corresponden con cada uno de los campos del mensaje.

Por ejemplo, el servicio de libro de visitas les permite a los usuarios publicar notas. Si aún no lo hiciste, crea un archivo llamado postservice.py en el directorio de tu aplicación y revisa el instructivo del libro de visitas, si lo necesitas. En ese instructivo, los saludos del libro de visitas se colocan en el almacén de datos con la clase guestbook.Greeting. El PostService también usará la clase Greeting para almacenar una publicación en el almacén de datos. Define un mensaje que represente una nota:

from protorpc import messages

class Note(messages.Message):

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

El mensaje de la nota se define mediante dos campos, text y when. Cada campo tiene un tipo específico. El campo text es una string Unicode que representa el contenido de la publicación de un usuario en la página del libro de visitas. El campo when es un número entero que representa la marca de tiempo de la publicación. Cuando se define la string también se hace lo siguiente:

  • Se le da a cada campo un valor numérico único (1 para text y 2 para when) que el protocolo de red subyacente usa para identificar el campo.
  • Haz que text sea un campo obligatorio. Los campos son opcionales de forma predeterminada. Puedes marcarlos como obligatorios si configuras required=True. Los mensajes deben inicializarse con la configuración de los campos obligatorios para un valor. Los métodos de la RPC de protocolo de Google solo aceptan mensajes que se hayan inicializado de forma correcta.

Puedes establecer valores para los campos con el constructor de la clase Note:

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

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

También puedes leer y establecer valores en un mensaje como valores de atributo Python normales. Por ejemplo, para cambiar este mensaje:

print note_instance.text
note_instance.text = u'Good-bye guestbook!'
print note_instance.text
que produce lo siguiente
Hello guestbook!
Good-bye guestbook!

Cómo definir un servicio

Un servicio es una definición de clase que hereda de la clase base Service. Los métodos remotos de un servicio se indican mediante el uso del decorador remote. Cada método de un servicio acepta un solo mensaje como su parámetro y muestra un solo mensaje como su respuesta.

Define el primer método del PostService. Agrega lo siguiente a tu archivo 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()

El decorador remote toma dos parámetros:

  • El tipo de solicitud esperado. El método post_note() acepta una instancia de Note como su tipo de solicitud.
  • El tipo de respuesta esperada. La biblioteca del RPC del protocolo de Google viene con un tipo incorporado llamado VoidMessage (en el módulo protorpc.message_types), que se define como un mensaje sin campos. Esto significa que el mensaje post_note() no muestra nada útil al que realiza la llamada. Si se muestra el mensaje sin errores, se lo considera publicado.

Dado que Note.when es un campo opcional, es posible que el emisor no lo haya establecido. Cuando pasa esto, el valor de when se establece como None. Cuando Note.when se establece en None, post_note() crea una marca de tiempo mediante la hora en que recibió el mensaje.

El método remote crea una instancia del mensaje de respuesta y se convierte en el valor mostrado del método remoto.

Cómo registrar el servicio

Puedes publicar tu servicio nuevo como una aplicación de WSGI mediante la biblioteca protorpc.wsgi.service. Crea un archivo nuevo llamado services.py en el directorio de tu aplicación y agrega el código siguiente para crear tu servicio:

from protorpc.wsgi import service

import postservice

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

Ahora, agrega el siguiente controlador a tu archivo app.yaml sobre la entrada general existente:

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

Cómo probar el servicio de la línea de comandos

Ahora que creaste el servicio, puedes probarlo con curl o una herramienta de línea de comandos similar.

# 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

Una respuesta de JSON vacía indica que la nota se publicó con éxito. Puedes ver la nota yendo a la aplicación de libro de visitas en tu navegador (http://localhost:8080/).

Cómo agregar campos de mensaje

Ahora que se pueden publicar mensajes en PostService, agrega un método nuevo para obtener mensajes de PostService. Primero, define un mensaje de solicitud en postservice.py que defina algunos valores predeterminados y un nuevo campo de enumeración que le indique al servidor cómo ordenar las notas en la respuesta. Defínelo sobre la clase PostService que definiste antes:

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)

Cuando se lo envía al PostService, este mensaje solicita una cantidad de notas en cierta fecha o antes de esta y en un orden particular. El campo limit indica la cantidad máxima de notas para recuperar. Si no se configura de forma explícita, limit se configura de forma predeterminada en 10 notas (como lo indica el argumento de palabra clave default=10).

El campo de orden ingresa la clase EnumField, que habilita el tipo de campo enum cuando el valor de un campo está restringido a una cantidad limitada de valores simbólicos conocidos. En este caso, enum le indica al servidor cómo ordenar las notas en la respuesta. Para definir los valores de enumeración, crea una subclase de la clase Enum. Cada nombre debe asignarse a un número único para el tipo. Cada número se convierte en una instancia de tipo enumeración y se puede acceder a él desde la clase.

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

Cada valor enum tiene una característica especial que hace que sea fácil la conversión a su nombre o número. En lugar de acceder al atributo del nombre o del número, tan solo convierte cada valor en una string o en un número entero:

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

Los campos de enumeración se declaran de forma similar a otros campos, excepto que deben tener el tipo de enumeración como su primer parámetro antes del número de campo. Los campos de enumeración también pueden tener valores predeterminados.

Cómo definir el mensaje de respuesta

Ahora, define el mensaje de respuesta get_notes(). La respuesta debe ser una colección de mensajes Note. Los mensajes pueden contener otros mensajes. En el caso del campo Notes.notes definido a continuación, indicamos que es una colección de mensajes al proporcionar la clase Note como el primer parámetro en el constructor messages.MessageField (antes del número de campo):

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

El campo Notes.notes también es un campo repetido, como lo indica el argumento de palabra clave repeated=True. Los valores de los campos repetidos deben ser listas del tipo de campo de su declaración. En este caso, Notes.notes debe ser una lista de instancias de Nota. Las listas se crean de forma automática y no pueden asignarse a None.

Por ejemplo, así es cómo se crea un objeto 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

Cómo implementar get_notes

Ahora, se puede agregar el método get_notes() a la clase 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)