第 11 章. HTTP Web 服务

11.1. 深入探讨

您已经了解了HTML 处理XML 处理,并且在此过程中您看到了如何下载网页以及如何解析来自 URL 的 XML,但让我们深入探讨更通用的主题:HTTP Web 服务。

简单地说,HTTP Web 服务是使用 HTTP 操作直接从远程服务器发送和接收数据的编程方法。如果要从服务器获取数据,请使用直接的 HTTP GET;如果要将新数据发送到服务器,请使用 HTTP POST。(一些更高级的 HTTP Web 服务 API 还定义了使用 HTTP PUT 和 HTTP DELETE 修改现有数据和删除数据的方法。)换句话说,内置于 HTTP 协议中的“动词”(GET、POST、PUT 和 DELETE)直接映射到用于接收、发送、修改和删除数据的应用程序级操作。

这种方法的主要优点是简单性,其简单性已在许多不同站点中得到证明。数据(通常是 XML 数据)可以静态构建和存储,也可以由服务器端脚本动态生成,并且所有主要语言都包含用于下载数据的 HTTP 库。调试也更容易,因为您可以在任何 Web 浏览器中加载 Web 服务并查看原始数据。现代浏览器甚至会为您很好地格式化和美化 XML 数据,以便您快速浏览它。

纯 XML over HTTP Web 服务示例

在后面的章节中,您将探索使用 HTTP 作为传输层来发送和接收数据的 API,但不会将应用程序语义映射到底层 HTTP 语义。(它们通过 HTTP POST 隧道传输所有内容。)但本章将集中讨论使用 HTTP GET 从远程服务器获取数据,并且您将探索可以使用的一些 HTTP 功能,以充分利用纯 HTTP Web 服务。

以下是您在上一章中看到的 openanything 模块的更高级版本

示例 11.1. openanything.py

如果您还没有这样做,则可以下载本书中使用的此示例和其他示例


import urllib2, urlparse, gzip
from StringIO import StringIO

USER_AGENT = 'OpenAnything/1.0 +https://diveintopythonbook.pythonlang.cn/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                                                    

延伸阅读