diff options
| author | Elias Haugsbakk <[email protected]> | 2026-07-27 16:26:42 +0200 |
|---|---|---|
| committer | Elias Haugsbakk <[email protected]> | 2026-07-27 16:53:21 +0200 |
| commit | 9b716b8406726fea2b27509fdeec9f4fbe32091e (patch) | |
| tree | c6e33969467caef2f3eb5dadfd5459b38fdad652 /clams-server | |
| parent | 260ad2efa7c61bc1f6b04a528e67ace97d5d1d41 (diff) | |
implement page caching
Diffstat (limited to 'clams-server')
| -rw-r--r-- | clams-server/pom.xml | 2 | ||||
| -rw-r--r-- | clams-server/src/main/java/no/eliashaugsbakk/clams/server/App.java (renamed from clams-server/src/main/java/no/eliashaugsbakk/clams/server/Main.java) | 10 | ||||
| -rw-r--r-- | clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java | 4 | ||||
| -rw-r--r-- | clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java | 53 | ||||
| -rw-r--r-- | clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PageCache.java | 37 | ||||
| -rw-r--r-- | clams-server/src/main/resources/templates/404.html | 4 |
6 files changed, 99 insertions, 11 deletions
diff --git a/clams-server/pom.xml b/clams-server/pom.xml index 2f77778..434dbfa 100644 --- a/clams-server/pom.xml +++ b/clams-server/pom.xml @@ -78,7 +78,7 @@ <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> - <mainClass>no.eliashaugsbakk.clams.server.Main</mainClass> + <mainClass>no.eliashaugsbakk.clams.server.App</mainClass> </transformer> </transformers> </configuration> diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/Main.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/App.java index eba65f8..5910f43 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/Main.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/App.java @@ -9,15 +9,18 @@ import no.eliashaugsbakk.clams.server.config.AppRoutes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Main { - private static final Logger log = LoggerFactory.getLogger(Main.class); +public class App { + private static final Logger log = LoggerFactory.getLogger(App.class); void main() { // Initialize dependencies AppContext context = new AppContext(); try { Javalin.create(config -> { - config.staticFiles.add("/public"); + config.staticFiles.add(staticFiles -> { + staticFiles.hostedPath = "/"; + staticFiles.directory = "/public"; + }); config.fileRenderer(new JavalinPebble()); config.routes.error(404, ctx -> { @@ -51,7 +54,6 @@ public class Main { }); config.routes.apiBuilder(new AppRoutes(context)); - config.events.serverStopped(context::close); }).start(7070); diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java index 2d52374..189edc7 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java @@ -13,6 +13,7 @@ import no.eliashaugsbakk.clams.server.repository.ProjectsRepo; import no.eliashaugsbakk.clams.server.repository.ProjectsRepoSqlite; import no.eliashaugsbakk.clams.server.repository.SqliteManager; import no.eliashaugsbakk.clams.server.service.AuthService; +import no.eliashaugsbakk.clams.server.service.PageCache; import no.eliashaugsbakk.clams.server.service.SlugService; public class AppContext implements AutoCloseable { @@ -22,6 +23,7 @@ public class AppContext implements AutoCloseable { private final PostController postController; private final ProjectsController projectsController; private final AuthService authService; + private final PageCache pageCache; public AppContext() { AppConfig appConfig = new AppConfig(); @@ -41,6 +43,7 @@ public class AppContext implements AutoCloseable { this.projectsController = new ProjectsController(projectsRepo); this.postController = new PostController(postsRepo, slugService); this.authService = new AuthService(appConfig); + this.pageCache = new PageCache(15); } public PostsController getPostsController() { return postsController; } @@ -48,6 +51,7 @@ public class AppContext implements AutoCloseable { public ProjectsController getProjectsController() { return projectsController; } public PostController getPostController() { return postController; } public AuthService getAuthService() { return authService; } + public PageCache pageCache() { return pageCache; } @Override public void close() { diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java index abd2926..b296f4f 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java @@ -1,5 +1,6 @@ package no.eliashaugsbakk.clams.server.config; +import static io.javalin.apibuilder.ApiBuilder.after; import static io.javalin.apibuilder.ApiBuilder.before; import static io.javalin.apibuilder.ApiBuilder.delete; import static io.javalin.apibuilder.ApiBuilder.get; @@ -19,6 +20,51 @@ public class AppRoutes implements EndpointGroup { @Override public void addEndpoints() { + before(ctx -> { + String path = ctx.path(); + + // Skip non-GET requests and API endpoints + if (!ctx.method().name().equalsIgnoreCase("GET") + || path.startsWith("/api") + || ctx.queryString() != null) { + return; + } + + String cachedHtml = appContext.pageCache().get(path); + if (cachedHtml != null) { + ctx.result(cachedHtml); + ctx.contentType("text/html"); + ctx.skipRemainingHandlers(); + } + }); + + after(ctx -> { + String method = ctx.method().name(); + String path = ctx.path(); + + // Invalidate cache on any modifying request if successful + if ((method.equalsIgnoreCase("POST") + || method.equalsIgnoreCase("PUT") + || method.equalsIgnoreCase("DELETE")) + && ctx.status().getCode() >= 200 && ctx.status().getCode() < 300) { + appContext.pageCache().clear(); + return; + } + + // 2. Cache GET responses + if (method.equalsIgnoreCase("GET") + && !path.startsWith("/api") // exclude API responses + && ctx.queryString() == null // exclude queried pages + && ctx.status().getCode() == 200) { // only cache successful responses + + String renderedHtml = ctx.result(); + if (renderedHtml != null && !renderedHtml.isBlank()) { + appContext.pageCache().put(path, renderedHtml); + } + } + }); + + get("/", ctx -> ctx.redirect("/home")); get("/home", ctx -> ctx.render("templates/home.html", Map.of("page_title", "Elias Haugsbakk", "page_css", "home"))); @@ -35,8 +81,8 @@ public class AppRoutes implements EndpointGroup { String authHeader = ctx.header("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { - ctx.status(401).json(Map.of("error", "Unauthorized", - "message", "Missing or malformed Authorization header.")); + ctx.status(401).json(Map.of("error", "Unauthorized", "message", + "Missing or malformed Authorization header.")); ctx.skipRemainingHandlers(); return; } @@ -44,8 +90,7 @@ public class AppRoutes implements EndpointGroup { String token = authHeader.substring(7).trim(); if (!appContext.getAuthService().isValid(token)) { ctx.status(403) - .json(Map.of("error", "Forbidden", - "message", "Invalid API validation token.")); + .json(Map.of("error", "Forbidden", "message", "Invalid API validation token.")); ctx.skipRemainingHandlers(); } }); diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PageCache.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PageCache.java new file mode 100644 index 0000000..4313cce --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PageCache.java @@ -0,0 +1,37 @@ +package no.eliashaugsbakk.clams.server.service; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class PageCache { + private final Map<String, String> cache; + + /** + * Least Recently Used Cache + */ + public PageCache(int capacity) { + + LinkedHashMap<String, String> map = new LinkedHashMap<>(capacity, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { + return size() > capacity; + } + }; + + // Wrap with synchronizedMap to make thread-safe + this.cache = Collections.synchronizedMap(map); + } + + public String get(String url) { + return cache.get(url); + } + + public void put(String url, String html) { + cache.put(url, html); + } + + public void clear() { + cache.clear(); + } +} diff --git a/clams-server/src/main/resources/templates/404.html b/clams-server/src/main/resources/templates/404.html index 3a655ae..0fa0329 100644 --- a/clams-server/src/main/resources/templates/404.html +++ b/clams-server/src/main/resources/templates/404.html @@ -4,10 +4,10 @@ <main class="main-container"> <div class="error-container"> <div class="error-code">404</div> - <h1>Fil iIkke Funnet</h1> + <h1>Fil Ikke Funnet</h1> <p>Filen du ser etter finnes ikke.</p> <p class="error-home-link"> - <a href="/">Tilbake til Hemmesiden</a> + <a href="/">Tilbake til Hjemmesiden</a> </p> </div> </main> |
