- 后端
- API网关
- 微服务
【免费下载链接】fabio
Consul Load-Balancing made simple
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) }几个值得注意的细节:
$path前可带/也可不带:https://www.foo.com$path与https://www.foo.com/$path效果等价,源码会先移除/$path中的斜杠再做替换,避免生成//双斜杠;- 请求查询参数自动继承:当目标 URL 自身没有 RawQuery 而请求带查询串时,查询串会被追加到重定向目标上(如请求
/?aaa=1会得到http://bar.com/?aaa=1); - URL 编码保留:替换时同时处理
Path与RawPath,编码字符(如%20、%2f)会原样保留,见 route/target_test.go 中的测试用例; - 空路径兜底:如果最终 Path 为空,统一补为
/,保证重定向目标永远是一个合法绝对 URL。
$path 展开行为对照表
以下行为均由 route/target_test.go 中的表驱动测试逐一验证:
| 路由定义 | 请求路径 | 重定向结果 |
|---|---|---|
route add svc / http://bar.com/$path | /abc | http://bar.com/abc |
route add svc / http://bar.com/bbb/$path | /a/b/c | http://bar.com/bbb/a/b/c |
route add svc / http://bar.com/bbb$path | /a/b/c | http://bar.com/bbb/a/b/c |
route add svc / http://bar.com/$path | /?aaa=1 | http://bar.com/?aaa=1 |
route add svc / http://bar.com/$path | /%20 | http://bar.com/%20 |
route add svc /stripme http://bar.com/$path opts "strip=/stripme" | /stripme/abc | http://bar.com/abc |
route add svc / http://bar.com/$path opts "prepend=/prefix" | /abc | http://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/abc | https://foo.com/abc |
http://foo.com/abc/?aaa=1 | https://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/abc | https://bar.com/bbb/abc |
/stripme/?aaa=1 | https://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。
七、底层实现:重定向如何被触发
重定向目标确定后,整个请求处理流程如下:
- 路由查找:proxy/http_proxy.go 通过
p.Lookup(r)找到命中的 Target;若未命中则返回NoRouteStatus配置的状态码(默认 404)与 noroute HTML; - 重定向 URL 预生成:route/table.go 在请求到达时调用
target.BuildRedirectURL(req.URL)生成并缓存重定向 URL——这正是 route/target.go 注释“This is cached here to prevent multiple generations per request”的含义,避免每个请求重复生成; - 响应返回: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 }- 指标统计:每次重定向都会累加
RedirectCounter指标,并按状态码打标签(code=301等),可接入 fabio 的 Prometheus/StatsD 等监控体系(见 metrics 目录)。
八、状态码选型建议与注意事项
- 301 Moved Permanently:永久性迁移,浏览器与搜索引擎会缓存新地址,适合域名/路径永久变更、HTTP→HTTPS 全局升级;
- 302 Found:临时性跳转,适合 A/B 测试、临时维护页;
- 303 See Other:语义上明确“请用 GET 重新请求”,适合原文档示例中的表单提交后跳转场景;
- 307/308:保留请求方法与请求体,适合需要保持 POST 语义的跳转。
配置时请记住原文档强调的两条硬性约束:
redirect的值必须是 300–399 之间的数字,否则目标上的RedirectCode会被置 0,请求将按普通代理转发而非重定向(错误信息会打到日志中);- 使用
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
相关推荐
Apache APISIX redirect 插件完全指南:URI 重定向与 HTTP 跳转 HTTPS 的配置实战
Apache APISIX redirect 插件完全指南:URI 重定向与 HTTP 跳转 HTTPS 的配置实战 redirect 是 Apache API
后端微服务云原生go_router 重定向(Redirection)完全指南:基于应用状态的路由跳转、顶层与路由级重定向及 redirectLimit 限制
go_router 重定向(Redirection)完全指南:基于应用状态的路由跳转、顶层与路由级重定向及 redirectLimit 限制 导读 重定向(Re
跨平台移动开发UI组件开发工具Apache APISIX redirect 插件详解:URI 重定向、HTTP 强制跳转 HTTPS 与查询字符串保留实战
Apache APISIX redirect 插件详解:URI 重定向、HTTP 强制跳转 HTTPS 与查询字符串保留实战 导读 本文聚焦 Apache AP
后端微服务云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考