fix: merge jte extension refactor with static serving features

- Move JteStaticServing to dev.relism.flash package
- Fix imports in HttpStatus and test files
- Add fluent config methods to JteExtension (templateRoot, serveStatics, staticPrefix, etc.)
- Implement routes() for static asset serving with HEAD support
- Include Main.java entry point
This commit is contained in:
Relism
2026-04-26 13:00:08 +02:00
parent 5b3b887acd
commit a1cf4ada49
9 changed files with 144 additions and 55 deletions
@@ -6,19 +6,19 @@ public final class JtehomeGenerated {
public static final String JTE_NAME = "pages/home.jte";
public static final int[] JTE_LINE_INFO = {0,0,1,2,2,2,2,6,6,6,6,7,7,7,8,8,8,9,9,9,10,10,10,2,3,4,4,4,4};
public static void render(gg.jte.html.HtmlTemplateOutput jteOutput, gg.jte.html.HtmlInterceptor jteHtmlInterceptor, HomePage page, String build, Map<String, Object> global) {
jteOutput.writeContent("\r\n<h1>");
jteOutput.writeContent("\n<h1>");
jteOutput.setContext("h1", null);
jteOutput.writeUserContent(page.title());
jteOutput.writeContent("</h1>\r\n<p>");
jteOutput.writeContent("</h1>\n<p>");
jteOutput.setContext("p", null);
jteOutput.writeUserContent(page.author());
jteOutput.writeContent("</p>\r\n<small>");
jteOutput.writeContent("</p>\n<small>");
jteOutput.setContext("small", null);
jteOutput.writeUserContent(build);
jteOutput.writeContent("</small>\r\n<small>");
jteOutput.writeContent("</small>\n<small>");
jteOutput.setContext("small", null);
jteOutput.writeUserContent((String) global.get("appName"));
jteOutput.writeContent("</small>\r\n");
jteOutput.writeContent("</small>\n");
}
public static void renderMap(gg.jte.html.HtmlTemplateOutput jteOutput, gg.jte.html.HtmlInterceptor jteHtmlInterceptor, java.util.Map<String, Object> params) {
HomePage page = (HomePage)params.get("page");
@@ -1,5 +1,7 @@
package dev.relism.flash.ext.view.jte;
import dev.relism.flash.extension.FlashContext;
import dev.relism.flash.extension.FlashRegistrar;
import dev.relism.flash.ext.view.core.BaseViewExtension;
import dev.relism.flash.ext.view.core.GlobalValue;
import dev.relism.flash.ext.view.core.ViewRuntimeBridge;
@@ -10,9 +12,6 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Opinionated jte SSR extension for Flash.
*/
public final class JteExtension extends BaseViewExtension<JteTarget> {
private final JteSettings settings;
@@ -21,7 +20,7 @@ public final class JteExtension extends BaseViewExtension<JteTarget> {
}
public JteExtension() {
this(JteSettings.builder().build());
this.settings = JteSettings.builder().build();
}
public JteExtension(Consumer<JteSettings.Builder> customizer) {
@@ -34,6 +33,41 @@ public final class JteExtension extends BaseViewExtension<JteTarget> {
this.settings = settings;
}
public JteExtension templateRoot(String templateRoot) {
return new JteExtension(settings.toBuilder().templateRoot(templateRoot).build());
}
public JteExtension serveStatics(boolean serveStatics) {
return new JteExtension(settings.toBuilder().serveStatics(serveStatics).build());
}
public JteExtension staticPrefix(String staticPrefix) {
return new JteExtension(settings.toBuilder().staticPrefix(staticPrefix).build());
}
public JteExtension withStaticCors() {
return new JteExtension(settings.toBuilder().enableStaticCors(true).build());
}
public JteExtension staticCors(Consumer<JteSettings.Builder> corsConfig) {
JteSettings.Builder builder = settings.toBuilder();
builder.enableStaticCors(true);
corsConfig.accept(builder);
return new JteExtension(builder.build());
}
@Override
public void routes(FlashRegistrar<?> app, FlashContext ctx) {
if (!settings.serveStatics()) return;
JteStaticServing staticServing = JteStaticServing.load(settings);
if (staticServing == null) return;
String wildcard = settings.staticPrefix() + "/**";
StaticJteHandler handler = new StaticJteHandler(staticServing);
app.get(wildcard, handler::handle);
app.head(wildcard, (req, res) -> { staticServing.serve(req, res, true); return null; });
}
@Override
public JteExtension addGlobal(String key, Function<Request, Object> resolver) {
super.addGlobal(key, resolver);
@@ -60,6 +94,24 @@ public final class JteExtension extends BaseViewExtension<JteTarget> {
}
}
JteSettings getSettings() {
return settings;
}
private static final class StaticJteHandler extends dev.relism.flash.models.RequestHandler {
private final JteStaticServing serving;
StaticJteHandler(JteStaticServing serving) {
this.serving = serving;
}
@Override
public Object handle(dev.relism.flash.models.Request req, dev.relism.flash.models.Response res) {
serving.serve(req, res, false);
return null;
}
}
private static void ensureJtePresent() {
try {
Class.forName("gg.jte.TemplateEngine", false, JteExtension.class.getClassLoader());
@@ -1,8 +1,8 @@
package dev.relism.ext.view.jte;
package dev.relism.flash.ext.view.jte;
import dev.relism.http.HttpStatus;
import dev.relism.models.Request;
import dev.relism.models.Response;
import dev.relism.flash.http.HttpStatus;
import dev.relism.flash.models.Request;
import dev.relism.flash.models.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,7 +92,7 @@ class JteExtensionTest {
void routes_register_static_wildcard_when_enabled() {
JteExtension ext = new JteExtension(cfg -> cfg.staticPrefix("/assets"));
TestRegistrar app = new TestRegistrar();
ext.routes(app, new dev.relism.extension.FlashContext());
ext.routes(app, new dev.relism.flash.extension.FlashContext());
assertTrue(app.routes.containsKey("GET /assets/**"));
assertTrue(app.routes.containsKey("HEAD /assets/**"));
}
@@ -101,7 +101,7 @@ class JteExtensionTest {
void routes_do_not_register_static_when_disabled() {
JteExtension ext = new JteExtension().serveStatics(false);
TestRegistrar app = new TestRegistrar();
ext.routes(app, new dev.relism.extension.FlashContext());
ext.routes(app, new dev.relism.flash.extension.FlashContext());
assertTrue(app.routes.isEmpty());
}
@@ -111,10 +111,10 @@ class JteExtensionTest {
.staticPrefix("/assets")
.largeFileThresholdBytes(1));
TestRegistrar app = new TestRegistrar();
ext.routes(app, new dev.relism.extension.FlashContext());
ext.routes(app, new dev.relism.flash.extension.FlashContext());
Request req = request("/assets/sample.css", null, null, null);
Response res = new Response(200, dev.relism.http.ContentType.TEXT_PLAIN);
Response res = new Response(200, dev.relism.flash.http.ContentType.TEXT_PLAIN);
Object out = app.routes.get("GET /assets/**").handle(req, res);
assertEquals(null, out);
@@ -124,13 +124,7 @@ class JteExtensionTest {
}
private static JteSettings readSettings(JteExtension ext) {
try {
var f = JteExtension.class.getDeclaredField("settings");
f.setAccessible(true);
return (JteSettings) f.get(ext);
} catch (Exception e) {
throw new RuntimeException(e);
}
return ext.getSettings();
}
private static void invokeValidate(JteExtension ext, Class<? extends RequestHandler> type) throws Exception {
@@ -147,39 +141,39 @@ class JteExtensionTest {
}
private static Request request(String path, String query, String acceptEncoding, String origin) {
dev.relism.models.HeaderMap headers = new dev.relism.models.HeaderMap();
dev.relism.flash.models.HeaderMap headers = new dev.relism.flash.models.HeaderMap();
String raw = "Host: localhost\r\n";
if (acceptEncoding != null) raw += "Accept-Encoding: " + acceptEncoding + "\r\n";
if (origin != null) raw += "Origin: " + origin + "\r\n";
byte[] bytes = raw.getBytes(StandardCharsets.UTF_8);
headers.reset(bytes, 0, bytes.length);
dev.relism.models.RequestLine line = new dev.relism.models.RequestLine(
dev.relism.http.HttpMethod.GET,
new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView(path),
query == null ? null : new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView(query),
new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView("HTTP/1.1"),
dev.relism.flash.models.RequestLine line = new dev.relism.flash.models.RequestLine(
dev.relism.flash.http.HttpMethod.GET,
new dev.relism.flash.routing.routers.fastpathrouter.FastPathViews.StringByteView(path),
query == null ? null : new dev.relism.flash.routing.routers.fastpathrouter.FastPathViews.StringByteView(query),
new dev.relism.flash.routing.routers.fastpathrouter.FastPathViews.StringByteView("HTTP/1.1"),
headers
);
return new Request(line, new byte[0]);
}
private static final class TestRegistrar extends dev.relism.extension.FlashRegistrar<TestRegistrar> {
private static final class TestRegistrar extends dev.relism.flash.extension.FlashRegistrar<TestRegistrar> {
private final Map<String, RequestHandler> routes = new HashMap<>();
private final List<dev.relism.routing.Middleware> mws = new ArrayList<>();
private final List<dev.relism.flash.routing.Middleware> mws = new ArrayList<>();
@Override
public dev.relism.extension.FlashContext ctx() {
return new dev.relism.extension.FlashContext();
public dev.relism.flash.extension.FlashContext ctx() {
return new dev.relism.flash.extension.FlashContext();
}
@Override
protected void addRoute(dev.relism.http.HttpMethod method, String path, RequestHandler handler, List<dev.relism.routing.Middleware> mw) {
protected void addRoute(dev.relism.flash.http.HttpMethod method, String path, RequestHandler handler, List<dev.relism.flash.routing.Middleware> mw) {
routes.put(method.name() + " " + path, handler);
}
@Override
protected void addMiddleware(dev.relism.routing.Middleware mw) {
protected void addMiddleware(dev.relism.flash.routing.Middleware mw) {
mws.add(mw);
}
}
@@ -1,4 +1,4 @@
package dev.relism.ext.view.jte;
package dev.relism.flash.ext.view.jte;
import org.junit.jupiter.api.Test;
@@ -0,0 +1,48 @@
package dev.relism.flash;
import dev.relism.flash.extension.FlashApp;
import dev.relism.flash.http.ContentType;
import dev.relism.flash.http.HttpMethod;
import dev.relism.flash.routing.Middleware;
import lombok.extern.slf4j.Slf4j;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
public class Main {
private static final int PORT = 8080;
private static final Path UPLOAD_DIR = Path.of(System.getProperty("java.io.tmpdir"), "flash-uploads");
private static final Middleware CORS = next -> (req, res) -> {
res
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method() == HttpMethod.OPTIONS) { res.status(204); return null; }
return next.handle(req, res);
};
private static final Middleware ACCESS_LOG = next -> (req, res) -> {
long start = System.nanoTime();
try {
return next.handle(req, res);
} finally {
log.info("{} {} -> {}ms", req.method(), req.path(),
(System.nanoTime() - start) / 1_000_000.0);
}
};
public static void main(String[] args) throws Exception {
Files.createDirectories(UPLOAD_DIR);
FlashApp.create(PORT)
.get("/plaintext", (req, res) -> {
res.type(ContentType.TEXT_PLAIN);
return "Hello, World!";
}, ACCESS_LOG, CORS)
.startAndBlock();
}
}
@@ -1,10 +1,5 @@
package dev.relism.flash.http;
import dev.relism.models.Request;
import dev.relism.models.Response;
import dev.relism.routing.GET;
import dev.relism.routing.Route;
import java.nio.charset.StandardCharsets;
import java.util.List;