Capítulo 11. Servicios Web HTTP

11.1. Inmersión

Hemos aprendido cosas sobre procesamiento de HTML y de XML, y por el camino también vio cómo descargar una página web y analizar XML de una URL, pero profundicemos algo más en el tema general de los servicios web HTTP.

Dicho sencillamente, los servicios web HTTP son formas programáticas de enviar y recibir datos a/desde servidores remotos usando directamente operaciones HTTP. Si queremos obtener datos del servidor usaremos HTTP GET; si queremos enviar datos nuevos usaremos HTTP POST. (Algunos API más avanzados de servicios web HTTP también definen maneras para modificar y eliminar datos existentes, usando HTTP PUT y HTTP DELETE). En otras palabras, los “verbos” que incorpora el protocolo HTTP (GET, POST, PUT y DELETE) se relacionan directamente con las operaciones de las aplicaciones para recibir, enviar, modificar y borrar datos.

La ventaja principal de este enfoque es la simplicidad, y esta simplicidad ha resultado ser popular en muchos sitios diferentes. Se pueden crear y almacenar de forma estática los datos (normalmente XML), o generarlos de forma dinámica mediante un script en el servidor, y todos los lenguajes más usados incluyen bibliotecas HTTP para descargarlos. La depuración también es sencilla porque se puede cargar el servicio web en cualquier servidor web y ver los datos sin tratar. Los navegadores modernos incluso dan un formato de aspecto agradable a los datos XML, para permitirle navegar por ellos rápidamente.

Ejemplos de servicios web puros XML-sobre-HTTP:

  • La Amazon API le permite descargar información de productos de la tienda web de Amazon.com.
  • El National Weather Service (Estados Unidos) y el Hong Kong Observatory (Hong Kong) ofrecen alertas meteorológicas como servicio web.
  • La Atom API para gestión de contenido basado en web.
  • Los feeds sindicados de los weblog y sitios de noticias le traen noticias de última hora desde gran variedad de sitios.

En próximos capítulos explorará APIs que usan HTTP como transporte para enviar y recibir datos, pero no relacionará la semántica de la aplicación a la propia de HTTP (hacen todo usando HTTP POST). Pero este capítulo se concentrará en usar HTTP GET para obtener datos de un servidor remoto, y exploraremos varias características de HTTP que podemos usar para obtener el máximo beneficio de los servicios web puramente HTTP.

Aquí tiene una versión más avanzada del módulo openanything que vio en el capítulo anterior:

Ejemplo 11.1. openanything.py

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.


import urllib2, urlparse, gzip
from StringIO import StringIO

USER_AGENT = 'OpenAnything/1.0 +http://diveintopython.org/http_web_services/'

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):
    '''URL, filename, or string --> stream

    This function lets you define parsers that take any input source
    (URL, pathname to local or network file, or actual data as a string)
    and deal with it in a uniform manner.  Returned object is guaranteed
    to have all the basic stdio read methods (read, readline, readlines).
    Just .close() the object when you're done with it.

    If the etag argument is supplied, it will be used as the value of an
    If-None-Match request header.

    If the lastmodified argument is supplied, it must be a formatted
    date/time string in GMT (as returned in the Last-Modified header of
    a previous request).  The formatted date/time will be used
    as the value of an If-Modified-Since request header.

    If the agent argument is supplied, it will be used as the value of a
    User-Agent request header.
    '''

    if hasattr(source, 'read'):
        return source

    if source == '-':
        return sys.stdin

    if urlparse.urlparse(source)[0] == 'http':                                      
        # open URL with urllib2                                                     
        request = urllib2.Request(source)                                           
        request.add_header('User-Agent', agent)                                     
        if etag:                                                                    
            request.add_header('If-None-Match', etag)                               
        if lastmodified:                                                            
            request.add_header('If-Modified-Since', lastmodified)                   
        request.add_header('Accept-encoding', 'gzip')                               
        opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler())
        return opener.open(request)                                                 
    
    # try to open with native open function (if source is a filename)
    try:
        return open(source)
    except (IOError, OSError):
        pass

    # treat source as string
    return StringIO(str(source))

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)             
    result['data'] = f.read()                                        
    if hasattr(f, 'headers'):                                        
        # save ETag, if the server sent one                          
        result['etag'] = f.headers.get('ETag')                       
        # save Last-Modified header, if the server sent one          
        result['lastmodified'] = f.headers.get('Last-Modified')      
        if f.headers.get('content-encoding', '') == 'gzip':          
            # data came back gzip-compressed, decompress it          
            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                                                    

Lecturas complementarias