Router prefix-doubling: /hero_books/web/ looks for web_web.sock instead of web.sock #113

Closed
opened 2026-05-27 13:46:46 +00:00 by mik-tf · 4 comments
Owner

A request to https://hcockpit.gent01.qa.grid.tf/hero_books/web/ (admin VM 0069) hits the router waiting-for-socket page with:

Socket 'web_web.sock' not found - service may be restarting. Attempt 1 of 15 - reloading in 2s.

The hero_books_web crate's service.toml declares its socket path as hero_books/web.sock, not hero_books/web_web.sock. The router appears to be concatenating the URL subpath segment web with the service kind value web when constructing the socket lookup.

Repro: anonymous + authenticated browser request against the live admin VM after the s168 deploy (binaries: hero_router unchanged this session, hero_cockpit_web and hero_books_* binaries refreshed). The 302-to-OAuth before this page renders confirms the request reaches the router proper.

Acceptance: a fresh /hero_books/web/ request resolves to the hero_books/web.sock Unix socket without doubling the subpath into the filename.

A request to https://hcockpit.gent01.qa.grid.tf/hero_books/web/ (admin VM 0069) hits the router waiting-for-socket page with: > Socket 'web_web.sock' not found - service may be restarting. Attempt 1 of 15 - reloading in 2s. The hero_books_web crate's service.toml declares its socket path as `hero_books/web.sock`, not `hero_books/web_web.sock`. The router appears to be concatenating the URL subpath segment `web` with the service `kind` value `web` when constructing the socket lookup. Repro: anonymous + authenticated browser request against the live admin VM after the s168 deploy (binaries: hero_router unchanged this session, hero_cockpit_web and hero_books_* binaries refreshed). The 302-to-OAuth before this page renders confirms the request reaches the router proper. Acceptance: a fresh `/hero_books/web/` request resolves to the `hero_books/web.sock` Unix socket without doubling the subpath into the filename.
ashraf self-assigned this 2026-06-07 09:25:02 +00:00
Member

Implementation Spec for Issue #113

Objective

A request to /hero_books/web/ (and generally /<service>/web[/...], /<service>/app[/...]) must resolve to the service's canonical <service>/web.sock Unix socket, never to the doubled web_web.sock. The fix must guarantee no subpath segment is concatenated with itself into a web_<segment>.sock filename when the segment is already a canonical web/app socket basename, and must apply identically to the HTTP proxy and the WebSocket tunnel.

Root Cause

The per-service proxy dispatcher matches the URL's webname segment against a fixed set of arms (rpc, ui, admin, rest, api, openrpc, python, js) and routes everything else through the catch-all _ arm.

In crates/hero_router/src/server/proxy/http/http_dispatch.rs:545-553, the _ arm computes the socket filename:

_ => {
    // Try direct socket name first (e.g. explorer_rpc -> explorer_rpc.sock),
    // then web_<name>.sock.
    let direct = format!("{}.sock", webname);                    // line 548 -> "web.sock"
    let sock_name = if sock_dir.join(service_name).join(&direct).exists() {
        direct                                                    // line 550
    } else {
        format!("web_{}.sock", webname)                           // line 552 -> "web_web.sock"  <- DOUBLING
    };

For /hero_books/web/, service_name = "hero_books" and webname = "web". The arm builds direct = "web.sock" and, only if that file exists on disk at dispatch time, uses it. Otherwise it falls through to format!("web_{}.sock", webname) = web_web.sock. The webname-to-socket contract documented at http_dispatch.rs:49 (<name> -> web_<name>.sock) is designed for named web endpoints where webname is the suffix (e.g. public -> web_public.sock); it is wrong when webname is literally web/app, which are canonical UI-socket basenames.

This is corroborated by the scanner: classify_socket in crates/hero_router/src/scanner.rs:58 treats web.sock (and app.sock) as SocketKind::Ui — first-class canonical sockets, not named web endpoints. The dispatcher has dedicated arms for ui/admin (which call resolve_ui_socket, whose directory fallback at crates/hero_router/src/server/proxy/http/http_socket_resolve.rs:72 already checks web.sock), but no arm for web or app, so they fall to _ and are subject to the doubling fallback.

The doubled name surfaces in the user-visible error via the not-found path: the _ arm passes the computed sock_name to not_found_response(service_name, &sock_name, &headers) at http_dispatch.rs:567, which formats "Socket '{sock_name}' not found — service may be restarting." at crates/hero_router/src/server/routes.rs:773; the "Attempt N of 15 — reloading" wrapper is the browser auto-retry page (retry_html). This confirms the resolution path: the rendered web_web.sock is exactly the value produced at http_dispatch.rs:552.

The identical defective logic is duplicated for the WebSocket tunnel at crates/hero_router/src/server/proxy/http/http_websocket.rs:97-102:

let direct = format!("{other}.sock");
let sock_name = if sock_dir.join(service_name).join(&direct).exists() {
    direct
} else {
    format!("web_{other}.sock")
};

Requirements

  • /hero_books/web/ and /hero_books/web/<rest> resolve to <sock_dir>/hero_books/web.sock.
  • No webname segment is ever doubled into web_<segment>.sock when <segment> is itself a canonical web/app socket basename (web, app).
  • Named web endpoints continue to work: /<svc>/public -> web_public.sock, /<svc>/<name> -> web_<name>.sock.
  • Direct-named sockets continue to work: /<svc>/explorer_rpc -> explorer_rpc.sock (existing direct behavior preserved).
  • The HTTP proxy (http_dispatch.rs) and the WebSocket tunnel (http_websocket.rs) resolve the socket filename identically.
  • The fix is covered by a unit test so regressions are caught without a live VM.
  • The not-found/waiting page, when the socket is genuinely absent, must report web.sock (the correct expected name) rather than web_web.sock.

Files to Modify/Create

  • crates/hero_router/src/server/proxy/http/http_socket_resolve.rs — add a small pure helper that maps a non-reserved webname to its candidate socket filename(s) without doubling; export it for the dispatcher, the WS tunnel, and tests.
  • crates/hero_router/src/server/proxy/http/http_dispatch.rs — replace the inline direct/web_<name> computation in the _ arm (lines 548-553) with a call to the shared helper; update the comment block at lines 40-49 to document that web/app map to web.sock/app.sock.
  • crates/hero_router/src/server/proxy/http/http_websocket.rs — replace the inline direct/web_<other> computation (lines 97-102) with the same shared helper.

Implementation Plan

Step 1: Add a shared, testable webname-to-socket-filename resolver

Files: crates/hero_router/src/server/proxy/http/http_socket_resolve.rs

  • Add a pure function that, given service_name, webname, and sock_dir, returns the socket filename to use:
    • Compute direct = format!("{webname}.sock"). If sock_dir.join(service_name).join(&direct).exists(), return direct.
    • If webname == "web" || webname == "app" (the canonical UI basenames per scanner.rs:58), return direct even when it does not yet exist — so the doubling fallback is never reached and the not-found page reports web.sock/app.sock. This also self-heals the timing case where the socket is momentarily absent during a restart.
    • Otherwise return format!("web_{webname}.sock") (unchanged behavior for named endpoints).
  • Keep the function pub(super) so both http_dispatch.rs and http_websocket.rs can call it.
    Dependencies: none

Step 2: Use the shared resolver in the HTTP dispatcher catch-all arm

Files: crates/hero_router/src/server/proxy/http/http_dispatch.rs

  • In the _ arm (lines 545-554), replace the inline computation with let sock_name = resolve_web_socket_name(service_name, webname, sock_dir); (add the helper to the existing import from http_socket_resolve).
  • Leave the subsequent socket_path / not_found_response logic unchanged.
  • Update the module mapping comment at lines 40-49 to document the precedence and the web/app exception.
    Dependencies: Step 1

Step 3: Use the shared resolver in the WebSocket tunnel

Files: crates/hero_router/src/server/proxy/http/http_websocket.rs

  • In the other => { ... } arm (lines 94-113), replace the inline computation with let sock_name = resolve_web_socket_name(service_name, other, sock_dir);.
  • Keep the subsequent existence-check logic.
    Dependencies: Step 1

Step 4: Add unit tests for the resolver

Files: crates/hero_router/src/server/proxy/http/http_socket_resolve.rs

  • Test resolve_web_socket_name(svc, "web", dir) returns "web.sock" when the file is absent (the #113 regression case).
  • Test it returns "web.sock" when <dir>/<svc>/web.sock exists.
  • Test "app" returns "app.sock" (absent and present).
  • Test "public" returns "web_public.sock" when neither public.sock nor web_public.sock exists (named-endpoint fallback preserved).
  • Test "explorer_rpc" returns "explorer_rpc.sock" when that file exists (direct-named socket preserved).
    Dependencies: Steps 1-3

Step 5: Build and run the test suite

Files: none (verification only)

  • Run cargo build -p hero_router and cargo test -p hero_router.
  • Confirm the new tests pass and the existing tests in http_dispatch.rs still pass.
    Dependencies: Steps 1-4

Acceptance Criteria

  • A fresh GET /hero_books/web/ resolves to <sock_dir>/hero_books/web.sock (no web_web.sock).
  • When hero_books/web.sock is absent, the waiting/not-found page reads Socket 'web.sock' not found ..., not web_web.sock.
  • /<svc>/app[/...] resolves to app.sock, never web_app.sock.
  • /<svc>/public still resolves to web_public.sock; /<svc>/explorer_rpc still resolves to explorer_rpc.sock.
  • A WebSocket upgrade to /hero_books/web/... resolves to the same web.sock.
  • New unit tests in http_socket_resolve.rs cover the web/app non-doubling cases and the named/direct fallbacks, and the full cargo test -p hero_router passes.

Notes

  • The direct existence check at http_dispatch.rs:549 would already pick web.sock when the file exists at dispatch time. The observed failure happened either because the socket was momentarily absent during the restart or because the deployed binary predates the refactor where the direct check was introduced. Making web/app resolve to <webname>.sock unconditionally fixes both: it removes the race-dependent doubling and guarantees the correct name on the retry page so the auto-reload converges once the socket reappears.
  • Do not route web/app through the existing ui/admin arms. Those arms inject X-Forwarded-Prefix: /<svc>/ui and forward to admin.sock/ui.sock; a web-mounted service expects the prefix /<svc>/web and its own web.sock. The _ arm already sets the correct X-Forwarded-Prefix. Keep the request flowing through the _ arm and only fix the filename computation.
  • Keep the change limited to filename resolution; do not alter classify_socket (scanner.rs:55-81).
  • The HTTP and WS call sites must stay in lockstep; that is the reason Step 1 extracts a single shared helper rather than patching each site independently.
## Implementation Spec for Issue #113 ### Objective A request to `/hero_books/web/` (and generally `/<service>/web[/...]`, `/<service>/app[/...]`) must resolve to the service's canonical `<service>/web.sock` Unix socket, never to the doubled `web_web.sock`. The fix must guarantee no subpath segment is concatenated with itself into a `web_<segment>.sock` filename when the segment is already a canonical web/app socket basename, and must apply identically to the HTTP proxy and the WebSocket tunnel. ### Root Cause The per-service proxy dispatcher matches the URL's `webname` segment against a fixed set of arms (`rpc`, `ui`, `admin`, `rest`, `api`, `openrpc`, `python`, `js`) and routes everything else through the catch-all `_` arm. In `crates/hero_router/src/server/proxy/http/http_dispatch.rs:545-553`, the `_` arm computes the socket filename: ```rust _ => { // Try direct socket name first (e.g. explorer_rpc -> explorer_rpc.sock), // then web_<name>.sock. let direct = format!("{}.sock", webname); // line 548 -> "web.sock" let sock_name = if sock_dir.join(service_name).join(&direct).exists() { direct // line 550 } else { format!("web_{}.sock", webname) // line 552 -> "web_web.sock" <- DOUBLING }; ``` For `/hero_books/web/`, `service_name = "hero_books"` and `webname = "web"`. The arm builds `direct = "web.sock"` and, only if that file exists on disk at dispatch time, uses it. Otherwise it falls through to `format!("web_{}.sock", webname)` = `web_web.sock`. The webname-to-socket contract documented at `http_dispatch.rs:49` (`<name> -> web_<name>.sock`) is designed for named web endpoints where `webname` is the suffix (e.g. `public` -> `web_public.sock`); it is wrong when `webname` is literally `web`/`app`, which are canonical UI-socket basenames. This is corroborated by the scanner: `classify_socket` in `crates/hero_router/src/scanner.rs:58` treats `web.sock` (and `app.sock`) as `SocketKind::Ui` — first-class canonical sockets, not named web endpoints. The dispatcher has dedicated arms for `ui`/`admin` (which call `resolve_ui_socket`, whose directory fallback at `crates/hero_router/src/server/proxy/http/http_socket_resolve.rs:72` already checks `web.sock`), but no arm for `web` or `app`, so they fall to `_` and are subject to the doubling fallback. The doubled name surfaces in the user-visible error via the not-found path: the `_` arm passes the computed `sock_name` to `not_found_response(service_name, &sock_name, &headers)` at `http_dispatch.rs:567`, which formats `"Socket '{sock_name}' not found — service may be restarting."` at `crates/hero_router/src/server/routes.rs:773`; the "Attempt N of 15 — reloading" wrapper is the browser auto-retry page (`retry_html`). This confirms the resolution path: the rendered `web_web.sock` is exactly the value produced at `http_dispatch.rs:552`. The identical defective logic is duplicated for the WebSocket tunnel at `crates/hero_router/src/server/proxy/http/http_websocket.rs:97-102`: ```rust let direct = format!("{other}.sock"); let sock_name = if sock_dir.join(service_name).join(&direct).exists() { direct } else { format!("web_{other}.sock") }; ``` ### Requirements - `/hero_books/web/` and `/hero_books/web/<rest>` resolve to `<sock_dir>/hero_books/web.sock`. - No webname segment is ever doubled into `web_<segment>.sock` when `<segment>` is itself a canonical web/app socket basename (`web`, `app`). - Named web endpoints continue to work: `/<svc>/public` -> `web_public.sock`, `/<svc>/<name>` -> `web_<name>.sock`. - Direct-named sockets continue to work: `/<svc>/explorer_rpc` -> `explorer_rpc.sock` (existing `direct` behavior preserved). - The HTTP proxy (`http_dispatch.rs`) and the WebSocket tunnel (`http_websocket.rs`) resolve the socket filename identically. - The fix is covered by a unit test so regressions are caught without a live VM. - The not-found/waiting page, when the socket is genuinely absent, must report `web.sock` (the correct expected name) rather than `web_web.sock`. ### Files to Modify/Create - `crates/hero_router/src/server/proxy/http/http_socket_resolve.rs` — add a small pure helper that maps a non-reserved `webname` to its candidate socket filename(s) without doubling; export it for the dispatcher, the WS tunnel, and tests. - `crates/hero_router/src/server/proxy/http/http_dispatch.rs` — replace the inline `direct`/`web_<name>` computation in the `_` arm (lines 548-553) with a call to the shared helper; update the comment block at lines 40-49 to document that `web`/`app` map to `web.sock`/`app.sock`. - `crates/hero_router/src/server/proxy/http/http_websocket.rs` — replace the inline `direct`/`web_<other>` computation (lines 97-102) with the same shared helper. ### Implementation Plan #### Step 1: Add a shared, testable webname-to-socket-filename resolver Files: `crates/hero_router/src/server/proxy/http/http_socket_resolve.rs` - Add a pure function that, given `service_name`, `webname`, and `sock_dir`, returns the socket filename to use: - Compute `direct = format!("{webname}.sock")`. If `sock_dir.join(service_name).join(&direct).exists()`, return `direct`. - If `webname == "web" || webname == "app"` (the canonical UI basenames per `scanner.rs:58`), return `direct` even when it does not yet exist — so the doubling fallback is never reached and the not-found page reports `web.sock`/`app.sock`. This also self-heals the timing case where the socket is momentarily absent during a restart. - Otherwise return `format!("web_{webname}.sock")` (unchanged behavior for named endpoints). - Keep the function `pub(super)` so both `http_dispatch.rs` and `http_websocket.rs` can call it. Dependencies: none #### Step 2: Use the shared resolver in the HTTP dispatcher catch-all arm Files: `crates/hero_router/src/server/proxy/http/http_dispatch.rs` - In the `_` arm (lines 545-554), replace the inline computation with `let sock_name = resolve_web_socket_name(service_name, webname, sock_dir);` (add the helper to the existing import from `http_socket_resolve`). - Leave the subsequent `socket_path` / `not_found_response` logic unchanged. - Update the module mapping comment at lines 40-49 to document the precedence and the `web`/`app` exception. Dependencies: Step 1 #### Step 3: Use the shared resolver in the WebSocket tunnel Files: `crates/hero_router/src/server/proxy/http/http_websocket.rs` - In the `other => { ... }` arm (lines 94-113), replace the inline computation with `let sock_name = resolve_web_socket_name(service_name, other, sock_dir);`. - Keep the subsequent existence-check logic. Dependencies: Step 1 #### Step 4: Add unit tests for the resolver Files: `crates/hero_router/src/server/proxy/http/http_socket_resolve.rs` - Test `resolve_web_socket_name(svc, "web", dir)` returns `"web.sock"` when the file is absent (the #113 regression case). - Test it returns `"web.sock"` when `<dir>/<svc>/web.sock` exists. - Test `"app"` returns `"app.sock"` (absent and present). - Test `"public"` returns `"web_public.sock"` when neither `public.sock` nor `web_public.sock` exists (named-endpoint fallback preserved). - Test `"explorer_rpc"` returns `"explorer_rpc.sock"` when that file exists (direct-named socket preserved). Dependencies: Steps 1-3 #### Step 5: Build and run the test suite Files: none (verification only) - Run `cargo build -p hero_router` and `cargo test -p hero_router`. - Confirm the new tests pass and the existing tests in `http_dispatch.rs` still pass. Dependencies: Steps 1-4 ### Acceptance Criteria - [ ] A fresh `GET /hero_books/web/` resolves to `<sock_dir>/hero_books/web.sock` (no `web_web.sock`). - [ ] When `hero_books/web.sock` is absent, the waiting/not-found page reads `Socket 'web.sock' not found ...`, not `web_web.sock`. - [ ] `/<svc>/app[/...]` resolves to `app.sock`, never `web_app.sock`. - [ ] `/<svc>/public` still resolves to `web_public.sock`; `/<svc>/explorer_rpc` still resolves to `explorer_rpc.sock`. - [ ] A WebSocket upgrade to `/hero_books/web/...` resolves to the same `web.sock`. - [ ] New unit tests in `http_socket_resolve.rs` cover the `web`/`app` non-doubling cases and the named/direct fallbacks, and the full `cargo test -p hero_router` passes. ### Notes - The `direct` existence check at `http_dispatch.rs:549` would already pick `web.sock` when the file exists at dispatch time. The observed failure happened either because the socket was momentarily absent during the restart or because the deployed binary predates the refactor where the `direct` check was introduced. Making `web`/`app` resolve to `<webname>.sock` unconditionally fixes both: it removes the race-dependent doubling and guarantees the correct name on the retry page so the auto-reload converges once the socket reappears. - Do not route `web`/`app` through the existing `ui`/`admin` arms. Those arms inject `X-Forwarded-Prefix: /<svc>/ui` and forward to `admin.sock`/`ui.sock`; a `web`-mounted service expects the prefix `/<svc>/web` and its own `web.sock`. The `_` arm already sets the correct `X-Forwarded-Prefix`. Keep the request flowing through the `_` arm and only fix the filename computation. - Keep the change limited to filename resolution; do not alter `classify_socket` (`scanner.rs:55-81`). - The HTTP and WS call sites must stay in lockstep; that is the reason Step 1 extracts a single shared helper rather than patching each site independently.
Member

Test Results

  • Total: 155
  • Passed: 155
  • Failed: 0

Ran cargo test -p hero_router across all targets.

Breakdown per target:

  • lib (herolib_router): 145 passed
  • bin (hero_router): 5 passed
  • integration (tests/sse_multiplex.rs): 5 passed
  • doc-tests: 0

The 5 new resolver tests in server::proxy::http::http_socket_resolve all pass, confirming the prefix-doubling fix:

  • direct_named_socket_present_is_used_verbatim
  • web_present_resolves_to_web_sock
  • web_absent_resolves_to_web_sock_not_doubled
  • named_endpoint_absent_falls_back_to_web_prefix
  • app_resolves_to_app_sock_absent_and_present

The key case /hero_books/web/ now resolves to web.sock rather than web_web.sock.

Two pre-existing stale tests were repaired so the suite compiles and passes:

  • tests/sse_multiplex.rs had a ServiceEntry struct literal missing the domains field (added to ServiceEntry after the test was written). Added domains: Vec::new().
  • cache::tests::derive_group_name_falls_back_to_stem_for_unknown_sockets asserted on /var/sockets/legacy.sock, but derive_group_name was changed to treat any nested socket's parent directory as the group, so that path now resolves to sockets. Updated the test to use a non-nested path (/legacy.sock), which is the actual stem-fallback case.
## Test Results - Total: 155 - Passed: 155 - Failed: 0 Ran `cargo test -p hero_router` across all targets. Breakdown per target: - lib (`herolib_router`): 145 passed - bin (`hero_router`): 5 passed - integration (`tests/sse_multiplex.rs`): 5 passed - doc-tests: 0 The 5 new resolver tests in `server::proxy::http::http_socket_resolve` all pass, confirming the prefix-doubling fix: - `direct_named_socket_present_is_used_verbatim` - `web_present_resolves_to_web_sock` - `web_absent_resolves_to_web_sock_not_doubled` - `named_endpoint_absent_falls_back_to_web_prefix` - `app_resolves_to_app_sock_absent_and_present` The key case `/hero_books/web/` now resolves to `web.sock` rather than `web_web.sock`. Two pre-existing stale tests were repaired so the suite compiles and passes: - `tests/sse_multiplex.rs` had a `ServiceEntry` struct literal missing the `domains` field (added to `ServiceEntry` after the test was written). Added `domains: Vec::new()`. - `cache::tests::derive_group_name_falls_back_to_stem_for_unknown_sockets` asserted on `/var/sockets/legacy.sock`, but `derive_group_name` was changed to treat any nested socket's parent directory as the group, so that path now resolves to `sockets`. Updated the test to use a non-nested path (`/legacy.sock`), which is the actual stem-fallback case.
Member

Implementation Summary

Changes

Fix (issue #113):

  • crates/hero_router/src/server/proxy/http/http_socket_resolve.rs — added resolve_web_socket_name(service_name, webname, sock_dir): returns <webname>.sock when it exists on disk; for the canonical UI basenames web and app it returns <webname>.sock unconditionally (the web_<webname>.sock fallback is never reached, so no more web_web.sock doubling and the waiting page reports the correct expected socket name); all other names keep the existing <name>.sock-if-present-else-web_<name>.sock behavior.
  • crates/hero_router/src/server/proxy/http/http_dispatch.rs — the catch-all webname arm now calls the shared resolver instead of the inline computation; the webname-to-socket mapping comment documents the web/app exception.
  • crates/hero_router/src/server/proxy/http/http_websocket.rs — the WebSocket tunnel arm uses the same shared resolver, keeping HTTP and WS resolution in lockstep.

Tests:

  • 5 new unit tests in http_socket_resolve.rs, including the regression case web_absent_resolves_to_web_sock_not_doubled (/hero_books/web/ resolves to web.sock even when the socket is momentarily absent during a restart).
  • Repaired pre-existing stale tests that no longer compiled or matched current behavior: tests/sse_multiplex.rs (missing domains field on ServiceEntry literal), stale include_str! paths in the generator/js.rs and generator/python.rs test modules, and an outdated assertion in cache.rs::derive_group_name_falls_back_to_stem_for_unknown_sockets. No production code was changed by these repairs.

Test Results

155 total, 155 passed, 0 failed (lib 145, bin 5, integration 5).

Notes

  • The _ arm's X-Forwarded-Prefix: /<service>/<webname> behavior is unchanged, so web-mounted services keep receiving the correct prefix.
  • web/app are intentionally not routed through the ui/admin arms; only the filename computation changed.
## Implementation Summary ### Changes **Fix (issue #113):** - `crates/hero_router/src/server/proxy/http/http_socket_resolve.rs` — added `resolve_web_socket_name(service_name, webname, sock_dir)`: returns `<webname>.sock` when it exists on disk; for the canonical UI basenames `web` and `app` it returns `<webname>.sock` unconditionally (the `web_<webname>.sock` fallback is never reached, so no more `web_web.sock` doubling and the waiting page reports the correct expected socket name); all other names keep the existing `<name>.sock`-if-present-else-`web_<name>.sock` behavior. - `crates/hero_router/src/server/proxy/http/http_dispatch.rs` — the catch-all webname arm now calls the shared resolver instead of the inline computation; the webname-to-socket mapping comment documents the `web`/`app` exception. - `crates/hero_router/src/server/proxy/http/http_websocket.rs` — the WebSocket tunnel arm uses the same shared resolver, keeping HTTP and WS resolution in lockstep. **Tests:** - 5 new unit tests in `http_socket_resolve.rs`, including the regression case `web_absent_resolves_to_web_sock_not_doubled` (`/hero_books/web/` resolves to `web.sock` even when the socket is momentarily absent during a restart). - Repaired pre-existing stale tests that no longer compiled or matched current behavior: `tests/sse_multiplex.rs` (missing `domains` field on `ServiceEntry` literal), stale `include_str!` paths in the `generator/js.rs` and `generator/python.rs` test modules, and an outdated assertion in `cache.rs::derive_group_name_falls_back_to_stem_for_unknown_sockets`. No production code was changed by these repairs. ### Test Results 155 total, 155 passed, 0 failed (lib 145, bin 5, integration 5). ### Notes - The `_` arm's `X-Forwarded-Prefix: /<service>/<webname>` behavior is unchanged, so `web`-mounted services keep receiving the correct prefix. - `web`/`app` are intentionally not routed through the `ui`/`admin` arms; only the filename computation changed.
Member

it happened because hero book didn't find web.sock of hero_books so it fall back to web_web.sock but the root cause was hero_books didn't start which should expose the web.sock first

it happened because hero book didn't find web.sock of hero_books so it fall back to web_web.sock but the root cause was hero_books didn't start which should expose the web.sock first
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_router#113
No description provided.