在 HTTP 协议中,一般 3 开头的状态码,都用于表示 重定向
:因为某些原因,例如目标网页已经存在其它网站,服务器会通知客户端访问另一个网页。
Location
为了告诉客户端应改前往哪一个页面,服务器在返回的响应 (response) 的 headers 中用 Location
字段标明具体应该访问的页面。
例如,访问 http://example.com
时,如果服务器想让浏览器跳转到 http://google.com
,可以在 response 中写:
1 | HTTP/1.0 302 Redirect |
一般浏览器收到后,会自动跳转。
另外,URL 也可以标明为相对路径,比如,在上个例子中,如果跳转到 http://example.com/hello.html
,则可以标记为:
1 | HTTP/1.0 302 Redirect |
Koa.js
在 Koa.js 中,context 有一个方法为 redirect
,专门用于定向,而这个方法实际委托给了 lib/response.js
。
其具体代码为:
1 | redirect(url, alt) { |
如果 URL 为 back
,那么会跳转回请求来源的方向,比如你在 http://github.com/lazzzis
点击了 lazzzis.github.io
,那么在请求 lazzzis.github.io
的 request 的头部中,字段为 Referrer: http://github.com/lazzzis
。换句话说,back
的意思就是 “从哪里来,就回哪里去”。
而 this.set('Location', url)
作用则就是之前说的,将头部 headers 中 Location
设置为客户端应该去访问的那个 URL。之后,便是将状态码设置为 302。
这边,Koa.js 怕浏览器不会自动跳转,因此将也设置了消息主体部分,通知用户应该跳转。
requests
接下来用 Python 的 requests 做实验。我们先用 Koa.js 写一个简单的服务端:
1 | const Koa = require('koa') |
然后发起请求:
1 | import requests |
可以发现,requests 已经帮我们做了自动跳转。如果不想让它跳转的话,可以设置 allow_redirects
参数(默认为 True):
1 | r = requests.post("http://localhost:5000", allow_redirects=False) |
取消跳转后,可以看到它这次停止了跳转。关于限制跳转的相关源码在 requests.py (代码太长,所以就不粘贴了)。
在 653 行: yield_requests=True
使得在 resolve_redirects 中时,不会进入下一步的 send:在 206 - 225 的分支可以看到, 如果 yield_requests=True
,那么 requests 会做接下来的请求。
获取下一个请求的 URL
在 requests.py 的 98 行的 get_redirect_target 的实现中,location = resp.headers['location']
表明了这里的处理和 Koa.js
是一样的,也是从 location
字段获取。
更改请求方式
另外,还有一个有意思的事情,我在请求的时候,发的是 POST
请求,可是 GitHub Pages 不支持 POST 的呀,那么 requests 一定换了另一种方法:
1 | r = requests.post("http://localhost:5000") |
看的出来,requests 使其变为了 GET。这里的实现在于: requests.py 的 164 行 self.rebuild_method(prepared_request, resp)
和 292 行开始的 rebuild_method
的实现。尤其是 304 行和 309 行,将请求方法改为了 GET
。
递归请求
这里想象两种极端情况。
一是如果服务端 A 实现出错,使得要求客户端依旧跳到 A。那么,requests 请求 A, 而之后有继续请求 A。这样,陷入了一个死循环。
第二种,类似,但不只一个服务器出错: A 要求跳转到 B,而 B 要求跳转到 C,可是 C 又要求跳转到 A,那么,这里也同样陷入了一个死循环。
requests 考虑到了这点,做了限制,避免一直跳转:
先对上面的服务端做一点修改:
1 | app.use(async (ctx, next) => { |
1 | r = requests.post("http://localhost:5000") |
python 自定义了一个异常,用于说明引起的原因是过多的重定向,并且说明了 requests 最先跳转次数为 30 次。
可以看到在 requests.py 的第 139 行处,requests 本身记录了请求的历史,如果历史条数,也就是请求的次数,大于限制时会抛出异常。