Visão geral da biblioteca de RPC do protocolo do Google

Observação: se você quiser usar a autenticação com o protocolo de RPC do Google, use a autenticação atualmente disponível para aplicativos do App Engine no Google Cloud Console. Será preciso também especificar o requisito de login de uso no arquivo app.yaml. No momento, outras metodologias de autenticação não são compatíveis com a biblioteca do protocolo de RPC do Google no App Engine.

A biblioteca do protocolo de RPC do Google é uma estrutura para a implementação de serviços de Chamada de procedimento remoto (RPC, na sigla em inglês) baseados em HTTP. Um serviço de RPC é uma coleção de tipos de mensagens e métodos remotos para a interação entre aplicativos externos e aplicativos da Web de maneira estruturada. É possível definir mensagens e serviços na linguagem de programação Python, por isso, é fácil desenvolver serviços do protocolo de RPC, testar e escalonar esses serviços no App Engine.

É possível usar a biblioteca do protocolo de RPC do Google para qualquer tipo de serviço de RPC baseado em HTTP, mas alguns casos de uso comuns incluem:

  • Como publicar APIs da web para uso por terceiros
  • Como criar back-ends Ajax estruturados
  • Como clonar para comunicação com o servidor de longa duração

Defina um serviço do protocolo de RPC do Google em uma única classe Python que tenha qualquer número de métodos remotos declarados. Cada método remoto aceita um conjunto de parâmetros específico como uma solicitação e retorna uma resposta específica. Esses parâmetros de solicitação e resposta são classes definidas pelo usuário, conhecidas como mensagens.

O Hello World do protocolo de RPC do Google

Nesta seção, apresentamos um exemplo de definição de serviço muito simples que recebe uma mensagem de um cliente remoto. A mensagem contém o nome de um usuário (HelloRequest.my_name) e envia uma saudação para 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)])

Primeiros passos com o protocolo de RPC do Google

Nesta seção, demonstraremos como começar a usar o protocolo de RPC do Google com o aplicativo de livro de visitas desenvolvido no Guia de primeiros passos do App Engine (Python). Os usuários podem visitar o livro de visitas, que também está incluído como demonstração no SDK para Python on-line, criar entradas e visualizar entradas de todos os usuários. Os usuários interagem diretamente com a interface, no entanto, não há acesso fácil a essas informações para os aplicativos da Web.

É aí que entra o protocolo de RPC. Neste tutorial, o protocolo de RPC do Google será aplicado a este livro de visitas básico para que o acesso aos dados respectivos fique disponível para outros aplicativos da Web. Neste tutorial, é abordado apenas o uso do protocolo de RPC do Google para estender a funcionalidade do livro de visitas. Você decide o que fazer em seguida. Por exemplo, é possível programar uma ferramenta que leia as mensagens postadas por usuários e crie um gráfico de linha de tempo de postagens por dia. A maneira como você usa o protocolo de RPC do Google depende do aplicativo específico que você quer usar. O importante é que, com o protocolo de RPC do Google, as possibilidades de uso dos dados do aplicativo são bastante expandidas.

Para começar, crie um arquivo, postservice.py, que implemente métodos remotos para acessar dados no armazenamento de dados do aplicativo de livro de visitas.

Como criar o módulo PostService

O primeiro passo para começar a usar o protocolo de RPC do Google é criar um arquivo chamado postservice.py no diretório do aplicativo. Você usará esse arquivo para definir o novo serviço e implementar dois métodos: um para a publicação remota, e outro para o recebimento remoto dos dados.

Você não precisa adicionar nada a este arquivo agora, mas é nele que você colocará todo o código definido nas próximas seções. Na próxima seção, você vai criar uma mensagem que representa uma nota publicada no armazenamento de dados do aplicativo de livro de visitas.

Como trabalhar com mensagens

As mensagens são o tipo de dados fundamental que é usado no protocolo de RPC do Google. Elas são definidas pela declaração de uma classe herdeira da classe base Message. Em seguida, você especifica atributos de classe correspondentes a cada um dos campos da mensagem.

Por exemplo, o serviço de livro de visitas permite que usuários postem uma nota. Se você ainda não fez isso, crie um arquivo chamado postservice.py no diretório do aplicativo e leia o tutorial do livro de visitas, se necessário. Neste tutorial, as saudações do livro de visitas são colocadas no armazenamento de dados por meio da classe guestbook.Greeting. O PostService também usa a classe Greeting para armazenar uma postagem no armazenamento 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 é um string unicode que representa o conteúdo da publicação de um usuário na página do livro de visitas. O campo when é um número inteiro que representa o carimbo de data e hora da postagem. Ao definir a string, também realizamos as seguintes ações:

  • Damos a cada campo um valor numérico exclusivo (1 para text e 2 para when) que o protocolo de rede subjacente usa para identificar o campo.
  • Tornamos text um campo obrigatório. Os campos são opcionais por padrão, mas é possível marcá-los como obrigatórios ao definir required=True. É preciso que as mensagens sejam inicializadas com a definição de um valor para os campos obrigatórios. Nos métodos de serviço do protocolo de RPC do Google, são aceitas somente mensagens corretamente inicializadas.

É possível definir valores para os campos usando o 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()))

Você também pode ler e definir valores em uma mensagem como valores de atributo Python normais. Por exemplo, para alterar a mensagem:

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

Como definir um serviço

Um serviço é uma definição de classe herdeira da classe base Service. Os métodos remotos de um serviço são indicados pelo uso do decorador remote. Cada método de um serviço aceita uma única mensagem como seu parâmetro e retorna uma única mensagem como sua resposta.

Veja uma definição do primeiro método do PostService. Adicione a instrução a seguir ao seu arquivo 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 usa dois parâmetros:

  • O tipo de solicitação esperado. O método post_note() aceita uma instância de Note como seu tipo de solicitação.
  • O tipo de resposta esperado. A biblioteca do protocolo de RPC do Google contém um tipo integrado chamado VoidMessage no módulo protorpc.message_typesque é definido como uma mensagem sem campos. Isso quer dizer que a mensagem post_note() não retorna nada de útil para quem a chamou. Se ela retorna com erro, a mensagem é considerada como postada.

Uma vez que Note.when é um campo opcional, ele pode não ter sido configurado pelo chamador. Quando isso ocorre, o valor de when é definido como "None". Quando Note.when é definido como "None", post_note() cria o carimbo de data/hora usando o horário em que recebeu a mensagem.

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

Como registrar o serviço

Use a biblioteca protorpc.wsgi.service para publicar o novo serviço como um aplicativo WSGI. Crie um arquivo chamado services.py no diretório do seu aplicativo e adicione o código a seguir para criar 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 gerenciador ao arquivo app.yaml, acima da entrada "pega-tudo" existente:

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

Como testar o serviço pela linha de comando

Depois da criação do serviço, teste-o usando curl ou uma ferramenta de linha de comando 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 postada. Para ver a observação, visite o aplicativo de livro de visitas no seu navegador (http://localhost:8080/).

Como adicionar campos de mensagens

Agora que podemos publicar mensagens no PostService, vamos adicionar um novo método para buscar mensagens do PostService. Primeiro, definimos uma mensagem de solicitação em postservice.py, estabelecendo alguns padrões e um novo campo enum que informa ao servidor como ordenar notas na resposta. Faça a definição acima da classe PostService que você 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, a mensagem solicita um número de notas até determinada data em uma ordem específica. O campo limit indica o número máximo de notas a serem buscadas. Se não é definido explicitamente, limit assume o valor padrão de 10 notas (conforme indicado pelo argumento de palavra-chave default=10).

Com o campo order, é introduzida a classe EnumField que ativa o tipo de campo enum quando o valor de um campo for restrito a um número limitado de valores simbólicos conhecidos. Nesse caso, enum indica ao servidor como ordenar as notas na resposta. Para definir os valores de enum, crie uma subclasse da classe Enum. Cada nome precisa receber um número exclusivo para o tipo. Cada número é convertido em uma instância do tipo enum e pode ser acessado pela classe.

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

Cada valor de enum tem uma característica especial que facilita a conversão para o nome ou o número dele. Em vez de acessar o atributo de nome e número, simplesmente converta cada valor em uma string ou em um número inteiro:

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

Os campos enum são declarados de modo semelhante aos outros campos, mas precisam ter o tipo enum como primeiro parâmetro antes do número do campo. Campos enum também têm valores padrão.

Como definir a mensagem de resposta

Agora, vamos definir a mensagem de resposta de get_notes(). É preciso que a mensagem seja uma coleção de mensagens de nota. As mensagens podem conter outras mensagens. No caso do campo Notes.notes definido abaixo, indicamos que se trata de uma coleção de mensagens ao informar a classe Note como o primeiro parâmetro para o 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. É preciso que os valores de campos repetidos sejam listas do tipo de campo da declaração. Nesse caso, Notes.notes precisa ser uma lista de instâncias Note. As listas são criadas automaticamente e não podem ser atribuídas a "None".

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

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