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 这是一个我已经设置了永久重定向到我的 Atom feed http://diveintomark.org/xml/atom.xml 的 URL。
3 毫无疑问, 当你试图从那个地址下载数据时, 服务器会返回 301 状态代码, 告诉你你访问的资源已经被永久移动了。
4 服务器同时返回 Location: 头信息,它给出了这个数据的新地址。
5 urllib2 注意到了重定向状态代码并会自动从Location: 头信息中给出的新地址获取数据。
6 opener 返回的对象包括新的永久地址和第二次请求获得的所有头信息 (从一个新的永久地址获得)。 但是状态代码不见了, 因此你无从知晓重定向到底是永久重定向还是临时重定向。 这是至关重要的: 如果这是临时重定向, 那么你应该继续使用旧地址访问数据。 但是如果是永久重定向 (正如本例), 你应该从现在起使用新地址访问数据。

这不太理想, 但很容易改进。 实际上当 urllib2 遇到 301302 时并不做行为, 所以让我们来覆盖这些行为。 如何实现呢? 用一个自定义的头信息, 正如你处理 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, 而且获得了重定向的状态代码, 所以你可以断定这是一个永久重定向。 下一次你请求这个数据时, 就应该在 f.url) 指定使用新地址 (http://diveintomark.org/xml/atom.xml。 如果你已经在配置文件或数据库中存储了这个地址, 就需要更新旧地址而不是反复地使用旧地址请求服务。 现在是更新你的地址簿的时候了。

同样的重定向处理也可以告诉你 不该 更新你的地址簿。

例 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) 请求数据。 也许下一次它仍然被重定向, 也许不会。 也许会重定向到不同的地址。 这也不好说。 服务器说这个重定向仅仅是临时的, 你应该尊重它。 并且现在你获得了调用程序能尊重它的充分信息。