[nu-demo] Books island builds URL as {base}/ + /rpc → double-slash 404 in All Books / library listing #157

Closed
opened 2026-04-24 02:28:00 +00:00 by mik-tf · 2 comments
Owner

Symptom

Books island All Books tab (and library tabs) shows:

Error: JSON parse error: EOF while parsing a value at line 1 column 0

…with no books listed, despite libraries.list via direct socket RPC returning all 4 configured libraries (hero, geomind, mycelium, ourworld = 40 books).

Root cause

hero_archipelagos/archipelagos/embed/books/src/island.rs:28 builds the island's base URL with a trailing slash:

let base_url = format!("{}/hero_books/ui/", props.context.api_host);

The Dioxus island's RPC client at hero_archipelagos/archipelagos/embed/books/src/services/mod.rs:42 then concatenates /rpc:

gloo_net::http::Request::post(&format!("{}/rpc", base_url))

Resulting URL: /hero_books/ui//rpc — double slash.

Axum's router on hero_books_ui doesn't normalize consecutive slashes, so /rpc (registered route) ≠ //rpc (incoming path) → 404 → empty body → JSON parse error in the island's fetch handler.

Verification

# Via router, with single slash — works:
curl -X POST 'http://10.1.2.2:9988/hero_books/ui/rpc' \
  -H 'content-type:application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"collections.list","params":{}}'
# → 200 OK, 8405 bytes, list of collections

# Double slash (what the WASM actually sends) — 404:
curl -X POST 'http://10.1.2.2:9988/hero_books/ui//rpc' ...
# → 404, 0 bytes

Same double-slash issue likely affects every island that follows this base_url pattern. Searches for format!("{}/..." with trailing slash + subsequent format!("{}/...: photos_grid had a similar bug (see home#156).

Demo fix (applied on heronu development_mik_nu_demo)

Added collapse_slashes_middleware to hero_books_ui's Axum router — runs before routing, rewrites request URI to collapse consecutive slashes in the path. Full patch in hero_books_ui/src/main.rs — ~30 lines inc. middleware fn and one .layer(...) call. Rebuild time: ~60s (one crate).

After rebuild + restart:

  • curl 'http://10.1.2.2:9988/hero_books/ui//rpc' -X POST ... → 200 (same as single-slash)
  • Books island All Books tab lists all configured libraries

Proper fixes (3 layers)

1. Root cause (the correct fix)

hero_archipelagos/archipelagos/embed/books/src/island.rs:28:

// Before
let base_url = format!("{}/hero_books/ui/", props.context.api_host);
// After
let base_url = format!("{}/hero_books/ui", props.context.api_host);

Requires full hero_os_app WASM rebuild (~25 min). Same one-char fix needed in any other island whose base_url has trailing slash — audit.

2. Defensive URL construction in rpc_call

hero_archipelagos/archipelagos/embed/books/src/services/mod.rs:42:

let url = format!("{}/rpc", base_url.trim_end_matches('/'));

Apply the same pattern to every format!("{}/..." in the island's services module. Sibling fix to #156 (photos_grid needed the same treatment).

3. Server-side normalization (belt-and-suspenders)

Keep the collapse_slashes_middleware in hero_books_ui (and every other *_ui crate behind hero_router) as a guard against future WASM regressions. Alternative: add slash-collapse in hero_router's proxy so ALL services benefit without per-service middleware.

  • #148 — nu-demo architecture index
  • #156 — same double-slash bug in Photos (fixed by rewriting seed storage_path; here the bug is in base_url construction)
  • Sibling: the Q&A cache bypass bug (see companion issue filed alongside this one) — hero_books ignores pre-extracted .ai/<page>.toml because content_hash doesn't match the post-export .md

Signed-off-by: mik-tf

## Symptom Books island All Books tab (and library tabs) shows: ``` Error: JSON parse error: EOF while parsing a value at line 1 column 0 ``` …with no books listed, despite `libraries.list` via direct socket RPC returning all 4 configured libraries (hero, geomind, mycelium, ourworld = 40 books). ## Root cause `hero_archipelagos/archipelagos/embed/books/src/island.rs:28` builds the island's base URL with a trailing slash: ```rust let base_url = format!("{}/hero_books/ui/", props.context.api_host); ``` The Dioxus island's RPC client at `hero_archipelagos/archipelagos/embed/books/src/services/mod.rs:42` then concatenates `/rpc`: ```rust gloo_net::http::Request::post(&format!("{}/rpc", base_url)) ``` Resulting URL: `/hero_books/ui//rpc` — double slash. Axum's router on hero_books_ui doesn't normalize consecutive slashes, so `/rpc` (registered route) ≠ `//rpc` (incoming path) → 404 → empty body → JSON parse error in the island's fetch handler. ## Verification ```bash # Via router, with single slash — works: curl -X POST 'http://10.1.2.2:9988/hero_books/ui/rpc' \ -H 'content-type:application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"collections.list","params":{}}' # → 200 OK, 8405 bytes, list of collections # Double slash (what the WASM actually sends) — 404: curl -X POST 'http://10.1.2.2:9988/hero_books/ui//rpc' ... # → 404, 0 bytes ``` Same double-slash issue likely affects every island that follows this base_url pattern. Searches for `format!("{}/..."` with trailing slash + subsequent `format!("{}/...`: photos_grid had a similar bug (see home#156). ## Demo fix (applied on heronu `development_mik_nu_demo`) Added `collapse_slashes_middleware` to hero_books_ui's Axum router — runs before routing, rewrites request URI to collapse consecutive slashes in the path. Full patch in `hero_books_ui/src/main.rs` — ~30 lines inc. middleware fn and one `.layer(...)` call. Rebuild time: ~60s (one crate). After rebuild + restart: - `curl 'http://10.1.2.2:9988/hero_books/ui//rpc' -X POST ...` → 200 (same as single-slash) - Books island All Books tab lists all configured libraries ## Proper fixes (3 layers) ### 1. Root cause (the correct fix) `hero_archipelagos/archipelagos/embed/books/src/island.rs:28`: ```rust // Before let base_url = format!("{}/hero_books/ui/", props.context.api_host); // After let base_url = format!("{}/hero_books/ui", props.context.api_host); ``` Requires full hero_os_app WASM rebuild (~25 min). Same one-char fix needed in any other island whose base_url has trailing slash — audit. ### 2. Defensive URL construction in rpc_call `hero_archipelagos/archipelagos/embed/books/src/services/mod.rs:42`: ```rust let url = format!("{}/rpc", base_url.trim_end_matches('/')); ``` Apply the same pattern to every `format!("{}/..."` in the island's services module. Sibling fix to https://forge.ourworld.tf/lhumina_code/home/issues/156 (photos_grid needed the same treatment). ### 3. Server-side normalization (belt-and-suspenders) Keep the `collapse_slashes_middleware` in hero_books_ui (and every other *_ui crate behind hero_router) as a guard against future WASM regressions. Alternative: add slash-collapse in hero_router's proxy so ALL services benefit without per-service middleware. ## Related - https://forge.ourworld.tf/lhumina_code/home/issues/148 — nu-demo architecture index - https://forge.ourworld.tf/lhumina_code/home/issues/156 — same double-slash bug in Photos (fixed by rewriting seed `storage_path`; here the bug is in base_url construction) - Sibling: the Q&A cache bypass bug (see companion issue filed alongside this one) — hero_books ignores pre-extracted `.ai/<page>.toml` because content_hash doesn't match the post-export .md Signed-off-by: mik-tf
Author
Owner

Verified resolved in current code 2026-04-25

hero_books_app/src/rpc.rs:46-51 correctly uses:

let url = if base.is_empty() {
    "/rpc".to_string()
} else {
    format!("{}/rpc", base.trim_end_matches('/'))
};

trim_end_matches('/') strips any trailing slash on the base before appending /rpc, so trailing-slash bases produce single-slash URLs.

Tested live on herodemo.gent01.grid.tf:

$ curl -X POST http://10.1.2.2:9990/hero_books/rpc/rpc -d '{}' -H 'Content-Type: application/json' -o /dev/null -w '%{http_code}\n'
200

Closing as fixed. If the double-slash 404 is still observed in any path, please reopen with the exact URL and I'll trace.

Signed-off-by: mik-tf

## Verified resolved in current code 2026-04-25 `hero_books_app/src/rpc.rs:46-51` correctly uses: ```rust let url = if base.is_empty() { "/rpc".to_string() } else { format!("{}/rpc", base.trim_end_matches('/')) }; ``` `trim_end_matches('/')` strips any trailing slash on the base before appending `/rpc`, so trailing-slash bases produce single-slash URLs. Tested live on herodemo.gent01.grid.tf: ``` $ curl -X POST http://10.1.2.2:9990/hero_books/rpc/rpc -d '{}' -H 'Content-Type: application/json' -o /dev/null -w '%{http_code}\n' 200 ``` Closing as fixed. If the double-slash 404 is still observed in any path, please reopen with the exact URL and I'll trace. Signed-off-by: mik-tf
Author
Owner

Moved to hero_demo#25 — see lhumina_code/hero_demo#25

Moved to hero_demo#25 — see https://forge.ourworld.tf/lhumina_code/hero_demo/issues/25
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
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/home#157
No description provided.