Zanurkuj w Pythonie/Obsługa przekierowań

Obsługa przekierowań

edytuj

Możemy obsługiwać trwałe i tymczasowe przekierowania używając różnego rodzaju własnych klas obsługi URL-i.

Po pierwsze zobaczmy dlaczego obsługa przekierowań jest konieczna.

Przykład. Dostęp do usługi internetowej bez obsługi przekierowań
>>> import urllib2
>>> opener = urllib2.build_opener()
>>> opener.handle_open['http'][0]._debuglevel=0         #(1)
>>> request = urllib2.Request(
...     'http://diveintomark.org/redir/example301.xml') #(2)
>>> 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. Lepiej będziesz mógł zobaczyć co się dzieje, gdy włączysz tryb debugowania. (podane polecenie nieco różni się od wykorzystywanych poprzednio, nie wykorzystujemy oddzielnej instancji klasy 'HTTPHandler' lecz tą znajdującą się wewnątrz zmiennej 'opener', nie używamy, także metody 'set_http_debuglevel', lecz bezpośrednio zmieniamy wartość tego atrybutu)
  2. To jest URL, który ma ustawione trwałe przekierowanie do RSS-a pod adresem http://diveintomark.org/xml/atom.xml.
  3. Gdy próbujemy pobrać dane z tego adresu, to serwer odsyła kod statusu 301 informujący o tym, że ten zasób został przeniesiony na stałe.
  4. Serwer przesyła także nagłówek Location:, który zawiera nowy adres tych danych.
  5. urllib2 zauważa ten kod statusu dotyczący przekierowania i automatycznie próbuje pobrać dane spod nowej lokalizacji podanej w nagłówku Location:.
  6. Obiekt zwrócony przez opener zawiera już nowy adres (po przekierowaniu) i wszystkie nagłówki zwrócone po drugim żądaniu (zwrócone z nowego adresu). Jednak brakuje kodu statusu, a więc nie mamy możliwości programowego stwierdzenia, czy to przekierowanie było trwałe, czy tylko tymczasowe. A to ma wielkie znaczenie: jeśli to było przekierowanie tymczasowe, wtedy musimy ponowne żądania kierować pod stary adres, ale jeśli to było trwałe przekierowanie (jak w tym przypadku), to nowe żądania od tego momentu powinny być kierowane do nowej lokalizacji.

To nie jest optymalne, ale na szczęście łatwe do naprawienia. urllib2 nie zachowuje się dokładnie tak, jak tego chcemy, gdy napotyka na kody 301 i 302, a więc zmieńmy to zachowanie. Jak? Przy pomocy własnej klasy obsługi URL-i, tak jak to zrobiliśmy w przypadku kodu 304.

Poniższa klasa jest zdefiniowana w openanything.py.

Przykład. Definiowanie klasy obsługi przekierowań
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. Obsługa przekierowań w urllib2 jest zdefiniowana w klasie o nazwie HTTPRedirectHandler. Nie chcemy całkowicie zmieniać działania tej klasy, chcemy je tylko lekko rozszerzyć, a więc dziedziczymy po niej, a wtedy będziemy mogli wywoływać metody klasy nadrzędnej do wykonania całej ciężkiej roboty.
  2. Gdy napotkany zostanie status kodu 301 przesłany przez serwer, urllib2 przeszuka listę klas obsługi i wywoła metodę http_error_301. Pierwszą rzeczą, jaką wykona nasza wersja, jest po prostu wywołanie metody http_error_301 przodka, która wykona całą robotę związaną ze znalezieniem nagłówka Location: i przekierowaniem żądania pod nowy adres.
  3. Tu jest kluczowa sprawa: zanim wykonamy return, zachowujemy kod statusu (301), aby program wywołujący mógł go później odczytać.
  4. Przekierowania tymczasowe (kod statusu 302) działają w ten sam sposób: nadpisujemy metodę http_error_302, wywołujemy metodę przodka i zachowujemy kod statusu przed powrotem z metody.

A więc jaką mamy z tego korzyść? Możemy teraz utworzyć klasę pozwalającą na dostęp do zasobów internetowych wraz z naszą własną klasą obsługi przekierowań i będzie ona nadal dokonywała przekierowań automatycznie, ale tym razem będzie ona także udostępniała kod statusu przekierowania.

Przykład. Użycie klasy obsługi przekierowań do wykrycia przekierowań trwałych
>>> 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. Po pierwsze tworzymy opener z przed chwilą zdefiniowaną klasą obsługi przekierowań.
  2. Wysłaliśmy żądanie i otrzymaliśmy w odpowiedzi kod statusu 301. W tym momencie wołana jest metoda http_error_301. Wywołujemy metodę przodka, która odnajduje przekierowanie i wysyła żądanie pod nową lokalizację (http://diveintomark.org/xml/atom.xml).
  3. Tu jest nasza korzyść: teraz nie tylko mamy dostęp do nowego URL-a, ale także do kodu statusu przekierowania, a więc możemy stwierdzić, że było to przekierowanie trwałe. Przy następnym żądaniu tych danych, powinniśmy użyć nowego adresu (http://diveintomark.org/xml/atom.xml, jak widać w f.url). Jeśli mamy zachowaną daną lokalizację w pliku konfiguracyjnym lub w bazie danych, to powinniśmy zaktualizować ją, aby nie odwoływać się ponownie do starego adresu. To jest pora do aktualizacji książki adresowej.

Ta sama klasa obsługi przekierowań może także pokazać, że nie powinniśmy aktualizować naszej książki adresowej.

Przykład. Użycie klasy obsługi przekierowań do wykrycia przekierowań tymczasowych
>>> 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. To jest przykładowy URL skonfigurowany tak, aby powiadamiać klientów o tymczasowym przekierowaniu do http://diveintomark.org/xml/atom.xml.
  2. Serwer odsyła z powrotem kod statusu 302 wskazujący na tymczasowe przekierowanie. Tymczasowa lokalizacja danych jest podana w nagłówku Location:.
  3. urllib2 wywołuje naszą metodę http_error_302, która wywołuje metodę przodka o tej samej nazwie w urllib2.HTTPRedirectHandler, która wykonuje przekierowanie do nowej lokalizacji. Wtedy nasza metoda http_error_302 zachowuje kod statusu (302), a więc wywołująca aplikacja może go później odczytać.
  4. I oto mamy prawidłowo wykonane przekierowanie do http://diveintomark.org/xml/atom.xml. f.status informuje, iż było to przekierowanie tymczasowe, co oznacza, że ponowne żądania powinniśmy kierować pod stary adres (http://diveintomark.org/redir/example302.xml). Może następnym razem znowu nastąpi przekierowanie, a może nie. Może nastąpi przekierowanie pod całkiem inny adres. Nie do nas należy ta decyzja. Serwer powiedział, że to przekierowanie było tylko tymczasowe, a więc powinniśmy to uszanować. Teraz dostarczamy wystarczającą ilość informacji, aby aplikacja wywołująca była w stanie to uszanować.