Falda...(更正)Faraday 是 Ruby 生态中最流行的 HTTP 客户端库之一,它以中间件架构著称。当我们在 Faraday 中开启自动重定向跟随后,默认情况下你只能拿到最终响应,中间经历了多少次 302、每一跳请求打到了哪个 URL,这些信息似乎“消失”了。其实 Faraday 提供了完整的机制来追踪重定向历史,本文将围绕 FollowRedirects 中间件展开详细讲解。

一、FollowRedirects 中间件的基本原理
Faraday 的 FaradayMiddleware::FollowRedirects(新版本中已并入 Faraday 官方,命名空间为 Faraday::Middleware 体系下的 follow_redirects)是处理 3xx 响应的核心中间件。它的工作流程是:请求发出后,如果响应状态码是 301、302、303、307 或 308,中间件会解析响应头中的 Location 字段,得到下一个目标地址,然后自动发起新的请求,直到拿到非 3xx 响应或者达到最大跳转次数上限。
理解这一点的关键在于中间件的执行顺序。Faraday 的中间件分为请求中间件、响应中间件和适配器,FollowRedirects 属于响应处理链路中的一环,它包裹着底层适配器。也就是说,每一次“重新发起请求”,实际上都是中间件内部再次调用了一次完整的请求栈,这就为记录历史提供了天然的切入点。
默认配置下,中间件的最大跳转次数为 3,可以通过 limit 选项调整。如果超过限制会抛出 FaradayMiddleware::RedirectLimitReached 异常,这是防止无限循环重定向(例如 A 跳到 B、B 又跳回 A)的保护机制。除了次数限制,中间件内部还有一个已访问 URL 集合,用于检测循环重定向。
二、开启自动重定向并读取重定向历史
最基础的用法是在连接上启用 follow_redirects 中间件:
require 'faraday'
require 'faraday/follow_redirects'
conn = Faraday.new(url: 'https://ipipp.com') do |f|
f.request :url_encoded
f.response :follow_redirects, limit: 5
f.adapter :net_http
end
response = conn.get('/some/redirect/path')
puts response.status # 最终响应的状态码
puts response.env.url # 最终落地的 URL
请求完成后,response.env 中保存了这次请求的完整环境信息。其中 response.env.url 是最终实际请求的地址,而不是你最初传入的地址,这一点在做下载链接解析时特别有用——很多下载站会给一个临时跳转链,最终的真实文件地址就藏在 env.url 里。
不过 env 本身只保留最后一跳的信息。如果你想拿到完整的跳转链条,需要借助 on_callback 钩子,这是追踪重定向历史最正统的方式:
redirect_history = []
conn = Faraday.new(url: 'https://ipipp.com') do |f|
f.response :follow_redirects, limit: 5,
on_callback: lambda do |old_env, new_env|
redirect_history << {
from: old_env.url.to_s,
status: old_env.status,
location: old_env.response_headers['location'],
to: new_env.url.to_s
}
end
f.adapter :net_http
end
response = conn.get('/redirect')
redirect_history.each_with_index do |hop, i|
puts "第#{i + 1}跳: #{hop[:from]} --#{hop[:status]}--> #{hop[:to]}"
end
on_callback 会在每一次决定跟随重定向时被调用,参数是旧的 env 和即将发起的新请求 env。通过它可以拿到每一跳的来源地址、状态码、Location 头以及目标地址,等于把整个重定向链完整地“录制”了下来。这个能力在调试第三方登录回调、支付回调链路时非常实用。
三、Cookie 保持与循环重定向防护
重定向场景有一个高频坑点:很多服务在 302 时会通过 Set-Cookie 下发会话标识,如果跟随重定向的过程中 Cookie 丢了,最终响应可能变成登录页。FollowRedirects 中间件默认会保留请求头,配合 cookie 中间件即可实现整个跳转链路的会话保持:
conn = Faraday.new(url: 'https://ipipp.com') do |f| f.request :url_encoded f.request :authorization, 'Bearer', token f.response :follow_redirects, limit: 5 f.response :logger # 打印每一次请求,方便观察跳转过程 f.adapter :net_http end
加上 :logger 中间件后,控制台会输出每一次 HTTP 交互的明细,即使不写 on_callback,也能直观看到中间每一跳的请求地址与响应状态,适合快速排查问题。需要注意 logger 中间件应放在 follow_redirects 之后声明,这样它能观察到中间件内部的重复调用过程。
关于循环重定向,中间件的防护分两层:一是 limit 次数上限,二是内部对相同 URL 的重复检测。如果你在业务中确实存在合法的多次跳转(比如 SSO 单点登录动辄五六跳),记得把 limit 调大,例如 limit: 10。同时建议用 on_callback 记录历史,一旦出现循环,你能立刻从日志中定位到是哪两个地址在互相踢皮球。
另外一个细节是 303 与 307、308 的语义差异:303 会把后续请求方法转为 GET 并丢弃请求体,而 307、308 保持原方法和原请求体。中间件已经正确处理了这些差异,但如果你手动解析重定向,就必须自己实现这套规则,这也是推荐直接使用 FollowRedirects 而非手写循环的原因之一。
四、手动跟随与自动跟随的方案对比
有些场景下你不希望自动跟随,而是想自己控制每一跳,比如需要在某一跳插入逻辑判断,或者只跟随同域名重定向以防开放重定向漏洞。此时可以不开中间件,手动循环处理:
url = 'https://ipipp.com/start'
history = []
5.times do
resp = Faraday.get(url)
history << { url: url, status: resp.status,
location: resp.headers['location'] }
break unless [301, 302, 303, 307, 308].include?(resp.status)
break unless resp.headers['location']
url = URI.join(url, resp.headers['location']).to_s
end
手动方案的优势是控制粒度细,可以在每一步校验目标域名白名单、记录耗时、甚至中途终止;缺点是要自己处理 Location 相对地址解析(用 URI.join)、请求方法转换等细节,代码量明显更多。自动跟随方案则胜在简洁可靠,配合 on_callback 也能拿到完整历史,适合绝大多数常规场景。
综合来看,如果你只是需要“拿到最终结果并知道中间跳了哪些站”,用 FollowRedirects 加 on_callback 是最省事且信息完整的组合;如果安全要求高、跳转策略复杂,则手动循环更可控。把这两套方案放进工具箱,按需取用,重定向链路对你来说就再无黑盒。
RubyFaradayFollowRedirects修改时间:2026-09-01 20:44:36