在 HTTPX 中发起链式代理请求时,客户端会先连接下游代理,再由下游代理连接上游代理。下游代理是整个链路的第一跳,如果它启用了 Basic 认证,就必须在第一次连接时携带正确的凭证。HTTPX 使用 HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials 来封装下游代理的用户名和密码,并生成 Proxy-Authorization: Basic ... 请求头。理解这个对象的挂载位置,是避免 407 错误的关键。

一、下游代理认证在链式代理中的位置
HTTPX 的 proxy 插件允许通过 proxy 选项构建多层代理链路。最外层的代理配置对应下游代理,也就是客户端直接连接的第一台代理服务器。如果下游代理启用了 Basic 认证,那么它会在客户端发送 CONNECT 或普通 HTTP 请求时检查 Proxy-Authorization 请求头。这个请求头的值由 Credentials 对象根据用户名和密码生成,格式为 Basic 加 Base64 编码后的 username:password。
很多请求失败并不是因为认证信息错误,而是因为凭证被配置到了错误的位置。以下面的配置结构为例,外层是下游代理,内层 next 是上游代理。下游代理的 Basic 凭证必须放在外层,上游代理的凭证必须放在 next 内部。如果交换位置,下游代理会直接返回 407,而上游代理可能根本不会收到正确的认证信息。
# 链式代理配置模型:外层为下游代理,next 为上游代理
proxy_options = {
uri: "http://downstream-proxy:3128",
username: "downstream_user",
password: "downstream_pass",
next: {
uri: "http://upstream-proxy:8080",
username: "upstream_user",
password: "upstream_pass"
}
}
在这个结构中,username 和 password 并不是简单的字符串,它们最终会被 HTTPX 转换为 HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials 实例。这个类记录了用于下游代理认证的用户名和密码,并负责生成对应的 Basic 认证头。理解这一点后,就能明白为什么配置顺序如此重要:认证头的生成依赖凭证对象是否被正确挂载到下游节点上。
二、通过 proxy 选项配置下游 Basic 凭证
最常用的配置方式是在加载 proxy 插件后,通过 with 方法传入 proxy 选项。下面的代码演示了同时配置下游代理和上游代理 Basic 认证的完整写法。注意外层 username 和 password 对应的就是下游代理,而 next 中的用户名密码对应上游代理。
require "httpx"
client = HTTPX.plugin(:proxy).with(
proxy: {
uri: "http://downstream-proxy:3128",
username: "downstream_user",
password: "downstream_pass",
next: {
uri: "http://upstream-proxy:8080",
username: "upstream_user",
password: "upstream_pass"
}
}
)
response = client.get("https://ipipp.com")
puts response.status
这种写法下,HTTPX 会自动为下游代理创建 Credentials 对象,并在请求下游代理时添加 Proxy-Authorization: Basic ... 头。你不需要手动实例化 HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials。对于只使用一层代理的场景,可以省略 next,只保留外层配置即可。
# 仅配置需要 Basic 认证的下游代理
client = HTTPX.plugin(:proxy).with(
proxy: {
uri: "http://127.0.0.1:3128",
username: "local_user",
password: "local_pass"
}
)
实际开发中,也经常将代理配置放在环境变量或配置文件中。由于下游代理和上游代理的认证信息需要区分层级,建议在加载配置时分别读取不同的变量,避免共用同一组用户名密码。如果下游代理不需要认证,但上游代理需要认证,则不要在外层设置用户名密码,只保留 uri 并在 next 中配置认证信息。
三、显式构建 Credentials 对象与复用
虽然大多数情况下直接使用选项配置即可,但如果你需要在多个客户端之间复用同一套下游代理凭证,或者希望把认证信息独立成可测试的对象,可以显式构建 HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials。这个类的初始化参数通常接受 username 和 password 两个关键字,对应下游代理的 Basic 认证用户名和密码。
# 显式构建下游代理 Basic 凭证对象 credentials = HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials.new( username: "deploy_user", password: "s3cret" )
创建完成后,可以将其属性读取出来,重新合并到下游代理的选项哈希中。这样做的好处是认证信息只维护一份,修改密码时不必在多个请求客户端里重复修改。下面是一个简单的包装示例:
# 通过凭证对象构建下游代理配置
def downstream_proxy_options(credentials)
{
uri: "http://downstream-proxy:3128",
username: credentials.username,
password: credentials.password
}
end
credentials = HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials.new(
username: "deploy_user",
password: "s3cret"
)
client = HTTPX.plugin(:proxy).with(
proxy: downstream_proxy_options(credentials)
)
需要注意的是,Credentials 属于 HTTPX 代理插件内部实现的一部分,直接实例化它的适用场景比较有限。如果你的 HTTPX 版本没有公开这个类,或者初始化参数有所变化,应优先使用稳定的 proxy 选项配置。显式构建对象更适合源码阅读、调试或封装内部工具类,而不应作为常规配置的首选。
四、常见错误与排查方法
下游代理返回 407 Proxy Authentication Required 是最常见的配置错误信号。首先要检查的是 username 和 password 是否被错误地放到了 next 层级。由于 next 对应上游代理,下游代理在第一次握手时看不到上游的凭证,因此会直接拒绝连接。解决方法是把认证信息移动到外层,确保它们挂载在 HTTPX::Plugins::Proxy::Chain::Downstream::Auth::Basic::Credentials 对应的下游节点上。
另一个容易混淆的地方是,HTTP 请求本身可能也需要认证,但这是 Authorization 头,和代理认证使用的 Proxy-Authorization 头不是一回事。即使目标服务器需要登录,也不应该用相同的凭证去配置下游代理。下游代理只关心 Proxy-Authorization 头中的 Basic 凭证,而目标服务器只关心 Authorization 头。两者要分别设置。
如果确认配置层级正确但仍然被拒绝,可以检查密码中是否包含特殊字符。虽然 Basic 认证本身会进行 Base64 编码,但在通过 URI 传递用户名密码时,某些字符可能被错误解析。建议不要将用户名和密码直接拼在代理 URI 的 userinfo 部分,而是使用独立的 username 和 password 键。这样可以避免 @、: 等字符导致的解析歧义。
排查时还可以临时打开 HTTPX 的调试输出,观察向下游代理发送的请求中是否包含 Proxy-Authorization: Basic ... 头。如果该头缺失,说明凭证没有进入下游认证配置;如果该头存在但状态码仍为 407,则需要进一步核对用户名和密码是否正确。完成排查后记得关闭调试输出,避免泄露认证信息。
Ruby HTTPX下游代理基本认证凭证修改时间:2026-10-03 04:10:24