IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
11.6. Prise en charge de Last-Modified et ETag

11.6. Prise en charge de Last-Modified et ETag

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                       1
{'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'))  2
>>> seconddatastream = opener.open(request)            3
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
1 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.
2 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.
3 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):    1
    def http_error_default(self, req, fp, code, msg, headers): 2
        result = urllib2.HTTPError(                           
            req.get_full_url(), code, msg, headers, fp)       
        result.status = code                                   3
        return result                                         
1 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.
2 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.
3 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

>>> request.headers                           1
{'If-modified-since': 'Thu, 15 Apr 2004 19:45:21 GMT'}
>>> import openanything
>>> opener = urllib2.build_opener(
...     openanything.DefaultErrorHandler())   2
>>> seconddatastream = opener.open(request)
>>> seconddatastream.status                   3
304
>>> seconddatastream.read()                   4
''
1 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.
2 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.
3 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.
4 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

>>> import urllib2, openanything
>>> request = urllib2.Request('http://diveintomark.org/xml/atom.xml')
>>> opener = urllib2.build_opener(
...     openanything.DefaultErrorHandler())
>>> firstdatastream = opener.open(request)
>>> firstdatastream.headers.get('ETag')        1
'"e842a-3e53-55d97640"'
>>> firstdata = firstdatastream.read()
>>> print firstdata                            2
<?xml version="1.0" encoding="iso-8859-1"?>
<feed version="0.3"
  xmlns="http://purl.org/atom/ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xml:lang="en">
  <title mode="escaped">dive into mark</title>
  <link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
  <-- rest of feed omitted for brevity -->
>>> request.add_header('If-None-Match',
...     firstdatastream.headers.get('ETag'))   3
>>> seconddatastream = opener.open(request)
>>> seconddatastream.status                    4
304
>>> seconddatastream.read()                    5
''
1 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).
2 Voilà, nous avons les données.
3 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.
4 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é.
5 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.
NOTE
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.