Override HTTP headers for an XPage
Normally, we don't need to override HTTP headers - but you may need it for caching
When you create web pages in HCL XPages then they often have quite a lot of interactivity and you want all changes to be updated and shown right away. However, I recently had a case where I wanted to cache one particular page as it would get a high number of users in. a short time. We had seen that over a couple of occasions.
So after having verified my ideas I went into setting the cache controls in the beforeRenderResponse header of the page in question. That looked something like this:
<xp:this.beforeRenderResponse><![CDATA[#{javascript:
var m = Configuration.getCacheExpiryMinutes();
var response = facesContext.getExternalContext().getResponse();
var now = new Date();
var expiresDate = now.getTime() + (m * 60 * 1000);
response.setHeader("Cache-Control", "public");
response.setDateHeader("Expires", expiresDate);
print("m=" + m + ", Expires: " + expiresDate.toString()); // For debugging
}]]></xp:this.beforeRenderResponse>
Then I just needed to test, right?
Well, it turned out that something wasn't going as expected. Using the developer tools of my browser I found that the response headers were not like I expected:
HTTP/1.1 200 OK
Server: Lotus-Domino
Date: Wed, 30 Apr 2025 20:17:14 GMT
Cache-Control: public
Expires: Wed, 30 Apr 2025 20:19:12 GMT
Content-Type: text/html;charset=utf-8
Expires: -1
Cache-Control: no-cache
Content-Encoding: gzip
Content-Length: 1546
Please note that the two headers "Cache-Control" and "Expires" appears TWICE.... So Domino adds its own headers ignoring that I added them as well. This breaks any proxy caching (I use Nginx). I did many things to try and solve this. But it was only with a little help from a colleague, Sjef Bosman, and one particular old article by Sven Hasselbach that I found out what to do. ..
Basically, there is an old IBM cache header class that you need to extend and override one method. It's all in Sven's article. So this is the class I created:
package dk..catchlog.control;
import javax.servlet.http.HttpServletResponse;
import com.ibm.xsp.context.RequestParameters.ResponseCacheHeader;
// Allow overriding cache headers for an XPage. See more: https://hasselba.ch/blog/?p=709
// Add to beforePageLoad event
public class CacheHeader implements ResponseCacheHeader {
public CacheHeader() {
}
@Override
public boolean initResponseCacheHeader(HttpServletResponse arg0) {
// Important: Return true to allow overriding XPages headers
return true;
}
}
Please note the overriden function returning "true". That is the key to make this work.
So back to our XPage and modify the code in the beforeRenderResponse (or beforeLoadPage) is extended a little:
<xp:this.beforeRenderResponse><![CDATA[#{javascript:
var m = Configuration.getCacheExpiryMinutes();
var response = facesContext.getExternalContext().getResponse();
var now = new Date();
var expiresDate = now.getTime() + (m * 60 * 1000);
response.setHeader("Cache-Control", "public");
response.setDateHeader("Expires", expiresDate);
// Set response cache header object
var reqParam = facesContext.getRequestParameters();
var cacheHeader = new dk.catchlog.control.CacheHeader();
reqParam.setResponseCacheHeader(cacheHeader);
print("m=" + m + ", Expires: " + expiresDate.toString()); // For debugging
}]]></xp:this.beforeRenderResponse>
Back to testing - and now we find these headers:
HTTP/1.1 200 OK
Server: Lotus-Domino
Date: Wed, 30 Apr 2025 20:56:06 GMT
Cache-Control: public
Expires: Wed, 30 Apr 2025 20:58:04 GMT
Content-Type: text/html;charset=utf-8
Content-Encoding: gzip
Content-Length: 1546
And bingo! Now we have the right headers - so the rest is up to setting caching in the Nginx proxy server and you are good to go!