Maintenant que nous savons comment ajouter des en-têtes HTTP à nos requêtes de services Web, voyons comment prendre en charge
les en-têtes Last-Modified et ETag.
Ces exemples montrent la sortie avec le mode débogage désactivé. Si il est toujours activé, vous pouvez le désactiver en tapant
httplib.HTTPConnection.debuglevel = 0. Vous pouvez aussi le laisser activé, si cela vous aide.
Exemple 11.6. Test de Last-Modified
>>> import urllib2>>> request = urllib2.Request('http://diveintomark.org/xml/atom.xml')>>> opener = urllib2.build_opener()>>> firstdatastream = opener.open(request)>>> firstdatastream.headers.dict{'date': 'Thu, 15 Apr 2004 20:42:41 GMT',
'server': 'Apache/2.0.49 (Debian GNU/Linux)',
'content-type': 'application/atom+xml',
'last-modified': 'Thu, 15 Apr 2004 19:45:21 GMT',
'etag': '"e842a-3e53-55d97640"',
'content-length': '15955',
'accept-ranges': 'bytes',
'connection': 'close'}>>> request.add_header('If-Modified-Since',... firstdatastream.headers.get('Last-Modified'))>>> seconddatastream = opener.open(request)Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "c:\python23\lib\urllib2.py", line 326, in open
'_open', req)
File "c:\python23\lib\urllib2.py", line 306, in _call_chain
result = func(*args)
File "c:\python23\lib\urllib2.py", line 901, in http_open
return self.do_open(httplib.HTTP, req)
File "c:\python23\lib\urllib2.py", line 895, in do_open
return self.parent.error('http', req, fp, code, msg, hdrs)
File "c:\python23\lib\urllib2.py", line 352, in error
return self._call_chain(*args)
File "c:\python23\lib\urllib2.py", line 306, in _call_chain
result = func(*args)
File "c:\python23\lib\urllib2.py", line 412, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 304: Not Modified
Vous vous rappelez de tous les en-têtes HTTP qui s'affichaient lorsque nous avions activé le débogage ? Voici la manière d'y
accéder par la programmation : firstdatastream.headers est un objet qui se comporte comme un dictionnaire et permet d'accéder à chacun des en-têtes retournés par le serveur HTTP.
A la seconde requête, nous ajoutons l'en-tête If-Modified-Since avec la date de dernière modification de la première requête. Si les données n'ont pas changé, le serveur devrait retourner
un code de status 304.
Les données n'ont pas changé. Nous pouvons voir dans la trace de pile que urllib2 déclenche une exception spécifique, HTTPError, en réponse au code de statut 304. C'est assez inhabituel et pas forcément pratique. Après tout, ce n'est pas une erreur, nous avons spécifiquement demandé
au serveur de ne pas renvoyer les données si elles n'avaient pas changé, ce qu'il a fait, puisqu'elles n'avaient pas changé.
Ce n'est pas une erreur, c'est exactement le résultat que nous recherchions.
La bibliothèque urllib2 déclenche également une exception HTTPError pour des situations que nous considérerions sans doute comme des erreurs, comme le code 404 (page non trouvée). En fait, elle déclenche HTTPError pour n'importe quel code de statut autre que 200 (OK), 301 (redirection permanente) ou 302 (redirection temporaire). Il serait plus utile pour notre programme qu'elle capture le code de statut et qu'elle le retourne
simplement, sans déclencher d'exception. Pour cela, nous devons définir un gestionnaire d'URL spécialisé.
Exemple 11.7. Définition de gestionnaires d'URL
Ce gestionnaire d'URL spécialisé fait partie de openanything.py.
class DefaultErrorHandler(urllib2.HTTPDefaultErrorHandler): def http_error_default(self, req, fp, code, msg, headers):
result = urllib2.HTTPError(
req.get_full_url(), code, msg, headers, fp)
result.status = code return result
La conception d'urllib2 est centrée sur les gestionnaires d'URL. Chaque gestionnaire est simplement une classe qui peut définir un nombre quelconque
de méthodes. Lorsque quelque chose se passe, comme une erreur HTTP ou même un code 304, urllib2 recherche par introspection dans la liste des gestionnaires définis une méthode qui puisse le prendre en charge. Nous avons
utilisé une technique semblable d'introspection au Chapitre 9, Traitement de données XML pour définir des gestionnaires pour différents types de noeuds, mais urllib2 est plus flexible et recherche par introspection dans tous les gestionnaires définis pour la requête en cours.
urllib2 recherche parmis les gestionnaires définis et appelle la méthode http_error_default lorsqu'il reçoit un code de statut 304 du serveur. En définissant un gestionnaire d'erreur spécialisé, nous pouvons empêcher urllib2 de déclencher une exception. Nous créons plutôt un objet HTTPError et le retournons au lieu de le déclencher.
C'est l'étape-clé : avant de retourner de la fonction, nous sauvegardons le code de statut retourné par le serveur HTTP. Cela
permettra d'y accéder facilement à partir du programme appelant.
Exemple 11.8. Utilisation de gestionnaires d'URL spécialisés
Nous continuons l'exemple précédent, donc l'objet Request est déjà défini et nous avons déjà ajouté l'en-tête If-Modified-Since.
C'est l'étape-clé : maintenant que nous avons défini notre gestionnaire d'URL spécialisé, nous devons dire à urllib2 de l'utiliser. Vous vous rappelez que j'ai dit qu'urllib2 décompose l'accès à une ressource en trois étapes et qu'il y avait de bonnes raisons à cela ? Voila pourquoi la construction
de l'opener d'URL est une étape séparée, pour que nous puissions le construire avec notre propre gestionnaire d'URL redéfinissant le
comportement par défaut d'urllib2.
Maintenant nous pouvons tranquillement ouvrir la ressource et ce que nous obtenons est un objet qui, en plus des en-têtes
habituels (accessibles par seconddatastream.headers.dict), contient aussi le code de statut HTTP. Dans ce cas, comme on peut s'y attendre, le code est 304, ce qui signifie que les données n'ont pas changé depuis la dernière requête.
Notez que lorsque le serveur retourne un code de statut 304, il ne renvoi pas les données. C'est tout là l'intérêt : préserver de la bande passante en ne retéléchargeant pas ce qui
n'a pas été modifié. Nous devons donc mettre ces données en cache la première fois que nous les recevons si nous voulons les
utiliser.
La gestion de ETag fonctionne de la même manière, mais au lieu de vérifier Last-Modified et d'envoyer If-Modified-Since, on vérifie ETag et on envoiIf-None-Match. Commençons une nouvelle session dans l'IDE
Exemple 11.9. Prise en charge de ETag/If-None-Match
A l'aide du pseudo-dictionnaire firstdatastream.headers, nous pouvons obtenir l'ETag retourné par le serveur (si le serveur n'a pas retourné d'ETag cette ligne retournera None).
Voilà, nous avons les données.
Maintenant, nous préparons le deuxième appel en assignant à l'en-tête If-None-Match l'ETag obtenu à la première requête.
La deuxième requête réussit silencieusement (sans déclencher d'exception) et nous voyons là encore que le serveur a renvoyé
un code de statut 304. En se basant sur le ETag que nous avons envoyé la deuxième fois, il sait que les données n'ont pas changé.
Qu'il soit produit par la vérification de date avec Last-Modified ou la correspondance de code de hachage avec ETag, les données ne sont jamais renvoyé avec le code de statut 304. C'est tout l'intérêt.
Dans ces exemples, le serveur HTTP supporte à la fois les en-têtes Last-Modified et ETag, mais ce n'est pas le cas de tous les serveurs. Pour vos clients de services Web, vous devez prévoir de supporter les deux
et programmer de manière défensive au cas ou un serveur ne supporterais que l'un des deux, ou aucun.