Zanurkuj w Pythonie/HTTP - wszystko razem
Python/HTTP - wszystko razem
edytujWidzieliśmy już wszystkie elementy potrzebne do utworzenia inteligentnego klienta usługi internetowej. Teraz zobaczmy jak to wszystko do siebie pasuje.
Przykład 11.17. Funkcja openanything
Ta funkcja jest zdefiniowana w pliku openanything.py.
def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT):
# non-HTTP code omitted for brevity
if urlparse.urlparse(source)[0] == 'http': #(1)
# open URL with urllib2
request = urllib2.Request(source)
request.add_header('User-Agent', agent) #(2)
if etag:
request.add_header('If-None-Match', etag) #(3)
if lastmodified:
request.add_header('If-Modified-Since', lastmodified) #(4)
request.add_header('Accept-encoding', 'gzip') #(5)
opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler()) #(6)
return opener.open(request) #(7)
- urlparse to bardzo poręczny moduł do, na pewno zgadłeś, parsowania URL-i. Jego podstawowa funkcja, także nazywająca się urlparse, przyjmuje na wejściu URL-a i dzieli go na taką krotkę (schemat, domena, ścieżka, parametry, parametry w żądaniu i identyfikator fragmentu). Jednyną wsród tych rzeczy jaką musimy się przejmować jest schemat, który decyduje o tym czy mamy do czynienie z URL-em HTTP (który to moduł urllib2 może obsłużyć).
- Przedstawiamy się serwerowi HTTP przy pomocy nagłówka User-Agent przesłanego przez funkcję wywołującą. Jeśli nie zostałaby podana wartość User-Agent, użylibysmy wcześniej zdefiniowanej wartośći w openanything.py. Nigdy nie należy używać domyślnej wartości zdefiniowanej w urllib2.
- Jeśli została podana suma kontrolna dla ETag, wysyłamy ją w nagłówku If-None-Match.
- Jeśli została podana data ostatniej modyfikacji, wysyłamy ją w nagłówku If-Modified-Since.
- Powiadamiamy serwer, że chcemy dane skompresowane, jeśli to jest tylko możliwe.
- Wywołujemy funkcję build_opener, która wykorzystuje nasze własne klasy obsługi URL-i: SmartRedirectHandler do obsługi przekierowań 301 i 302 i DefaultErrorHandler do taktownej obsługi 304, 404, i innych błędnych sytuacji.
- I to wszystko! Otwieramy URL-a i zwracamy plikopodobny (ang. file-like) obiekt do funkcji wywołującej.
Przykład 11.18. Funkcja fetch
Ta funkcja jest zdefiniowana w pliku openanything.py.
def fetch(source, etag=None, last_modified=None, agent=USER_AGENT):
'''Fetch data and metadata from a URL, file, stream, or string'''
result = {}
f = openAnything(source, etag, last_modified, agent) #(1)
result['data'] = f.read() #(2)
if hasattr(f, 'headers'):
# save ETag, if the server sent one
result['etag'] = f.headers.get('ETag') #(3)
# save Last-Modified header, if the server sent one
result['lastmodified'] = f.headers.get('Last-Modified') #(4)
if f.headers.get('content-encoding', '') == 'gzip': #(5)
# data came back gzip-compressed, decompress it
result['data'] = gzip.GzipFile(fileobj=StringIO(result['data']])).read()
if hasattr(f, 'url'): #(6)
result['url'] = f.url
result['status'] = 200
if hasattr(f, 'status'): #(7)
result['status'] = f.status
f.close()
return result
- Po pierwsze wywołujemy funkcję openAnything z URL-em, sumą kontrolną ETag, datą ostatniej modyfikacji (ang. Last-Modified date) i wartością User-Agent.
- Czytamy aktualne dane zwrócone przez serwer. Mogą one być spakowane; jeśli tak, to później je rozpakowujemy.
- Zachowujemy sumę kontrolną ETag zwróconą przez serwer; aplikacja wywołująca może ją przesłać następnym razem, przekazując ją dalej do openAnything, która umieści ją w nagłówku If-None-Match i przesłać do zdalnego serwera.
- Zachowujemy także datę ostatniej modyfikacji.
- Jeśli serwer powiedział, że wysłał spakowane dane, rozpakowujemy je.
- Jeśli dostaliśmy URL-a z powrotem od serwera, zachowujemy go i zakładamy, że kod statusu wynosi 200, dopóki nie przekonamy się, że jest inaczej.
- Jeśli któraś z naszych klas obsługi URL-i przechwyci jakiś kod statusu, zachowujemy go także.
Przykład 11.19. Użycie openanything.py
>>> import openanything >>> useragent = 'MyHTTPWebServicesApp/1.0' >>> url = 'http://diveintopython.org/redir/example301.xml' >>> params = openanything.fetch(url, agent=useragent) #(1) >>> params #(2) {'url': 'http://diveintomark.org/xml/atom.xml', 'lastmodified': 'Thu, 15 Apr 2004 19:45:21 GMT', 'etag': '"e842a-3e53-55d97640"', 'status': 301, 'data': '<?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" <-- rest of data omitted for brevity -->'} >>> if params['status'] == 301: #(3) ... url = params['url'] >>> newparams = openanything.fetch( ... url, params['etag'], params['lastmodified'], useragent) #(4) >>> newparams {'url': 'http://diveintomark.org/xml/atom.xml', 'lastmodified': None, 'etag': '"e842a-3e53-55d97640"', 'status': 304, 'data': } #(5)
- Za pierwszym razem, gdy pobieramy jakiś zasób, nie mamy żadnej sumy kontrolnej ETag ani daty ostatniej modyfikacji, a więc opuszczamy te parametry. (To są parametry opcjonalne.)
- Z powrotem otrzymujemy słownik kilku użytecznych nagłówków, kod statusu HTTP i aktualne dane zwrócone przez serwer. Funkcja openanything zajmuje się samodzielnie rozpakowaniem archiwum gzip; nie zajmujemy się tym na tym poziomie.
- Jeśli kiedykolwiek otrzymamy kod statusu 301, czyli trwałe przekierowanie, to musimy zaktualizować naszego URL-a na nowy adres.
- Gdy po raz drugi pobieramy ten sam zasób, to mamy wiele informacji, które możemy przekazać: (być może zaktualizowany) URL, ETag z ostatniego razu, data ostatniej modyfikacji i oczywiście nasz User-Agent.
- Z powrotem ponownie otrzymujemy słownik, ale dane nie uległy zmianie, a więc wszystko co dostaliśmy to był kod statusu 304 i żadnych danych.