diff --git a/thingifier/src/main/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServer.java b/thingifier/src/main/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServer.java index 64fa15a9..ee5552f8 100644 --- a/thingifier/src/main/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServer.java +++ b/thingifier/src/main/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServer.java @@ -27,6 +27,9 @@ import uk.co.compendiumdev.thingifier.api.response.ApiResponseError; public final class JavalinHttpServer implements AutoCloseable { + static final String STATIC_CACHE_CONTROL_PROPERTY = "thingifier.static.cache-control"; + private static final String STATIC_CACHE_CONTROL_ENV = "THINGIFIER_STATIC_CACHE_CONTROL"; + private static final String DEFAULT_STATIC_CACHE_CONTROL = "max-age=0"; private static final String[] STATIC_ASSET_PREFIXES = { "/css/", "/js/", "/favicon/", "/images/" }; @@ -116,7 +119,7 @@ private void serveClasspathStaticAsset(final Context ctx) throws Exception { } ctx.status(200); - ctx.header("Cache-Control", "max-age=0"); + ctx.header("Cache-Control", staticCacheControl()); String contentType = contentTypeFor(path); if (contentType != null) { ctx.contentType(contentType); @@ -144,6 +147,24 @@ private boolean isStaticAssetPath(final String path) { return false; } + static String staticCacheControl() { + final String configured = System.getProperty(STATIC_CACHE_CONTROL_PROPERTY); + if (hasText(configured)) { + return configured.trim(); + } + + final String environment = System.getenv(STATIC_CACHE_CONTROL_ENV); + if (hasText(environment)) { + return environment.trim(); + } + + return DEFAULT_STATIC_CACHE_CONTROL; + } + + private static boolean hasText(final String value) { + return value != null && !value.trim().isEmpty(); + } + private String classpathStaticResource(final String path) { String base = staticFilePath == null ? "" : staticFilePath.trim(); while (base.startsWith("/")) { diff --git a/thingifier/src/test/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServerTest.java b/thingifier/src/test/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServerTest.java index 3e92b256..5f083212 100644 --- a/thingifier/src/test/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServerTest.java +++ b/thingifier/src/test/java/uk/co/compendiumdev/thingifier/adapter/javalin/JavalinHttpServerTest.java @@ -21,6 +21,57 @@ void duplicateLegacyPathParamNamesAreMadeUniqueForJavalin() { JavalinHttpServer.javalinPath("todos/:id/tasksof/:id")); } + @Test + void classpathStaticAssetsUseDefaultNoCacheHeader() throws Exception { + final String originalValue = + System.getProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY); + System.clearProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY); + + try { + int port = availablePort(); + HttpRouteRegistry registry = new HttpRouteRegistry(); + + try (JavalinHttpServer server = new JavalinHttpServer(port, "/public", registry)) { + server.start(); + + HttpResponse response = + get("http://localhost:" + port + "/css/default.css"); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertEquals( + "max-age=0", response.headers().firstValue("Cache-Control").orElse("")); + } + } finally { + restoreStaticCacheControlProperty(originalValue); + } + } + + @Test + void classpathStaticAssetsCanUseConfiguredCacheHeader() throws Exception { + final String originalValue = + System.getProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY); + final String cacheControl = "public, max-age=31536000, immutable"; + System.setProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY, cacheControl); + + try { + int port = availablePort(); + HttpRouteRegistry registry = new HttpRouteRegistry(); + + try (JavalinHttpServer server = new JavalinHttpServer(port, "/public", registry)) { + server.start(); + + HttpResponse response = + get("http://localhost:" + port + "/css/default.css"); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertEquals( + cacheControl, response.headers().firstValue("Cache-Control").orElse("")); + } + } finally { + restoreStaticCacheControlProperty(originalValue); + } + } + @Test void emptyNoContentDoesNotReturnContentTypeHeader() throws Exception { int port = availablePort(); @@ -180,6 +231,14 @@ private HttpResponse get(final String url) throws Exception { HttpResponse.BodyHandlers.ofString()); } + private void restoreStaticCacheControlProperty(final String originalValue) { + if (originalValue == null) { + System.clearProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY); + } else { + System.setProperty(JavalinHttpServer.STATIC_CACHE_CONTROL_PROPERTY, originalValue); + } + } + private int availablePort() throws Exception { try (ServerSocket socket = new ServerSocket(0)) { return socket.getLocalPort();