Skip to content

12. Serviços Web em Python

  

Os scripts Python podem ser executados por um servidor web. É o servidor que irá receber os 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, incluindo Python. Precisamos de saber como «comunicar» com um serviço web, ou seja, compreender o protocolo HTTP utilizado para a comunicação entre um servidor web e os seus clientes. Este é o objetivo dos programas seguintes.

Os scripts do serviço web serão executados pelo servidor web Apache no WampServer. Devem ser colocados num 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.

 

Não basta simplesmente colocar os scripts Python na pasta <cgi-bin>. O script deve especificar o caminho para o interpretador Python a ser utilizado na primeira linha. Este caminho é incluído como um comentário:

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

Os leitores devem adaptar este caminho ao seu próprio ambiente.

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

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:\Programs\ActivePython\Python2.7.2\python.exe

import time

#    headers
print "Content-Type: text/plain\n"

#    dispatch time to customer
  #  localtime: number of milliseconds since 01/01/1970
  #    "date-time display format
  #    d: 2-digit day
  #    m: 2-digit month
  #    y: 2-digit year
  #    H: hour 0.23
  #    M: minutes
  #    S: seconds

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

Notas:

  • Linha 6: O script deve gerar alguns dos cabeçalhos HTTP para a resposta ao próprio cliente. Estes serão adicionados aos cabeçalhos HTTP gerados pelo próprio servidor Apache. O cabeçalho HTTP na linha 6 indica ao cliente que um recurso será enviado no formato text/plain, ou seja, texto sem formatação. Repare no "\n" no final do cabeçalho, que irá gerar uma linha em branco após o cabeçalho. Isto é obrigatório: é esta linha em branco que sinaliza ao cliente HTTP o fim dos cabeçalhos HTTP na resposta. Segue-se o recurso solicitado pelo cliente, neste caso texto sem formatação;
  • linha 18: o recurso enviado ao cliente é 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 comando, tal como temos feito até agora. Isto ajuda a eliminar quaisquer erros de sintaxe ou de tempo de execução. 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 no diretório <cgi-bin> do servidor Apache (ver parágrafo 12). Vamos iniciar a aplicação WampServer. Isto inicia tanto um servidor web Apache como um servidor de base de dados MySQL. Por enquanto, vamos utilizar apenas o servidor web. Em seguida, utilizando um navegador, introduza o seguinte 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. Este é, de facto, o código enviado pelo script Python.

Com certas 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

As linhas 7–8 são reconhecíveis como o cabeçalho HTTP enviado pelo script Python. As linhas anteriores foram geradas pelo servidor web Apache.

12.1.3. Um cliente programado


O programa (client_web_02)

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

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

import httplib,re

#    constant
HOST="localhost"
URL="/cgi-bin/web_02.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
#    locking connection
connexion.close()
print "------\n",contenu,"-----\n"
#    recovery of time elements
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 funções de cliente HTTP;
  • linha 9: É criada uma ligação HTTP utilizando a porta 80 no HOST definido na linha 6;
  • linha 11: o log permite visualizar os cabeçalhos HTTP da solicitação do cliente e da resposta do servidor;
  • linha 13: a URL do serviço web é solicitada. 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 utilizando o comando HTTP GET;
  • linha 15: a resposta do servidor é lida. A resposta completa é obtida aqui: cabeçalhos HTTP e o recurso solicitado pelo cliente. Na sua resposta, o servidor pode ter instruído o cliente a redirecionar. Neste caso, o cliente httplib executa 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 recuperar apenas os cabeçalhos HTTP, utilize [response].getHeaders(). Para recuperar o documento, utilize [response].read();
  • Linha 19: Assim que a resposta do servidor web é obtida, a ligação ao mesmo é encerrada;
  • Sabemos que o documento enviado pelo servidor é uma linha de texto no formato 15/06/11 14:56:36. Linhas 22–26: Utilizamos uma expressão regular para extrair os vários elementos desta 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 na resposta do servidor web;
  • Linha 8: o documento enviado pelo servidor;
  • Linha 11: o resultado do seu processamento;

12.2. O servidor recupera os parâmetros enviados pelo cliente

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

  1. solicita o URL do serviço no formulário

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

onde os valores válidos devem primeiro ser codificados para que determinados caracteres reservados sejam substituídos pelos seus valores hexadecimais.

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

depois, entre os cabeçalhos HTTP enviados ao servidor, inclui o seguinte cabeçalho:

Content-length: N

O resto dos cabeçalhos enviados pelo cliente terminam com uma linha em branco. Pode então enviar os seus dados na forma

val1&param2=val2&param3=val3…

onde os valores devem, tal como no método GET, ser codificados previamente. O número de caracteres enviados para o 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 três parâmetros do seu cliente: last_name, first_name e age. Ele recupera esses parâmetros a partir de uma estrutura semelhante a um dicionário chamada cgi.FieldStorage, fornecida pelo módulo cgi. O valor vali de um parâmetro parami é obtido usando vali = cgi.FieldStorage().getlist("parami"). Isto devolve uma matriz de:

  • 0 elementos 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 os parâmetros terem sido recuperados, o script envia-os de volta ao cliente.


O programa (web_03)

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

import cgi

#    headers
print "Content-Type: text/plain\n"

#  server retrieves information sent by the client
#    here firstname=P&lastname=N&age=A
formulaire=cgi.FieldStorage()

#  we send them back to the customer
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 utilizando um navegador web:

Em [1], a URL do serviço web. Repare na presença dos três parâmetros last_name, first_name, 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

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

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#  parameters are set at the end of URL
URL+="?"+params
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET",URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
connexion.close()

Notas:

  • linhas 8–10: os valores dos 3 parâmetros enviados ao serviço web;
  • linha 13: estes devem ser codificados. Isto é feito utilizando o método urlencode do módulo urllib. Este módulo é importado na linha 3. O método recebe um dicionário {param1:val1, param2:val2, ...} como parâmetro;
  • linha 15: numa solicitação GET (linha 21), o cliente deve colocar os parâmetros codificados no final da 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 (last_name, first_name, age);
  • linha 8: a resposta do serviço web.

12.2.4. O cliente POST

O cliente POST é semelhante ao cliente GET, exceto que os parâmetros codificados já não fazem parte do URL de destino. São passados como o terceiro argumento do pedido POST (linha 19).


O programa (client_web_03_POST)

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

import httplib,urllib

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

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("POST",URL,params)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
connexion.close()

12.2.5. 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:

  • Nota na linha 2: o método POST utilizado pelo cliente para enviar os parâmetros codificados:
    • O cabeçalho HTTP Content-Length indica o número de caracteres que serão enviados para o serviço web;
    • este cabeçalho HTTP é seguido por uma linha em branco 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 de 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 os respetivos valores estão disponíveis no dicionário os.environ.


O programa (web_04)

1
2
3
4
5
6
7
8
9
#   !D:\Programs\ActivePython\Python2.7.2\python.exe

import os

#    headers
print "Content-Type: text/plain\n"
#    environmental news
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Notas:

  • Linha 3: É necessário importar o módulo os para aceder às variáveis «system».

Se executar o script acima diretamente (ou seja, como um script de consola em vez de um script CGI), verá os seguintes resultados na consola:

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 (onde o script CGI é executado), obtêm-se os seguintes resultados:

 

Note 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

#    constant
HOST="localhost"
URL="/cgi-bin/web_04.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
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 que o cliente programado não recebe exatamente a mesma resposta que o navegador web. Isto deve-se ao facto de o navegador ter enviado informações ao servidor web que este utilizou para gerar a sua resposta. Neste caso, o cliente programado não enviou quaisquer informações sobre si próprio.