Override HTTP headers for an XPage

John Dalsgaard • 30. april 2025

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!


Blog

Af John Dalsgaard 22. oktober 2024
In today's IT landscape it is quite normal that you have to call "services" in client driven website - and that can easily be across domains...
Af John Dalsgaard 5. september 2024
Definer en lokal SSJS funktion uden at bruge et 'library'
Flere indlæg