diff options
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.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 |
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(); + } +} |
