切换应用程序的执行上下文
使用Nginx、Lua和Sidecar模式,在不同子路径下热切换 Web 应用程序。

在部署现代 Web 应用程序时,您可能需要在特定子路径而不是根路径下运行应用程序。这种方法称为动态上下文(Base URL)切换,需要跨后端、前端和代理层进行协调调整。
不同技术栈
1. 后端模板引擎(例如 Thymeleaf)
使用模板引擎(如 Thymeleaf)的后端可以在运行时注入动态上下文变量,使上下文切换变得简单。
示例(Spring Boot + Thymeleaf):
@Controller
public class HomeController {
@Value("${app.context:/}")
private String context;
@GetMapping("${app.context:/}")
public String home(Model model) {
model.addAttribute("context", context);
return "index";
}
}在 index.html 中:
<base th:href="${context}" />优点:
实现简单
内部路由清晰
缺点:
每次部署只有一个上下文
更改上下文需要重启服务器
2. 前端静态构建(例如 React)
像 React 这样的前端在编译时设置基础路径(PUBLIC_URL)构建静态资源。动态更改上下文并不直接可行。
<base /> 标签
<base href="/foo/"> 标签通过在指定基础处解析相对路径来简化 URL 管理。
静态构建无法自行动态切换上下文。中间件解决方案(代理)变得至关重要。
基于中间件的上下文切换
中间件充当智能代理,透明地重写请求路径和响应内容。受"中间人"概念启发,它有效地实现了动态上下文更改,而无需修改前端或后端配置。
为什么中间件有效
中间件:
更改请求路径和标头
修改响应体、标头、Cookie
避免重新部署
架构概述
┌─────────┐ ┌───────────────────┐ ┌───────────┐
│ 浏览器 │ → │ NGINX + Lua 代理 │ → │ 后端 │
│ │ /bar │ (中间件) │ /foo │ (/foo) │
└─────────┘ └───────────────────┘ └───────────┘
↑ ↓
└─── /bar ← /foo ← 来自应用的响应 ─────────────┘NGINX + Lua 中间件
关键工具:
rewrite(请求路径)sub_filter(HTML/文本响应)Lua 块(
header_filter_by_lua_block、access_by_lua_block)proxy_cookie_path(Cookie 作用域)
Kubernetes Sidecar 模式
在 Kubernetes 中,中间件通常作为 sidecar 运行:
+---------------------------------+
| Pod |
| +------------+ +------------+ |
| | 代理 |→ | 我的应用 | |
| | (sidecar) |← | (容器) | |
| +------------+ +------------+ |
+---------------------------------+
↑ ↓ ↑ ↓
请求 响应代理容器拦截请求和响应,动态重写它们而不影响主应用程序。
示例场景:在 /bar 下运行 /foo
步骤 1:重写请求路径
此步骤确保针对 /bar 的请求在内部重定向到 /foo。
location /bar/ {
rewrite ^/bar/(.*)$ /foo/$1 last;
}步骤 2:代理原始上下文并重写 HTML
此配置将请求代理到后端并调整 HTML 内容以获得正确的基础 URL。
location /foo/ {
proxy_pass https://<your-app-backend>;
proxy_set_header Host $http_host;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
sub_filter '<base href="/foo/' '<base href="/bar/';
sub_filter_once on;
sub_filter_types text/html;
}步骤 3:OIDC 集成(如果使用身份验证则必需)
a. 重写 Location 标头: 调整后端返回的重定向位置以匹配新上下文。
location /foo/api/oidc/auth {
proxy_pass https://<your-app-backend>;
header_filter_by_lua_block {
local location = ngx.header.location
if location then
ngx.header.location = ngx.re.sub(location, "/foo/", "/bar/", "jo")
ngx.header.location = ngx.re.sub(location, "foo%2F", "bar%2F", "jo")
end
}
}b. 重写查询字符串中的重定向 URI: 确保请求查询中的重定向 URI 格式正确。
access_by_lua_block {
local args = ngx.req.get_uri_args()
if args.redirect_uri then
args.redirect_uri = ngx.re.sub(args.redirect_uri, "https://[^:]+:[^/]+/bar/", "https://<your-app-backend>/foo/", "jo")
ngx.req.set_uri_args(args)
end
}c. 重写静态登录表单: 更新登录表单中的操作 URL 以反映新上下文。
location /foo/oidc/auth/ssh/login {
sub_filter 'action="/foo' 'action="/bar';
sub_filter_once on;
sub_filter_types text/html;
}d. 登录后的最终重定向: 在成功身份验证后调整最终重定向位置。
location /foo/oidc/approval {
header_filter_by_lua_block {
local location = ngx.header.location
if location then
ngx.header.location = ngx.re.sub(location, "https://[^:]+:[^/]+/foo/", "https://<your-app-frontend>/bar/", "jo")
end
}
}步骤 4:Cookie 路径隔离
正确限定会话 Cookie 的作用域以避免冲突。
proxy_cookie_path /foo/ /bar/;总结
使用中间件的动态上下文切换非常灵活,非常适合重新部署或重新编译不实用的场景。通过利用基于代理的解决方案(如结合 Lua 脚本的 NGINX),现代 Web 应用程序可以动态透明地重写请求和响应路径、标头和内容,而无需后端或前端修改。这种方法显著减少了停机时间,简化了多租户部署,并通过避免频繁的应用程序重建或重启来简化环境管理。总的来说,基于中间件的动态上下文切换为复杂的部署场景提供了强大的解决方案,既提供了灵活性又最大限度地减少了中断。


