Dynamic Context Switching for Sub-Applications
William Jing

Dynamic Context Switching for Sub-Applications
When deploying modern web applications, you might need to serve the app under a specific subpath rather than the root. This approach, called dynamic context switching or base URL customization, requires coordinated adjustments across backend, frontend, and proxy layers.
Approaches Based on Technology Stack
1. Backend Template Engines (e.g., Thymeleaf)
Backends using template engines like Thymeleaf can inject a dynamic context variable at runtime, making context switching simple.
Example (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";
}
}
In index.html
:
<base th:href="${context}" />
Pros:
Simple implementation
Clear internal routing
Cons:
Single context per deployment
Requires server restart to change context
2. Frontend Static Builds (e.g., React)
Frontends like React build static assets with base paths set at compile time (PUBLIC_URL
). Changing contexts dynamically isn't directly feasible.
<base />
Tag
The <base href="/foo/">
tag simplifies managing URLs by resolving relative paths at the specified base.
Static builds cannot dynamically switch contexts on their own. Middleware solutions (proxies) become essential.
Middleware-Based Context Shifting
Middleware acts as a smart proxy, rewriting request paths and response contents transparently. Inspired by a "man-in-the-middle" concept, it effectively enables dynamic context changes without modifying frontend or backend configurations.
Why Middleware Works
Middleware:
Alters request paths and headers
Modifies response bodies, headers, cookies
Avoids redeployments
Architecture Overview
┌─────────┐ ┌───────────────────┐ ┌───────────┐
│ Browser │ → │ NGINX + Lua Proxy │ → │ Backend │
│ │ /bar │ (middleware) │ /foo │ (/foo) │
└─────────┘ └───────────────────┘ └───────────┘
↑ ↓
└─── /bar ← /foo ← Response from App ─────────┘
NGINX + Lua Middleware
Key tools:
rewrite
(request paths)sub_filter
(HTML/text responses)Lua blocks (
header_filter_by_lua_block
,access_by_lua_block
)proxy_cookie_path
(cookie scoping)
Kubernetes Sidecar Pattern
In Kubernetes, middleware often runs as a sidecar:
+---------------------------------+
| Pod |
| +------------+ +------------+ |
| | Proxy |→ | My App | |
| | (sidecar) |← | (Container)| |
| +------------+ +------------+ |
+---------------------------------+
↑ ↓ ↑ ↓
Requests Responses
The proxy container intercepts requests and responses, dynamically rewriting them without affecting the main application.
Example Scenario: Run /foo
under /bar
Step 1: Rewrite Request Paths
This step ensures requests targeting /bar
are internally redirected to /foo
.
location /bar/ {
rewrite ^/bar/(.*)$ /foo/$1 last;
}
Step 2: Proxy Original Context and Rewrite HTML
This configuration proxies requests to the backend and adjusts HTML content for correct base URLs.
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;
}
Step 3: OIDC Integration (Required if Using Auth)
a. Rewrite Location Header: Adjusts redirect locations returned by the backend to match the new context.
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. Rewrite Redirect URI in Query Strings: Ensures redirect URIs within request queries are correctly formatted.
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. Rewrite Static Login Forms: Updates action URLs within login forms to reflect the new context.
location /foo/oidc/auth/ssh/login {
sub_filter 'action="/foo' 'action="/bar';
sub_filter_once on;
sub_filter_types text/html;
}
d. Final Redirects Post-login: Adjusts final redirect locations after successful authentication.
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
}
}
Step 4: Cookie Path Isolation
Scopes session cookies correctly to avoid collisions.
proxy_cookie_path /foo/ /bar/;
Summary
Dynamic context switching using middleware is highly flexible and ideal for scenarios where redeployment or recompilation is impractical. By leveraging proxy-based solutions such as NGINX combined with Lua scripting, modern web applications can dynamically and transparently rewrite request and response paths, headers, and content without requiring backend or frontend modifications. This approach significantly reduces downtime, simplifies multi-tenant deployments, and streamlines environment management by avoiding frequent application rebuilds or restarts. Overall, middleware-based dynamic context switching provides a robust solution for complex deployment scenarios, offering both flexibility and minimal disruption.