Skip to content

12. Serviços web em Python

  

Os scripts em Python podem ser executados por um servidor WEB. É este servidor que ficará à escuta dos pedidos 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, nomeadamente em Python. É necessário saber «comunicar» com um serviço WEB, ou seja, compreender o protocolo Http de comunicação entre um servidor Web e os seus clientes. É esse o objetivo dos programas que se seguem.

Os scripts do serviço Web serão executados pelo servidor Web Apache do WampServer. Devem ser colocados num diretório específico: <WampServer>\bin\apache\apachex.y.z\cgi-bin, em que <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 utilizar. Este caminho é indicado sob a forma de um comentário:

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

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

12.1. Aplicação cliente/servidor de data e hora

O 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 da 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())

Notas:

  • linha 6: o script deve gerar por si próprio alguns dos cabeçalhos HTTP da resposta ao cliente. Estes serão adicionados aos cabeçalhos HTTP gerados pelo próprio servidor Apache. O cabeçalho HTTP da linha 6 indica ao cliente que lhe será enviada uma recurso no formato text/plain, ou seja, texto não formatado. Note-se o «\n» no final do cabeçalho, que irá gerar uma linha vazia a seguir ao cabeçalho. Isto é obrigatório: é esta linha vazia que sinaliza ao cliente HTTP o fim dos cabeçalhos HTTP da resposta. Segue-se, em seguida, a recurso solicitado pelo cliente, neste caso um texto não formatado;
  • linha 18: a recurso enviada 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 numa janela de comandos, tal como temos feito até agora. Isto permite eliminar eventuais 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 de o script ter sido testado desta forma, pode ser colocado na pasta <cgi-bin> do servidor Apache (ver parágrafo 12). Iniciemos a aplicação WampServer. Isto inicia simultaneamente um servidor web Apache e um SGBD MySQL. Por enquanto, utilizaremos apenas o servidor web. Em seguida, utilizando um navegador, acedamos à seguinte página URL: http://localhost/cgi-bin/web_02.py:

  • em [1]: o URL solicitado;
  • em [2]: a resposta apresentada pelo navegador;
  • em [3]: o código-fonte recebido pelo navegador web. É, de facto, o código enviado pelo script Python.

Com algumas ferramentas (neste caso, o Firebug, um plugin do navegador Firefox), é possível aceder aos 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 o precedem foram gerados pelo servidor web Apache.

12.1.3. Um cliente programado


O programa (client_web_02)

Vamos agora escrever um script que funcionará 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"
# ligação
connexion=httplib.HTTPConnection(HOST)
# acompanhamento
connexion.set_debuglevel(1)
# envio do pedido
connexion.request("GET", URL)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
# encerramento da sessã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 as expressões regulares; o módulo httplib é necessário para as funções dos clientes HTTP;
  • linha 9: é criada uma ligação HTTP com a porta 80 do HOST, definida na linha 6;
  • linha 11: o acompanhamento permite visualizar os cabeçalhos HTTP do pedido do cliente e da resposta do servidor;
  • linha 13: é solicitado o URL do serviço web. Existem duas formas de a solicitar: utilizando um comando HTTP GET ou POST. A diferença entre os dois é explicada mais adiante. Aqui, será solicitada com o comando HTTP GET;
  • linha 15: a resposta do servidor é lida. É a resposta completa que aqui é obtida: cabeçalhos HTTP e recurso solicitado pelo cliente. Na sua resposta, o servidor pode ter solicitado ao cliente que se redirecione. Neste caso, o cliente httplib efetua automaticamente o redirecionamento. A resposta obtida é, portanto, a 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, utiliza-se [reponse].getHeaders(). Para obter o documento, utiliza-se [reponse].read();
  • linha 19: assim que a resposta do servidor web for obtida, a ligação ao mesmo é encerrada;
  • sabe-se que o documento enviado pelo servidor é uma linha de texto com o formato 15/06/11 14:56:36. Nas linhas 22-26, utiliza-se uma expressão regular para recuperar 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-6: os cabeçalhos HTTP da resposta do servidor web;
  • linha 8: o documento enviado pelo servidor;
  • linha 11: o resultado da 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 transmitir parâmetros ao servidor web:

  1. solicita o URL do serviço na forma

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

onde os valores vali têm de ser previamente codificados para que determinados caracteres reservados sejam substituídos pelo seu valor hexadecimal.

  1. 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 resto dos cabeçalhos enviados pelo cliente termina com uma linha em branco. O cliente pode então enviar os seus dados na forma

val1&param2=val2&param3=val3…

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

Content-length: N

12.2.1. O serviço web

O serviço web a seguir recebe 3 parâmetros do seu cliente: nom, prenom, age. Recupera-os numa espécie de dicionário denominado cgi.FieldStorage, fornecido pelo módulo cgi. O valor vali de um parâmetro parami é obtido através 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 uma vez na solicitação do cliente;
  • n elementos se o parâmetro parami estiver presente n vezes na solicitação do cliente.

Depois de recuperados os parâmetros, o script devolve-os 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&apelido=N&idade=A
formulaire=cgi.FieldStorage()

# estas 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. Note-se 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
# ligação
connexion=httplib.HTTPConnection(HOST)
# acompanhamento
connexion.set_debuglevel(1)
# envio do pedido
connexion.request("GET",URL)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
print contenu,"\n"
# encerramento da ligação
connexion.close()

Notas:

  • linhas 8-10: os valores dos 3 parâmetros enviados ao serviço web;
  • linha 13: é necessário codificá-los. Isto é feito através do método urlencode do módulo urllib. Este módulo é importado na linha 3. O método aceita como parâmetro um dicionário {param1:val1, param2:val2, ...};
  • linha 15: num comando GET (linha 21), o cliente deve colocar 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']]

Notas:

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

12.2.4. O cliente POST

O cliente POST é análogo ao cliente GET, com a diferença de que os parâmetros codificados já não fazem parte do URL de destino. Estes são 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 para o servidor
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# ligação
connexion=httplib.HTTPConnection(HOST)
# acompanhamento
connexion.set_debuglevel(1)
# envio do pedido
connexion.request("POST",URL,params)
# processamento da resposta
reponse=connexion.getresponse()
# conteúdo
contenu=reponse.read()
print contenu,"\n"
# encerramento da ligaçã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:

  • reparem-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;
    • este cabeçalho HTTP é seguido por uma linha vazia que indica o fim dos cabeçalhos HTTP;
    • depois, são enviados os 39 caracteres dos parâmetros codificados.
  • 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 num ambiente de sistema que possui atributos. Os seus atributos e respetivos valores estão disponíveis num 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 consola e não como CGI), obtemos na consola 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Ú

Num navegador da Web (neste caso, é o script CGI que é executado), obtêm-se os seguintes resultados:

 

Note-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"
# ligação
connexion=httplib.HTTPConnection(HOST)
# envio do pedido
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

Note-se que o cliente programado não recebe exatamente a mesma resposta que o navegador web. Isto deve-se ao facto de este último ter enviado ao servidor web informações que foram utilizadas pelo servidor web para criar a sua resposta. Neste caso, o cliente programado não enviou qualquer informação sobre si próprio.