Um blog com Google App Engine

Sumário

Este é um post-tutorial, onde vou descrever o desenvolvimento de um sistema de blogging bem simples usando os recursos nativos do Google App Engine (GAE, para os amigos). Caso não tenha familiaridade com o GAE, sugiro a leitura dos textos anteriores que publiquei aqui no Python Help sobre o assunto:

Com as leituras acima, você terá a base necessária para entender o tutorial aqui apresentado.

O que vamos desenvolver?

O objetivo é implementar um sistema de blogging bem simples, com as seguintes funcionalidades:

  • Tela principal com listagem de todos os posts
  • Tela para adicionar um novo post
  • Tela para visualizar um post
  • Restrição de acesso para a tela de criar post

A tela para adicionar posts deve redirecionar para o post recém criado.

Mãos à obra

A primeira coisa que temos que fazer é criar o projeto, ou seja, definir a estrutura de arquivos que nosso projeto vai ter e configurá-lo no arquivo app.yaml, como já vimos nos posts anteriores. Veja abaixo a estrutura de diretórios que vamos utilizar em nosso projeto:

├── app.yaml
├── main.py
├── models.py
├── static
│   └── main.css
└── templates
    ├── base.html
    ├── detail.html
    ├── form.html
    └── front.html

Crie o arquivo de configurações app.yaml com as seguintes opções:

application: pythonhelp-blog
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: main.app

Como você deve lembrar, a diretiva handlers no arquivo de configuração app.yaml configura como nossa aplicação vai tratar as requisições HTTP vindas do cliente. Aqui configuramos nossa aplicação para servir arquivos estáticos a partir do diretório static e para publicar a aplicação criada na variável app dentro do módulo main.

Ah, não se esqueça de alterar o seu application id (valor de application no app.yaml), pois esse identificador deve ser único no GAE. Ou seja, se eu colocar esse sisteminha no ar com o app id "pythonhelp-blog", você não poderá colocá-lo no ar com o mesmo identificador.

O modelo de dados

Agora, vamos modelar o nosso problema. Como iremos representar as informações no nosso banco de dados? De acordo com os requisitos, iremos precisar de um model para representar um post, que tenha vinculado a si alguns atributos, incluindo o autor do texto (que deve ser um usuário cadastrado).

Graças ao GAE, não precisaremos criar uma entidade User, pois ele já fornece um serviço de gerenciamento e autenticação de usuários (usando contas de usuário na google mesmo). Dessa forma, teremos apenas um model de uma entidade que iremos chamar de BlogPost, contendo um atributo do tipo UserProperty, fornecido pelo GAE. Crie um model BlogPost de acordo com o código a seguir e salve no arquivo models.py:

from google.appengine.ext import db

class BlogPost(db.Model):
    subject = db.StringProperty(required=True)
    author = db.UserProperty(required=True,
                             auto_current_user=True)
    content = db.TextProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    last_updated = db.DateTimeProperty(auto_now=True)

Observe  o campo author. Passamos um atributo nomeado auto_current_user com o valor True. Isso faz com que o campo author seja setado com o usuário atualmente logado quando um objeto do tipo BlogPost for gravado no datastore. Barbadinha, né?

Implementação das ações

Agora vamos definir as URLs que serão utilizadas e para quais classes elas serão mapeadas em nosso projeto.

Defina o mapeamento das URLs para as classes manipuladoras no arquivo main.py:

    app = webapp2.WSGIApplication(
        [("/blog", BlogMainHandler),
         ("/blog/newpost", BlogNewPostHandler),
         ('/blog/post/(\d+)', BlogPostViewHandler),
         ],
        debug=True)

Dessa forma, nosso esquema de URLs será assim:

  • a página principal (que lista todos os posts), será acessível pela URL "/blog";
  • o formulário para criação de um novo post será acessível via "/blog/newpost";
  • um post em específico será acessível pela URL "/blog/post/id_do_post". Por exemplo, "/blog/post/19" irá abrir uma página de visualização do post cujo id seja 19.

Agora vamos à implementação das classes listadas acima. A primeira delas será a mais simples: BlogMainHandler, que será a página inicial do blog, listando todos os posts.

template_dir = os.path.join(os.path.dirname(__file__),
                            'templates')


class BlogMainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.out.write(
            template.render(template_dir +
                            '/front.html',
                            {'posts': BlogPost.all()}))

A segunda classe que iremos implementar é BlogPostViewHandler, responsável pela visualização de um post em específico.

class BlogPostViewHandler(webapp2.RequestHandler):
  def get(self, post_id):
    try:
      post = BlogPost.get_by_id(int(post_id))
      self.response.out.write(
          template.render(template_dir + '/detail.html',
                          {'post': post}))
    except:
      self.response.out.write("Erro: post não encontrado!")

Veja que o método get recebe um parâmetro post_id. Esse parâmetro é passado pelo URL dispatcher, pegando o pedaço da URL que está entre parênteses na definição ('/blog/post/(\d+)') e repassando como valor ao método (\d+ significa um ou mais dígitos e os parênteses ao redor fazem com que esse valor seja passado ao método get). Por exemplo, um acesso a /blog/post/19 irá fornecer ao parâmetro post_id o valor 19.

Você deve ser capaz de entender os códigos listados acima. Caso tenha dificuldades, reveja os posts em que explico como criar um projeto na GAE, como utilizar o Datastore e o mecanismo de templates.

Depois temos a classe responsável pela criação de novos posts: BlogNewPostHandler. Veja o código abaixo:


class BlogNewPostHandler(webapp2.RequestHandler):

  def get(self):
    user = users.get_current_user()
    if user is not None:
      self.response.out.write(
          template.render(template_dir + '/form.html',
                          {'title': 'Escrever post'}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

  def post(self):
    author = users.get_current_user()
    if author is not None:
      subject = self.request.get('subject')
      content = self.request.get('content')
      if subject and content:
        b = BlogPost(subject=subject, content=content)
        b.put()
        self.redirect("/blog/post/" + str(b.key().id()))
      else:
        error = 'Ambos os campos são obrigatórios'
        self.response.out.write(
            template.render(template_dir + '/form.html',
                            {'title': 'Escrever post', 
                             'error': error,
                             'subject': subject,
                             'content': content}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

Essa classe tem alguns detalhes que valem a pena ser observados. Em primeiro lugar, ela possui dois métodos: get e post. Como já vimos anteriormente, o método get será chamado pelo ambiente subjacente quando houver uma requisição do tipo GET à URL mapeada para a classe BlogNewPostHandler e o método post será chamado quando houver uma requisição do tipo POST (normalmente usada para fazer escrita de dados no servidor). Dessa forma, a classe possui duas funcionalidades:

  1. get: ao acessar a url /blog/newpost, o usuário irá receber como resposta um formulário para criação de um novo post.
  2. post: quando o formulário for submetido, é feito uma requisição POST, passando os dados do formulário para criar um objeto BlogPost no servidor.

Repare como é simples a autenticação do usuário no código usando o serviço provido pelo GAE. Veja que a primeira linha de cada método chama users.get_current_user para obter o usuário logado atualmente. Caso não haja usuário logado (se o objeto retornado for None), então redirecionamos o usuário para uma URL própria para fazer o login. Quando estiver rodando no ambiente externo, é apresentada a tela de autenticação dos serviços da Google mesmo. No ambiente de testes, é apresentada uma tela de login como a mostrada na figura abaixo.

Tela de login no ambiente local (powered by GAE)

Tela de login no ambiente local (powered by GAE)

Os templates

Em um projeto GAE, os templates contém o código HTML que será enviado para o usuário nas requisições. Como visto no post anterior sobre o assunto, é possível a introdução de campos dinâmicos para serem preenchidos de acordo com os parâmetros passados na renderização dos templates.

Como todas as páginas terão um cabeçalho padrão, definimos um template base que os demais irão estender. Veja abaixo o código de cada um dos templates:

templates/base.html:

<!DOCTYPE html>
<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/static/main.css" />
    <title>{{ title }}</title>
  </head>
  <body>
    <div class="main-title"><a href="/blog">stummjr Blog</a></div>
    {% block content %}
    {% endblock %}
  </body>
</html>

Dessa forma, basta aos subtemplates detail.html, form.html e front.html a descrição do bloco content.

templates/detail.html (usado para visualização de um post específico):

{% extends 'base.html' %}

{% block content %}
<div class="post">
  <div class="post-heading">
    <div class="post-title">
      {{ post.subject }}
    </div>
    <div class="post-date">
      {{ post.created}}
    </div>
  </div>
  <pre class="post-content">
    {{ post.content }}
  </pre>
</div>
{% endblock %}

templates/form.html (usado para apresentar a interface de criação de um novo post):

{% extends 'base.html' %}
{% block content %}
<form method="post">
  <label for="subject">Assunto:
    <input type="text" name="subject" value="{{ subject }}">
  </label>
  <label for="content">Conteúdo:
    <textarea name="content" rows="10" cols="50">
      {{ content }}
    </textarea>
  </label>
  <button type="submit">Salvar</button>
  <div class="error">
    {{ error }}
  </div>
</form>
{% endblock %}

templates/front.html (lista todos os posts existentes no banco de dados do blog):

{% extends 'base.html' %}

{% block content %}
{% for post in posts %}
  <div class="post">
    <div class="post-heading">
      <div class="post-title">
        <a href="/blog/post/{{ post.key.id }}">{{ post.subject }}</a>
      </div>
      <div class="post-date">
        {{ post.created }}
      </div>
    </div>
    <pre class="post-content">
      {{ post.content }}
    </pre>
  </div>
{% endfor %}
{% endblock %}

Como podemos ver, o código base.html referencia o arquivo de estilos main.css, que não será listado aqui devido ao seu tamanho. Todos os arquivos do projeto podem ser baixados no link que consta no final do post.

Testando o blog

Tendo as classes e os templates definidos, agora basta testar nosso projeto. Para isso, vamos usar o ambiente de testes fornecido pelo GAE. Veja como instalar e usar esse ambiente no primeiro post sobre o GAE feito no blog.

Próximos passos

Não pare por aqui. Leia a documentação oficial do GAE (em pt-br, inclusive) e incremente o projeto, adicionando comentários, categorias, etc. Bom trabalho!

Download dos arquivos do projeto

Obrigado ao Elias Dorneles, pela baita revisão que fez no texto!

Introdução ao memcache

O problema

Imagine um portal como o globo.com, com suas inúmeras chamadas para matérias que constam já na página principal.

Apelação milenar

Apelação milenar

Cada imagem, título e descrição de reportagem ou comercial que aparecem na página são informações dinâmicas dela. A cada vez que acessamos a página, podemos obter notícias mais recentes, resultando em uma página diferente. Assim, imagina-se que os dados apresentados na página estejam armazenados em um banco de dados e que, a cada acesso de usuário, temos várias consultas sendo realizadas pelo servidor ao banco de dados. Isso seria o mais natural, considerando-se a alta taxa de criação de novas notícias no site. Porém, você já deve saber que o disco é o grande gargalo dos computadores modernos e que acessos a disco são o grande vilão da performance da maioria dos programas de computador. Então, como fazer um sistema web com foco no conteúdo dinâmico sem realizar frequentes acessos ao BD (e consequentemente, ao disco)?

Uma possível solução

Uma solução pode ser fazer o caching de alguns dados na memória. Em um portal de notícias, os usuários que acessarem a página mais ou menos no mesmo horário vão receber o mesmo conteúdo, que são as últimas notícias postadas. Então por que repetir as consultas ao banco de dados para todo e qualquer acesso de usuário ao portal? Seria mais inteligente se o nosso sistema consultasse o banco de dados somente uma vez, armazenasse os resultados da consulta em memória (na nossa cache) e então passasse a responder às requisições subsequentes usando o conteúdo armazenado em memória sem precisar buscar nada no disco até que o conteúdo mude novamente. Assim, as requisições podem ser respondidas com o conteúdo da memória. Quando o conteúdo do BD sofrer alguma alteração, a cache pode ser invalidada e atualizada com o novo conteúdo.

Um exemplo

Vamos seguir com o foco em um portal de notícias, já que existem muitos casos de uso similares. Como já vimos em outro post, o Google App Engine suporta Python; e o mecanismo de datastore que ele oferece é legal pra caramba. Suponhamos que nesse portal existe um modelo de dados que contenha uma entidade Noticia, conforme representado abaixo:

class Noticia(db.Model):
    titulo = db.StringProperty(required=True)
    conteudo = db.TextProperty(required=True)
    url = db.LinkProperty(required=True)
    autor = db.UserProperty(required=True)
    thumbnail = db.LinkProperty()
    data_publicacao = db.DateTimeProperty()

Como todo bom portal de notícias, esse também vai ter aquela página inicial carregadíssima, cheia de notícias e outros elementos. Haja barramento pra aguentar tantos acessos ao disco!

O código a seguir poderia ser a view que gera a sua página principal:

    class PostHandler(webapp2.RequestHandler):

        def get(self):
            ultimas = get_noticias(100, '-data_publicacao')
            categorias = get_categorias()
            comerciais = get_comerciais_ativos()
            for n in ultimas:
                # lista e formata as noticias, categorias, comerciais
                ...

    def get_noticias(limit, order_by):
        return Noticia.all().order(order_by).fetch(limit)

    def get_categorias():
        return Categoria.all()

    def get_comerciais_ativos():
        return Comercial.all().filter('ativa = ', True)

Beleza, funciona. O problema começa quando o site começa a ficar popular e os usuários começam a reclamar de lentidão e de indisponibilidade constante. O que fazer? Bom, a resposta certa é fazer profiling da aplicação, isto é, medir o tempo que estão levando as operações necessárias para carregar a página — tanto no lado cliente quanto no lado servidor — e então você poderá decidir melhor como resolver o problema. Mas quando um site está realizando várias consultas ao banco para cada acesso do usuário, frequentemente uma solução é evitar a realização dessas consultas usando uma cache.

Let’s cache them all!

mecanismo de cache que vou apresentar aqui é o memcache específico para o Google AppEngine, embora existam várias implementações do memcache para as mais variadas plataformas e linguagens. O memcache é um mecanismo que permite o armazenamento de pares chave-valor (assim como um dicionário, ou uma tabela hash) em memória, de forma que o acesso aos valores ocorre de forma bem eficiente.

Agora, vamos modificar o método get_noticias() para fazer caching do conteúdo da página principal. O princípio de funcionamento é bem simples: antes de qualquer consulta ao banco, verifique se os dados que queremos estão presentes na cache. Veja o código:

from google.appengine.api import memcache

def get_noticias(limit, order_by):
    # busca na cache por um valor de chave 'noticias'
    ultimas = memcache.get('noticias')
    if ultimas is None: # valor ainda não está em cache
        # busca no BD
        ultimas = Noticia.all().order(order_by).fetch(limit)
        # e inclui o resultado na cache para os futuros acessos
        if not memcache.add('noticias', ultimas):
            logging.error('Erro ao setar memcache.')

O que foi feito acima para os dados das notícias pode ser feito também para os comerciais e para as categorias. Assim, o primeiro de todos os acessos ao site pode demorar um pouquinho mais, mas os acessos seguintes de todos os usuários vão ser muito rápidos.

Cuidado com a Sincronização

Uma vez que começamos a usar a cache, nós passamos a ter informações redundantes (BD e cache). Dessa forma, basta que alguém insira uma notícia nova no BD para que tenhamos os dados fora de sincronia. Uma alternativa para solucionar esse problema pode ser: logo após salvar uma nova notícia no BD, atualizar os valores da cache. Se isso for algo demorado, poderíamos iniciar uma tarefa em background para fazer isso.

Quando usar cache?

Como já comentei anteriormente, a cache não é a solução para todo e qualquer problema de um sistema web. O problema pode estar no plano de hospedagem, no excesso de arquivos estáticos, em lógica duplicada resultante de um mau hábito de copiar-e-colar, em algoritmos pouco otimizados, etc.

A cache é um mecanismo bem simples de ser implementado, em alguns casos, mas isso não quer dizer que você já deve sair de cara utilizando cache em todo e qualquer projeto a partir de agora. Afinal, premature optimization is the root of all evil. 😛

Para finalizar…

No curso Web Development, ministrado pelo grande Steve Huffman no Udacity.com, aprendi o seguinte:

Um simples acesso de usuário a uma página jamais deveria gerar um acesso ao banco de dados.

Ou seja, se um usuário qualquer quiser apenas visualizar sua página, blog, ou portal, esse acesso não deveria exigir do sistema um acesso ao banco de dados. É um princípio bem simples e que pode evitar que o seu site caia quando se tornar mega-popular. 🙂

Grandes sites como Twitter, Facebook e Reddit usam largamente os mecanismos de cache, de forma que seja possível responder aos tsunamis de requisições que eles recebem a cada segundo.

Leia mais sobre o memcache na Wikipedia.

 

Obrigado ao Elias pela revisão!

Templates de páginas com Python e Google App Engine

Sumário

def get(self):
    self.response.out.write("<html><head><title>Lista de Comentários</title></head><body>")
    for c in Comentario.objects.all():
        self.response.out.write("""<p>Nome: %s</p><p>Email: %s</p>
            <p>URL: %s</p><p>Comentário: %s</p>""" % 
                (c.nome_usuario, c.email, c.url, c.comentario))
    self.response.out.write("</body></html>")

Diga aí, o que você achou da implementação do método get() no código acima? Esse monte de HTML embutido em strings no código Python fica horrível, não?

Pois então, este post vai mostrar uma forma mais adequada para usar conteúdo em HTML no seu projeto web: os templates (ou, em bom português, modelos).

Templates são HTMLs açucarados

Templates, em um projeto Web com Python + Google App Engine, nada mais são do que arquivos HTML acrescidos de algumas marcações específicas para representar dados dinâmicos.

<html>
    <head><title></title></head>
    <body>
        <p>Nome: {{ nome }}</p>
        <p>Email: {{ email }}</p>
        <p>URL: {{ url }}</p>
        <p>Comentário: {{ comentario }}</p>
    </body>
</html>

Observe que em meio ao HTML, temos 4 campos diferentes: {{ nome }}, {{ url }}, {{ email }} e {{ mensagem }}. Em um template, sempre que houver uma string do tipo {{ nome }}, o mecanismo de templates tentará substituir tal string pelo valor da variável nome, de forma que esse valor apareça no HTML final (depois veremos como passar valores aos templates).

Além de campos para simples substituição pelo valor de variáveis, o mecanismo de template padrão do GAE fornece construções para fazer repetição, seleção, dentre outras coisas. Veja:

<html>
    <head><title></title></head>
    <body>
        {% for m in mensagens %}
            {% if m.publico %}
                <p>Nome: {{ m.nome_usuario }}</p>
                <p>Email: {{ m.email }}</p>
                <p>URL: {{ m.url }}</p>
                <p>Comentário: {{ m.comentario }}</p>
                <hr>
            {% endif %}
        {% endfor %}
    </body>
</html>

Tudo que o template acima precisaria receber é uma variável chamada comentarios, que fosse uma sequência de objetos do tipo Comentario.

Desse modo, separamos a parte de apresentação da parte lógica do nosso projeto. O código fica mais organizado, limpo e reusável. Vamos ver agora como acoplar esse mecanismo de templates ao nosso projetinho web do post anterior.

Juntando tudo

Precisamos, antes de mais nada, salvar o nosso arquivo que contém o template em um arquivo .html. Vamos chamá-lo de modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            Email: <p><input type="text" name="email"></p>
            URL: <p><input type="text" name="url"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Veja que nosso template primeiro renderiza os comentários e, no final deles, apresenta o formulário para envio de uma nova mensagem. Vamos agora reescrever o nosso método get() do post anterior. O que antes era:

def get(self):
    self.response.out.write(html)
    self.response.out.write('<ul>')
    for msg in Mensagem.all():
        self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
    self.response.out.write('</ul>')

Passará a ser:

def get(self):
    path = os.path.join(os.path.dirname(__file__), 'modelo.html')
    valores = {'mensagens': Mensagem.all()}
    self.response.out.write(template.render(path, valores))

Chamamos a função render() para gerar uma string com conteúdo HTML, com base no arquivo de template modelo.html (passado através da variável path) e do conjunto de valores a ser inserido de forma dinâmica no nosso HTML (dicionário valores). Lembra que, dentro do template, nós referenciávamos uma variável mensagens?

...    
{% for m in mensagens %}
    <hr>
    <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
    <p><strong>Email:</strong> {{ m.email }}</p>
    <p><strong>URL:</strong> {{ m.url }}</p>
    <p><strong>Comentário:</strong> {{ m.comentario }}</p>
{% endfor %}
...

No exemplo acima, mensagens terá o conteúdo de Mensagem.all(), que é uma sequência contendo todas as mensagens existentes no datastore. Assim, esse trecho do template irá percorrer as mensagens e imprimir os valores dos campos nome_usuario, email, url e comentario nos locais correspondentes do HTML. Tudo isso é feito no lado servidor, ou seja, o navegador irá receber o HTML já pronto com os valores nos locais adequados. Um exemplo de trecho de HTML gerado no servidor poderia ser:

...
    <hr>
    <h2>Comentário de <em>Pedro Pedreira</em></h2>
    <p><strong>Email:</strong> [email protected]</p>
    <p><strong>URL:</strong> https://pythonhelp.wordpress.com/</p>
    <p><strong>Comentário:</strong> Um comentário qualquer. Valeu!</p>
...

Onde Pedro Pedreira é o valor de {{ m.nome_usuario }}, e assim por diante, para um registro qualquer.

Caso não tenha visto o post anterior, veja o código do projeto para então aplicar as mudanças para inclusão do template, ou então siga a leitura neste post para ver o projeto completo.

O projeto completo

Nosso projeto está localizado no diretório gaetest/ e possui a seguinte estrutura:

gaetest/
    app.yaml
    handlers.py
    modelo.html

Agora vamos ver o conteúdo dos arquivos:

app.yaml:

application: gaetest
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

handlers.py:

import os
import webapp2
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app


class PostHandler(webapp2.RequestHandler):

    def get(self):
        path = os.path.join(os.path.dirname(__file__), 'modelo.html')
        valores = {'mensagens': Mensagem.all()}
        self.response.out.write(template.render(path, valores))

    def post(self):
        msg = Mensagem(
            nome_usuario=self.request.get('nome_usuario'),
            url=self.request.get('url'),
            email=self.request.get('email'),
            comentario=self.request.get('comentario')
        )
        msg.put()
        self.redirect('/')


class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            URL: <p><input type="text" name="url"></p>
            Email: <p><input type="text" name="email"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Executando o projeto

Salve o conteúdo acima nos arquivos correspondentes dentro da pasta gaetest e então rode o servidor local de testes. Para isso, vá até a pasta onde você descompactou o pacote na instalação do GAE e rode o seguinte comando:

./dev_appserver.py /caminho/para/o/projeto/gaetest/

Onde /caminho/para/o/projeto/gaetest/ é o caminho para o diretório onde está o projeto que queremos executar. Após isso, acesse o projeto no seu navegador através da URL localhost:8080.

Boas práticas

Boas práticas são muito importantes na hora de desenvolver um projeto de software. À medida que o projeto vai crescendo, a urgência por uma boa organização dos arquivos também cresce. É comum termos projetos com uma estrutura parecida com essa:

project/
    app.yaml
    handlers.py
    models.py
    templates/
        base.html
        form.html
        list.html

Veja que temos agora dois arquivos .py:

  • handlers.py: as classes que irão manipular nossas requisições;
  • models.py: as classes que representam os modelos de dados em nosso projeto (como a classe Mensagem no nosso exemplo);

Além disso, temos uma pasta específica para conter os templates e dentro dela ficam todos os templates que precisarmos.

Você é livre para definir a estrutura que quiser, mas é bom seguir um padrão que seja minimamente organizado.

Outros mecanismos de template

O mecanismo de template que vimos neste post é o que já vem incluso com o pacote do GAE, que por sua vez é o mecanismo do Django. Além desse, existem vários outros mecanismos que podem ser usados, como o jinja2, por exemplo.

Leia mais

Google App Engine e Datastore

Sumário

Em um post anterior, vimos como manipular no servidor os dados vindos de um formulário HTML, com Google App Engine (GAE) e Python. Porém, até agora não vimos como armazenar dados vindos do cliente em um banco de dados.

Neste post vamos incrementar a aplicação de envio de mensagens via formulários HTML que construímos em um post anterior, possibilitando que as mensagens enviadas pelos usuários sejam gravadas em um banco de dados e que sejam listadas logo abaixo do formulário na página principal. Vamos começar vendo o mecanismo para armazenamento de dados do GAE: o datastore.

Datastore

Diferentemente do que acontece quando estamos lidando diretamente com um banco de dados relacional, onde criamos as tabelas com suas propriedades no banco de dados, para então ajeitar a aplicação para que possa acessar tal banco, com o GAE nós poderemos implementar uma app inteira sem ver código SQL algum.

Para criar uma entidade no BD, basta definir uma classe contendo os atributos que desejamos que façam parte de nosso BD. Vamos criar uma entidade chamada de Mensagem, que irá armazenar os atributos relacionados às mensagens enviadas pelo usuário:

from google.appengine.ext import db

class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)

Nossa classe estende a classe db.Model, o que é um requisito para que ela possa representar uma entidade no BD. Veja que a definição de uma entidade é bem simples, bastando declarar os atributos desejados como variáveis de classe. Já definimos também os tipos dos dados que cada atributo irá representar. No exemplo acima, StringProperty é o tipo que usamos para definição de campos de texto com menos de 500 caracteres. Já o tipo TextProperty é usado para a definição de campos para textos mais longos. Para conhecer a lista completa dos tipos de atributos disponíveis no framework de persistência do Google App Engine, consulte a documentação oficial.

Persistência de objetos no datastore

Uma vez que definimos uma entidade para representação de nossas mensagens no banco de dados, criar e salvar dados no BD é tão simples quanto criar um objeto:

msg = Mensagem()
msg.nome_usuario = "stummjr"
msg.url = "https://pythonhelp.wordpress.com"
msg.email = "[email protected]"
msg.comentario = "Um comentário bem interessante!!!"
msg.put()

O procedimento é bem simples. Primeiramente, instanciamos um objeto da classe Mensagem (linha 1). Após isso, setamos os atributos desse objeto (linhas 2-5). Por fim, salvamos tal objeto no BD (linha 6).

Também poderíamos passar os valores para todos os atributos como parâmetros nomeados na construção do objeto:

msg = Mensagem(
    nome_usuario="stummjr",
    url="https://pythonhelp.wordpress.com",
    email="[email protected]",
    comentario="Um comentário bem interessante!!!"
)
msg.put()

Agora já podemos integrar o código acima em nossa app do post anterior. Basta criar o objeto Mensagem e persistir esse objeto no BD dentro do método post, que irá tratar os dados enviados pelo usuário via formulário:

def post(self):
    msg = Mensagem(
        nome_usuario=self.request.get('nome_usuario'),
        url=self.request.get('url'),
        email=self.request.get('email'),
        comentario=self.request.get('comentario')
    )
    msg.put()
    self.redirect('/')

Observe que, ao final do método post(), fizemos uma chamada ao método self.redirect('/'), que faz com que o usuário seja redirecionado para o recurso recebido como parâmetro (nesse caso, a raiz do site). Fizemos isso porque se apenas enviarmos ao usuário uma resposta de confirmação, ele poderá causar a submissão duplicada do formulário caso solicite ao navegador que este atualize a página (o famoso F5). Assim, redirecionamos a outro recurso para evitar que uma sequência de refreshes na página recebida como resposta a um POST possa causar várias inserções duplicadas.

Adicionamos os campos nome_usuario, url e email ao formulário contido na variável html:

html = """
    <html>
        <head>
            <title></title>
        </head>
        <body>
            <form method="POST" action="/">
                Nome: <p><input type="text" name="nome_usuario"></p>
                URL: <p><input type="text" name="url"></p>
                Email: <p><input type="text" name="email"></p>
                Comentário:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
                <button type="submit">Enviar</button>
            </form>
        </body>
    </html>
"""

Feito isso, agora nosso app já é capaz de salvar no BD os comentários enviados pelos usuários. Caso já tenha visto o post anterior, apenas complemente aquele código com o que foi apresentado até aqui e execute servidor de desevolvimento local. Se não viu o post anterior, o código completo é apresentado na seção “O projeto completo”.

A interface administrativa local

Enquanto estamos desenvolvendo nosso app, podemos querer verificar se determinado objeto foi realmente persistido no BD ou não. Caso ainda não tenhamos desenvolvido algum método para listagem de objetos em nosso app, podemos utilizar a interface administrativa local do GAE, que fica disponível na URL http://localhost:8080/_ah/admin/ (obs.: o servidor de testes precisa estar em execução)

http7.1

Basta selecionar o tipo de objeto e solicitar a listagem.

Consultas ao datastore

Para realizar consultas aos objetos que estão no banco de dados, basta utilizar os métodos all(), filter(), dentre outros que são comuns a todas as classes que estendem db.Model. Veja alguns exemplos:

# obtém todos os registros de Mensagem do BD
msgs = Mensagem.all()

# pega somente Mensagens do "John Doe"
msgs.filter('nome_usuario =', "John Doe")

# ordena pelo endereço do email, de forma DESC (-)
msgs.order('-email')

for msg in msgs:
    print msg.email

Veja mais métodos para consulta de dados na documentação oficial.

Em nosso projeto, precisamos apenas adicionar ao método get() a funcionalidade de listagem das mensagens já existentes no BD, logo abaixo do formulário de submissão de mensagens. Com base nos métodos de consulta que vimos, podemos montar nosso método:

def get(self):
    self.response.out.write(html)
    self.response.out.write('<ul>')
    for msg in Mensagem.all():
        self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
    self.response.out.write('</ul>')

Tenha em mente que imprimir código HTML diretamente não é uma boa abordagem para desenvolvimento web. Existem técnicas mais recomendadas, como a utilização de modelos de páginas. Mas isso é um assunto para um outro post.

O projeto completo

Como já vimos anteriormente, a estrutura do diretório do nosso projeto deve ficar como:


projeto/
  handlers.py
  app.yaml

Segue abaixo o código dos arquivos relacionados:

Arquivo handlers.py:

import webapp2
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app

html = """
    <html>
        <head>
            <title></title>
        </head>
        <body>
            <form method="POST" action="/">
                Nome: <p><input type="text" name="nome_usuario"></p>
                URL: <p><input type="text" name="url"></p>
                Email: <p><input type="text" name="email"></p>
                Comentário:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
                <button type="submit">Enviar</button>
            </form>
        </body>
    </html>
"""

class PostHandler(webapp2.RequestHandler):

    def get(self):
        self.response.out.write(html)
        self.response.out.write('<ul>')
        for msg in Mensagem.all():
            self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
        self.response.out.write('</ul>')

    def post(self):
        msg = Mensagem(
            nome_usuario=self.request.get('nome_usuario'),
            url=self.request.get('url'),
            email=self.request.get('email'),
            comentario=self.request.get('comentario')
        )
        msg.put()
        self.redirect('/')


class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Arquivo app.yaml:


application: gaepost
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

Com os arquivos acima, você já pode executar o projeto usando o ambiente de testes.

Aprenda mais

Caso tenha dificuldades para entender o funcionamento do Google App Engine, leia os posts anteriores nos quais introduzimos o assunto:

  1. Desenvolvendo com Python e Google App Engine
  2. Tratando dados de formulários com Google App Engine

Se quiser ir mais a fundo no GAE, sugiro que leia a documentação oficial, e também o livro Programming Google App Engine.

Tratando dados de formulários com Google App Engine

http6.0Os formulários HTML são uma das formas mais usadas para passagem de dados dos usuários para os servidores na web. Você, usuário, quantas vezes por dia preenche algum formulário e clica em um botãozinho próximo a ele? Se você faz uma busca no google.com, você preenche um campo de entrada de um formulário e clica no botão “Buscar”. Quando envia um tweet ou uma mensagem no Facebook, você preenche campos de entrada de um formulário e os submete a um servidor. Quando faz login no GMail, você preenche dois campos de entrada (usuário e senha) e então clica no botão “Login” para submeter os dados ao servidor para que este verifique suas credenciais. Ou seja, você usa formulários pra caramba!

Neste post rápido daremos sequência ao post anterior, vendo como implementar na prática o tratamento de dados vindos de formulários em um app web usando Python e Google App Engine (GAE). (caso você não conheça o Google App Engine, leia antes o post anterior)

POSTando dados na web

O objetivo é criar uma aplicaçãozinha web bem simples na qual o usuário possa escrever uma mensagem em um formulário e receber de volta essa mensagem no navegador. Vamos criar esse app do mesmo modo que fizemos no post anterior. Crie um diretório mural e dentro dele crie os arquivos app.yaml (que vai conter as configurações) e handlers.py (que vai conter o código Python para manipular as requisições).

app.yaml:

application: mural
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

O código acima apresentado é o conteúdo do arquivo de configurações app.yaml, que define alguns parâmetros para o projeto.
O manipulador de requisições (handlers.py) deverá ser implementado da seguinte forma:

  1. Ao receber uma requisição GET, este deve retornar um HTML contendo um formulário para que o usuário escreva sua mensagen;
  2. Ao receber uma requisição POST (vinda do formulário recebido pelo usuário quando executa o GET), este deve apresentar uma página HTML contendo o texto submetido pelo usuário.

Para simplificar a nossa implementação, vamos colocar o seguinte código HTML dentro de uma string no nosso código Python:

<html>
    <head><title></title></head>
    <body>
        <form method="POST" action="/">
            <textarea name="comentario" cols="40" rows="10">
            </textarea>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Faremos isso para que, ao receber uma requisição do tipo GET, nosso appzinho possa simplesmente gravar em self.response.out a string contendo o código acima, fazendo com o formulário HTML seja passado como resposta à requisição GET do cliente. (você já sabe, do post anterior, que ao escrever um valor qualquer em self.response.out estamos adicionando esse valor ao corpo da mensagem de resposta)

Segue abaixo o código de nosso app:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app


html = """
<html>
    <head><title></title></head>
    <body>
        <form method="POST" action="/">
            <textarea name="comentario" cols="40" rows="10">
            </textarea>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>
"""

class PostHandler(webapp2.RequestHandler):
    def getself):
        self.response.out.write(html)

    def post(self):
        comentario = self.request.get('comentario')
        self.response.out.write(comentario)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Observe que a escrita do formulário HTML como resposta ao cliente ocorre dentro do método get(), que é chamado pelo ambiente de suporte sempre que houver uma requisição GET para /. No form temos dois atributos:

  1. method="POST": indica o método HTTP a ser usado para transmitir os dados ao servidor quando o usuário clicar no botão Enviar;
  2. action="/": indica para qual recurso serão enviados os dados do form quando o usuário submeter as informações clicando no botão.

Ou seja, após preencher o campo comentario e clicar no botão Enviar, o usuário faz com que o navegador envie uma mensagem HTTP POST ao recurso / do servidor. Essa mensagem contém, além do cabeçalho, a variável comentario com o conteúdo escrito pelo usuário nesse campo.

Como todas as requisições ao recurso / são tratadas por instâncias de PostHandler (veja a variável mapeamento), definimos em PostHandler um método chamado post() para lidar com requisições HTTP POST a esse recurso. Assim como ocorre para o método get(), o framework que estamos usando (webapp2) define que requisições do tipo POST serão tratadas por um método chamado post() implementado na classe responsável por manipular as requisições.

Sabendo disso, agora observe nosso método post() (chamado pelo ambiente de suporte quando for recebida uma requisição POST).

def post(self):
    comentario = self.request.get('comentario')
    self.response.out.write(comentario)

Veja que temos um atributo self.request que contém os campos enviados pelo usuário e que os valores desses campos podem ser acessados através de self.request.get('nome_do_campo').

Para simplificar nosso app, estamos apenas ecoando a mensagem enviada pelo usuário, de forma que após clicar em Enviar, este receba de volta a mensagem que enviou ao servidor (self.response.out.write(comentario)).

Agora, vamos testar o nosso app.

Testando o app

Para testar o app recém desenvolvido, siga os passos descritos no post anterior, trocando apenas o nome do diretório para o nome do diretório do projeto que criamos neste post.

GET x POST

No código do nosso appzinho, o manipulador trata dois tipos de requisições HTTP: GET e POST. Vamos ver rapidamente as diferenças entre elas.

GET

Método usado geralmente para envio de requisições que não resultem em alterações no servidor. Como o nome já diz, é indicado para a obtenção de dados.

Os parâmetros são passados para o servidor através da própria URL:

http://www.google.com/search?q=teste

No exemplo acima, passamos a variável q com o valor teste para o recurso /search, de forma que o servidor da Google vai acessar essa variável e realizar a busca usando o valor dela como filtro.

POST

Método usado quando a requisição causa alterações no servidor, como em uma requisição que envia dados a serem gravados num banco de dados. Diferentemente do método GET, quando usamos o POST em uma requisição HTTP, os parâmetros não são codificados na URL, mas sim no corpo da mensagem HTTP a ser enviada ao servidor.

Atenção

Deve ficar claro que a forma como armazenamos o conteúdo HTML do formulário (em uma variável no código) não é a melhor solução. Fizemos isso com a única finalidade de simplificar nosso exemplo. Em projetos “de verdade”, normalmente usamos um mecanismo para renderização de templates, que vem incluso com a maioria dos frameworks web. Se quiser saber mais, veja a documentação oficial do GAE sobre templates.

Sabendo como tratar dados recebidos do usuário e como manipular requisições GET e POST, você já pode começar a fazer projetinhos maiores. Vá em frente! 🙂

Desenvolvendo com Python e Google App Engine

Importante: a documentação oficial do Google App Engine aqui referenciada está disponível em português. Basta selecionar o idioma no canto inferior direito, caso esteja em outro idioma.

Sumário do post:

No último post nós vimos como desenvolver um serviço web bem simples usando Python e CGI. Mas, conforme vimos no próprio post, CGI não é o mecanismo mais adequado para dar suporte a um aplicativo web.

Do seu início até final da década de 90, a web era usada principalmente para a publicação de conteúdo em HTML. Porém, algum tempo depois ela passou a ser usada também como um ambiente para execução de aplicações completas.

O crescimento das aplicações possibilitou a observação de que boa parte dos projetos web tinham alguns componentes básicos em comum, gerando uma grande quantidade de retrabalho a cada novo projeto, o que acarretaria um alto custo para cada projeto, e também aumentaria as chances de o sistema apresentar defeitos. Visando eliminar boa parte do retrabalho, foram criados os frameworks de desenvolvimento, que fornecem vários serviços básicos para serem reutilizados pela aplicação, de forma que esta não precise reimplementá-los. (obs.: framework poderia ser traduzido como arcabouço)

A segurança de um aplicativo é um dos aspectos para os quais os frameworks possuem um conjunto de serviços para oferecer ao desenvolvedor. Isso é muito importante, pois não é todo programador que sabe como lidar com aspectos de segurança de uma forma adequada. Gerenciamento do banco de dados é outro serviço comumente oferecidos por frameworks para desenvolvimento web. Enfim, eles oferecem um ferramental bem interessante para que o desenvolvedor de uma aplicação web se preocupe principalmente com as funcionalidades e com a lógica de negócio do seu aplicativo, deixando um pouco “de lado” alguns aspectos que ficam sob responsabilidade do framework.

Alguns dos principais frameworks para desenvolvimento web em Python são:

O que nós vamos utilizar neste post não é apenas um framework, mas sim um conjunto completo para desenvolvimento, implantação e execução de aplicativos web, fornecido pela Google: o Google App Engine. Neste post, iremos apresentar o desenvolvimento de um aplicativozinho web que retorna alguma frase famosa para o usuário, algo como a frase do dia. Mas antes disso, vamos ver do que se trata o Google App Engine.

O Google App Engine

gaelogo

O Google App Engine (GAE) é uma plataforma para desenvolvimento de aplicativos web para serem hospedados na infraestrutura da Google. Além de oferecer uma interface bem interessante para gerenciamento dos aplicativos web (veja na figura abaixo), o GAE fornece de lambuja o balanceamento de carga da aplicação, espalhando a execução dela por vários servidores se assim for necessário, e alocando automaticamente mais recursos para a aplicação que estiver rodando nessa infraestrutura. Além da disponibilizar e gerenciar a infraestrura para o desenvolvedor, o GAE ainda provê uma série de facilidades para o desenvolvimento, como um framework para persistência de dados, para tratamento de requisições, para caching de dados, dentre outras coisas legais. E, pra ficar mais interessante ainda, é gratuito para quem quiser testar ou desenvolver aplicativos sem maiores pretensões.

http.5.1

Painel de controle do GAE

Bom, chega de propaganda e vamos ao que interessa!

Instalando e configurando o GAE

Como já foi comentado, a hospedagem de aplicativos escritos para o GAE fica por conta da Google, mas para podermos testar localmente nosso app, é preciso que instalemos um ambiente próprio para isso. Siga os passos abaixo:

  1. Baixe o arquivo correspondente à sua plataforma clicando aqui;
  2. Descompacte o arquivo acima no diretório de sua preferência;

Mais informações sobre a instalação do ambiente podem ser encontradas aqui.

Desenvolvendo nosso app

Vamos começar agora o desenvolvimento de nosso aplicativo de frase do dia. A primeira coisa a fazer é criar um diretório onde nosso app ficará localizado. Podemos chamar tal diretório de frasedodia, ou o nome que você achar mais conveniente. Dentro do diretório recém criado, crie um arquivo chamado frasedodia.py e outro chamado app.yaml. O primeiro é a nossa aplicação em si e o último é o arquivo que irá conter as configurações do nosso projeto.
A estrutura do nosso projeto ficará assim:

frasedodia/
  frasedodia.py
  app.yaml

Agora, precisamos escrever as configurações do nosso projeto. Para isso, cole o seguinte conteúdo dentro do arquivo app.yaml:

application: frasedodia
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: frasedodia.py

O mais importante a entender neste momento é a opção handlers. Dentro dela, temos um item chamado url com valor /.* e um atributo script com o nome do nosso arquivo .py como valor. Essa configuração significa que requisições para qualquer recurso dentro do projeto (/.*) serão encaminhadas para o arquivo frasedodia.py. Se por acaso quiséssemos que somente as requisições para os recursos contidos em uma pasta app fossem encaminhadas ao arquivo handlers.py, faríamos o seguinte:

handlers:
- url: /app/.*
  script: handlers.py

Assim, uma requisição para frasedodia.appspot.com/app/qualquercoisa seria encaminhada para o script definido na configuração acima (handlers.py). Agora vamos nos concentrar no código que irá atender às requisições do usuário.

Manipulador de requisições

Se quisermos tomar proveito das vantagens que o GAE nos fornece, é interessante que não usemos CGI. Vamos utilizar um framework para web bem simples chamado webapp2 que já é fornecido juntamente com o GAE.

O primeiro passo que devemos realizar é definir quais classes terão suas instâncias responsáveis por manipular requisições para determinadas URLs. Uma vez que já definimos que todas as requisições para o aplicativo serão direcionadas a frasedodia.py, devemos agora definir, dentro desse arquivo, o mapeamento de URLs para classes. Insira o código a seguir em frasedodia.py:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

mapeamento = [
    ('/', FraseDoDia)
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

No código acima, definimos que toda requisição para a raiz de nosso app será tratada por instâncias da classe FraseDoDia. Mas, a classe FraseDoDia não é uma classe qualquer. Ela deve obrigatoriamente estender a classe webapp2.RequestHandler, para que suas instâncias sejam também instâncias de RequestHandler. Nosso app irá criar uma instância da classe FraseDoDia e irá chamar nessa instância o método apropriado para tratar a requisição (get() para o caso de requisição GET ou post() para o caso de requisição POST). Esse método irá tratar a requisição e devolver uma resposta ao cliente. Tudo isso é feito automagicamente pelo framework de suporte, ficando ao nosso cargo apenas criar as classes que irão ser responsáveis por tratar as requisições.

Objetos do tipo RequestHandler possuem dois atributos muito importantes para o desenvolvedor:

  • self.request: é uma instância de Request, contendo a requisição recebida do usuário;
  • self.response: é uma instância de Response, contendo a mensagem a ser enviada como resposta à requisição do usuário.

Dessa forma, instâncias da classe RequestHandler devem fazer o seguinte, a cada requisição recebida:

  1. Tratar os dados recebidos através do objeto self.request;
  2. Gravar os dados a serem enviados ao usuário no objeto self.response.

Assim, devemos então implementar nossa classe FraseDoDia como uma subclasse de RequestHandler e nela implementar o método get() para retornar a frase para o cliente. Vamos então adicionar o código da classe ao nosso arquivo frasedodia.py:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

class FraseDoDia(webapp2.RequestHandler):

    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        self.response.out.write('{"frase": "Uma frase qualquer!"}')

mapeamento = [
    ('/', FraseDoDia)
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Vendo o código acima, dá pra entender que quando ocorre uma requisição GET para o recurso / de nossa aplicação, um objeto do tipo FraseDoDia será designado pelo framework de suporte para tratar tal requisição, executando o método get(). Esse método, por sua vez, faz duas coisas:

  1. Adiciona ao cabeçalho da resposta HTTP — self.response.headers — o tipo do conteúdo a ser enviado como resposta ao cliente (text/json), de forma que o navegador saiba o que fazer com o conteúdo recebido;
  2. Escreve no corpo da resposta HTTP — self.response — o conteúdo a ser enviado ao cliente, que são dados representados em formato JSON.

Tudo o que precisamos fazer é implementar a classe que vai tratar das requisições à determinadas URLs, sem a necessidade de chamá-las nem nada. Quem faz isso é o ambiente de suporte provido pelo framework webapp.

Tendo implementado uma versão básica de nosso serviço, vamos colocar nosso ambiente de testes em funcionamento.

Executando o ambiente de testes

Vá até o local onde você extraiu o ambiente do GAE em sua máquina. Dentro dessa pasta, execute o seguinte comando:

./dev_appserver.py /caminho/para/o/projeto/frasedodia/

O caminho mostrado acima, passado como argumento para o programa dev_appserver.py, é referente ao diretório onde criamos os arquivos app.yaml e frasedodia.py.

O comando acima iniciou o servidor local para desenvolvimento e testes na porta 8080. Podemos acessar o serviço através da URL: http://localhost:8080/. Ao acessar essa URL, você terá como resposta o conteúdo JSON retornado pelo nosso serviço Web:

{"frase": "Uma frase qualquer!"}

Por enquanto estamos retornando apenas uma frase constante, e a idéia do serviço é retornar uma frase aleatória. Para tanto, temos algumas opções:

  • Manter algumas strings em memória em uma lista;
  • Manter registros no banco de dados e buscar um ao receber uma requisição.

Por fins de simplicidade, vamos seguir a primeira opção. Vamos adicionar uma nova classe chamada FraseAleatoriaDoDia, implementar o método get(), já que essa classe vai estender RequestHandler, e adicionar a nova classe no mapeamento de URLs, relacionado a URL /random. Veja o código completo abaixo:

import random
import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

class FraseDoDia(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        self.response.out.write('{"frase": "Uma frase qualquer!"}')

class FraseAleatoriaDoDia(webapp2.RequestHandler):
    frases = [
        ('Insanity: doing the same thing over and over again and expecting different results', 'Albert Einstein'),
        ('The world is a dangerous place to live; not because of the people who are evil, but because of the people who don\'t do anything about it.', 'Albert Einstein'),
        ('A person who never made a mistake never tried anything new.', 'Albert Einstein'),
        ('Love all, trust a few, do wrong to none.', 'William Shakespeare'),
        ('A fool thinks himself to be wise, but a wise man knows himself to be a fool.', 'William Shakespeare'),
        ('Darkness cannot drive out darkness; only light can do that. Hate cannot drive out hate; only love can do that.', 'Martin Luther King, Jr.')
    ]

    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        i = random.randint(0, len(self.frases)-1)
        self.response.out.write('{"frase": "%s", "autor": "%s"}' % (self.frases[i][0], self.frases[i][1]))

mapeamento = [
    ('/', FraseDoDia),
    ('/random', FraseAleatoriaDoDia),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Rode o servidor de aplicação local com./dev_appserver.py /caminho/para/o/projeto/frasedodia/ e acesse http://localhost:8080/random para obter uma das frases.

Com o app funcionando, agora você pode registrar e fazer upload do mesmo para os servidores da Google, se assim quiser. Se desejar colocar em produção o app que implementamos aqui, siga as instruções contidas na documentação oficial, na seção que trata sobre o upload de um app.

Depois de fazer o upload de seu app, você poderá acessá-lo através de uma URL dentro de .appspot.com, como: http://id_da_aplicacao.appspot.com. Para ver informações sobre a sua aplicação, você pode acessar o painel de controle da sua conta no GAE através do endereço: appengine.google.com/.

O Google App Engine é uma ótima opção para dar suporte a uma aplicação web, visto que com ele eliminamos a necessidade de contratar um plano de hospedagem, de gerenciar o servidor web, de cuidar do balanceamento de carga, etc. Vale a pena dar uma estudada mais a fundo no assunto. 🙂

Aprenda mais