From 3952de7e7865453e5fe65cdaaff1e56642ea6679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Fri, 14 Jul 2023 14:54:50 +0200 Subject: [PATCH] add htmlflow, add utility methods to HtmlTemplateResource --- net-http-htmlflow/build.gradle | 4 + .../src/main/java/module-info.java | 8 ++ .../htmlflow/HtmlFlowResourceHandler.java | 60 +++++++++++++ .../net/http/htmlflow/HtmlFlowService.java | 15 ++++ .../http/htmlflow/HtmlFlowServiceBuilder.java | 69 ++++++++++++++ .../http/j2html/J2HtmlResourceHandler.java | 28 +++--- net-http-server-application-web/build.gradle | 1 + .../application/web/j2html/Bootstrap.java | 1 - .../j2html/InternalServerErrorHandler.java | 4 +- .../server/application/BaseApplication.java | 1 + .../server/resource/HtmlTemplateResource.java | 89 +++++++++++++++++-- settings.gradle | 4 +- 12 files changed, 260 insertions(+), 24 deletions(-) create mode 100644 net-http-htmlflow/build.gradle create mode 100644 net-http-htmlflow/src/main/java/module-info.java create mode 100644 net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowResourceHandler.java create mode 100644 net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowService.java create mode 100644 net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowServiceBuilder.java diff --git a/net-http-htmlflow/build.gradle b/net-http-htmlflow/build.gradle new file mode 100644 index 0000000..16a18a3 --- /dev/null +++ b/net-http-htmlflow/build.gradle @@ -0,0 +1,4 @@ +dependencies { + api project(':net-http-server') + api libs.htmlflow +} diff --git a/net-http-htmlflow/src/main/java/module-info.java b/net-http-htmlflow/src/main/java/module-info.java new file mode 100644 index 0000000..b2b20dc --- /dev/null +++ b/net-http-htmlflow/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module org.xbib.net.http.htmlflow { + exports org.xbib.net.http.htmlflow; + requires org.xbib.net; + requires org.xbib.net.http; + requires org.xbib.net.http.server; + requires org.xbib.config; + requires java.logging; +} \ No newline at end of file diff --git a/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowResourceHandler.java b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowResourceHandler.java new file mode 100644 index 0000000..992c090 --- /dev/null +++ b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowResourceHandler.java @@ -0,0 +1,60 @@ +package org.xbib.net.http.htmlflow; + +import org.xbib.net.Attributes; +import org.xbib.net.Resource; +import org.xbib.net.buffer.DataBuffer; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.server.application.Application; +import org.xbib.net.http.server.resource.HtmlTemplateResource; +import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler; +import org.xbib.net.http.server.route.HttpRouterContext; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE; + +public class HtmlFlowResourceHandler extends HtmlTemplateResourceHandler { + + public HtmlFlowResourceHandler(Path root, String suffix, String indexFileName) { + super(root, suffix, indexFileName); + } + + @Override + protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException { + return new HtmlFlowResource(this, httpRouterContext); + } + + protected static class HtmlFlowResource extends HtmlTemplateResource { + + private static final Logger logger = Logger.getLogger(HtmlFlowResource.class.getName()); + + protected HtmlFlowResource(HtmlTemplateResourceHandler templateResourceHandler, HttpRouterContext httpRouterContext) throws IOException { + super(templateResourceHandler, httpRouterContext); + } + + @Override + public void render(HttpRouterContext context) throws IOException { + Application application = context.getAttributes().get(Application.class, "application"); + if (application == null) { + logger.log(Level.WARNING, "application is null"); + return; + } + DataBuffer dataBuffer = context.getDataBufferFactory().allocateBuffer(); + dataBuffer.write(render(application, context.getAttributes()), StandardCharsets.UTF_8); + HttpResponseStatus httpResponseStatus = context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK); + context.status(httpResponseStatus) + .header("cache-control", "no-cache") // override default must-revalidate behavior + .header("content-length", Integer.toString(dataBuffer.writePosition())) + .header(CONTENT_TYPE, "text/html; charset=" + StandardCharsets.UTF_8.displayName()) + .body(dataBuffer); + } + + protected String render(Application application, Attributes attributes) { + return null; + } + } +} diff --git a/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowService.java b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowService.java new file mode 100644 index 0000000..36fd756 --- /dev/null +++ b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowService.java @@ -0,0 +1,15 @@ +package org.xbib.net.http.htmlflow; + +import org.xbib.net.http.server.service.BaseHttpService; +import org.xbib.net.http.server.service.BaseHttpServiceBuilder; + +public class HtmlFlowService extends BaseHttpService { + + protected HtmlFlowService(BaseHttpServiceBuilder builder) { + super(builder); + } + + public static HtmlFlowServiceBuilder builder() { + return new HtmlFlowServiceBuilder(); + } +} diff --git a/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowServiceBuilder.java b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowServiceBuilder.java new file mode 100644 index 0000000..3815cbc --- /dev/null +++ b/net-http-htmlflow/src/main/java/org/xbib/net/http/htmlflow/HtmlFlowServiceBuilder.java @@ -0,0 +1,69 @@ +package org.xbib.net.http.htmlflow; + +import org.xbib.net.ParameterDefinition; +import org.xbib.net.http.HttpMethod; +import org.xbib.net.http.server.HttpHandler; +import org.xbib.net.http.server.domain.HttpSecurityDomain; +import org.xbib.net.http.server.service.BaseHttpServiceBuilder; + +import java.nio.file.Path; + +public class HtmlFlowServiceBuilder extends BaseHttpServiceBuilder { + + protected Path prefix; + + protected String suffix; + + public HtmlFlowServiceBuilder() { + super(); + this.prefix = null; + } + + @Override + public HtmlFlowServiceBuilder setPath(String path) { + super.setPath(path); + return this; + } + + @Override + public HtmlFlowServiceBuilder setMethod(HttpMethod... httpMethod) { + super.setMethod(httpMethod); + return this; + } + + @Override + public HtmlFlowServiceBuilder setHandler(HttpHandler... handler) { + super.setHandler(handler); + return this; + } + + @Override + public HtmlFlowServiceBuilder setParameterDefinition(ParameterDefinition... parameterDefinition) { + super.setParameterDefinition(parameterDefinition); + return this; + } + + @Override + public HtmlFlowServiceBuilder setSecurityDomain(HttpSecurityDomain securityDomain) { + super.setSecurityDomain(securityDomain); + return this; + } + + public HtmlFlowServiceBuilder setPrefix(Path prefix) { + this.prefix = prefix; + return this; + } + + public HtmlFlowServiceBuilder setSuffix(String suffix) { + this.suffix = suffix; + return this; + } + + public HtmlFlowService build() { + if (this.handlers == null) { + HttpHandler httpHandler = new HtmlFlowResourceHandler(prefix, suffix, "index.java"); + setHandler(httpHandler); + } + return new HtmlFlowService(this); + } +} diff --git a/net-http-j2html/src/main/java/org/xbib/net/http/j2html/J2HtmlResourceHandler.java b/net-http-j2html/src/main/java/org/xbib/net/http/j2html/J2HtmlResourceHandler.java index 74d51dc..624c8bb 100644 --- a/net-http-j2html/src/main/java/org/xbib/net/http/j2html/J2HtmlResourceHandler.java +++ b/net-http-j2html/src/main/java/org/xbib/net/http/j2html/J2HtmlResourceHandler.java @@ -1,10 +1,9 @@ package org.xbib.net.http.j2html; -import org.xbib.net.Attributes; import org.xbib.net.Resource; import org.xbib.net.buffer.DataBuffer; import org.xbib.net.http.HttpResponseStatus; -import org.xbib.net.http.server.application.Application; +import org.xbib.net.http.server.HttpRequest; import org.xbib.net.http.server.resource.HtmlTemplateResource; import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler; import org.xbib.net.http.server.route.HttpRouterContext; @@ -12,8 +11,7 @@ import org.xbib.net.http.server.route.HttpRouterContext; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Objects; import static j2html.TagCreator.body; import static j2html.TagCreator.h1; @@ -36,22 +34,24 @@ public class J2HtmlResourceHandler extends HtmlTemplateResourceHandler { protected static class J2HtmlResource extends HtmlTemplateResource { - private static final Logger logger = Logger.getLogger(J2HtmlResource.class.getName()); + protected HttpRequest request; - protected J2HtmlResource(HtmlTemplateResourceHandler templateResourceHandler, HttpRouterContext httpRouterContext) throws IOException { + protected J2HtmlResource(HtmlTemplateResourceHandler templateResourceHandler, + HttpRouterContext httpRouterContext) throws IOException { super(templateResourceHandler, httpRouterContext); } @Override public void render(HttpRouterContext context) throws IOException { - Application application = context.getAttributes().get(Application.class, "application"); - if (application == null) { - logger.log(Level.WARNING, "application is null"); - return; - } + this.request = context.getRequest(); + Objects.requireNonNull(responseBuilder); DataBuffer dataBuffer = context.getDataBufferFactory().allocateBuffer(); - dataBuffer.write(render(application, context.getAttributes()), StandardCharsets.UTF_8); - HttpResponseStatus httpResponseStatus = context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK); + dataBuffer.write(renderBody(context), StandardCharsets.UTF_8); + HttpResponseStatus httpResponseStatus = responseBuilder.getResponseStatus(); + if (httpResponseStatus == null) { + httpResponseStatus = HttpResponseStatus.OK; + } + //context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK); context.status(httpResponseStatus) .header("cache-control", "no-cache") // override default must-revalidate behavior .header("content-length", Integer.toString(dataBuffer.writePosition())) @@ -59,7 +59,7 @@ public class J2HtmlResourceHandler extends HtmlTemplateResourceHandler { .body(dataBuffer); } - protected String render(Application application, Attributes attributes) { + protected String renderBody(HttpRouterContext context) { return body( h1("Hello World") ).render(); diff --git a/net-http-server-application-web/build.gradle b/net-http-server-application-web/build.gradle index dbab609..8d7f283 100644 --- a/net-http-server-application-web/build.gradle +++ b/net-http-server-application-web/build.gradle @@ -5,6 +5,7 @@ dependencies { api project(':net-http-server-application-config') api project(':net-http-template-groovy') api project(':net-http-j2html') + api project(':net-http-htmlflow') api libs.jdbc.query implementation libs.webjars.bootstrap implementation libs.webjars.jquery diff --git a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/Bootstrap.java b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/Bootstrap.java index d9b3bdc..23bdc6f 100644 --- a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/Bootstrap.java +++ b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/Bootstrap.java @@ -197,7 +197,6 @@ public final class Bootstrap { return 0; } - private static final String hexFavIcon = "000001000100101000000100200068040000160000002800000010000000" + "200000000100200000000000000000000000000000000000000000000000" + diff --git a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/InternalServerErrorHandler.java b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/InternalServerErrorHandler.java index be810fc..f86ec20 100644 --- a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/InternalServerErrorHandler.java +++ b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/j2html/InternalServerErrorHandler.java @@ -3,7 +3,6 @@ package org.xbib.net.http.server.application.web.j2html; import org.xbib.net.Attributes; import org.xbib.net.Resource; import org.xbib.net.http.j2html.J2HtmlResourceHandler; -import org.xbib.net.http.server.application.Application; import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler; import org.xbib.net.http.server.route.HttpRouterContext; import org.xbib.net.util.ExceptionFormatter; @@ -30,7 +29,8 @@ public class InternalServerErrorHandler extends J2HtmlResourceHandler { } @Override - protected String render(Application application, Attributes attributes) { + protected String renderBody(HttpRouterContext httpRouterContext) { + Attributes attributes = httpRouterContext.getAttributes(); String message = attributes.get(String.class, "_message"); Throwable throwable = attributes.get(Throwable.class, "_throwable"); String stackTrace = ExceptionFormatter.format(throwable); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java index a17e366..3d636ce 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; + import org.xbib.net.http.HttpAddress; import org.xbib.net.http.cookie.SameSite; import org.xbib.net.http.server.route.BaseHttpRouterContext; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/resource/HtmlTemplateResource.java b/net-http-server/src/main/java/org/xbib/net/http/server/resource/HtmlTemplateResource.java index 066d9bd..0b4d152 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/resource/HtmlTemplateResource.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/resource/HtmlTemplateResource.java @@ -4,9 +4,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import org.xbib.net.URL; +import org.xbib.net.http.HttpHeaderNames; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.server.HttpRequest; +import org.xbib.net.http.server.HttpResponseBuilder; import org.xbib.net.http.server.route.HttpRouterContext; import org.xbib.net.http.server.application.Application; @@ -40,12 +45,18 @@ public class HtmlTemplateResource implements HttpServerResource { protected final boolean negotiateLocale; + protected final HttpResponseBuilder responseBuilder; + + protected final Application application; + protected HtmlTemplateResource(HtmlTemplateResourceHandler templateResourceHandler, HttpRouterContext httpRouterContext) throws IOException { this.templateResourceHandler = templateResourceHandler; - String indexFileName = templateResourceHandler.getIndexFileName(); - Application application = httpRouterContext.getAttributes().get(Application.class, "application"); + this.application = httpRouterContext.getAttributes().get(Application.class, "application"); + Objects.requireNonNull(application); this.negotiateLocale = application.getSettings().getAsBoolean("negotiateLocale", false); + this.responseBuilder = httpRouterContext.getAttributes().get(HttpResponseBuilder.class, "response"); + Objects.requireNonNull(responseBuilder); Path root = templateResourceHandler.getRoot(); root = root != null ? root : application.getHome(); if (root == null) { @@ -57,13 +68,13 @@ public class HtmlTemplateResource implements HttpServerResource { " root = " + root + " resource path = " + resourcePath + " path = " + path + - " index file name = " + indexFileName); + " index file name = " + templateResourceHandler.getIndexFileName()); this.name = path.getFileName().toString(); this.baseName = AbstractResourceHandler.basename(name); this.suffix = AbstractResourceHandler.suffix(name); if (Files.isDirectory(path)) { if (getIndexFileName() != null) { - Path indexPath = path.resolve(indexFileName); + Path indexPath = path.resolve(templateResourceHandler.getIndexFileName()); logger.log(Level.FINEST, "resolved to index path = " + indexPath); if (Files.exists(indexPath)) { logger.log(Level.FINEST, "index path exists"); @@ -137,7 +148,7 @@ public class HtmlTemplateResource implements HttpServerResource { @Override public String getIndexFileName() { - return templateResourceHandler.getIndexFileName(); + return templateResourceHandler != null ? templateResourceHandler.getIndexFileName() : null; } @Override @@ -163,11 +174,77 @@ public class HtmlTemplateResource implements HttpServerResource { ",lastmodified=" + lastModified + ",length=" + length + ",isExists=" + isExists + - ",isDirectory=" + isDirectory() + "]"; + ",isDirectory=" + isDirectory + "]"; } @Override public void render(HttpRouterContext httpRouterContext) throws IOException { // to be overriden } + + public String contextPath(HttpRequest request, String rel) { + return url(request, rel, false); + } + + public String url(HttpRequest request, String rel) { + return url(request, rel, true); + } + + public String url(HttpRequest request, String rel, boolean absolute) { + String prefix = application.getSettings().get("web.prefix", "/"); + if (!prefix.endsWith("/")) { + prefix = prefix + "/"; + } + URL url = request.getServerURL().resolve(prefix).resolve(rel); + return absolute ? url.toExternalForm() : toOrigin(url); + } + + public void setResponseStatus(HttpResponseStatus responseStatus) { + this.responseBuilder.setResponseStatus(responseStatus); + } + + public void movedPermanently(String url) { + this.responseBuilder.setResponseStatus(HttpResponseStatus.MOVED_PERMANENTLY); + this.responseBuilder.setHeader(HttpHeaderNames.LOCATION, url); + } + + public void found(String url) { + this.responseBuilder.setResponseStatus(HttpResponseStatus.FOUND); + this.responseBuilder.setHeader(HttpHeaderNames.LOCATION, url); + } + + public void seeOther(String url) { + this.responseBuilder.setResponseStatus(HttpResponseStatus.SEE_OTHER); + this.responseBuilder.setHeader(HttpHeaderNames.LOCATION, url); + } + + public void temporaryRedirect(String url) { + this.responseBuilder.setResponseStatus(HttpResponseStatus.TEMPORARY_REDIRECT); + this.responseBuilder.setHeader(HttpHeaderNames.LOCATION, url); + } + + public void notFound() { + this.responseBuilder.setResponseStatus(HttpResponseStatus.NOT_FOUND); + } + + public void gone() { + this.responseBuilder.setResponseStatus(HttpResponseStatus.GONE); + } + + private static String toOrigin(URL url) { + StringBuilder sb = new StringBuilder(); + if (url.getPath() != null) { + sb.append(url.getPath()); + } + if (url.getQuery() != null) { + sb.append('?').append(url.getQuery()); + } + if (url.getFragment() != null) { + sb.append('#').append(url.getFragment()); + } + if (sb.length() == 0) { + sb.append('/'); + } + return sb.toString(); + } } diff --git a/settings.gradle b/settings.gradle index cdbda91..83e8b32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,10 +25,11 @@ dependencyResolutionManagement { library('jackson', 'com.fasterxml.jackson.core', 'jackson-databind').version('2.14.2') library('jna', 'net.java.dev.jna', 'jna').version('5.13.0') library('groovy-templates', 'org.apache.groovy', 'groovy-templates').versionRef('groovy') + library('j2html', 'com.j2html', 'j2html').version('1.6.0') + library('htmlflow', 'com.github.xmlet', 'htmlflow').version('4.0') library('webjars-bootstrap', 'org.webjars', 'bootstrap').version('5.2.3') library('webjars-jquery', 'org.webjars', 'jquery').version('3.6.4') library('webjars-fontawesome', 'org.webjars', 'font-awesome').version('6.3.0') - library('j2html', 'com.j2html', 'j2html').version('1.6.0') library('net', 'org.xbib', 'net').versionRef('net') library('net-mime', 'org.xbib', 'net-mime').versionRef('net') library('net-security', 'org.xbib', 'net-security').versionRef('net') @@ -62,6 +63,7 @@ include 'net-http-server-nio' include 'net-http-server-simple' include 'net-http-server-simple-secure' include 'net-http-template-groovy' +include 'net-http-htmlflow' include 'net-http-j2html' include 'net-http-server-application-web' include 'net-http-server-application-config'