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.
>>> 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'
- 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)
- To jest URL, który ma ustawione trwałe przekierowanie do RSS-a pod adresem http://diveintomark.org/xml/atom.xml.
- 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.
- Serwer przesyła także nagłówek Location:, który zawiera nowy adres tych danych.
- urllib2 zauważa ten kod statusu dotyczący przekierowania i automatycznie próbuje pobrać dane spod nowej lokalizacji podanej w nagłówku Location:.
- 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.
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
- Obsługa przekierowań w
urllib2
jest zdefiniowana w klasie o nazwieHTTPRedirectHandler
. 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. - 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 metodyhttp_error_301
przodka, która wykona całą robotę związaną ze znalezieniem nagłówkaLocation:
i przekierowaniem żądania pod nowy adres. - Tu jest kluczowa sprawa: zanim wykonamy
return
, zachowujemy kod statusu (301), aby program wywołujący mógł go później odczytać. - 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.
>>> 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'
- Po pierwsze tworzymy
opener
z przed chwilą zdefiniowaną klasą obsługi przekierowań. - 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). - 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.
>>> 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
- To jest przykładowy URL skonfigurowany tak, aby powiadamiać klientów o tymczasowym przekierowaniu do http://diveintomark.org/xml/atom.xml.
- Serwer odsyła z powrotem kod statusu 302 wskazujący na tymczasowe przekierowanie. Tymczasowa lokalizacja danych jest podana w nagłówku Location:.
urllib2
wywołuje naszą metodęhttp_error_302
, która wywołuje metodę przodka o tej samej nazwie wurllib2.HTTPRedirectHandler
, która wykonuje przekierowanie do nowej lokalizacji. Wtedy nasza metodahttp_error_302
zachowuje kod statusu (302), a więc wywołująca aplikacja może go później odczytać.- 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ć.