.. highlight:: python :linenothreshold: 5 =============================================================== Capítulo 6: Perca o medo do servidor Apache e do protocolo CGI =============================================================== É hora de colocar em prática os nossos conhecimentos de funções e módulos para criar nosso primeiro programa para a Internet: o Calendário Dinâmico. Uma vez instalado em um servidor web, ele exibirá o calendário do mês atual com o dia de hoje assinalado. Ao final desse capítulo você terá construído seus primeiros programas CGI em linguagem Python. Mas para chegar lá, é preciso entender o funcionamento de um CGI, e conhecer como se dá a operação básica de um servidor HTTP. Quem é quem do HTTP A Web é construída a partir de duas tecnologias fundamentais: a linguagem HTML e o protocolo HTTP. HTML, ou Hypertext Markup Language é a codificação usada para criar as páginas da Web. Este não é o assunto deste curso, mas vamos usar um pouco de HTML nos exemplos desse capítulo. O segundo pilar da Web é o HTTP, ou Hypertext Transport Protocol - protocolo de transporte de hipertexto. Esse é o conjunto de comandos e regras que define como deve ser a comunicação entre um browser (como o Internet Explorer ou o Mozilla) e um servidor HTTP (como o Apache ou o Interner Information Server). A expressão "servidor HTTP" pode significar duas coisas: o software que serve páginas via HTTP, ou o computador onde esse software está instalado. No mundo Unix, softwares servidores são chamados de "daemons", e a sigla HTTPd descreve um "HTTP daemon" genérico. Essa é a sigla que vamos usar para diferenciar o software do hardware. .. image:: _static/img_06_01.png A relação entre um browser e um HTTPd é descrita pelos computólogos como "cliente-servidor". Isso significa que a interação entre esses dois softwares sempre parte do browser, que é o cliente. O servidor não tem nenhuma iniciativa, limitando-se a responder aos comandos enviados pelo cliente. Quando você digita uma URL como https://codigo42.github.io/aprendaprog/, o seu browser localiza e conecta-se ao servidor ``codigo42.github.io`` e envia-lhe o comando ``GET /aprendaprog/``. O servidor então lê o arquivo ``index.html`` da pasta ``aprendaprog``, transmite seu conteúdo para o cliente e encerra a conexão. Esses são os passos básicos de qualquer interação de um browser com um HTTPd: conexão, solicitação, resposta e desconexão. Páginas dinâmicas ================== No exemplo que acabamos de ver, ``index.html`` é o que chamamos de uma página estática. A resposta do servidor consiste apenas em enviar uma cópia do documento para o cliente. Sites que incluem transações (como lojas virtuais), interatividade (como chats), ou atualizações muito freqüentes (como este) utilizam páginas dinâmicas. Neste caso, ao receber a URL http://www.magnet.com.br/ index_html, nosso servidor HTTPd Apache passa a solicitação para o aplicativo Zope, instalado no servidor. O Zope monta imediatamente a página ``index_html`` listando as notícias mais recentes de nosso banco de dados, a hora atual e outros elementos. A página recém montada então é passada para o Apache, que finalmente a transmite para o seu navegador. O Zope é apenas uma das tecnologias de páginas dinâmicas que existem hoje. O ASP da Microsoft, o !ColdFusion da Macromedia e o software livre PHP são outros sistemas dinâmicos de montagem de páginas. Mas o mecanismo mais antigo, e também o mais simples de entender e de configurar, é o velho e bom CGI - ou Common Gateway Interface, um protocolo básico para interação entre um HTTPd e um programa gerador de páginas dinâmicas. É com ele que nos vamos trabalhar a partir de agora. Configurando o seu HTTPd ========================= Para desenvolver aplicativos CGI é importante ter um bom ambiente de testes. O ideal é ter acesso a um HTTPd só para você na fase de desenvolvimento, para maior agilidade na depuração, e não correr o risco de comprometer o funcionamento de um servidor público com bugs nos seus CGIs em construção. Pode ser que o seu micro já possua um servidor HTTP. A Microsoft inclui o Personal Web Server ou o IIS em diferentes versões do Windows. Você pode tentar usar um desses HTTPd para fazer os exemplos, mas sugerimos fortemente que você vá até o http://www.apache.org baixe o Apache, que não custa nada (é open source), roda em qualquer plataforma Win32 ou Unix, é fácil de instalar e é tão robusto e versátil que é o HTTPd mais usado em todo mundo, alem de ser o favorito disparado entre os melhores e maiores sites da Web. Vale a pena conhecê-lo, e o download tem apenas 3 MB. No Windows, o Apache vem com um instalador bem amigável. Nossa única recomendação é instalar diretamente em um diretório como c:\apache e não no famoso c:\Arquivos de Programas. Isso porque os espaços em nomes de diretórios às vezes causam problemas na execução de programas originários do Unix como Python e o próprio Apache. Uma vez terminada a instalação, você deve rodar o servidor, acionando o programa "Start Apache" que foi instalado em Iniciar > Programas > Apache Web Server. Isso faz abrir uma janela DOS com uma mensagem como "Apache/1.3.9 (Win32) running...". Não feche esta janela, pois isso encerrará a execução do servidor. Agora você pode ver se está tudo certo digitando essa URL mágica em seu browser: http://127.0.0.1/. Se a instalação foi bem sucedida, você verá uma página com o texto: "It Worked! The Apache Web Server is Installed on this Web Site!" (Funcionou! O servidor Apache está instalado neste Web Site!). (Figura 1). .. image:: _static/img_06_02.png Vale a pena saber que o endereço 127.0.0.1 tem um significado especial. Os criadores da Internet reservaram esse número IP para o "loopback", ou seja, testes de conexão de uma máquina com ela mesma. Em outras palavras, o endereço 127.0.0.1 sempre se refere à maquina onde você está, que é conhecida também pelo nome "localhost". Se o seu micro estiver bem configurado, a URL http://localhost/ deve ter o mesmo efeito. Caso contrário, utilize o número IP e vamos em frente. Seu primeiro CGI ================= Chegamos ao grande momento. Seguindo a tradição milenar, vamos começar fazendo um CGI em Python que produz uma página com as palavras "Olá, Mundo!". O programa completo você vê na listagem abaixo:: #!/python/python print 'Content-type: text/html' print print '
' print '* de acordo com o relógio interno deste servidor
' print '' Uma vez salvo no diretório cgi-bin, este script poderá ser acessado pela URL http:// 127.0.0.1/cgi-bin/hora.py. A página gerada conterá a hora, minutos e segundos do instante em que ela foi invocada. Qual o defeito do nosso relógio em CGI? Experimente e você verá. Um relógio que se atualiza =========================== É um pouco frustrante acessar uma página que mostra a hora certa, com precisão de segundos, mas fica parada no tempo (Figura 2). Para atualizar os segundos, você tem que acionar o comando de "reload" do seu browser (Exibir ¡ Atualizar ou [F5] no Internet Explorer; View ¡ Reload ou [Control][R] no Navigator). Nossa página parece um relógio quebrado, que só mostra a hora certa quanto chacoalhado. .. image:: _static/img_06_03.png O ideal seria que o servidor atualizasse a página que está no seu browser a cada segundo. Infelizmente, isso é impossível. Como já dissemos, o protocolo HTTP é do tipo cliente-servidor, e isto quer dizer que a iniciativa de toda interação fica do lado do cliente. Não há como o servidor por conta própria enviar uma nova página sem que ela seja antes solicitada pelo navegador. Esta é uma limitação importante do protocolo HTTP que você precisa ter em mente ao bolar seus programas CGI. Os browsers modernos suportam uma uma solução parcial para este problema. Eles reconhecem um cabeçalho especial chamado Refresh, cuja presença em um documento serve para instruir o browser a solicitar novamente a página após algum tempo. O argumento do Refresh é um número de segundos que o navegador deve esperar para pedir a atualização. Logo veremos como isso funciona na prática. Para usar o Refresh basta acrescentar uma linha ao cabeçalho da resposta gerado pelo nosso CGI hora.py. A nova versão, ``hora2.py`` ficará assim:: #!/python/python # hora2.py - CGI que exibe continuamente hora local do servidor from time import time, localtime print 'Content-type: text/html' print 'Refresh: 0.6' print h, m, s = localtime(time())[3:6] print '' print '* de acordo com o relógio interno deste servidor
' print '' A única novidade é a linha 7, onde acrescentamos "Refresh: 0.6" ao cabeçalho. Em vez de mandar o browser atualizar a página a cada 1 segundo, após alguns testes decidimos fazê-lo a cada 6 décimos de segundo. Fizemos assim porque quanto experimentamos com "Refresh: 1" a contagem freqüentemente pulava um segundo, por exemplo de 10:30:20 direto para 10:30:22. Isso não quer dizer que o relógio adiantava, porque a cada nova solicitação a hora certa estava sendo consultada pelo nosso CGI; mas como o tempo de espera somado ao tempo de solicitação e resposta era maior que 1 segundo, a exibição da hora sofria alguns sobressaltos. Fazendo o refresh a cada 6 décimos, muitas vezes estamos atualizando a página duas vezes no mesmo segundo, o que é um desperdício de processamento. Mas pelo menos nos livramos da enervante perturbação na contagem. É claro que se o servidor estiver sobrecarregado, ele pode levar mais de um segundo para responder. Nesse caso, de nada adiantará se o browser fizer novas solicitações a cada 0.6 segundo. Calendário Dinâmico ===================== Agora vamos juntar o que já sabemos sobre CGI com o módulo calendar que vimos no capítulo anterior para fazer um protótipo rápido do nosso Calendário Dinâmico. As passagens mais interessantes da listagem abaixo são comentados a seguir:: #!/python/python # calendin.py - calendário dinâmico - protótipo 1 print 'Content-type: text/html\n' try: from time import time, localtime from calendar import monthcalendar from string import join ano, mes = localtime(time())[:2] print '' for semana in monthcalendar(ano, mes): print join( map(str, semana),'\t' ) print '' except: import sys sys.stderr = sys.stdout from traceback import print_exc print '
' print_exc() print '' print '' O que fizemos, linha a linha: Linha 4 Logo de saída produzimos o cabeçalho mínimo necessário. A linha em branco, em vez de ser gerada por um segundo comando print, está incluída no final própria string do cabeçalho (o '\n' representa uma quebra de linha, e o próprio print produz outra quebra; assim obtemos a linha em branco para encerrar o cabeçalho). Linha 6 Para facilitar a depuração, colocamos praticamente o CGI inteiro dentro de um bloco try/except. Qualquer falha na execução deste bloco será tratada a partir da linha 23. Com isso, esse script só deverá gerar um "Internal Server Error" se houver um erro de sintaxe, justamente o tipo de falha mais fácil de localizar rodando o script a partir de uma linha de comando. Desta forma a depuração fica bem mais simples. Linhas 7 a 9 Importamos várias funções, todas velhas conhecidas. Linha 11 Extraímos o ano e o mês do resultado de localtime(time()). Linha 13 Iniciamos a produção do HTML, agora colocando um título na página (que aparecerá na barra de título da janela do browser). Linhas 17 a 20 Para simplificar a formatação do calendário, colocamos seu conteúdo entre um par de tags . O tag
faz com que o browser respeite as quebras de linha até o tag. Normalmente, o navegador ignora tabulações e quebras de linha, tratando tudo como simples espaços, mas isso estragaria nosso calendário, pois queremos mostrar uma semana por linha. O código das linhas 18 e 19 foi roubado sem alterações da listagem 3 do capítulo anterior. Linha 22 Abrimos um bloco except para tratar qualquer erro que tenha ocorrido até aqui. Abrir um bloco except sem qualificar o tipo de exceção que será tratado é normalmente uma má idéia, porque pode mascarar muitos bugs. Nesse caso, o except "pega-tudo" está justificado porque em seguida exibiremos o traceback completo, revelando qualquer bug que tentar se esconder. Linhas 23 e 24 Importamos o módulo sys, para podermos manipular os objetos stdout e stderr. Esses são os chamados "dispositivos lógicos" para onde toda a saída de dados do Python é direcionada. Mensagens geradas pelo comando print são enviadas para stdout, que normalmente está associado à tela do computador ou terminal. Durante a execução de um CGI, o stdout é redirecionado para o HTTPd, que vai enviar para o cliente tudo o que passar por este dispositivo. Mensagens de erro e tracebacks do Python, no entanto, vão para stderr. Se o script é invocado pela linha de comando, o stderr também está associado à tela, e assim podemos ver os tracebacks. Mas ao executar um CGI, o HTTPd simplesmente ignora o dispositivo stderr após o envio do cabeçalho, ocasionando a perda dos tracebacks. Na linha 24 associamos sys.stderr ao objeto sys.stdout. Desta maneira as mensagens de erro passam a ser enviadas para o browser através do HTTPd, como ocorre com os textos gerados por print. Linha 25 Importamos uma função do módulo traceback para uso na linha 27. Linha 26 Geramos tags para uma linha horizontal (
para manter a formatação original das linhas do traceback. Linha 27 Usamos a função print_exc() do módulo traceback para gerar o texto de uma descrição de erro. Linha 30 Encerramos o programa gerando os tags que marcam o fim de uma página HTML. Protótipo melhorado ==================== Agora que colocamos o calendário básico para funcionar, está na hora de melhorar sua apresentação. Vamos deixar de lado o recurso preguiçoso do tage colocar os dias do mês dentro de uma tabela construída em HTML (Figura 3). Aproveitando outros recursos daquela linguagem, vamos também colorir os finais de semana e assinalar o dia de hoje. Você encontra o programa ``calendin2.py`` na listagem abaixo. .. image:: _static/img_06_04.png :: #!/python/python # calendin2.py - calendário dinâmico - protótipo 2 print 'Content-type: text/html\n' try: from time import time, localtime from calendar import monthcalendar from string import join ano, mes, hoje = localtime(time())[:3] print 'Calendário Dinâmico ' print '' print '' print ' ' except: import sys from traceback import print_exc sys.stderr = sys.stdout print 'Calendário de %02d/%04d
' % (mes, ano) print '' print '
' for dia_sem in ['seg','ter','qua','qui','sex','sab','dom']: if dia_sem in ['sab','dom']: bgcolor = 'green' else: bgcolor = 'blue' print ' ' for semana in monthcalendar(ano, mes): print '' % bgcolor print ' ' % dia_sem print '%s
' num_dia_sem = 0 for dia in semana: if dia == hoje: bgcolor = 'pink' elif num_dia_sem >= 5: bgcolor = 'lightgreen' else: bgcolor = 'lightblue' print ' ' print '' % bgcolor if dia != 0: print ' ' num_dia_sem = num_dia_sem + 1 print '%2d
' % dia print 'Erro no CGI:
' print_exc() print '' print '' Principais novidades em relação à versão anterior: Linha 11 Além do ano e do mês, guardamos o dia de hoje, para poder assinalá-lo no calendário. Linha 15 Vamos centralizar tudo na página. Linha 17 Abrimos o tag