☰
fabio 路由重定向完全指南:用 redirect 选项实现 HTTP 状态码跳转与 HTTPS 强制跳转
2026/9/29 1:15:16 网站建设 项目流程
  • 后端
  • API网关
  • 微服务

【免费下载链接】fabio

Consul Load-Balancing made simple

项目地址:https://gitcode.com/gh_mirrors/fa/fabio
点击查看免费下载

fabio(Consul Load-Balancing made simple)为每个路由目标提供了redirect=<code>选项,可以在不修改后端服务的前提下,让进入的 HTTP 请求直接以指定 3xx 状态码跳转到任意目标 URL。本文基于仓库文档与源码,完整讲解 redirect 选项的语法、状态码约束、$path/$host伪变量的展开规则、HTTP→HTTPS 跳转的配置要点,并结合源码与测试用例说明其底层实现原理,帮助你快速落地 301/302/303 等各类重定向场景。

一、核心机制:redirect 选项与 3xx 状态码校验

redirect=<code>是 fabio 路由目标(Target)上的一个选项(opts)。code是重定向响应的 HTTP 状态码,必须位于 300–399 之间,否则路由无效。

从源码看,该选项在 route/route.go 的目标解析阶段被处理:

if opts["redirect"] != "" { t.RedirectCode, err = strconv.Atoi(opts["redirect"]) if err != nil { log.Printf("[ERROR] redirect status code should be numeric in 3xx range. Got: %s", opts["redirect"]) } else if t.RedirectCode < 300 || t.RedirectCode > 399 { t.RedirectCode = 0 log.Printf("[ERROR] redirect status code should be in 3xx range. Got: %s", opts["redirect"]) } }

也就是说,fabio 对 redirect 值做了两层校验:

校验内容结果
必须是纯数字字符串(strconv.Atoi可解析)否则输出错误日志,RedirectCode保持为 0(不重定向)
数值必须落在[300, 399]闭区间否则RedirectCode被强制重置为 0,路由退化为普通代理

对应的结构体字段定义在 route/target.go:

// RedirectCode is the HTTP status code used for redirects. // When set to a value > 0 the client is redirected to the target url. RedirectCode int

当RedirectCode > 0时,目标即被标记为“重定向目标”,请求不会再被转发到上游,而是直接返回跳转响应。

二、配置方式一:route add 命令

通过动态路由命令route add为服务添加重定向目标。目标 URL 写在命令中,opts里携带redirect=<code>:

# 将 /path 重定向到 https://www.google.com/ route add svc /path https://www.google.com/ opts "redirect=301"

这条命令的完整语法格式(见 route/parse_new.go 中内嵌的命令文档)为:

route add <svc> <src> <dst>[ weight <w>][ tags "<t1>,<t2>,..."][ opts "k1=v1 k2=v2 ..."]

其中opts支持多个键值对,用空格分隔,例如:

route add svc /old https://new.example.com/ opts "redirect=301 strip=/old"

三、配置方式二:urlprefix- 服务标签

在使用 Consul 等注册中心时,fabio 通过服务的urlprefix-标签自动生成路由。此时重定向目标 URL 必须写在状态码之后,因为标签本身表达的是“注册该标签的服务地址”,而不是重定向目标:

urlprefix-/path redirect=301,https://www.google.com/

标签的构成规则可以理解为:

urlprefix-<匹配前缀> <option>=<value>,<重定向目标URL>
  • <匹配前缀>:决定哪些请求命中该路由(如/path);
  • redirect=301:跳转状态码;
  • 逗号后的https://www.google.com/:完整的重定向目标 URL。

四、$path 伪变量:保留原始请求路径

重定向目标通常需要保留客户端请求的原始 URI。在目标 URL 末尾追加$path伪变量即可实现:

urlprefix-/path redirect=303,https://www.foo.com$path

$path会被替换为当前请求的完整路径(含查询参数)。BuildRedirectURL的实现位于 route/target.go,其展开逻辑相当精细:

func (t *Target) BuildRedirectURL(requestURL *url.URL) { t.RedirectURL = &url.URL{ Scheme: t.URL.Scheme, Host: t.URL.Host, Path: t.URL.Path, RawPath: t.URL.Path, RawQuery: t.URL.RawQuery, } // treat case of $path not separated with a / from host if strings.HasSuffix(t.RedirectURL.Host, "$path") { t.RedirectURL.Host = t.RedirectURL.Host[:len(t.RedirectURL.Host)-len("$path")] t.RedirectURL.Path = "$path" } // remove / before $path in redirect url if strings.Contains(t.RedirectURL.Path, "/$path") { t.RedirectURL.Path = strings.Replace(t.RedirectURL.Path, "/$path", "$path", 1) t.RedirectURL.RawPath = strings.Replace(t.RedirectURL.RawPath, "/$path", "$path", 1) } // remove strip path, insert passed request path, set query if strings.Contains(t.RedirectURL.Path, "$path") { replacePath := requestURL.Path var replaceRawPath string if requestURL.RawPath == "" { replaceRawPath = requestURL.Path } else { replaceRawPath = requestURL.RawPath } // strip path before replacement if t.StripPath != "" { replacePath = strings.TrimPrefix(replacePath, t.StripPath) replaceRawPath = strings.TrimPrefix(replaceRawPath, t.StripPath) } // add prepend path if t.PrependPath != "" { replacePath = t.PrependPath + replacePath replaceRawPath = t.PrependPath + replaceRawPath } // do path replacement t.RedirectURL.Path = strings.Replace(t.RedirectURL.Path, "$path", replacePath, 1) t.RedirectURL.RawPath = strings.Replace(t.RedirectURL.RawPath, "$path", replaceRawPath, 1) // set query if t.RedirectURL.RawQuery == "" && requestURL.RawQuery != "" { t.RedirectURL.RawQuery = requestURL.RawQuery } } if t.RedirectURL.Path == "" { t.RedirectURL.Path = "/" } t.RedirectURL.Host = strings.Replace(t.RedirectURL.Host, "$host", requestURL.Host, 1) }

几个值得注意的细节:

  1. $path前可带/也可不带:https://www.foo.com$path与https://www.foo.com/$path效果等价,源码会先移除/$path中的斜杠再做替换,避免生成//双斜杠;
  2. 请求查询参数自动继承:当目标 URL 自身没有 RawQuery 而请求带查询串时,查询串会被追加到重定向目标上(如请求/?aaa=1会得到http://bar.com/?aaa=1);
  3. URL 编码保留:替换时同时处理Path与RawPath,编码字符(如%20、%2f)会原样保留,见 route/target_test.go 中的测试用例;
  4. 空路径兜底:如果最终 Path 为空,统一补为/,保证重定向目标永远是一个合法绝对 URL。

$path 展开行为对照表

以下行为均由 route/target_test.go 中的表驱动测试逐一验证:

路由定义请求路径重定向结果
route add svc / http://bar.com/$path/abchttp://bar.com/abc
route add svc / http://bar.com/bbb/$path/a/b/chttp://bar.com/bbb/a/b/c
route add svc / http://bar.com/bbb$path/a/b/chttp://bar.com/bbb/a/b/c
route add svc / http://bar.com/$path/?aaa=1http://bar.com/?aaa=1
route add svc / http://bar.com/$path/%20http://bar.com/%20
route add svc /stripme http://bar.com/$path opts "strip=/stripme"/stripme/abchttp://bar.com/abc
route add svc / http://bar.com/$path opts "prepend=/prefix"/abchttp://bar.com/prefix/abc

五、$host 伪变量与 HTTP→HTTPS 强制跳转

$host伪变量会替换为当前请求的 Host。最典型的用法是把 HTTP 80 端口的流量统一跳到 HTTPS 对应主机,例如 route/table_test.go 中的“典型 HTTPS 跳转”配置:

route add my-service example.com:80/ https://example.com$path opts "redirect=301"

而要让$host生效(把任意主机名的请求跳到同名 HTTPS 地址),需要使用通配主机*:

route add redirect *:80/ https://$host/$path

请求http://foo.com/abc会被跳转到https://foo.com/abc。若只保留主机、不保留路径,则写为:

route add redirect *:80/ https://$host/

$host的替换发生在BuildRedirectURL的最后一行(route/target.go)。

关键前提(来自原文档):要从 HTTP 跳到 HTTPS,路由的 src 必须包含 HTTP 端点的host:port,即example.com:80/或*:80/这样的写法,这样 fabio 才能在 80 端口监听并匹配到 HTTP 请求,否则无法触发跳转。同理,若在 8080 端口提供 HTTP 服务,应写为*:8080/。

组合使用$host与$path的完整跳转(见 route/target_test.go):

route add redirect *:80/ https://$host/$path
请求重定向结果
http://foo.com/https://foo.com/
http://foo.com/abchttps://foo.com/abc
http://foo.com/abc/?aaa=1https://foo.com/abc/?aaa=1

六、与 strip / prepend 选项的协同

重定向路径替换发生在 strip 与 prepend 处理之后(见 route/target.go),因此$path展开的值是“先剥离前缀、再附加前缀”后的结果。这正是 GitHub issue #824 所修复的行为(route/target_test.go):

route add svc *:80/stripme https://bar.com/bbb$path opts "strip=/stripme"
请求路径重定向结果
/stripme/abchttps://bar.com/bbb/abc
/stripme/?aaa=1https://bar.com/bbb/?aaa=1

如果同一目标同时配置strip与prepend,则先 strip 后 prepend:

route add svc / http://bar.com/$path opts "prepend=/prefix strip=/stripme"

请求/stripme/abc将得到http://bar.com/prefix/abc。

七、底层实现:重定向如何被触发

重定向目标确定后,整个请求处理流程如下:

  1. 路由查找:proxy/http_proxy.go 通过p.Lookup(r)找到命中的 Target;若未命中则返回NoRouteStatus配置的状态码(默认 404)与 noroute HTML;
  2. 重定向 URL 预生成:route/table.go 在请求到达时调用target.BuildRedirectURL(req.URL)生成并缓存重定向 URL——这正是 route/target.go 注释“This is cached here to prevent multiple generations per request”的含义,避免每个请求重复生成;
  3. 响应返回:proxy/http_proxy.go 检查t.RedirectCode != 0 && t.RedirectURL != nil,命中后调用标准库http.Redirect(w, r, t.RedirectURL.String(), t.RedirectCode)返回 3xx 响应,不再向任何上游发起请求:
if t.RedirectCode != 0 && t.RedirectURL != nil { http.Redirect(w, r, t.RedirectURL.String(), t.RedirectCode) if p.Stats.RedirectCounter != nil { p.Stats.RedirectCounter.With("code", strconv.Itoa(t.RedirectCode)).Add(1) } return }
  1. 指标统计:每次重定向都会累加RedirectCounter指标,并按状态码打标签(code=301等),可接入 fabio 的 Prometheus/StatsD 等监控体系(见 metrics 目录)。

八、状态码选型建议与注意事项

  • 301 Moved Permanently:永久性迁移,浏览器与搜索引擎会缓存新地址,适合域名/路径永久变更、HTTP→HTTPS 全局升级;
  • 302 Found:临时性跳转,适合 A/B 测试、临时维护页;
  • 303 See Other:语义上明确“请用 GET 重新请求”,适合原文档示例中的表单提交后跳转场景;
  • 307/308:保留请求方法与请求体,适合需要保持 POST 语义的跳转。

配置时请记住原文档强调的两条硬性约束:

  1. redirect的值必须是 300–399 之间的数字,否则目标上的RedirectCode会被置 0,请求将按普通代理转发而非重定向(错误信息会打到日志中);
  2. 使用urlprefix-标签时,重定向目标 URL 必须写在状态码之后(redirect=301,https://...),因为标签默认把“服务注册地址”当作目标。

九、相关参考

  • 原始文档:docs/content/feature/http-redirects.md
  • redirect 解析与状态码校验:route/route.go
  • 重定向 URL 生成与$path/$host展开:route/target.go
  • 重定向触发与指标统计:proxy/http_proxy.go
  • 重定向 URL 缓存:route/table.go
  • 行为测试矩阵:route/target_test.go、route/table_test.go
  • 路由命令完整语法:route/parse_new.go
  • 后端
  • API网关
  • 微服务

【免费下载链接】fabio

Consul Load-Balancing made simple

项目地址:https://gitcode.com/gh_mirrors/fa/fabio
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询