...
Because all traffic arrives at a single host (demo.tatsoft.com), IIS URL Rewrite is used as a reverse-proxy router. The configuration uses RewriteMaps to centralize project-to-port resolution, eliminating per-project rule duplication and making it trivial to add new demos.
Two RewriteMaps drive all routing decisions:
Map name | Input | Output | Purpose |
|---|---|---|---|
| Lowercase project name | Route code ( | Resolves the cookie value from the URL segment |
| Route code ( | Local port ( | Resolves the backend port from the cookie |
Entry redirect to _setroute
Visiting the site root or /<Project> (with or without trailing slash) redirects to /<Project>/_setroute. The root redirects to /ProveIt/_setroute by default.
Cookie-based route selection
The _setroute endpoint resolves the cookie value dynamically using ProjectNameToRoute and sets the routing cookie:
The user is then redirected to the unified /html5/ endpoint.
Deep-link normalization
Direct access to /<Project>/html5/... (bookmarks, refreshes, deep links) is handled by a dedicated rule that re-sets the cookie and redirects to /html5/<path>, preserving the sub-path.
Unified reverse proxy for /html5/
All requests under /html5/ are forwarded to the correct local backend. The target port is resolved dynamically from the cookie using ProjectRouteToPort:
http://127.0.0.1:{ProjectRouteToPort:{C:2}}/html5/{R:1}
Unified reverse proxy for /rtServices/
WebSocket upgrade requests and all /rtServices/ traffic are routed to the correct backend using the same cookie + RewriteMap mechanism:
http://127.0.0.1:{ProjectRouteToPort:{C:2}}/rtServices/{R:1}After the WebSocket upgrade completes, subsequent client ↔ backend traffic flows through that long-lived connection and routing decisions are locked in for the session.
Demo path | Cookie | Local backend | Public URL |
|---|---|---|---|
| p1 |
| |
| p2 |
| |
| p3 |
| |
| p4 |
|
| Code Block | ||
|---|---|---|
|
...
| |||
<rewriteMaps>
<!-- Project name (lowercase) -> route code (cookie value) -->
<rewriteMap name="ProjectNameToRoute">
<add key="proveit" value="p1" />
<add key="tupinix" value="p2" />
<add key="solarpanel" value="p3" />
<add key="bottlingline" value="p4" />
</rewriteMap>
<!-- Route code (cookie value) -> local backend port -->
<rewriteMap name="ProjectRouteToPort">
<add key="p1" value="3101" />
<add key="p2" value="3201" />
<add key="p3" value="3301" />
<add key="p4" value="3401" />
</rewriteMap>
</rewriteMaps> |
Rule 1: Site root → ProveIt _setroute - Stop processing other rules
Visiting the bare hostname redirects to the default demo entry point.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="Default -> ProveIt _setroute" stopProcessing="true">
<match url="^$" />
<action type="Redirect" url="/ProveIt/_setroute" redirectType="Found" />
</rule> |
Rule 2:/<Project> → /<Project>/_setroute - Stop processing other rules
A single rule handles all project root redirects using a grouped match. Case-insensitive.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="Project root -> _setroute" stopProcessing="true">
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine)/?$" ignoreCase="true" />
<action type="Redirect" url="/{R:1}/_setroute" redirectType="Found" />
</rule> |
Rule 3: Set routing cookie and redirect to /html5/- Stop Processing Other rules
Resolves the project name to its cookie value via ProjectNameToRoute, sets the ProjectRoute cookie, then redirects to the unified /html5/ endpoint.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="SetRoute by project" stopProcessing="true">
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine)/_setroute$" ignoreCase="true" />
<serverVariables>
<set name="RESPONSE_Set-Cookie"
value="ProjectRoute={ProjectNameToRoute:{ToLower:{R:1}}}; Path=/; Secure; SameSite=None" />
</serverVariables>
<action type="Redirect" url="/html5/" redirectType="Found" />
</rule> |
Rule 4: Deep-link normalization — /<Project>/html5/... → /html5/... - Stop processing other rules
Handles bookmarks, browser refreshes, and direct deep links that include the project prefix. Re-sets the cookie and redirects to /html5/, preserving any sub-path.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="Normalize project html5 -> /html5" stopProcessing="true">
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine)/html5/?(.*)$" ignoreCase="true" />
<serverVariables>
<set name="RESPONSE_Set-Cookie"
value="ProjectRoute={ProjectNameToRoute:{ToLower:{R:1}}}; Path=/; Secure; SameSite=None" />
</serverVariables>
<action type="Redirect" url="/html5/{R:2}" redirectType="Found" />
</rule> |
Rule 5: Reverse proxy — /html5/... → local backend - Stop processing other rules
Reads the ProjectRoute cookie, resolves the port via ProjectRouteToPort, and proxies the request to the correct local backend. The cookie capture group {C:2} extracts the route code.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="html5 -> backend by ProjectRoute cookie" stopProcessing="true">
<match url="^html5/(.*)$" />
<conditions>
<add input="{HTTP_COOKIE}" pattern="(^|;\s*)ProjectRoute=(p[1-4])($|;)" />
</conditions>
<action type="Rewrite" url="http://127.0.0.1:{ProjectRouteToPort:{C:2}}/html5/{R:1}" />
</rule> |
Rule 6: Reverse proxy — /rtServices/... → local backend - Stop processing other rules
Same mechanism as Rule 5. Routes WebSocket upgrade requests and all /rtServices/ traffic to the correct local backend using the ProjectRoute cookie.
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
<rule name="rtServices -> backend by ProjectRoute cookie" stopProcessing="true">
<match url="^rtServices/(.*)$" />
<conditions>
<add input="{HTTP_COOKIE}" pattern="(^|;\s*)ProjectRoute=(p[1-4])($|;)" />
</conditions>
<action type="Rewrite" url="http://127.0.0.1:{ProjectRouteToPort:{C:2}}/rtServices/{R:1}" />
</rule> |
Parameter | Example | Notes |
|---|---|---|
Demo path |
| Used in URLs and rule patterns |
Cookie value |
| Next available value after |
Local backend port |
| Must be reachable at |
Entry page |
| Keep consistent with existing demos |
The backend must be reachable from IIS on the Azure VM:
| Code Block | ||||
|---|---|---|---|---|
| ||||
http://127.0.0.1:3501/ |
| Code Block | ||||
|---|---|---|---|---|
| ||||
<!-- In ProjectNameToRoute -->
<add key="newdemo" value="p5" />
<!-- In ProjectRouteToPort -->
<add key="p5" value="3501" /> |
Three rules use an alternation pattern listing all project names. Add the new name to each:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<!-- Rule 2: Project root -> _setroute -->
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine|NewDemo)/?$" ignoreCase="true" />
<!-- Rule 3: SetRoute by project -->
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine|NewDemo)/_setroute$" ignoreCase="true" />
<!-- Rule 4: Normalize project html5 -->
<match url="^(ProveIt|Tupinix|SolarPanel|BottlingLine|NewDemo)/html5/?(.*)$" ignoreCase="true" /> |
Update the regex to include the new route code:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<!-- Before -->
<add input="{HTTP_COOKIE}" pattern="(^|;\s*)ProjectRoute=(p[1-4])($|;)" />
<!-- After (to include p5) -->
<add input="{HTTP_COOKIE}" pattern="(^|;\s*)ProjectRoute=(p[1-5])($|;)" /> |
Test | Expected result |
|---|---|
Open | Browser redirects through |
Inspect cookies |
|
Open a deep link, e.g. | Cookie is set; browser lands on |
Monitor | Requests are routed to |
Refresh the page | Cookie persists; page reloads correctly without redirect loop |
Rule order matters.
The first matching rule with stopProcessing="true" wins. Do not reorder rules without understanding the full evaluation flow.
The ProjectRoute cookie is set with Secure; SameSite=None. This is consistent with modern browser requirements when cross-site behaviour may occur. Do not change these attributes without a clear reason.
FrameworX creates a dedicated WebSocket connection per demo. The initial WebSocket upgrade request is made against the shared /rtServices endpoint — it is not namespaced under /DemoName/. Because WebSockets are long-lived, IIS must route the upgrade request to the correct backend from the start. That is why Rules 5 and 6 both rely on the ProjectRoute cookie: it is guaranteed to be present by the time any /rtServices/ or /html5/ request is made.
After the WebSocket upgrade completes, all subsequent client ↔ backend traffic flows through that long-lived connection. Routing decisions are effectively locked in for the session and are no longer evaluated by IIS URL Rewrite.
If a second demo is opened in a new tab while another demo's WebSocket upgrade is still being negotiated, the overwritten ProjectRoute cookie may cause the second demo's /rtServices/ request to be routed to the wrong backend. Once both WebSocket connections are established, sessions are independent.