11.7. Prise en charge des redirections.

La prise en charge des redirections temporaires et permanentes se fait avec un autre type de gestionnaire d'URL spécialisé.

D'abord, voyons pourquoi un gestionnaire de redirection est nécessaire.

Exemple 11.10. Accéder à des services Web sans gestionnaire de redirection

>>> import urllib2, httplib
>>> httplib.HTTPConnection.debuglevel = 1           1
>>> request = urllib2.Request(
...     'http://diveintomark.org/redir/example301.xml') 2
>>> opener = urllib2.build_opener()
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: '
GET /redir/example301.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 301 Moved Permanently\r\n'             3
header: Date: Thu, 15 Apr 2004 22:06:25 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml  4
header: Content-Length: 338
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0                              5
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:06:25 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml
>>> f.url                                               6
'http://diveintomark.org/xml/atom.xml'
>>> f.headers.dict
{'content-length': '15955', 
'accept-ranges': 'bytes', 
'server': 'Apache/2.0.49 (Debian GNU/Linux)', 
'last-modified': 'Thu, 15 Apr 2004 19:45:21 GMT', 
'connection': 'close', 
'etag': '"e842a-3e53-55d97640"', 
'date': 'Thu, 15 Apr 2004 22:06:25 GMT', 
'content-type': 'application/atom+xml'}
>>> f.status
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: addinfourl instance has no attribute 'status'
1 Pour mieux comprendre ce qu'il se passe, nous activons le débogage.
2 Voici un URL que j'ai mise en place pour rediriger de manière permanent vers mon fil Atom à l'adresse http://diveintomark.org/xml/atom.xml.
3 Evidemment, lorsque nous essayons de télécharger les données à cette adresse, le serveur renvoi un code de statut 301, signalant que la ressource a été déplacée de manière permanente.
4 Le serveur renvoi également un en-tête Location: avec la nouvelle adresse de ces données.
5 urllib2 remarque le code de redirection et tente automatiquement d'obtenir les données à la nouvelle adresse spécifiée dans l'en-tête Location:.
6 L'objet obtenu de opener contient la nouvelle adresse permanente et tous les en-têtes retourné à la seconde requête (faite sur la nouvelle adresse permanente). Mais le code de statut manque, nous n'avons donc aucun moyen de savoir par la programmation si cette redirection est temporaire ou permanente. Or, cette information est très importante. Si c'est une redirection temporaire, nous devons continuer de demander les données à l'ancienne adresse. Si c'est une redirection permanente, nous devons désormais demander les données à la nouvelle adresse.

C'est loin d'être parfait, mais c'est facile à corriger. urllib2 ne se comporte pas exactement comme nous le souhaitons dans la gestion des codes 301 et 302, nous allons donc redéfinir ce comportement. Comment ? Avec un gestionnaire d'URL spécialisé, comme nous l'avons fait pour prendre en charge les codes 304.

Exemple 11.11. Definition du gestionnaire de redirection

Cette classe est définie dans openanything.py.


class SmartRedirectHandler(urllib2.HTTPRedirectHandler):     1
    def http_error_301(self, req, fp, code, msg, headers):  
        result = urllib2.HTTPRedirectHandler.http_error_301( 2
            self, req, fp, code, msg, headers)              
        result.status = code                                 3
        return result                                       

    def http_error_302(self, req, fp, code, msg, headers):   4
        result = urllib2.HTTPRedirectHandler.http_error_302(
            self, req, fp, code, msg, headers)              
        result.status = code                                
        return result                                       
1 La gestion des redirections est définie dans urllib2 dans une classe appelée HTTPRedirectHandler. Nous ne voulons pas redéfinir entièrement son comportement, nous voulons simplement l'étendre un peu, nous dérivons donc HTTPRedirectHandler de manière à pouvoir appeler la classe ancêtre pour faire le gros du travail.
2 Quand il reçoit un code de statut 301 du serveur, urllib2 recherche parmi ses gestionnaires et appelle la méthode http_error_301. La première chose que la notre fait est d'appeler la méthode http_error_301 de la classe ancêtre, qui s'occupe du travail de base consistant à chercher l'en-tête Location: et à suivre la redirection à la nouvelle adresse.
3 Voici l'étape-clé : avant le retour de fonction, nous stockons le code de statut (301) pour que le programme appelant puisse y accéder plus tard.
4 Les redirections temporaires (codes de statut 302) fonctionnent de la même manière : réécriture de la méthode http_error_302, appel de l'ancêtre et sauvegarde du code de statut avant retour.

A quoi cela nous avance-t-il ? Nous pouvons maintenant construire un opener d'URL avec notre gestionnaire de redirection spécialisé et il suivra toujours les redirections automatiquement, mais maintenant il exposera également le code de statut de redirection.

Exemple 11.12. Utilisation du gestionnaire de redirection pour détecter les redirections permanentes

>>> request = urllib2.Request('http://diveintomark.org/redir/example301.xml')
>>> import openanything, httplib
>>> httplib.HTTPConnection.debuglevel = 1
>>> opener = urllib2.build_opener(
...     openanything.SmartRedirectHandler())           1
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: 'GET /redir/example301.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 301 Moved Permanently\r\n'            2
header: Date: Thu, 15 Apr 2004 22:13:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml
header: Content-Length: 338
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:13:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml

>>> f.status                                           3
301
>>> f.url
'http://diveintomark.org/xml/atom.xml'
1 D'abord, nous construisons un opener d'URL avec le gestionnaire de redirection que nous venons de définir.
2 Nous avons envoyé une requête et nous avons reçu un code de statut 301 en réponse. A ce moment, la méthode http_error_301 est appelée. Nous appelons la méthode ancêtre qui suit la redirection et envoi une requête à la nouvelle adresse (http://diveintomark.org/xml/atom.xml).
3 Voici le bénéfice de notre travail : maintenant, nous n'avons pas seulement accès à la nouvelle URL, mais également au code de statut de redirection, nous pouvons donc voir qu'il s'agit d'une redirection permanente. La prochaine fois que nous demanderont ces données, nous devrons les demander à la nouvelle adresse (http://diveintomark.org/xml/atom.xml, comme spécifié dans f.url). Si nous avons stocké l'adresse dans un fichier de configuration ou une base de données, nous devons la mettre à jour pour ne pas continuer à envoyer des requêtes à l'ancienne adresse. Il faut mettre à jour notre carnet d'adresse.

Le gestionnaire de redirection peut aussi nous apprendre quand nous ne devons pas mettre à jour notre carnet d'adresse.

Exemple 11.13. Utilisation du gestionnaire de redirection pour détecter les redirections temporaires.

>>> request = urllib2.Request(
...     'http://diveintomark.org/redir/example302.xml')   1
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: '
GET /redir/example302.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 302 Found\r\n'                           2
header: Date: Thu, 15 Apr 2004 22:18:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml
header: Content-Length: 314
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0                                3
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:18:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml
>>> f.status                                              4
302
>>> f.url
http://diveintomark.org/xml/atom.xml
1 C'est une URL d'exemple que j'ai configurée pour signaler aux clients de se rediriger temporairement sur http://diveintomark.org/xml/atom.xml.
2 Le serveur renvoi un code de statut 302, ce qui indique une redirection temporaire. Le nouvel emplacement temporaire des données est donné dans l'en-tête Location:.
3 urllib2 appelle notre méthode http_error_302, qui appelle la méthode ancêtre du même nom dans urllib2.HTTPRedirectHandler, qui suit la redirection vers le nouvel emplacement. Puis notre méthode http_error_302 stocke le code de statut (302) pour que l'application appelante puisse l'utiliser plus tard.
4 Nous y voilà, après avoir suivi la redirection vers http://diveintomark.org/xml/atom.xml. f.status nous dit que c'était une redirection temporaire, ce qui signifie que nous devons continuer à chercher les données à l'adresse originelle (http://diveintomark.org/redir/example302.xml). Peut-être que nous serons redirigés encore la prochaine fois, mais peut-être que non. Peut-être que nous seront redirigés vers une adresse différente, nous ne pouvons pas le dire. Le serveur nous a indiqué que cette redirection était temporaire, nous devons suivre cette indication et maintenant que nous exposons assez d'information pour le faire, l'application appelante peut suivre cette indication.