Repository navigation
Conversation
added 4 commits
October 23, 2026 17:03
cache.serverSide.statsPort (default 0 = off) starts an HTTP server on 127.0.0.1:<port + NODE_APP_INSTANCE> serving GET /app/cache-stats as HTML or ?format=json: per-cache entry count vs limit, stale count, HTML bytes and remaining TTL per entry, process RSS/heap. Snapshots use LRU.dump() so expired-but-resident entries are reported even with allowStale:false. A failed bind is logged and never stops the UI.
…heir own
cache.serverSide.stats { enabled, port } replaces statsPort. port 0 serves
/app/cache-stats from the Express app, but only to loopback socket
addresses (req.socket.remoteAddress, not the proxy-trusting req.ip); a
port number keeps the separate 127.0.0.1 listener with the per-worker
offset. The request handler is shared by both paths.
…ts to the test env A same-host reverse proxy connects from loopback, so the socket address alone let public traffic through; requests carrying Forwarded, Via, X-Forwarded-* or X-Real-IP are now refused as well. The required stats key was missing from environment.test.ts and broke tsconfig.spec.json.
Drop the HTML view, the ?format query parameter and the headers and URL parsing that existed only for it.
vidiecan
force-pushed
the
feat/ssr-cache-stats
branch
from
October 23, 2026 15:04
191f719 to
77756f2
Compare
added 3 commits
October 23, 2026 22:06
…id port never stops the UI Review findings: non-loopback callers got Express's plain 404 instead of the catch-all route, so the feature was detectable; an invalid stats port threw synchronously before the UI started; 'enabled' could serialize as 0 or be dropped. Test now covers the query string, trailing slash and HEAD.
Refuse every X-Forwarded-* header, not just three of them; narrow the caught value (useUnknownInCatchVariables on DSpace 9 branches); never bind the stats listener to the UI port; log an invalid stats port such as a YAML string instead of ignoring it; let loopback HEAD fall through like other clients; answer 500 instead of crashing if the snapshot throws.
…ards Review nits: exact-path check on the Express route (non-strict routing matched /app/cache-stats/ and case variants), warn when the stats are on the UI port while ui.useProxies is on, distinguish a bind failure from a later server error in the log, drop a stale peek() mention.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change set
Opt-in endpoint that lists what each Node process holds in its SSR page caches (bot + anonymous): entry count vs limit, stale count, HTML bytes per entry, remaining TTL, process RSS/heap.
GET /app/cache-stats→ JSON.Config
cache.serverSide.stats(envDSPACE_CACHE_SERVERSIDE_STATS_ENABLED/_PORT), defaultenabled: false:port: 0→ served by the Express app on the UI port next to/app/health, only to requests made directly on the host: loopback socket address (req.socket.remoteAddress, notreq.ip, becausetrust proxyhonours a spoofable X-Forwarded-For) and none ofForwarded,Via,X-Real-IPor anyX-Forwarded-*, so traffic relayed by a same-host reverse proxy is refused too. Everyone else falls through to the catch-all route vianext(), so the response is byte-identical to an unknown path. Residual risk: a same-host proxy that adds none of those headers; the config comment says to use a port there. Under pm2 cluster mode an arbitrary worker answers.port: N→ a separatehttpserver bound to127.0.0.1:N + NODE_APP_INSTANCE, so each of the 7 pm2 workers indocker/dspace-ui.jsonis addressable. An invalid port (incl. a YAML string), a clash with the UI port or a failed bind (EADDRINUSE) is logged and does not stop the UI.src/backend/ssr-cache-stats.ts: snapshot viaLRU.dump()(includes expired entries that still occupy memory whenallowStale: false;forEachwould skip them),has()marks stale; no recency/TTL side effects. Non-GET → 404.botCache/anonymousCachetypedLRU<string, SsrCacheEntry>instead ofany.Test:
scripts/test-ssr-cache-stats.js(node --test), wired asyarn run test:ssr-cache-statsand a CI step after lint.Out of scope: aggregating across workers, auth.
Test evidence
node dist/server/main(NODE_ENV=production), three runs:Local Node 24; CI matrix (18/20) covers
node --test+ globalfetch.Notes / assumptions
docker exec <ui> wget -qO- http://127.0.0.1:<port>/app/cache-stats.server.close();http-terminatorwas not used because its bundled@types/node(24.x) clashes with the project's 14.xhttp.Servertype on the explicitly typed stats server.