Skip to content

OF-2970: Implement HTTP Cache-Control headers in the Admin Console#3254

Open
MilanTyagi2004 wants to merge 1 commit intoigniterealtime:mainfrom
MilanTyagi2004:OF-2970
Open

OF-2970: Implement HTTP Cache-Control headers in the Admin Console#3254
MilanTyagi2004 wants to merge 1 commit intoigniterealtime:mainfrom
MilanTyagi2004:OF-2970

Conversation

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator

@MilanTyagi2004 MilanTyagi2004 commented Apr 6, 2026

Jira Issue: OF-2970

Problem Statement

  • The Openfire Admin Console currently lacks consistent HTTP Cache-Control headers. This leads to:

  • Performance Degradation: Browsers frequently re-request stable static assets (JS, CSS, images), increasing latency and server load.

  • Security Risks: Sensitive administrative pages and session-specific layouts can be stored in browser history or intermediary proxy caches.

Solution Overview

  • This PR introduces a centralized CacheControlFilter registered early in the filter chain. It provides a robust, extension-based classification system to apply appropriate caching strategies automatically.

Key Improvements

  • Aggressive Static Caching: Assets such as .js, .css, .png, and .svg now receive Cache-Control: public, max-age=31536000, immutable. This eliminates redundant network roundtrips for returning administrators.
  • Enhanced Security for Dynamic Pages: All dynamic content (primarily .jsp) is explicitly marked with no-cache, no-store, must-revalidate. This ensures that sensitive configurations and logs are never cached.

Robust Implementation:

  • Handles URIs with query parameters (e.g., versioned scripts).
  • Case-insensitive extension matching (e.g., .JPG vs .jpg).
  • Non-Destructive: Checks for existing Cache-Control headers before application to preserve specific servlet-level overrides.

Verification Results

  • Verified on a running Openfire instance:

Dynamic Response (index.jsp)

Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT

Static Resource (logo.png)

Cache-Control: public, max-age=31536000, immutable

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements centralized HTTP caching policy for the Openfire Admin Console by introducing a servlet filter that applies Cache-Control headers based on requested resource type (static vs dynamic), aiming to improve performance while preventing caching of sensitive admin pages.

Changes:

  • Register a new CacheControlFilter early in the admin console filter chain (web.xml) and map it to all requests.
  • Add org.jivesoftware.admin.CacheControlFilter to apply aggressive caching to static assets and no-store/no-cache headers to dynamic content.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
xmppserver/src/main/webapp/WEB-INF/web.xml Registers and maps CacheControlFilter for all admin console requests.
xmppserver/src/main/java/org/jivesoftware/admin/CacheControlFilter.java Implements extension-based caching rules for static vs dynamic resources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +77
// Aggressive caching for static assets to improve performance and reduce server load.
// 'immutable' is used as UI assets are typically stable between version upgrades.
httpResponse.setHeader("Cache-Control", "public, max-age=31536000, immutable");
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static resources are marked as max-age=31536000, immutable, but the admin console references assets with stable URLs (e.g. /style/global.css, /js/cookies.js in decorators/main.jsp) without any cache-busting. After an upgrade, browsers can keep serving stale CSS/JS for up to a year, potentially breaking the UI. Consider either adding explicit cache-busting to asset URLs (eg, append an Openfire version/build query param) or reducing the caching policy (shorter max-age and/or drop immutable).

Suggested change
// Aggressive caching for static assets to improve performance and reduce server load.
// 'immutable' is used as UI assets are typically stable between version upgrades.
httpResponse.setHeader("Cache-Control", "public, max-age=31536000, immutable");
// Cache static assets briefly to improve performance, but avoid long-lived immutable
// caching because admin console asset URLs are stable across upgrades.
httpResponse.setHeader("Cache-Control", "public, max-age=3600");

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +89
// Only set headers if not already present (preserving specific servlet/filter overrides)
if (!httpResponse.containsHeader("Cache-Control")) {
if (isStaticResource(uri)) {
// Aggressive caching for static assets to improve performance and reduce server load.
// 'immutable' is used as UI assets are typically stable between version upgrades.
httpResponse.setHeader("Cache-Control", "public, max-age=31536000, immutable");
} else {
// Disable caching for sensitive administrative and dynamic content to ensure
// that data is never stored in browser history or proxy caches.
httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpResponse.setHeader("Pragma", "no-cache");
httpResponse.setDateHeader("Expires", 0);
}
}
}

chain.doFilter(request, response);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sets cache headers before chain.doFilter(...), without knowing the eventual response status. If a static resource request results in a 404/500, the current logic can still attach a 1-year cache policy to that error response, causing clients/proxies to cache failures for a long time. Consider applying the caching headers after chain.doFilter(...) and only when the response status is cacheable (eg, 200/304) and the response is not committed, or use a response wrapper to capture status reliably.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +70
String uri = httpRequest.getRequestURI();

// Strip query parameters for extension checking
int queryIndex = uri.indexOf('?');
if (queryIndex != -1) {
uri = uri.substring(0, queryIndex);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code attempts to strip query parameters from getRequestURI(), but the standard Servlet API exposes the query string separately (getQueryString()), and getRequestURI() typically does not contain ?…. As written, this logic is likely redundant and can be misleading. Consider removing it, or (if you really need query-aware classification) use getQueryString() / getRequestURL() explicitly and update the method javadoc accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +89
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

String uri = httpRequest.getRequestURI();

// Strip query parameters for extension checking
int queryIndex = uri.indexOf('?');
if (queryIndex != -1) {
uri = uri.substring(0, queryIndex);
}

// Only set headers if not already present (preserving specific servlet/filter overrides)
if (!httpResponse.containsHeader("Cache-Control")) {
if (isStaticResource(uri)) {
// Aggressive caching for static assets to improve performance and reduce server load.
// 'immutable' is used as UI assets are typically stable between version upgrades.
httpResponse.setHeader("Cache-Control", "public, max-age=31536000, immutable");
} else {
// Disable caching for sensitive administrative and dynamic content to ensure
// that data is never stored in browser history or proxy caches.
httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpResponse.setHeader("Pragma", "no-cache");
httpResponse.setDateHeader("Expires", 0);
}
}
}

chain.doFilter(request, response);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New caching behavior is introduced here, but there are no unit tests to validate the classification (static vs dynamic) and header behavior (eg, respects pre-existing Cache-Control, adds Pragma/Expires for dynamic). There are existing tests for other admin filters (see AuthCheckFilterTest), so adding targeted tests for this filter would help prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@guusdk guusdk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @MilanTyagi2004 - thanks for another contribution! This looks pretty good already, but I think it can be improved a bit further.

As I've also commented on a few other of your PRs: please make sure that the commit message(s) contain a good description of why the commit was added. The text of a commit message becomes part of the history of the project. Pull Request descriptions are not.

Please review the review comments made by Copilot. That contains good hints.

I don't think it's quite necessary to have a 'cache busting' mechanism if the cache duration is not 'forever'. Consider creating a default cache timeout of an hour or so. That will give a good balance between 'most requests will be answered from cache' and 'faulty cached objects linger forever'.

Please introduce two SystemProperties, to make the behavior of this new implementation configurable:

  • A property that defines the set of file extensions to which caching is applied.
  • Another property that is used to define the Duration of the cache

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

Sure Guus,
i will go through with the review change of Copilot and proposed the changes to make this more robust .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants