Dynamic Context Switching for Sub-Applications

Wi

William Jing

Web Development
NGINX
Lua
Proxy
K8s
Middleware
5
5 min read
Last modified: May 25, 2025
Dynamic Context Switching for Sub-Applications

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
  }
}

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.

Comments

You must be logged in to comment.