Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
012e70a
Test the client-side endpoints' caching of a failed response and thei…
jwrosewell Nov 7, 2026
b8b53dc
Send no ETag and private, no-cache on a failed client-side response
jwrosewell Nov 7, 2026
be27ab7
Merge rc into fix/client-side-cache-on-failed-response
jwrosewell Nov 7, 2026
d310fad
Say what a failed response's headers are in setHeaders' comment
jwrosewell Nov 7, 2026
4f62ea4
Test that a matching If-None-Match gets 412 for POST and a 304 with t…
jwrosewell Nov 7, 2026
6a74ed1
Answer a matching If-None-Match with 412 for POST, and give the 304 t…
jwrosewell Nov 7, 2026
411c8fe
Test that the 412 carries the CORS header
jwrosewell Nov 8, 2026
ade7e98
Send the CORS header with the 412 as the 304 and the 200 do
jwrosewell Nov 8, 2026
1f92896
Test that a revalidation gets a 304 when a Flow Element adds Evidence…
jwrosewell Nov 9, 2026
36f2526
Calculate the client-side ETag from the Evidence the request supplied
jwrosewell Nov 9, 2026
330f707
Merge rc into fix/client-side-cache-on-failed-response
jwrosewell Nov 9, 2026
f42344a
Test the weak ETag of the supplied Evidence, If-None-Match lists, Var…
jwrosewell Nov 9, 2026
fe5e7c5
Send a weak ETag of the supplied Evidence, and evaluate If-None-Match…
jwrosewell Nov 9, 2026
a35f017
Merge the supplied Evidence key into the weak ETag change
jwrosewell Nov 9, 2026
902290d
Remove generateSuppliedKey, as the ETag reads the web request record
jwrosewell Nov 9, 2026
4e42723
Make the client-side endpoints' max-age configurable, 1800 seconds by…
jwrosewell Nov 9, 2026
013146b
Test the missing builder, empty Vary and negative max-age cases
jwrosewell Nov 10, 2026
7a2686b
Check the builder before a 304, leave out an empty Vary, and check th…
jwrosewell Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ public FiftyOneInterceptor(

resultService.setPipeline(pipeline);
clientsidePropertyService.setPipeline(pipeline);
clientsidePropertyService.setCacheMaxAge(
config.getClientsideCacheMaxAge());
fiftyOneJsService.enable(config.getClientsidePropertiesEnabled());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

import fiftyone.pipeline.web.mvc.components.FiftyOneInterceptor;

import static fiftyone.pipeline.web.Constants.DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;

/**
* Configuration class for the {@link FiftyOneInterceptor}.
*/
Expand All @@ -40,4 +42,13 @@ public interface FiftyOneInterceptorConfig {
* @return true if client-side properties are enabled
*/
boolean getClientsidePropertiesEnabled();

/**
* Get how many seconds a browser may keep the client-side JavaScript or
* JSON before it asks again, the max-age of their Cache-Control header.
* @return the max-age in seconds, 0 or more
*/
default int getClientsideCacheMaxAge() {
return DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
package fiftyone.pipeline.web.mvc.configuration;

import javax.servlet.ServletContext;

import static fiftyone.pipeline.web.Constants.DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;
import org.springframework.beans.factory.annotation.Autowired;

/**
Expand All @@ -34,6 +36,9 @@ public class FiftyOneInterceptorConfigDefault implements FiftyOneInterceptorConf

private boolean clientsidePropertiesEnabled;

private int clientsideCacheMaxAge =
DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;

@Autowired
public ServletContext servletContext;

Expand All @@ -45,6 +50,21 @@ public void setClientsidePropertiesEnabled(boolean clientsidePropertiesEnabled)
this.clientsidePropertiesEnabled = clientsidePropertiesEnabled;
}

/**
* Set how many seconds a browser may keep the client-side JavaScript or
* JSON before it asks again, the max-age of their Cache-Control header.
* @param seconds the max-age, 0 or more
* @throws IllegalArgumentException if seconds is negative
*/
public void setClientsideCacheMaxAge(int seconds) {
if (seconds < 0) {
throw new IllegalArgumentException(
"The max-age of the client-side responses cannot be " +
"negative, and was " + seconds + ".");
}
this.clientsideCacheMaxAge = seconds;
}

@Override
public String getDataFilePath() {
return dataFilePath;
Expand All @@ -54,4 +74,9 @@ public String getDataFilePath() {
public boolean getClientsidePropertiesEnabled() {
return clientsidePropertiesEnabled;
}

@Override
public int getClientsideCacheMaxAge() {
return clientsideCacheMaxAge;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
@Service
public interface ClientsidePropertyService extends ClientsidePropertyServiceCore {
void setPipeline(Pipeline pipeline);

/**
* Set how many seconds a browser may keep a good response before it
* asks again, the max-age of the Cache-Control header. An
* implementation that sets no Cache-Control header ignores it.
* @param seconds the max-age, 0 or more
*/
default void setCacheMaxAge(int seconds) {
}
void serveJavascript(
HttpServletRequest request,
HttpServletResponse response) throws IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ public void init() throws IOException {
uachService);
}

/**
* The interceptor gives the client-side service the max-age its
* configuration sets.
*/
@Test
public void FiftyOneInterceptor_SetsTheCacheMaxAge() {
FiftyOneInterceptorConfig config =
mock(FiftyOneInterceptorConfig.class);
when(config.getDataFilePath())
.thenReturn("src/test/resources/Test.xml");
when(config.getClientsideCacheMaxAge()).thenReturn(60);
ClientsidePropertyService clientsidePropertyService =
mock(ClientsidePropertyService.class);

new FiftyOneInterceptor(
config,
mock(PipelineResultService.class),
mock(FlowDataProvider.class),
clientsidePropertyService,
mock(FiftyOneJSService.class),
mock(UACHService.class));

verify(clientsidePropertyService, times(1)).setCacheMaxAge(60);
}

/**
* Check that 'resultService.process()' is called during
* interceptor's preHandle process
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */

package fiftyone.pipeline.web.mvc.configuration;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class FiftyOneInterceptorConfigDefaultTests {

/**
* A negative max-age is refused where it is set, rather than when the
* interceptor starts.
*/
@Test
public void setClientsideCacheMaxAge_Negative_Throws() {
FiftyOneInterceptorConfigDefault config =
new FiftyOneInterceptorConfigDefault();
try {
config.setClientsideCacheMaxAge(-1);
fail("An IllegalArgumentException should have been thrown");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains("-1"));
}
}

/**
* A max-age of 0 is kept, as it tells the browser to ask again on each
* use.
*/
@Test
public void setClientsideCacheMaxAge_Zero_Kept() {
FiftyOneInterceptorConfigDefault config =
new FiftyOneInterceptorConfigDefault();
config.setClientsideCacheMaxAge(0);
assertEquals(0, config.getClientsideCacheMaxAge());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,18 @@ public class Constants {
*/
public static final boolean DEFAULT_CLIENTSIDE_ENABLED = true;

/**
* Default number of seconds a browser may keep the client-side
* JavaScript or JSON before it asks again, the max-age of their
* Cache-Control header.
*/
public static final int DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS = 1800;

/**
* Name of the filter init parameter that sets the max-age of the
* client-side endpoints' Cache-Control header, in seconds.
*/
public static final String INIT_PARAM_CLIENTSIDE_CACHE_MAX_AGE =
"clientside-cache-max-age";

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
import java.io.File;
import java.io.IOException;

import static fiftyone.pipeline.web.Constants.DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;
import static fiftyone.pipeline.web.Constants.DEFAULT_CLIENTSIDE_ENABLED;
import static fiftyone.pipeline.web.Constants.DEFAULT_CONFIG_FILE;
import static fiftyone.pipeline.web.Constants.INIT_PARAM_CLIENTSIDE_CACHE_MAX_AGE;

/**
* Servlet filter used to intercept HTTP requests and process them using the
Expand Down Expand Up @@ -80,6 +82,7 @@ public void init(FilterConfig filterConfig) throws ServletException {
else {
clientsideEnabled = Boolean.parseBoolean(clientsideEnabledString);
}
int cacheMaxAgeSeconds = getCacheMaxAgeSeconds(filterConfig);

ServletContext context = config.getServletContext();

Expand All @@ -105,14 +108,47 @@ public void init(FilterConfig filterConfig) throws ServletException {
resultService = new PipelineResultServiceCore.Default(evidenceService, pipeline);
flowDataProviderCore = new FlowDataProviderCore.Default();
uachServiceCore = new UACHServiceCore.Default();
clientsidePropertyServiceCore = new ClientsidePropertyServiceCore.Default(
flowDataProviderCore,
pipeline);
ClientsidePropertyServiceCore.Default clientside =
new ClientsidePropertyServiceCore.Default(
flowDataProviderCore,
pipeline);
clientside.setCacheMaxAge(cacheMaxAgeSeconds);
clientsidePropertyServiceCore = clientside;
jsService = new FiftyOneJSServiceCore.Default(
clientsidePropertyServiceCore,
clientsideEnabled);
}

/**
* The max-age of the client-side endpoints' Cache-Control header, in
* seconds, from the filter's init parameter, or the default when the
* parameter is not set.
* @param filterConfig the filter's configuration
* @return the max-age in seconds
* @throws ServletException if the parameter is not a whole number of
* seconds, 0 or more
*/
static int getCacheMaxAgeSeconds(FilterConfig filterConfig)
throws ServletException {
String value = filterConfig.getInitParameter(
INIT_PARAM_CLIENTSIDE_CACHE_MAX_AGE);
if (value == null || value.trim().isEmpty()) {
return DEFAULT_CLIENTSIDE_CACHE_MAX_AGE_SECONDS;
}
try {
int seconds = Integer.parseInt(value.trim());
if (seconds >= 0) {
return seconds;
}
} catch (NumberFormatException e) {
// Reported below, as a negative number is.
}
throw new ServletException("The '" +
INIT_PARAM_CLIENTSIDE_CACHE_MAX_AGE + "' init parameter must " +
"be a whole number of seconds, 0 or more, and was '" + value +
"'.");
}

@Override
public void doFilter(
ServletRequest request,
Expand Down
Loading
Loading