Zanurkuj w Pythonie/HTTP

(Przekierowano z Python/HTTP)

Nurkujemy

edytuj

Do 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:

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