Skip to content

12. Serviços web em Python

  

Os scripts em Python podem ser executados por um servidor WEB. É esse servidor que ficará à espera das solicitações dos clientes. Do ponto de vista do cliente, chamar um serviço WEB equivale a solicitar o URL desse serviço. O cliente pode ser escrito em qualquer linguagem, inclusive em Python. Precisamos saber “conversar” com um serviço WEB, ou seja, compreender o protocolo Http de comunicação entre um servidor web e seus clientes. Esse é o objetivo dos programas a seguir.

Os scripts do serviço web serão executados pelo servidor web Apache do WampServer. É necessário colocá-los em um diretório específico: <WampServer>\bin\apache\apachex.y.z\cgi-bin, onde <WampServer> é a pasta de instalação do WampServer e x.y.z é a versão do servidor web Apache.

 

Colocar os scripts Python na pasta <cgi-bin> não é suficiente. É necessário que o script indique, na primeira linha, o caminho do interpretador Python a ser utilizado. Esse caminho é inserido na forma de um comentário:

#!D:\Programas\ActivePython\Python2.7.2\python.exe

O leitor deverá adaptar esse caminho ao seu próprio ambiente.

12.1. Aplicativo cliente/servidor de data e hora

Nosso primeiro serviço web será um serviço de data e hora: o cliente recebe a data e a hora atuais.

12.1.1. O servidor


O programa (web_02)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import time

# cabeçalhos
print "Content-Type: text/plain\n"

# envio da hora ao cliente
  # hora local: número de milissegundos desde 01/01/1970
  # "formato de exibição de data e hora
  # d: dia com 2 dígitos
  # m: mês com 2 dígitos
  # y: ano com 2 dígitos
  # H: hora 0,23
  # M: minutos
  # S: segundos

print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())

Observações:

  • linha 6: o script deve gerar por conta própria alguns dos cabeçalhos HTTP da resposta ao cliente. Eles serão adicionados aos cabeçalhos HTTP gerados pelo próprio servidor Apache. O cabeçalho HTTP da linha 6 indica ao cliente que será enviado a ele um recurso no formato text/plain, ou seja, texto não formatado. Observe-se o “\n” no final do cabeçalho, que irá gerar uma linha vazia após o cabeçalho. Isso é obrigatório: é essa linha vazia que sinaliza ao cliente HTTP o fim dos cabeçalhos HTTP da resposta. Em seguida, vem o recurso solicitado pelo cliente, neste caso, um texto não formatado;
  • linha 18: o recurso enviado ao cliente é um texto que exibe a data e a hora atuais.

12.1.2. Dois testes

O script anterior pode ser executado diretamente pelo interpretador Python em uma janela de comandos, como fizemos até agora. Isso permite eliminar possíveis erros de sintaxe ou de funcionamento. Obtém-se o seguinte resultado:

1
2
3
4
cmd>%python% web_02.py
Content-Type: text/plain

24/06/11 11:16:55

Depois que o script for testado dessa forma, ele pode ser colocado na pasta <cgi-bin> do servidor Apache (ver parágrafo 12). Vamos iniciar o aplicativo WampServer. Isso inicia simultaneamente um servidor web Apache e um SGBD MySQL. Por enquanto, usaremos apenas o servidor web. Em seguida, usando um navegador, acessemos a seguinte URL: http://localhost/cgi-bin/web_02.py:

  • em [1]: o URL solicitado;
  • em [2]: a resposta exibida pelo navegador;
  • em [3]: o código-fonte recebido pelo navegador. É exatamente o mesmo enviado pelo script Python.

Com algumas ferramentas (neste caso, o Firebug, um complemento do navegador Firefox), é possível acessar os cabeçalhos HTTP trocados com o servidor. Acima, o navegador recebeu os seguintes cabeçalhos HTTP:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 24 Jun 2011 09:35:02 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.5
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

Nas linhas 7 e 8, reconhece-se o cabeçalho HTTP enviado pelo script Python. Os que vêm antes foram gerados pelo servidor web Apache.

12.1.3. Um cliente programado


O programa (client_web_02)

Vamos agora escrever um script que atuará como cliente do serviço web anterior. Utilizamos as funcionalidades do módulo httplib, que facilita a criação de clientes HTTP.


# -*- coding=utf-8 -*-

import httplib,re

# constantes
HOST="localhost"
URL="/cgi-bin/web_02.py"
# conexão
connexion=httplib.HTTPConnection(HOST)
# rastreamento
connexion.set_debuglevel(1)
# envio da solicitação
connexion.request("GET", URL)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
# encerramento da conexão
connexion.close()
print "------\n",contenu,"-----\n"
# recuperação dos elementos da hora
elements=re.match(r"^(\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)\s*$",contenu).groups()
print "Jour=%s,Mois=%s,An=%s,Heures=%s,Minutes=%s,Secondes=%s" % (elements[0],elements[1],elements[2],elements[3],elements[4],elements[5])

Notas:

  • linha 3: o módulo re é necessário para expressões regulares, e o módulo httplib para as funções dos clientes HTTP;
  • linha 9: é criada uma conexão HTTP com a porta 80 do HOST, definida na linha 6;
  • linha 11: o monitoramento permite visualizar os cabeçalhos HTTP da solicitação do cliente e da resposta do servidor;
  • linha 13: o URL do serviço web é solicitado. Há duas maneiras de solicitá-la: usando um comando HTTP GET ou POST. A diferença entre os dois é explicada mais adiante. Aqui, ela será solicitada com o comando HTTP GET;
  • linha 15: a resposta do servidor é lida. Aqui é obtida a resposta completa: cabeçalhos HTTP e o recurso solicitado pelo cliente. Em sua resposta, o servidor pode ter solicitado que o cliente seja redirecionado. Nesse caso, o cliente httplib realiza automaticamente o redirecionamento. A resposta obtida é, portanto, aquela resultante do redirecionamento;
  • linha 17: a resposta é composta pelos cabeçalhos HTTP e pelo documento solicitado pelo cliente. Para obter apenas os cabeçalhos HTTP, utilizar-se-á [reponse].getHeaders(). Para obter o documento, utiliza-se [reponse].read();
  • linha 19: uma vez obtida a resposta do servidor web, a conexão com ele é encerrada;
  • sabe-se que o documento enviado pelo servidor é uma linha de texto no formato 15/06/11 14:56:36. Nas linhas 22 a 26, utiliza-se uma expressão regular para extrair os diferentes elementos dessa linha.

12.1.4. Resultados

send: 'GET /cgi-bin/web_02.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Tue, 14 Feb 2012 14:51:07 GMT
header: Server: Apache/2.2.17 (Win32) PHP/5.3.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
------
14/02/12 15:51:07
-----

Jour=14,Mois=02,An=12,Heures=15,Minutes=51,Secondes=07

Notas:

  • linha 1: os cabeçalhos HTTP enviados pelo cliente ao servidor web;
  • linhas 2 a 6: os cabeçalhos HTTP da resposta do servidor web;
  • linha 8: o documento enviado pelo servidor;
  • linha 11: o resultado de sua análise;

12.2. Recuperação pelo servidor dos parâmetros enviados pelo cliente

No protocolo HTTP, um cliente dispõe de dois métodos para passar parâmetros ao servidor web:

  1. ele solicita o URL do serviço na forma

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

onde os valores vali devem ser previamente codificados para que certos caracteres reservados sejam substituídos por seus valores hexadecimais.

  1. ele solicita o URL do serviço na forma
POST url HTTP/1.0

e, em seguida, entre os cabeçalhos HTTP enviados ao servidor, insere o seguinte cabeçalho:

Content-length: N

O restante dos cabeçalhos enviados pelo cliente termina com uma linha em branco. Ele pode, então, enviar seus dados na forma

val1&param2=val2&param3=val3…

onde os caracteres vali devem, assim como no método GET, ser previamente codificados. O número de caracteres enviados ao servidor deve ser N, sendo que N é o valor declarado no cabeçalho:

Content-length: N

12.2.1. O serviço web

O serviço web a seguir recebe três parâmetros de seu cliente: nom, prenom, age. Ele os recupera em uma espécie de dicionário chamado cgi.FieldStorage, fornecido pelo módulo cgi. O valor vali de um parâmetro parami é obtido por meio de vali=cgi.FieldStorage().getlist("parami"). Obtém-se uma matriz com:

  • 0 elemento se o parâmetro parami não estiver presente na solicitação do cliente;
  • 1 elemento se o parâmetro parami estiver presente 1 vez na solicitação do cliente;
  • n elementos se o parâmetro parami estiver presente n vezes na solicitação do cliente.

Após recuperar os parâmetros, o script os retorna ao cliente.


O programa (web_03)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import cgi

# cabeçalhos
print "Content-Type: text/plain\n"

# recuperação pelo servidor das informações enviadas pelo cliente
# aqui nome=P&sobrenome=N&idade=A
formulaire=cgi.FieldStorage()

# elas são reenviadas ao cliente
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

É possível realizar um teste com um navegador da web:

Em [1], o URL do serviço web. Observe a presença dos três parâmetros nom, prenom e age. Em [2], a resposta do serviço web.

12.2.2. O cliente GET


O programa (client_web_03_GET)


# -*- coding=utf-8 -*-

import httplib,urllib

# constantes
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# os parâmetros devem ser codificados antes de serem enviados ao servidor
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# os parâmetros são colocados no final do URL
URL+="?"+params
# conexão
connexion=httplib.HTTPConnection(HOST)
# rastreamento
connexion.set_debuglevel(1)
# envio da solicitação
connexion.request("GET",URL)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
print contenu,"\n"
# encerramento da conexão
connexion.close()

Notas:

  • linhas 8-10: os valores dos três parâmetros enviados ao serviço web;
  • linha 13: é necessário codificá-los. Isso é feito por meio do método urlencode do módulo urllib. Esse módulo é importado na linha 3. O método aceita como parâmetro um dicionário {param1:val1, param2:val2, ...};
  • linha 15: em um comando GET (linha 21), o cliente deve inserir os parâmetros codificados no final do URL do serviço web;
  • as linhas seguintes já foram abordadas.

12.2.3. Os resultados

1
2
3
4
5
6
7
8
dos>%python% client_03_GET.py
send: 'GET /cgi-bin/web_03.py?nom=de+la+Huche&age=42&prenom=Jean-Paul HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Wed, 15 Jun 2011 13:22:15 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du client [['Jean-Paul'],['de la Huche'],['42']]

Observações:

  • linha 2: observe a codificação dos parâmetros (sobrenome, nome, idade);
  • linha 8: a resposta do serviço web.

12.2.4. O cliente POST

O cliente POST é análogo ao cliente GET, exceto que os parâmetros codificados não fazem mais parte do URL de destino. Eles foram passados como terceiro argumento da solicitação POST (linha 19).


O programa (client_web_03_POST)


# -*- coding=utf-8 -*-

import httplib,urllib

# constantes
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# os parâmetros devem ser codificados antes de serem enviados ao servidor
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# conexão
connexion=httplib.HTTPConnection(HOST)
# rastreamento
connexion.set_debuglevel(1)
# envio da solicitação
connexion.request("POST",URL,params)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
print contenu,"\n"
# encerramento da conexão
connexion.close()

12.2.5. Os resultados

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\n\r\nnom=de+la+Huche&age=42&prenom=Jean-Paul'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 24 Jun 2011 12:03:31 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du service web [prenom=['Jean-Paul'],nom=['de la Huche'],age=['42']]

Notas:

  • observe-se, na linha 2, o método utilizado pelo cliente POST para enviar os parâmetros codificados:
    • o cabeçalho HTTP Content-Length indica o número de caracteres que serão enviados ao serviço web;
    • esse cabeçalho HTTP é seguido por uma linha vazia, indicando o fim dos cabeçalhos HTTP;
    • em seguida, os 39 caracteres dos parâmetros codificados são enviados.
  • linha 8: a resposta do serviço web.

12.3. Recuperação das variáveis de ambiente de um serviço web

12.3.1. O serviço web

O script CGI em Python é executado em um ambiente de sistema que possui atributos. Seus atributos e seus valores estão disponíveis em um dicionário os.environ.


O programa (web_04)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import os

# cabeçalhos
print "Content-Type: text/plain\n"
# informações de ambiente
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Notas:

  • linha 3: é necessário importar o módulo os para ter acesso às variáveis “do sistema”.

Se executarmos diretamente o script acima (c.a.d, como script de console e não como CGI), obtemos na console os seguintes resultados:

Content-Type: text/plain

TMP : C:\Users\SERGET~1\AppData\Local\Temp
COMPUTERNAME : GPORTPERS3
USERDOMAIN : Gportpers3
VS100COMNTOOLS : D:\Programs\dotnet\Visual Studio 10\Common7\Tools\
VISUALSTUDIODIR : D:\Documents\Visual Studio 2010
PSMODULEPATH : C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
COMMONPROGRAMFILES : C:\Program Files (x86)\Common Files
PROCESSOR_IDENTIFIER : Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
PROGRAMFILES : C:\Program Files (x86)
PROCESSOR_REVISION : 2a07
SYSTEMROOT : C:\Windows
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
PROGRAMFILES(X86) : C:\Program Files (x86)
WINDOWS_TRACING_FLAGS : 3
TEMP : C:\Users\SERGET~1\AppData\Local\Temp
COMMONPROGRAMFILES(X86) : C:\Program Files (x86)\Common Files
PROCESSOR_ARCHITECTURE : x86
ALLUSERSPROFILE : C:\ProgramData
LOCALAPPDATA : C:\Users\Serge TahÚ\AppData\Local
HOMEPATH : \Users\Serge TahÚ
PROGRAMW6432 : C:\Program Files
USERNAME : Serge TahÚ
LOGONSERVER : \\GPORTPERS3
PROMPT : $P$G
SESSIONNAME : Console
PROGRAMDATA : C:\ProgramData
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
FP_NO_HOST_CHECK : NO
WINDIR : C:\Windows
PYTHON : D:\Programs\python\python2.7.2\python
WINDOWS_TRACING_LOGFILE : C:\BVTBin\Tests\installpackage\csilogfile.log
HOMEDRIVE : C:
SYSTEMDRIVE : C:
COMSPEC : C:\Windows\system32\cmd.exe
NUMBER_OF_PROCESSORS : 8
VBOX_INSTALL_PATH : D:\Programs\systeme\Oracle\VirtualBox\
APPDATA : C:\Users\Serge TahÚ\AppData\Roaming
PROCESSOR_LEVEL : 6
PROCESSOR_ARCHITEW6432 : AMD64
COMMONPROGRAMW6432 : C:\Program Files\Common Files
OS : Windows_NT
PUBLIC : C:\Users\Public
USERPROFILE : C:\Users\Serge TahÚ

Em um navegador da web (nesse caso, é o script CGI que é executado), obtêm-se os seguintes resultados:

 

Observe-se que, dependendo do contexto de execução, o ambiente obtido não é o mesmo.

12.3.2. O cliente programado


O programa (client_web_04)


# -*- coding=utf-8 -*-

import httplib

# constantes
HOST="localhost"
URL="/cgi-bin/web_04.py"
# conexão
connexion=httplib.HTTPConnection(HOST)
# envio da solicitação
connexion.request("GET", URL)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
print reponse.read()

12.3.3. Resultados

SERVER_SOFTWARE : Apache/2.2.17 (Win32) PHP/5.3.5
SCRIPT_NAME : /cgi-bin/web_04.py
SERVER_SIGNATURE :
REQUEST_METHOD : GET
SERVER_PROTOCOL : HTTP/1.1
QUERY_STRING :
SYSTEMROOT : C:\Windows
SERVER_NAME : localhost
REMOTE_ADDR : 127.0.0.1
SERVER_PORT : 80
SERVER_ADDR : 127.0.0.1
DOCUMENT_ROOT : D:/Programs/sgbd/wamp/www/
COMSPEC : C:\Windows\system32\cmd.exe
SCRIPT_FILENAME : D:/Programs/sgbd/wamp/bin/apache/Apache2.2.17/cgi-bin/web_04.py
SERVER_ADMIN : admin@localhost
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.
2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
HTTP_HOST : localhost
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
REQUEST_URI : /cgi-bin/web_04.py
WINDIR : C:\Windows
GATEWAY_INTERFACE : CGI/1.1
REMOTE_PORT : 58468
HTTP_ACCEPT_ENCODING : identity

Observe-se que o cliente programado não recebe exatamente a mesma resposta que o navegador da web. Isso ocorre porque este último enviou ao servidor web informações que foram utilizadas pelo servidor web para criar sua resposta. Neste caso, o cliente programado não enviou nenhuma informação sobre si mesmo.