11.9. Todo junto

Ya hemos visto todas las partes necesarias para construir un cliente inteligente de servicios web HTTP. Ahora veamos cómo encaja todo.

Ejemplo 11.17. La función openanything

Esta función está definida en openanything.py.


def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT):
    # se omite el código no relativo a HTTP por brevedad
    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
1 urlparse es un módulo de utilidad muy a mano para, adivínelo, analizar URLs. Su función primaria, llamada también urlparse, toma una URL y la divide en tuplas de (esquema, dominio, ruta, parámetros, parámetros de la cadena de consulta, e identificador de fragmento). De estos, lo único que nos interesa es el esquema, para asegurarnos de que tratamos con una URL HTTP (que pueda manejar urllib2).
2 Nos identificamos ante el servidor HTTP con la User-Agent que pasamos a la función de llamada. Si no se especifica una User-Agent trabajaremos con la definida por omisión anteriormente en el módulo openanything.py. Nunca usamos la que se define por omisión en urllib2.
3 Si se nos dio una suma ETag, la enviamos en la cabecera If-None-Match.
4 Si se nos dio una fecha de última modificación, la enviamos en la cabecera If-Modified-Since.
5 Le decimos al servidor que querríamos datos comprimidos si fuera posible.
6 Creamos un abridor de URL que usa ambos manejadores de URL personalizados: SmartRedirectHandler para controlar las redirecciones 301 y 302, y DefaultErrorHandler para controlar grácilmente 304, 404 y otras condiciones de error.
7 ¡Eso es todo! Abre la URL y devuelve a quien invocó un objeto tipo fichero.

Ejemplo 11.18. La función fetch

Esta función está definida en 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                                                    
1 Primero llamamos a la función openAnything con una URL, una suma ETag, una fecha Last-Modified y una User-Agent.
2 Leemos los datos devueltos por el servidor. Pueden estar comprimidos; si lo están los descomprimiremos luego.
3 Guarda la suma ETag devuelta por el servidor para que la aplicación que llama pueda pasárnosla de nuevo la siguiente vez, y a su vez nosotros a openAnything, que puede adjuntarla a la cabecera If-None-Match y enviarla al servidor remoto.
4 Guardamos también la fecha Last-Modified.
5 Si el servidor dice que envió datos comprimidos, los descomprimimos.
6 Si obtuvimos una URL del servidor, la guardamos y asumimos que el código de estado es 200 a menos que encontremos otra cosa.
7 Si uno de los manejadores de URL personalizados capturó un código de estado, también lo guardamos.

Ejemplo 11.19. Uso de 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
1 La primera vez que pedimos un recurso no tenemos suma ETag o fecha Last-Modified, así que no las incluimos. (Son parámetros opcionales.)
2 Lo que obtenemos es un diccionario de varias cabeceras útiles, el código de estado HTTP y los datos en sí devueltos por el servidor. openanything trata la compresión gzip internamente; no tenemos que preocuparnos de eso a este nivel.
3 Si alguna vez obtenemos un código de estado 301, eso es una redirección permanente y necesitamos actualizar la URL a la nueva dirección.
4 La segunda vez que pedimos el mismo recurso, tenemos todo tipo de información para acompañar: Una URL (posiblemente actualizada), la ETag y la fecha Last-Modified de la última vez, y por supuesto nuestra User-Agent.
5 De nuevo lo que obtenemos es un diccionario, pero los datos no han cambiado, así que todo lo que obtenemos es un código de estado 304 sin datos.