11.7. 处理重定向

您可以使用不同类型的自定义 URL 处理程序来支持永久和临时重定向。

首先,让我们看看为什么首先需要重定向处理程序。

示例 11.10. 不使用重定向处理程序访问 Web 服务

>>> import urllib2, httplib
>>> httplib.HTTPConnection.debuglevel = 1           1
>>> request = urllib2.Request(
...     'http://diveintomark.org/redir/example301.xml') 2
>>> opener = urllib2.build_opener()
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: '
GET /redir/example301.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 301 Moved Permanently\r\n'             3
header: Date: Thu, 15 Apr 2004 22:06:25 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml  4
header: Content-Length: 338
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0                              5
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:06:25 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml
>>> f.url                                               6
'http://diveintomark.org/xml/atom.xml'
>>> f.headers.dict
{'content-length': '15955', 
'accept-ranges': 'bytes', 
'server': 'Apache/2.0.49 (Debian GNU/Linux)', 
'last-modified': 'Thu, 15 Apr 2004 19:45:21 GMT', 
'connection': 'close', 
'etag': '"e842a-3e53-55d97640"', 
'date': 'Thu, 15 Apr 2004 22:06:25 GMT', 
'content-type': 'application/atom+xml'}
>>> f.status
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: addinfourl instance has no attribute 'status'
1 如果打开调试功能,您将能够更好地了解正在发生的事情。
2 这是一个我已设置为永久重定向到我在 http://diveintomark.org/xml/atom.xml 的 Atom 提要的 URL。
3 当然,当您尝试下载该地址的数据时,服务器会返回 301 状态码,告诉您资源已永久移动。
4 服务器还会返回一个 Location: 标头,其中包含此数据的新地址。
5 urllib2 会注意到重定向状态码,并自动尝试检索 Location: 标头中指定的新位置的数据。
6 您从 opener 获取的对象包含新的永久地址以及从第二个请求(从新的永久地址检索)返回的所有标头。但是缺少状态码,因此您无法以编程方式知道此重定向是临时的还是永久的。这非常重要:如果是临时重定向,则您应该继续在旧位置请求数据。但如果是永久重定向(就像这样),您应该从现在开始在新位置请求数据。

这并非最佳选择,但很容易修复。当 urllib2 遇到 301302 时,它的行为并不完全符合您的期望,所以让我们覆盖它的行为。怎么做?使用自定义 URL 处理程序,就像您处理 304 代码一样

示例 11.11. 定义重定向处理程序

此类在 openanything.py 中定义。


class SmartRedirectHandler(urllib2.HTTPRedirectHandler):     1
    def http_error_301(self, req, fp, code, msg, headers):  
        result = urllib2.HTTPRedirectHandler.http_error_301( 2
            self, req, fp, code, msg, headers)              
        result.status = code                                 3
        return result                                       

    def http_error_302(self, req, fp, code, msg, headers):   4
        result = urllib2.HTTPRedirectHandler.http_error_302(
            self, req, fp, code, msg, headers)              
        result.status = code                                
        return result                                       
1 重定向行为在 urllib2 中定义在一个名为 HTTPRedirectHandler 的类中。您不想完全覆盖该行为,您只想稍微扩展一下,因此您将继承 HTTPRedirectHandler,以便您可以调用祖先类来完成所有艰苦的工作。
2 当它从服务器遇到 301 状态码时,urllib2 将搜索其处理程序并调用 http_error_301 方法。我们的第一个方法只是调用祖先中的 http_error_301 方法,该方法处理查找 Location: 标头并跟随重定向到新地址的繁重工作。
3 关键是:在返回之前,您存储状态码(301),以便调用程序稍后可以访问它。
4 临时重定向(状态码 302)的工作方式相同:覆盖 http_error_302 方法,调用祖先,并在返回之前保存状态码。

那么这给我们带来了什么?您现在可以使用自定义重定向处理程序构建 URL 打开程序,它仍然会自动跟随重定向,但现在它还会公开重定向状态码。

示例 11.12. 使用重定向处理程序检测永久重定向

>>> request = urllib2.Request('http://diveintomark.org/redir/example301.xml')
>>> import openanything, httplib
>>> httplib.HTTPConnection.debuglevel = 1
>>> opener = urllib2.build_opener(
...     openanything.SmartRedirectHandler())           1
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: 'GET /redir/example301.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 301 Moved Permanently\r\n'            2
header: Date: Thu, 15 Apr 2004 22:13:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml
header: Content-Length: 338
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:13:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml

>>> f.status                                           3
301
>>> f.url
'http://diveintomark.org/xml/atom.xml'
1 首先,使用您刚刚定义的重定向处理程序构建一个 URL 打开程序。
2 您发送了一个请求,并收到了一个 301 状态码作为响应。此时,将调用 http_error_301 方法。您调用祖先方法,该方法跟随重定向并在新位置(http://diveintomark.org/xml/atom.xml)发送请求。
3 这就是回报:现在,您不仅可以访问新的 URL,还可以访问重定向状态码,因此您可以判断这是永久重定向。下次您请求此数据时,您应该从新位置(http://diveintomark.org/xml/atom.xml,如 f.url 中指定)请求它。如果您已将位置存储在配置文件或数据库中,则需要更新它,这样您就不会继续向旧地址的服务器发送请求。是时候更新您的地址簿了。

相同的重定向处理程序还可以告诉您不应该更新您的地址簿。

示例 11.13. 使用重定向处理程序检测临时重定向

>>> request = urllib2.Request(
...     'http://diveintomark.org/redir/example302.xml')   1
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: '
GET /redir/example302.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 302 Found\r\n'                           2
header: Date: Thu, 15 Apr 2004 22:18:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Location: http://diveintomark.org/xml/atom.xml
header: Content-Length: 314
header: Connection: close
header: Content-Type: text/html; charset=iso-8859-1
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0                                3
Host: diveintomark.org
User-agent: Python-urllib/2.1
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:18:21 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Content-Length: 15955
header: Connection: close
header: Content-Type: application/atom+xml
>>> f.status                                              4
302
>>> f.url
http://diveintomark.org/xml/atom.xml
1 这是我设置的一个示例 URL,它被配置为告诉客户端临时重定向到 http://diveintomark.org/xml/atom.xml
2 服务器返回 302 状态码,表示临时重定向。数据的临时新位置在 Location: 标头中给出。
3 urllib2 调用您的 http_error_302 方法,该方法调用 urllib2.HTTPRedirectHandler 中同名的祖先方法,该方法跟随重定向到新位置。然后您的 http_error_302 方法存储状态码(302),以便调用应用程序稍后可以获取它。
4 在这里,您已成功跟随重定向到 http://diveintomark.org/xml/atom.xmlf.status 告诉您这是一个临时重定向,这意味着您应该继续从原始地址(http://diveintomark.org/redir/example302.xml)请求数据。也许下次它也会重定向,也许不会。也许它会重定向到不同的地址。这不是你说了算的。服务器说此重定向只是暂时的,因此您应该尊重这一点。现在,您正在公开足够的信息,以便调用应用程序可以尊重这一点。