Zanurkuj w Pythonie/Korzystanie z Last-Modified i ETag

Korzystanie z Last-Modified i ETag

edytuj

Teraz gdy już wiesz jak dodawać własne nagłówki do swoich żądań HTTP, zobaczmy jak wykorzystać nagłówki Last-Modified i ETag.

Poniższe przykłady pokazują wyjście z wyłączonym debugowaniem. Jeśli nadal masz je włączone (tak jak w poprzedniej sekcji), możesz je wyłączyć poprzez takie ustawienie: opener.handle_open['http'][0].set_http_debuglevel(0) odwołujące się pośrednio do instancji 'handler' klasy 'urllib2.HTTPHandler', przez słownik w instancji 'opener' lub bezpośrednio handler.set_http_debuglevel(0). Albo możesz pozostawić debugowanie włączone, jeśli to Ci pomoże.

Przykład. Testowanie 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. Pamiętasz wszystkie te nagłówki HTTP, które były wyświetlane przy włączonym debugowaniu? To jest sposób na dotarcie do nich programowo: firstdatastream.headers jest obiektem działającym jak słownik i pozwala on na pobranie każdego nagłówka zwracanego przez serwer HTTP.
  2. Przy drugim żądaniu dodajemy nagłówek If-Modified-Since z datą ostatniej modyfikacji z pierwszego żądania. Jeśli data nie uległa zmianie, serwer powinien zwrócić kod statusu 304.
  3. To wystarczy do stwierdzenia, że dane nie uległy zmianie. Możemy zobaczyć w zrzucie błędów, że urllib2 rzucił wyjątek specjalny: HTTPError w odpowiedzi na kod statusu 304. To jest trochę niezwykłe i nie całkiem pomocne. W końcu to nie jest błąd; specjalnie poprosiliśmy serwer o nie przesyłanie żadnych danych, jeśli nie uległy one zmianie i dane nie uległy zmianie, a więc serwer powiedział, iż nie wysłał żadnych danych. To nie jest błąd; to jest dokładnie to czego oczekiwaliśmy.

urllib2 rzuca wyjątek HTTPError także w sytuacjach, które można traktować jak błędy, np. 404 (strona nieznaleziona). Właściwie to rzuca on wyjątek HTTPError dla każdego kodu statusu innego niż 200 (OK), 301 (stałe przekierowanie), lub 302 (tymczasowe przekierowanie). Do naszych celów byłoby przydatne przechwycenie tych kodów statusów i po prostu zwrócenie ich bez rzucania żadnych wyjątków. Aby to zrobić, musimy zdefiniować własną klasę obsługi URL-i (ang. URL handler).

Poniższa klasa obsługi URL-i jest częścią modułu openanything.py.

Przykład. Definiowanie klas obsługi URL-i
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. urllib2 jest zaprojektowana jako zbiór klas obsługi URL-i. Każda z tych klas może definiować dowolną liczbę metod. Gdy coś się wydarzy -- jak np. błąd HTTP lub nawet kod 304 -- urllib2 używa introspekcji do odnalezienia w liście zdefiniowanych klas obsługi URL-i metody, która może obsłużyć to zdarzenie. Używaliśmy podobnej introspekcji w rozdziale 9-tym, Przetwarzanie XML-a do zdefiniowania metod obsługi dla różnych typów węzłów, ale urllib2 jest bardziej elastyczny i przeszukuje tyle klas obsługi ile jest zdefiniowanych dla bieżącego żądania.
  2. urllib2 przeszukuje zdefiniowane klasy obsługi i wywołuje metodę http_error_default, gdy otrzyma kod statusu 304 od serwera. Definiując własną klasę obsługi błędów, możemy zapobiec rzucaniu wyjątków przez urllib2. Zamiast tego tworzymy obiekt HTTPError, ale zwracamy go, zamiast rzucania go jako wyjątek.
  3. To jest kluczowa część: przed zwróceniem zachowujemy kod statusu zwrócony przez serwer HTTP. To pozwala na dostęp do niego programowi wywołującemu.
Przykład. Używanie własnych klas obsługi URL-i
>>> 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. Kontynuujemy poprzedni przykład, a więc obiekt Request jest już utworzony i nagłówek If-Modified-Since został już dodany.
  2. To jest klucz: ponieważ mamy zdefiniowaną własną klasę obsługi URL-i, musimy powiedzieć urllib2, aby teraz jej używał. Pamiętasz jak mówiłem, iż urllib2 podzielił proces dostępu do zasobów HTTP na trzy etapy i to nie bez powodu? Oto dlaczego wywołanie funkcji build_opener jest odrębnym etapem. Ponieważ na jej wejściu możesz podać własne klasy obsługi URL-i, które powodują zmianę domyślnego działania urllib2.
  3. Teraz możemy zasób otworzyć po cichu a z powrotem otrzymujemy obiekt, który wraz z nagłówkami (użyj seconddatastream.headers.dict, aby je pobrać), zawiera także kod statusu HTTP. W tym wypadku, tak jak oczekiwaliśmy, tym statusem jest 304, który oznacza że dane nie zmieniły się od ostatniego razu, gdy o nie prosiliśmy.
  4. Zauważ, że gdy serwer odsyła kod statusu 304, to nie przesyła ponownie danych. I o to w tym wszystkim chodzi: aby oszczędzić przepustowość poprzez niepobieranie ponowne danych, które nie uległy zmianie. A więc jeśli potrzebujesz tych danych, to musisz zachować je w lokalnym buforze po pierwszym pobraniu.

Z nagłówka ETag korzystamy bardzo podobnie. Jednak zamiast sprawdzania nagłówka Last-Modified i przesyłania If-Modified-Since, sprawdzamy nagłówek ETag a przesyłamy If-None-Match. Zacznijmy całkiem nową sesję w naszym IDE.

Przykład. Użycie 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/"/>
   [.. ciach ..]
>>> request.add_header('If-None-Match',
...     firstdatastream.headers.get('ETag'))   #(3)
>>> seconddatastream = opener.open(request)
>>> seconddatastream.status                    #(4)
304
>>> seconddatastream.read()                    #(5)
''
  1. Używając pseudo-słownika firstdatastream.headers możemy pobrać nagłówek ETag zwrócony przez serwer. (Co się stanie, jeśli serwer nie zwróci nagłówka ETag? Wtedy ta linia powinna zwrócić None.)
  2. OK, mamy dane.
  3. Teraz przy drugim wywołaniu ustawiamy w nagłówku If-None-Match wartość sumy kontrolnej z ETag otrzymanego przy pierwszym wywołaniu.
  4. Drugie wywołanie działa prawidłowo bez żadnych zakłóceń (bez rzucania żadnych wyjątków) i ponownie widzimy, że serwer odesłał status kodu 304. Bazując na sumie kontrolnej nagłówka ETag, którą wysłaliśmy za drugim razem, wie on że dane nie zmieniły się.
  5. Niezależnie od tego, czy kod 304 jest rezultatem sprawdzania daty Last-Modified czy sumy kontrolnej ETag, nigdy nie otrzymamy z powrotem ponownie tych samych danych, a jedynie kod statusu 304. I o to chodziło.