重定向看起来只是“换个地址”,但 301、302、303、307、308 对请求方法和请求体的处理并不一样,不同的 HTTP 客户端库处理 301 / 302 的方式甚至互相矛盾。这篇用本机服务器逐个试,再看循环重定向的上限和 Location 的各种写法。
http://www.python.org/ → 301 Moved Permanently,Location = https://www.python.org/,消息体 0 字节,用时 1014 ms;Cache-Control = None;Content-Type = None http://python.org/ → 301 Moved Permanently,Location = https://www.python.org/,消息体 0 字节,用时 1030 ms;Cache-Control = None;Content-Type = None requests 跟随 http://python.org/:经过 1 次重定向,最终 200 https://www.python.org/,总用时 1187 ms;跳转链 [(301, 'https://www.python.org/')]
python.org 明文请求 301 到 https://www.python.org/,同时升级 HTTPS 并统一 www。
状态码 requests urllib3 urllib.request (每格:最终到达 /final 时的 方法+请求体字节数)
301 GET+0B POST+5B GET+0B
302 GET+0B POST+5B GET+0B
303 GET+0B GET+0B GET+0B
307 POST+5B POST+5B 异常 HTTP 307
308 POST+5B POST+5B 异常 HTTP 308
规范(RFC 9110)的意图:301 / 302 历史上浏览器把 POST 改成 GET;303 明确要求改成 GET;307 / 308 必须保持方法和请求体。301 / 302 各库不一致:requests、urllib.request 改成 GET,urllib3 保持 POST;303 都变 GET;307 / 308 保持方法(urllib.request 不跟随)。
requests :服务器共收到 31 次 /loop 请求后客户端放弃,异常 TooManyRedirects urllib3 默认 :服务器共收到 4 次 /loop 请求后客户端放弃,异常 MaxRetryError urllib.request :服务器共收到 11 次 /loop 请求后客户端放弃,异常 HTTPError
各库默认的最大重定向数不同:urllib3 3,urllib.request 10,requests 30。
Location: /abs/path → 最终请求 /abs/path;最终 URL http://127.0.0.1:<端口>/abs/path Location: rel/path → 最终请求 /rel/path;最终 URL http://127.0.0.1:<端口>/rel/path Location: ../up → 最终请求 /up;最终 URL http://127.0.0.1:<端口>/up Location: //127.0.0.1:9400/proto-rel → 最终请求 /proto-rel;最终 URL http://127.0.0.1:<端口>/proto-rel Location: /with?x=1#frag → 最终请求 /with?x=1;最终 URL http://127.0.0.1:<端口>/with?x=1#frag
可以是绝对路径、相对路径、父目录或协议相对 URL;片段不发给服务器。