Zanurkuj w Pythonie/HTTP
HTTP
edytujNurkujemy
edytujDo tej pory nauczyliśmy się już przetwarzać HTML i XML, zobaczyliśmy jak pobrać stronę internetową, a także jak parsować dane XML pobrane poprzez URL. Zagłębmy się teraz nieco bardziej w tematykę usług sieciowych HTTP.
W uproszczeniu, usługi sieciowe HTTP są programistycznym sposobem wysyłania i odbierania danych ze zdalnych serwerów, wykorzystując do tego bezpośrednio transmisje po HTTP. Jeżeli chcesz pobrać dane z serwera, użyj po prostu metodę GET protokołu HTTP; jeżeli chcesz wysłać dane do serwera, użyj POST. (Niektóre, bardziej zaawansowane API serwisów HTTP definiują także sposób modyfikacji i usuwania istniejących danych -- za pomocą metod HTTP PUT i DELETE). Innymi słowy, "czasowniki" wbudowane w protokół HTTP (GET, POST, PUT i DELETE) pośrednio przekształcają operacje HTTP na operacje na poziomie aplikacji: odbierania, wysyłania, modyfikacji i usuwania danych.
Główną zaletą tego podejścia jest jego prostota. Popularność tego rozwiązania została udowodniona poprzez ogromną liczbę różnych witryn. Dane -- najczęściej w formacie XML -- mogą być wygenerowane i przechowane statycznie, albo też generowane dynamicznie poprzez skrypty po stronie serwera, i inne popularne języki, włączając w to bibliotekę HTTP. Łatwiejsze jest także debugowanie, ponieważ możemy wywołać dowolną usługę sieciową w dowolnej przeglądarce internetowej i obserwować zwracane surowe dane. Współczesne przeglądarki także czytelnie sformatują otrzymane dane XML, i pozwolą na szybką nawigację wśród nich.
Przykłady użycia czystych usług sieciowych typu XML poprzez HTTP:
- Amazon API (http://www.amazon.com/webservices) pozwala na pobieranie informacji o produktach oferowanych w sklepie Amazon.com
- National Weather Service (http://www.nws.noaa.gov/alerts/) (United States) i Hong Kong Observatory (http://demo.xml.weather.gov.hk/) (Hong Kong) oferuje informowanie o pogodzie w formie usługi sieciowej
- Atom API (http://atomenabled.org/) -- zarządzanie zawartością stron www
W kolejnych rozdziałach zapoznamy się z różnymi API, które wykorzystują protokół HTTP jako nośnik do wysyłania i odbierania danych, ale które nie przekształcają operacji na poziomie aplikacji na operacje w HTTP (zamiast tego tunelują wszystko poprzez HTTP POST). Ale ten rozdział koncentruje się na wykorzystywaniu metody GET protokołu HTTP do pobierania danych z serwera -- poznamy kilka cech HTTP, które pozwolą nam jak najlepiej wykorzystać możliwości czystych usług sieciowych HTTP.
Poniżej jest bardziej zaawansowana wersja modułu openanything.py, który przedstawiliśmy w poprzednim rozdziale:
import urllib2, urlparse, gzip
from StringIO import StringIO
USER_AGENT = 'OpenAnything/%s +http://diveintopython.org/http_web_services/' % __version__
class SmartRedirectHandler(urllib2.HTTPRedirectHandler):
def http_error_301(self, req, fp, code, msg, headers):
result = urllib2.HTTPRedirectHandler.http_error_301(
self, req, fp, code, msg, headers)
result.status = code
return result
def http_error_302(self, req, fp, code, msg, headers):
result = urllib2.HTTPRedirectHandler.http_error_302(
self, req, fp, code, msg, headers)
result.status = code
return result
class DefaultErrorHandler(urllib2.HTTPDefaultErrorHandler):
def http_error_default(self, req, fp, code, msg, headers):
result = urllib2.HTTPError(
req.get_full_url(), code, msg, headers, fp)
result.status = code
return result
def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT):
u"""URL, nazwa pliku lub łańcuch znaków --> strumień
Funkcja ta pozwala tworzyć parsery, które przyjmują jakieś źródło wejścia
(URL, ścieżkę do pliku lokalnego lub gdzieś w sieci lub dane w postaci łańcucha znaków),
a następnie zaznajamia się z nim w odpowiedni sposób. Zwracany obiekt będzie
posiadał wszystkie podstawowe metody czytania wejścia (read, readline, readlines).
Ponadto korzystamy z .close(), gdy obiekt już nam nie będzie potrzebny.
Kiedy zostanie podany argument etag, zostanie on wykorzystany jako wartość
nagłówka żądania URL-a If-None-Match.
Jeśli argument lastmodified zostanie podany, musi być on formie
łańcucha znaków określającego czas i datę w GMT.
Data i czas sformatowana w tym łańcuchu zostanie wykorzystana
jako wartość nagłówka żądania If-Modified-Since.
Jeśli argument agent zostanie określony, będzie on wykorzystany
w nagłówku żądania User-Agent.
"""
if hasattr(source, 'read'):
return source
if source == '-':
return sys.stdin
if urlparse.urlparse(source)[0] == 'http':
# otwiera URL za pomocą urllib2
request = urllib2.Request(source)
request.add_header('User-Agent', agent)
if lastmodified:
request.add_header('If-Modified-Since', lastmodified)
if etag:
request.add_header('If-None-Match', etag)
request.add_header('Accept-encoding', 'gzip')
opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler())
return opener.open(request)
# próbuje otworzyć za pomocą wbudowanej funkcji open (jeśli source to nazwa pliku)
try:
return open(source)
except (IOError, OSError):
pass
# traktuje source jak łańcuch znaków
return StringIO(str(source))
def fetch(source, etag=None, lastmodified=None, agent=USER_AGENT):
u"""Pobiera dane z URL, pliku, strumienia lub łańcucha znaków"""
result = {}
f = openAnything(source, etag, lastmodified, agent)
result['data'] = f.read()
if hasattr(f, 'headers'):
# zapisuje ETag, jeśli go wysłał do nas serwer
result['etag'] = f.headers.get('ETag')
# zapisuje nagłówek Last-Modified, jeśli został do nas wysłany
result['lastmodified'] = f.headers.get('Last-Modified')
if f.headers.get('content-encoding') == 'gzip':
# odkompresowuje otrzymane dane, ponieważ są one zakompresowane jako gzip
result['data'] = gzip.GzipFile(fileobj=StringIO(result['data'])).read()
if hasattr(f, 'url'):
result['url'] = f.url
result['status'] = 200
if hasattr(f, 'status'):
result['status'] = f.status
f.close()
return result