Class AppDirectoryHandler

All Implemented Interfaces:
Handler, Handler.Container, Handler.Singleton, Request.Handler, Container, Destroyable, Dumpable, Dumpable.DumpableContainer, LifeCycle, Invocable

public class AppDirectoryHandler extends Handler.Wrapper
The virtual directory /app/<appID>/ at the head of the handler chain (work order CC-PWA-V5A-APPDIR-ROUTE, design CC-DESIGN-APP-URL-V2 sections 2.2, 2.3, 2.4 and 2.7, rulings AU-2 to AU-5 of 2026-09-29). Nothing exists on disk under /app/. For a GET or HEAD whose path starts with /app/:
  • /app/<id> without a slash, and /app/<alias>/...: 302 to the canonical directory, the rest of the path and the query kept (AU-3).
  • /app/<segment>/... for a segment the server refuses or does not know: 404, from IExternalBrowserPages.getAppDirectory(String), which validates the segment before any lookup.
  • /app/<id>/... and /app/<alias>/... for an application that is not served on the port configuration the request arrived on (IExternalBrowserPages.isAppRefusedOn(String,com.iizix.comm.PortConfig), CC-FIX-APP-DEFINITIONS): the same 404 as for an unknown segment, before any redirect, decided on the application the directory names - so an alias is refused with its application.
  • /app/<id>/ and /app/<id>/iiziApp.html: the application page for the lang entry parameter, both served, no redirect (AU-2), behind the same Browser gate as the legacy page.
  • /app/<id>/iiziAppSorry.html: the sorry page, the application implied by the path.
  • /app/<id>/sw.js, offline.html, manifest-<lang>.webmanifest (or manifest.webmanifest without a language) and the icons: the directory's generated files (work order CC-PWA-V5E-APPDIR-PWA), as IAppDirectory.getFile(String,String) answers them - the worker and the offline page for every application, the manifest and the icons only for an installable one - and 404 for a name the directory does not hold; never a fall-through, so that no file of the web root can take their place.
  • /app/<id>/<anything else>: passed on to the chain as /<anything else> by an AppPathRequest, so the resources handler, the servlet context, the precompressed variants and the CORS filter behave for it exactly as for the web root's path (AU-4). The rest is passed on encoded as it came, its dot segments resolved by the canonical path; a rest that, decoded, would re-enter /app/ or carries ':' or ".." is 404.
Every other method under /app/ is passed on untouched, as WebServerResources passes it: OPTIONS and POST belong to the servlet context.

The page. Per response, on the directory's page for the language: the client gate's BrowserGate.APP_ID_MARKER, the Web Analytics line of AppAnalytics.script(AppAnalytics.Settings, boolean) (the user's decision lives on the device only since CC-CLIENT-STORAGE-V5: the server reads no cookie), and the registration of the APPLICATION's Service Worker, /app/<id>/sw.js with scope /app/<id>/ (design 2.5), in the same places and the same order as CustomDefaultServlet puts the legacy page's. Then Content-Type: text/html;charset=utf-8, Cache-Control: no-cache, Last-Modified from the directory's version time, Referrer-Policy: same-origin, no Vary, and an ETag over the FINAL bytes (AU-5): "<stamp>-<12 hexadecimal digits of SHA-256 over the response body>", since the substituted page varies per request - its language, the application's name, until version 5 the consent cookie - and a stamp-only ETag would answer 304 to a page that changed (design F-7). If-None-Match answers 304 with the same headers and no body; If-Modified-Since alone is not honoured, for the same reason. HEAD gets the headers of the GET.

The generated files. Sent as the directory holds them, with the same Last-Modified, ETag and 304: the file's media type (application/manifest+json, text/javascript, text/html or image/png), Cache-Control: max-age=86400 for an icon and no-cache for the others, and the page's Referrer-Policy on the offline page, which the worker shows at the URL of the page it stands in for. No Browser gate: it guards the application page only. Compression follows Accept-Encoding through the compression handler that wraps this one in production.

Why a wrapper and not a member of the sequence. A member of a Handler.Sequence receives the request the sequence was given and cannot hand the members after it a rewritten one, so this handler wraps the sequence: it is the head of the chain, ahead of WebServerResources, and everything it does not answer itself goes to the sequence, rewritten or untouched.

Author:
Christopher Mindus
  • Field Details

    • PREFIX

      public static final String PREFIX
      The prefix of the virtual directories: "/app/". Changing it after one installation orphans that installation, whose manifest id is its directory (design section 5, Q1). It is the one constant of com.iizix.pwa, which the manifest, the worker and the offline page build their URLs from (decision V5B-D-9).
      See Also:
  • Constructor Details

    • AppDirectoryHandler

      public AppDirectoryHandler(String root, IExternalBrowserPages extPages, Handler handler)
      Creates the handler at the head of the chain.
      Parameters:
      root - The web server root.
      extPages - The external pages, which answer the directories, the sorry page's information and the Analytics settings; null for none.
      handler - The handler chain that follows: the production sequence.
  • Method Details