Vista geral da biblioteca de RPC do protocolo Google

Nota: se quiser usar a autenticação com o RPC do Protocolo Google, pode usar a autenticação atualmente disponível para apps do App Engine na Google Cloud consola. Também tem de especificar o requisito de início de sessão de utilização no seu ficheiro app.yaml. Atualmente, a biblioteca Google Protocol RPC no App Engine não suporta outras metodologias de autenticação.

A biblioteca RPC do Protocol Buffer da Google é uma framework para implementar serviços de chamadas de procedimentos remotos (RPC) baseados em HTTP. Um serviço RPC é uma coleção de tipos de mensagens e métodos remotos que oferecem uma forma estruturada para as aplicações externas interagirem com as aplicações Web. Uma vez que pode definir mensagens e serviços na linguagem de programação Python, é fácil desenvolver serviços RPC de protocolo, testar esses serviços e escalá-los no App Engine.

Embora possa usar a biblioteca de RPC do Protocolo Google para qualquer tipo de serviço de RPC baseado em HTTP, alguns exemplos de utilização comuns incluem:

  • Publicar APIs Web para utilização por terceiros
  • Criar back-ends Ajax estruturados
  • Clonagem para comunicação com o servidor de execução prolongada

Pode definir um serviço RPC do protocolo Google numa única classe Python que contenha qualquer número de métodos remotos declarados. Cada método remoto aceita um conjunto específico de parâmetros como um pedido e devolve uma resposta específica. Estes parâmetros de pedido e resposta são classes definidas pelo utilizador conhecidas como mensagens.

O Hello World da RPC do Protocolo Google

Esta secção apresenta um exemplo de uma definição de serviço muito simples que recebe uma mensagem de um cliente remoto. A mensagem contém o nome de um utilizador (HelloRequest.my_name) e envia uma saudação a essa pessoa (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)])

Introdução à RPC do Protocol Buffer da Google

Esta secção demonstra como começar a usar o RPC de protocolo Google com a aplicação guestbook desenvolvida em Python. Os utilizadores podem visitar o livro de visitas (também incluído como uma demonstração no SDK Python) online, escrever entradas e ver entradas de todos os utilizadores. Os utilizadores interagem diretamente com a interface, mas não existe uma forma de as aplicações Web acederem facilmente a essas informações.

É aqui que entra o Protocol RPC. Neste tutorial, vamos aplicar o RPC do Protocolo Google a este livro de visitas básico, permitindo que outras aplicações Web acedam aos dados do livro de visitas. Este tutorial apenas aborda a utilização do Google Protocol RPC para expandir a funcionalidade do livro de visitas. Cabe-lhe a si decidir o que fazer a seguir. Por exemplo, pode querer escrever uma ferramenta que leia as mensagens publicadas pelos utilizadores e crie um gráfico de séries cronológicas de publicações por dia. A forma como usa o Protocol RPC depende da sua app específica. O importante é que o Protocol RPC da Google expande significativamente o que pode fazer com os dados da sua aplicação.

Para começar, vai criar um ficheiro, postservice.py, que implementa métodos remotos para aceder aos dados no arquivo de dados da aplicação de livro de visitas.

Criar o módulo PostService

O primeiro passo para começar a usar o Google Protocol RPC é criar um ficheiro denominado postservice.py no diretório da aplicação. Vai usar este ficheiro para definir o novo serviço, que implementa dois métodos: um que publica dados remotamente e outro que obtém dados remotamente.

Não tem de adicionar nada a este ficheiro agora, mas é neste ficheiro que vai colocar todo o código definido nas secções subsequentes. Na secção seguinte, vai criar uma mensagem que representa uma nota publicada no arquivo de dados da aplicação de livro de visitas.

Trabalhar com a app Mensagens

As mensagens são o tipo de dados fundamental usado no Google Protocol RPC. As mensagens são definidas declarando uma classe que herda da classe base Message. Em seguida, especifica atributos de classe que correspondem a cada um dos campos da mensagem.

Por exemplo, o serviço de livro de visitas permite que os utilizadores publiquem uma nota. Se ainda não o fez, crie um ficheiro denominado postservice.py no diretório da aplicação. O PostService usa a classe Greeting para armazenar uma publicação no arquivo de dados. Vamos definir uma mensagem que represente essa nota:

from protorpc import messages

class Note(messages.Message):

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

A mensagem da nota é definida por dois campos: text e when. Cada campo tem um tipo específico. O campo de texto é uma string Unicode que representa o conteúdo da publicação de um utilizador na página do livro de visitas. O campo when é um número inteiro que representa a data/hora da publicação. Ao definir a string, também:

  • Atribua a cada campo um valor numérico único (1 para text e 2 para when) que o protocolo de rede subjacente usa para identificar o campo.
  • Tornar text um campo obrigatório. Os campos são opcionais por predefinição. Pode marcá-los como obrigatórios definindo required=True. As mensagens têm de ser inicializadas definindo os campos obrigatórios para um valor. Os métodos de serviço RPC do Protocol Buffer da Google só aceitam mensagens devidamente inicializadas.

Pode definir valores para os campos através do construtor da 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()))

Também pode ler e definir valores numa mensagem como valores de atributos Python normais. Por exemplo, para alterar a mensagem:

print note_instance.text
note_instance.text = u'Good-bye guestbook!'
print note_instance.text
que produz o seguinte
Hello guestbook!
Good-bye guestbook!

Definir um serviço

Um serviço é uma definição de classe que herda da classe base Service. Os métodos remotos de um serviço são indicados através do decorador remote. Cada método de um serviço aceita uma única mensagem como parâmetro e devolve uma única mensagem como resposta.

Vamos definir o primeiro método do PostService. Adicione o seguinte ao ficheiro 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()

O decorador remote recebe dois parâmetros:

  • O tipo de pedido esperado. O método post_note() aceita uma instância de Note como o respetivo tipo de pedido.
  • O tipo de resposta esperado. A biblioteca RPC do protocolo Google inclui um tipo incorporado denominado VoidMessage (no módulo protorpc.message_types), que é definido como uma mensagem sem campos. Isto significa que a mensagem post_note() não devolve nada útil ao respetivo autor da chamada. Se for devolvida sem erro, considera-se que a mensagem foi publicada.

Uma vez que Note.when é um campo opcional, pode não ter sido definido pelo autor da chamada. Quando isto acontece, o valor de when é definido como Nenhum. Quando Note.when está definido como Nenhum, post_note() cria a data/hora com base na hora em que recebeu a mensagem.

A mensagem de resposta é instanciada pelo método remoto e torna-se o valor de retorno do método remoto.

Registar o serviço

Pode publicar o seu novo serviço como uma aplicação WSGI através da biblioteca protorpc.wsgi.service. Crie um novo ficheiro denominado services.py no diretório da aplicação e adicione o seguinte código para criar o seu serviço:

from protorpc.wsgi import service

import postservice

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

Agora, adicione o seguinte controlador ao seu ficheiro app.yaml acima da entrada geral existente:

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

Testar o serviço a partir da linha de comandos

Agora que criou o serviço, pode testá-lo através de curl ou de uma ferramenta de linha de comandos semelhante.

# 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

Uma resposta JSON vazia indica que a nota foi publicada com êxito. Pode ver a nota acedendo à aplicação de livro de visitas no navegador (http://localhost:8080/).

Adicionar campos de mensagens

Agora que podemos publicar mensagens no PostService, vamos adicionar um novo método para receber mensagens do PostService. Primeiro, vamos definir uma mensagem de pedido em postservice.py que define alguns valores predefinidos e um novo campo de enumeração que indica ao servidor como ordenar as notas na resposta. Defina-o acima da classe PostService que definiu anteriormente:

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)

Quando enviada para o PostService, esta mensagem pede um número de notas numa determinada data ou antes desta e numa ordem específica. O campo limit indica o número máximo de notas a obter. Se não for definido explicitamente, limit é predefinido para 10 notas (conforme indicado pelo argumento da palavra-chave default=10).

O campo de ordem introduz a classe EnumField, que ativa o tipo de campo enum quando o valor de um campo está restrito a um número limitado de valores simbólicos conhecidos. Neste caso, o enum indica ao servidor como ordenar as notas na resposta. Para definir os valores enum, crie uma subclasse da classe Enum. A cada nome tem de ser atribuído um número exclusivo para o tipo. Cada número é convertido numa instância do tipo de enumeração e pode ser acedido a partir da classe.

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

Cada valor enum tem uma característica especial que facilita a conversão para o respetivo nome ou número. Em vez de aceder ao atributo de nome e número, basta converter cada valor numa string ou num número inteiro:

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

Os campos de enumeração são declarados de forma semelhante a outros campos, exceto que têm de ter o tipo de enumeração como o primeiro parâmetro antes do número do campo. Os campos enum também podem ter valores predefinidos.

Definir a mensagem de resposta

Agora, vamos definir a mensagem de resposta get_notes(). A resposta tem de ser uma coleção de mensagens Note. As mensagens podem conter outras mensagens. No caso do campo Notes.notes definido abaixo, indicamos que se trata de uma coleção de mensagens fornecendo a classe Note como o primeiro parâmetro ao construtor messages.MessageField (antes do número do campo):

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

O campo Notes.notes também é um campo repetido, conforme indicado pelo argumento de palavra-chave repeated=True. Os valores dos campos repetidos têm de ser listas do tipo de campo da respetiva declaração. Neste caso, Notes.notes tem de ser uma lista de instâncias de Note. As listas são criadas automaticamente e não podem ser atribuídas a Nenhum.

Por exemplo, veja como criar um 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

Implemente get_notes

Agora, podemos adicionar o método get_notes() à 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)