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 up caching in the Nginx proxy server and you are good to go!


A litte side note... I had a look at the events beforeRenderResponse and beforePageLoad. This is actually a splendid example of something that you would put into the beforePageLoad as it is called very early in the life cycle before any data initialization and ideally is used for initialization. So I ended up putting my SSJS code in this event. But it will work in both events!


Blog

Af John Dalsgaard 26. maj 2025
Efter lang ventetid og 3 EAPs (early access products) bliver den nye version snart frigivet
Af John Dalsgaard 26. maj 2025
After a long wait and 3 EAPs (early access products) it will soon be released
Flere indlæg