Programando para a Web em Python

Sumário

Este é o quarto post da série sobre Python + Web. Nós já vimos como fazer requisições HTTP a partir de um programa Python, como fazer extração de dados de conteúdo HTML obtido via HTTP e como lidar com dados em formato JSON retornados por um serviço Web. Porém, até agora trabalhamos somente no lado cliente, obtendo dados via HTTP para então fazer algo com eles. A partir de agora vamos para o lado do servidor, onde iremos escrever o provedor do serviço.

Neste post, veremos como construir um servicinho web bem simples, que retorne para o cliente o dia da semana relativo a determinada data, codificado em formato JSON.

Web em Python

Python é uma linguagem muito versátil, podendo ser usada para vários fins. Um dos nichos em que Python mais tem sido usada ultimamente é no desenvolvimento web. E quando se trata de web, temos várias ferramentas disponíveis para usar com Python. Este post irá apresentar a forma mais simples de todas: o CGI (Common Gateway Interface).

CGI

O funcionamento do CGI é bem simples: ao receber uma requisição, o servidor web roda um programa executável para gerar o conteúdo a ser retornado para o cliente (é esse programinha executável que vamos implementar). Assim, um programinha bem simples que poderia funcionar juntamente com um servidor CGI é:

#!/usr/bin/python

# cabecalho que informa o browser para renderizar como HTML
print "Content-Type: text/html\n\n"
# o conteudo em si
print "<strong>Hello, world!</strong>"

Se você quiser brincar um pouquinho com CGI, siga os seguintes passos:

  1. Crie uma pasta chamada src e dentro dela outra pasta chamada cgi-bin;
  2. Salve o arquivo acima como hello.py dentro da pasta cgi-bin recém criada;
  3. Dê permissão de execução ao arquivo hello.py: chmod +x hello.py;
  4. Pelo shell de comandos do seu sistema operacional, vá até a pasta src e execute o servidorzinho CGI que Python traz consigo: python -m CGIHTTPServer 8000
  5. Abra um navegador e acesse a URL: http://localhost:8000/cgi-bin/hello.py
  6. Como resultado, você deverá ver a mensagem “Hello, world!” em negrito no seu navegador.

Mas, para que faça sentido, é preciso que nosso aplicativo seja capaz de obter eventuais informações que o usuário passar a ele. Usando o módulo cgi, podemos manipular parâmetros fornecidos pelo usuário, dentre outras possibilidades. Veja o exemplo abaixo:

#!/usr/bin/python
import cgi

# obtem referencia as variaveis passadas pelo cliente
fields = cgi.FieldStorage()

# cabecalho que informa o browser para renderizar como HTML
print "Content-Type: text/html\n\n"

nome = fields.getvalue('nome')
print "<strong>Hello, %s!</strong>" % (nome)

Salve o arquivo acima no mesmo diretório cgi-bin com o nome de hellouser.py, dê permissão de execução para o mesmo (chmod +x hellouser.py) e então acesse no seu browser: http://localhost:8000/hellouser.py?nome=Seu Nome Aqui.

Como você deve saber, /hellouser.py?nome=Seu Nome Aqui significa que estamos passando para o “programa” /hellouser.py a variável nome com o valor Seu Nome Aqui, tudo pela URL, através do método HTTP GET.

Preparando a implementação do serviço

Antes de sairmos implementando detalhes do serviço web que nos comprometemos a implementar, vamos criar uma funçãozinha que será parte essencial do nosso projeto. Essa função irá receber 3 valores: dia, mês e ano, e irá retornar o dia da semana em forma de string. Por exemplo:

dia_da_semana(27, 3, 2013) --retorna--> "Quarta-feira"
dia_da_semana(24, 3, 2013) --retorna--> "Domingo"

Para implementá-la, vamos usar o método weekday() dos objetos date, disponível no módulo datetime. A documentação dessa função diz o seguinte (tradução própria):

Retorna o dia da semana como um inteiro, onde Segunda-feira é 0 e Domingo é 6.

Assim, tudo o que teremos que fazer é construir uma instância de date com os dados fornecidos como parâmetro para nossa função, e invocar o método weekday() nesse objeto. Exemplo:

>>> from datetime import date
>>> d = date(2013, 3, 27)
>>> print d.weekday()
2

2 significa que a data em questão, 27/03/2013 cai numa Quarta-feira, visto que Segunda-feira é 0 e que Domingo é 1. Perceba que para construir um objeto do tipo date, nós passamos as informações sobre dia, mês e ano em ordem inversa à que estamos acostumados no Brasil. A assinatura do método construtor de objetos date é:

date(ano, mês, dia)

Perfeito, agora só falta definirmos uma forma de obtermos os nomes dos dias em língua portuguesa. Vamos usar uma abordagem bem simples, mas que resolve nosso problema. Vamos usar uma lista contendo as strings representando os dias da semana. (claro que se fôssemos dar suporte a usuários de vários países, a história seria diferente)

from datetime import date

dias = [
    'Segunda-feira',
    'Terça-feira',
    'Quarta-feira',
    'Quinta-feira',
    'Sexta-feira',
    'Sábado',
    'Domingo'
]

def dia_da_semana(dia, mes, ano):
    try:
        return dias[date(ano, mes, dia).weekday()]
    except ValueError:
        return 'Erro: data mal-formatada.'

Simples, huh? Utilizamos o inteiro retornado pelo método weekday() como índice para nossa lista com os nomes dos dias da semana. Além disso, envolvemos nosso código com try-except pois o usuário pode muito bem fornecer uma data inválida, como 31/02/2013 ou 100/123/2013. O construtor date() levanta a exceção ValueError quando recebe parâmetros que não representam uma data válida.

Feito isso, agora podemos implementar nosso serviço. (leia atentamente o código acima, caso não tenha entendido algo)

Implementando o serviço web

Já vimos como implementar um aplicativozinho web beeeeeeeem simples usando CGI, também já vimos como obter o dia da semana em que determinada data cai, e num post anterior vimos como codificar e decodificar dados em formato JSON. Agora, basta juntarmos as peças para implementar o serviço.

Só para relembrar, estamos implementando um serviço web que retorna o dia da semana relativo à determinada data fornecida pelo usuário. O usuário irá fornecer a data no formato dd/mm/aaaa. Por exemplo, o usuário pode solicitar a data fazendo uma requisição ao serviço da seguinte forma: http://um.host.qualquer.com/diasemana.py?data=27/3/2013.

Segue o código do nosso serviço:

#!/usr/bin/python
# -*- encoding:utf-8 -*-
import cgi
import json
from datetime import date

dias = [
    'Segunda-feira',
    'Terça-feira',
    'Quarta-feira',
    'Quinta-feira',
    'Sexta-feira',
    'Sábado',
    'Domingo'
]

def quebra_data(data):
    if (data.count('/') == 2 and
            all((x.isdigit() for x in data.split('/')))):
        return [int(x) for x in data.split('/')]
    else:
        raise ValueError

def dia_da_semana(data):
    try:
        dia, mes, ano = quebra_data(data)
        return dias[date(ano, mes, dia).weekday()]
    except ValueError:
        return 'Erro: data mal-formatada.'

print "Content-Type: text/json\n\n"
fields = cgi.FieldStorage()
data = fields.getvalue('data')
dia_str = dia_da_semana(data) if data is not None else 'Erro'
print json.dumps({'data': data, 'diasemana': dia_str})

Como a data fornecida pelo usuário é recebida no programa através do cgi.FieldStorage() sob a forma de string, é preciso que separemos essa data em dia, mês e ano. Para isso, criamos a função quebra_data() que antes de mais nada verifica se a data fornecida pelo usuário está no formato adequado (dia/mês/ano), e caso não esteja, a função levanta uma exceção ValueError para ser capturada pela função dia_da_semana(). Se a data estiver no formato correto, é retornada uma lista com três valores inteiros: dia, mês e ano. O método split(), usado em nossa função quebra_data separa a string em questão em todas as ocorrências do caractere separador fornecido como entrada ('/') e retorna uma lista contendo as substrings.

Ao final de nosso programinha, geramos um dicionário com as informações a serem codificadas em JSON e então imprimimos a informação JSONificada.

Momento “Lição de Vida”

Como você pode perceber lendo o código acima, boa parte da lógica do programa envolve a verificação dos dados fornecidos pelo usuário. Em se tratando de serviços ou aplicativos web, nunca podemos ser relapsos com os dados fornecidos pelo usuário, afinal o sistema está exposto pela web, podendo ser acessado de qualquer lugar do mundo, por pessoas com os mais variados níveis de conhecimento e com as mais variadas intenções. Às vezes, a falta de validação de um simples dado fornecido pelo usuário pode ser o suficiente para que o usuário destrua o nosso serviço ou, o que muitas vezes é pior, obtenha acesso à informações que deveriam ser sigilosas.

Portanto, nunca deixe de verificar todo e qualquer dado que um usuário forneça para o seu sistema. Às vezes ficamos com preguiça, com pena de poluir o código com “todas aquelas validações”, ou até achando que “os nossos usuários nunca tentariam sacanear o sistema”, mas não caia na bobagem de seguir esses pensamentos. Existem várias histórias de sistemas que foram destruídos e de pessoas que perderam seus empregos por deixar de filtrar os dados fornecidos pelo usuário.

Python + CGI está na crista da onda, então?

Não. Programação web com CGI nada mais é do que escrevermos programas que serão executados pelo servidor web quando houver uma requisição para eles. Essa simplicidade toda tem o seu custo: a cada requisição recebida pelo servidor web, ele inicia um novo processo que irá executar o código do recurso solicitado. Assim, recursos que de outra forma poderiam ser compartilhados entre instâncias são perdidos ao fim de cada processo. Conexões com o banco de dados, por exemplo, são reestabelecidas a CADA requisição feita para o serviço quando usamos CGI. Ou seja, sistemas web baseados em CGI dificilmente irão escalar quando necessário for.

Além disso, programar somente com CGI não é muito prático, pois temos que nos preocupar com diversos elementos que podem ser abstraídos e passados para uma outra camada. E outra coisa, código Python imprimindo HTML é muito anos 90! 😉

Para facilitar a nossa vida e resolver alguns dos problemas acima descritos, existem os frameworks para desenvolvimento web, que serão assunto para um próximo post.

Então CGI é inútil?

Não, longe disso. Com CGI, podemos executar programas remotamente e, de lambuja, ver o resultado da execução desse programa em nosso navegador. Se ele gerar HTML na saída, melhor ainda! 🙂

MAS, é claro que não vamos deixar exposto na web um programa que apaga todos os arquivos do disco rígido do servidor em que está rodando. 😛 Só que nada impede que disponibilizemos um scriptzinho inocente via CGI para que outra pessoa possa o acessá-lo remotamente, sem a necessidade de possuir acesso SSH àquela máquina. O legal é que tudo que é preciso para o acesso é um web browser, e, como você sabe, hoje em dia até as TVs possuem web browsers, sem falar nos telefones celulares.

27 comentários sobre “Programando para a Web em Python

  1. Pingback: Desenvolvendo com Python e Google App Engine | Python Help
  2. Olá! Sou extremamente iniciante e tenho uma dúvida… Eu tenho um programa em python no meu pc. Tem como eu rodá-lo no meio de uma página em html ou php? Ex: meu programa gera nomes aleatórios. Dá pra eu criar um botão na minha página que, quando clicado, aciona esse programa python?

    • @Desmond: os navegadores, por padrão, não conseguem executar código Python. Eles conseguem sim executar código JavaScript. Entretanto, existem algumas formas de converter código Python para JavaScript para que seu código possa ser executado pelo navegador que receber a página HTML (https://wiki.python.org/moin/WebBrowserProgramming).

      Mas, como você se declarou extremamente iniciante, essa não é uma tarefa muito trivial. Os exemplos que mostrei acima são todos executados no lado servidor e não no cliente (navegador) e essa é a forma mais comum e recomendada de usar Python na web. Até existem maneiras de executar no cliente, como falei acima, mas não aconselho a fazer isso. Uma tarefa simples como gerar um número aleatório ao clicar no botão é tarefa para ser feita no cliente mesmo, então a melhor forma de resolver é com JavaScript mesmo.

  3. Olá, preciso da sua ajuda.
    Eu segui os passos corretamente mas não consigo rodar o script do hello.py.
    No meu terminal aparece printa na tela:
    localhost – – [23/Sep/2013 16:25:12] code 403, message CGI script is not a plain file (‘/cgi-bin/’)
    localhost – – [23/Sep/2013 16:25:12] “GET /cgi-bin/ HTTP/1.1” 403 –

    O arquivo hello.py é exatamente o mesmo que neste tutorial e o comando para rodar o mini servidor do python também é o mesmo. Não estou usando nem o python 3.x

    ps: Eu estou usando Ubuntu 12.04 LTS

    Obrigado antecipadamente

  4. Boa tarde, VALDIR STUMM JUNIOR, estou aqui com dúvida, pois meu servidor é o Apache porém ele roda no Wamp Server, vc tem como me explicar como faço para que o Wamp rode programas com CGI? Estou precisando muito, desde já obrigado, se tiver alguma vídeo aula ou um tutorial me passe o link por favor. Desde já Obrigado.

    • @Kaiki: Olá, nunca usei o wamp server, mas pelo que vi dando uma rápida procurada, você precisa fazer o seguinte:
      1. ter python instalado
      2. ter o wamp instalado e com o módulo cgi_module habilitado (pelo que vi você pode habilitá-lo diretamente no menu do wamp, na opção apache -> modules.
      3. salvar seu script arquivo.py na pasta C:\wamp\bin\apache\ApacheX.Y.ZZ\cgi-bin
      4. acessar http://localhost/cgi-bin/arquivo.py em um navegador

      É isso. Espero que ajude.

      • Não ta dando certo, vc tem como me mandar um e-mail, ou algo do tipo, estou precisando, por favor isso poderá ajudar no TCC, desde já obrigado pela atenção, se poder me passar um tutorial, pois já pesquisei em vários sites e sempre dar erro, se vc poder me dar mais essa ajuda agradeço. Desde já obrigado.

  5. Valdir. Gostei muito de seu artigo. Muito bom.
    Sobre a “questão do cgi”, apenas por curiosidade, talvez você queira ver:
    https://sites.google.com/site/artwebdb/ ou mais especificamente: Migração de Aplicações para Ambiente Web – Uma Abordagem Prática > Migração para ambiente Web – Geração e Mecanismo de Acesso > Migração para ambiente Web – Mecanismo de Acesso. Sei que é uma solução que normalmente não se usa, mas funciona.
    Sérgio

  6. @stummjr eu estava procurando uma vez na web e achei um meio de execulta script Python no html por meio de uma tag parecido com a tag do PHP só que esqueci e não encontrei mais ‘-‘
    você como um bom anfritrião poderia me ajudar?

    • Tudo está conspirando para você mudar para o Linux, eu so usava windows antes, pra mim Linux era um lixo, depois que eu pensei: Se muitos programadores defendem o Linux com unhas e dentes, vou dar uma estudada… Foi amor a primeira interação, quando você acostuma, tudo é mais facil, uma instalação, execução de script, e uma liberdade que so quem usa Linux sente…
      Esperimente Joilson…

  7. estou usando o servidor proprio criado com classes HTTPServer, CGIHTTPRequestHandler do objeto http.server, o problema e que quando digito localhost:8080/cgi2.py o webbrowser me retorna o codigo escrito em python, como se o cgi nao funcionasse, estou usando !#/usr/bin/python3 e ja dei permissao chmod 755, ao inves de usar print busei sys,out,write, mas nao funciona como deveria, so funciona se for pagina html, o meu codigo segue abaixo:

    ME AJUDEM< COMPLETAMENTE NA ONÇA!!!
    #!/usr/bin/python3 """ ja usei tbm env python3"""

    import cgitb
    import cgi

    print("”)
    print(” ola mundo”)

      • nao no shell entro na pagina onde esta escrito o scrpit do servidor (nome do arquivo e servidor.py) e digito sudo python3 servidor,py —>> este é o codigo do servidor:

        #!/usr/bin/env python3

        import os, sys
        from http.server import HTTPServer, CGIHTTPRequestHandler

        #diretorio utilizado para manter arquivos html e scipts cgi
        webdir = ‘.’

        port = 8080

        # os argvs pega os parametros da linha de comando no shell se estes forem fornecidos por lá
        if len(sys.argv) >1: webdir = sys.argv[1]

        if len(sys.argv) >1: port = int( sys.argv[2])

        #imprime so ilde do python3 as configurações utilizadas
        print(“webdir ‘%s’, port %s”%((webdir, port))

        #muda o diretorio de trabalho
        os.chdir(webdir)

        #configura o endereço do servidor
        srvraddr = ( ” “,port)

        #cria o servidor http com handler de http
        srvobj = HTTPServer(srvraddr, CGIHTTPRequestHandler)

        #espera receber algum comando a ser executado
        srvobj.serve_forever()

Deixe um comentário