11.7. 处理重定向
Description: Python from novice to pro
Keywords: Python, Dive Into Python, tutorial, object-oriented, programming, documentation, book, free

# 深入 Python :Dive Into Python 中文版 Python 从新手到专家 [Dip_5.4b_CPyUG_Release] | Find:

## 11.7. 处理重定向

你可以使用两种不同的自定义 URL 处理器来处理永久重定向和临时重定向。

首先，让我们来看看重定向处理的必要性。

### 例 11.10. 没有重定向处理的情况下，访问 web 服务

    >>> import urllib2, httplib
    >>> httplib.HTTPConnection.debuglevel = 1
    >>> request = urllib2.Request(
    ...     'http://diveintomark.org/redir/example301.xml')
    >>> 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'
    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
    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: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
    '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'

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

这不太理想，但很容易改进。实际上当 `urllib2` 遇到 `301` 或 `302` 时的行为并不是我们所期望的，所以让我们来覆盖这些行为。如何实现呢？用一个自定义的处理器，正如你处理 `304` 代码所做的。

### 例 11.11. 定义重定向处理器

这个类定义在 `openanything.py`。

    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

| 重定向行为定义在 `urllib2` 的一个叫做 `HTTPRedirectHandler` 的类中。我们不想完全地覆盖这些行为，只想做点扩展，所以我们子类化 `HTTPRedirectHandler`，从而我们仍然可以调用祖先类来实现所有原来的功能。
| 当从服务器获得 `301` 状态代码，`urllib2` 将搜索处理器并调用 `http_error_301` 方法。我们首先要做的就是在祖先中调用 `http_error_301` 方法，它将处理查找 `Location:` 头信息的工作并跟踪重定向到新地址。
| 这是关键：返回之前，你存储了状态代码 (`301`)，所以主调程序稍后就可以访问它了。
| 临时重定向 (状态代码 `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())
    >>> 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'
    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
    301
    >>> f.url
    'http://diveintomark.org/xml/atom.xml'

| 首先，用刚刚定义的重定向处理器创建一个 URL 开启器。
| 你发送了一个请求，并在响应中获得了 `301` 状态代码。 如此一来，`http_error_301` 方法就被调用了。你调用了祖先类，跟踪了重定向并且发送了一个新地址 (`http://diveintomark.org/xml/atom.xml`) 请求。
| 这是决定性的一步：现在，你不仅做到了访问一个新 URL，而且获得了重定向的状态代码，所以你可以断定这是一个永久重定向。下一次你请求这个数据时，就应该使用 `f.url` 指定的新地址 (`http://diveintomark.org/xml/atom.xml`)。如果你已经在配置文件或数据库中存储了这个地址，就需要更新旧地址而不是反复地使用旧地址请求服务。现在是更新你的地址簿的时候了。

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

### 例 11.13. 使用重定向处理器检查临时重定向

    >>> request = urllib2.Request(
    ...     'http://diveintomark.org/redir/example302.xml')
    >>> 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'
    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
    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
    302
    >>> f.url
    http://diveintomark.org/xml/atom.xml

| 这是一个 URL，我已经设置了它，让它告诉客户端临时 重定向到 `http://diveintomark.org/xml/atom.xml`。
| 服务器返回 `302` 状态代码，标识出一个临时重定向。数据的临时新地址在 `Location:` 头信息中给出。
| `urllib2` 调用你的 `http_error_302` 方法，它调用了 `urllib2.HTTPRedirectHandler` 中的同名的祖先方法，跟踪重定向到一个新地址。然后你的 `http_error_302` 方法存储状态代码 (`302`) 使主调程序在稍后可以获得它。
| 此时，已经成功追踪重定向到 `http://diveintomark.org/xml/atom.xml`。`f.status` 告诉你这是一个临时重定向，这意味着你应该继续使用原来的地址 (`http://diveintomark.org/redir/example302.xml`) 请求数据。也许下一次它仍然被重定向，也许不会。也许会重定向到不同的地址。这也不好说。服务器说这个重定向仅仅是临时的，你应该尊重它。并且现在你获得了能使主调程序尊重它的充分信息。