summaryrefslogtreecommitdiff
path: root/clams-server/src/main/java/no
diff options
context:
space:
mode:
authorElias Haugsbakk <[email protected]>2026-07-27 16:26:42 +0200
committerElias Haugsbakk <[email protected]>2026-07-27 16:53:21 +0200
commit9b716b8406726fea2b27509fdeec9f4fbe32091e (patch)
treec6e33969467caef2f3eb5dadfd5459b38fdad652 /clams-server/src/main/java/no
parent260ad2efa7c61bc1f6b04a528e67ace97d5d1d41 (diff)
implement page caching
Diffstat (limited to 'clams-server/src/main/java/no')
-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.java4
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java53
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PageCache.java37
4 files changed, 96 insertions, 8 deletions
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();
+ }
+}