diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java index 07269cf..d6e92c2 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java @@ -129,6 +129,7 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { } public void close() { + logger.log(Level.FINER, "closing channel " + ctx.channel()); ctx.close(); } @@ -218,9 +219,11 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { ChannelFuture channelFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); if (headers.containsHeader(HttpHeaderNames.CONTENT_LENGTH)) { if (!keepAlive) { + logger.log(Level.FINER, "adding close listener to channel future " + channelFuture); channelFuture.addListener(CLOSE); } } else { + logger.log(Level.FINER, "adding close listener to channel future " + channelFuture); channelFuture.addListener(CLOSE); } }); @@ -231,7 +234,6 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { logger.log(Level.WARNING, "channel not writeable: " + ctx.channel()); return; } - // TODO we write a single chunk, but we should use chunked output here. ByteBuf buffer; int count; try { @@ -246,7 +248,6 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { return; } if (count < bufferSize) { - // not chunked, no headers (???) internalBufferWrite(buffer, count); } else { // chunked @@ -269,14 +270,15 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { } defaultHttpResponse.headers().set(headers); ctx.write(defaultHttpResponse); - //ctx.write(buffer); ??? ctx.write(new ChunkedStream(inputStream, bufferSize)); ChannelFuture channelFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); if (headers.contains(HttpHeaderNames.CONTENT_LENGTH)) { if (!keepAlive) { + logger.log(Level.FINER, "adding close listener to channel future " + channelFuture); channelFuture.addListener(CLOSE); } } else { + logger.log(Level.FINER, "adding close listener to channel future " + channelFuture); channelFuture.addListener(CLOSE); } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponseBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponseBuilder.java index 158ffa6..b8c8d13 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponseBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponseBuilder.java @@ -80,7 +80,7 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder { public void reset() { this.version = HttpVersion.HTTP_1_1; - this.status = HttpResponseStatus.OK; + this.status = null; // must be undefined here this.headers = new HttpHeaders(); this.trailingHeaders = new HttpHeaders(); this.contentType = HttpHeaderValues.APPLICATION_OCTET_STREAM; @@ -107,11 +107,11 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder { @Override public BaseHttpResponseBuilder setResponseStatus(HttpResponseStatus status) { - if (this.status != null) { - logger.log(Level.WARNING, "status rejected: " + status + " status is already " + this.status); - return this; + if (this.status == null) { + this.status = status; + } else { + logger.log(Level.WARNING, "ignoring status = " + status + " because already set: " + this.status); } - this.status = status; return this; } @@ -129,20 +129,18 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder { public BaseHttpResponseBuilder setHeader(CharSequence name, String value) { if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) { setContentType(value); - } else { - if (!headers.containsHeader(name)) { - headers.set(name, value); - } else { - logger.log(Level.WARNING, "header rejected: " + name + " = " + value); - } } + if (headers.containsHeader(name)) { + logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " overwriting with " + value); + } + headers.set(name, value); return this; } @Override public BaseHttpResponseBuilder addHeader(CharSequence name, String value) { if (headers.containsHeader(name)) { - logger.log(Level.WARNING, "duplicate header: " + name + " old value = " + headers.get(name) + " new value = " + value); + logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " adding " + value); } headers.add(name, value); return this; @@ -288,6 +286,10 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder { } headers.add(HttpHeaderNames.CONTENT_TYPE, contentType); } + if (status == null) { + logger.log(Level.WARNING, "no status code set by handlers, assuming OK"); + status = HttpResponseStatus.OK; + } if (status.code() >= 200 && status.code() != 204) { if (!headers.containsHeader(HttpHeaderNames.CONTENT_LENGTH)) { headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(contentLength)); @@ -302,11 +304,15 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder { if (httpServerConfig != null && httpServerConfig.getServerName() != null) { headers.add(HttpHeaderNames.SERVER, httpServerConfig.getServerName()); } - logger.log(Level.FINER, "headers built: " + headers); + logger.log(Level.FINER, "done: status = " + status + " headers = " + headers); } public CharBuffer wrapHeaders() { StringBuilder sb = new StringBuilder(); + if (status == null) { + logger.log(Level.WARNING, "no status code set by handlers, assuming OK"); + setResponseStatus(HttpResponseStatus.OK); + } sb.append(version.text()).append(SPACE).append(status.code()).append(SPACE).append(status.reasonPhrase()).append(CRLF); for (Pair e : headers.entries()) { sb.append(e.getKey().toLowerCase(Locale.ROOT)).append(COLON).append(SPACE).append(e.getValue()).append(CRLF); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/resource/AbstractResourceHandler.java b/net-http-server/src/main/java/org/xbib/net/http/server/resource/AbstractResourceHandler.java index 2970291..0768ba3 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/resource/AbstractResourceHandler.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/resource/AbstractResourceHandler.java @@ -57,7 +57,7 @@ public abstract class AbstractResourceHandler implements HttpHandler { public void handle(HttpServerContext context) throws IOException { logger.log(Level.FINE, "handle: before creating resource " + this.getClass().getName()); Resource resource = createResource(context); - logger.log(Level.FINE, "handle: resource = " + (resource != null ? resource.getClass().getName() : null)); + logger.log(Level.FINE, "handle: resource = " + (resource != null ? resource.getClass().getName() + " " + resource : null)); if (resource == null || !resource.isExists()) { logger.log(Level.FINER, "resource does not exist: " + resource); throw new HttpException("resource not found", context, HttpResponseStatus.NOT_FOUND); @@ -74,14 +74,14 @@ public abstract class AbstractResourceHandler implements HttpHandler { logger.log(Level.FINER, "client must add a /, external redirect to = " + loc); context.response() .addHeader(HttpHeaderNames.LOCATION, loc) - .setResponseStatus(HttpResponseStatus.TEMPORARY_REDIRECT) + .setResponseStatus(HttpResponseStatus.TEMPORARY_REDIRECT) // 307 .build().flush(); // flush is important } else if (resource.isExistsIndexFile()) { // external redirect to default index file in this directory logger.log(Level.FINER, "external redirect to default index file in this directory: " + resource.getIndexFileName()); context.response() .addHeader(HttpHeaderNames.LOCATION, resource.getIndexFileName()) - .setResponseStatus(HttpResponseStatus.TEMPORARY_REDIRECT) + .setResponseStatus(HttpResponseStatus.TEMPORARY_REDIRECT) // 307 .build() .flush(); // write headers } else { @@ -102,14 +102,14 @@ public abstract class AbstractResourceHandler implements HttpHandler { private void generateCacheableResource(HttpServerContext context, Resource resource) throws IOException { // if resource is length of 0, there is nothing to send. Do not send any content, if (resource.getLength() == 0) { + logger.log(Level.FINE, "the resource length is 0, do nothing"); context.response().build().flush(); return; } HttpHeaders headers = context.request().getHeaders(); logger.log(Level.FINE, "before generating resource, the response headers are " + context.response().getHeaders()); String contentType = resource.getMimeType(); - context.response() - .addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); + context.response().addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); // heuristic for inline disposition String disposition = "inline"; if (!contentType.startsWith("text") && !contentType.startsWith("image")) { diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/resource/ClassLoaderResourceHandler.java b/net-http-server/src/main/java/org/xbib/net/http/server/resource/ClassLoaderResourceHandler.java index 0ad8aae..1dace69 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/resource/ClassLoaderResourceHandler.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/resource/ClassLoaderResourceHandler.java @@ -70,13 +70,13 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler { private final long length; - private final String contentType; + private final String mimeType; private URL url; ClassLoaderResource(HttpServerContext httpServerContext) throws IOException { - String effectivePath = httpServerContext.request().getRequestPath().substring(1); // httpServerContext.getEffectiveRequestPath(); - this.contentType = mimeTypeService.getContentType(effectivePath); + String effectivePath = httpServerContext.request().getRequestPath().substring(1); + this.mimeType = mimeTypeService.getContentType(effectivePath); this.resourcePath = effectivePath.startsWith("/") ? effectivePath.substring(1) : effectivePath; String path = prefix != null ? (prefix.endsWith("/") ? prefix : prefix + "/") : "/"; path = resourcePath.startsWith("/") ? path + resourcePath.substring(1) : path + resourcePath; @@ -161,7 +161,7 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler { @Override public String getMimeType() { - return contentType; + return mimeType; } @Override @@ -178,6 +178,7 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler { public String toString() { return "[ClassLoaderResource:resourcePath=" + resourcePath + ",url=" + url + + ",mimeType=" + mimeType + ",lastmodified=" + lastModified + ",length=" + length + ",isDirectory=" + isDirectory() + "]"; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/resource/FileResourceHandler.java b/net-http-server/src/main/java/org/xbib/net/http/server/resource/FileResourceHandler.java index 5462763..36da3db 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/resource/FileResourceHandler.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/resource/FileResourceHandler.java @@ -46,10 +46,14 @@ public class FileResourceHandler extends AbstractResourceHandler { Resource resource = null; if (pathSpec.endsWith("/")) { if (indexFileName != null) { - resource = new FileResource(httpServerContext, pathSpec + indexFileName); + resource = new FileResource(httpServerContext, pathSpec + indexFileName); } } else { resource = new FileResource(httpServerContext, pathSpec); + if (resource.isDirectory() && resource.isExistsIndexFile()) { + logger.log(Level.FINER, "we have a directory and existing index file, so we redirect internally"); + resource = new FileResource(httpServerContext, pathSpec + indexFileName); + } } return resource; } @@ -92,7 +96,7 @@ public class FileResourceHandler extends AbstractResourceHandler { private final boolean isExistsIndexFile; - private final String contentType; + private final String mimeType; private final String name; @@ -107,7 +111,6 @@ public class FileResourceHandler extends AbstractResourceHandler { if (root == null) { throw new IllegalArgumentException("no home path set for template resource resolving"); } - logger.log(Level.FINE, "root = " + root); if (resourcePath.startsWith("file:")) { this.path = Paths.get(URI.create(resourcePath)); this.name = path.getFileName().toString(); @@ -119,7 +122,7 @@ public class FileResourceHandler extends AbstractResourceHandler { this.name = normalizedPath; this.path = httpServerContext.resolve(webRoot).resolve(normalizedPath); } - this.contentType = mimeTypeService.getContentType(resourcePath); + this.mimeType = mimeTypeService.getContentType(resourcePath); this.url = URL.create(path.toUri().toString()); this.baseName = basename(name); this.suffix = suffix(name); @@ -136,7 +139,7 @@ public class FileResourceHandler extends AbstractResourceHandler { this.length = Files.size(path); httpServerContext.done(); } else { - this.lastModified = Instant.now(); + this.lastModified = Instant.ofEpochMilli(0L); this.length = 0; } } @@ -183,7 +186,7 @@ public class FileResourceHandler extends AbstractResourceHandler { @Override public String getMimeType() { - return contentType; + return mimeType; } @Override @@ -210,7 +213,9 @@ public class FileResourceHandler extends AbstractResourceHandler { public String toString() { return "[FileResource:resourcePath=" + resourcePath + ",url=" + url + + ",mimeType=" + mimeType + ",lastmodified=" + lastModified + + ",isexists=" + isExists + ",length=" + length + ",isDirectory=" + isDirectory() + "]"; } diff --git a/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/DefaultMarkupTemplate.java b/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/DefaultMarkupTemplate.java index aab119f..9338b94 100644 --- a/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/DefaultMarkupTemplate.java +++ b/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/DefaultMarkupTemplate.java @@ -165,75 +165,48 @@ public abstract class DefaultMarkupTemplate extends BaseTemplate { return ZonedDateTime.now().format(formatter); } + public String longDateTimeNow() { + DateTimeFormatter formatter = DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.LONG) + .withLocale(application.getLocale()) + .withZone(application.getZoneId()); + return ZonedDateTime.now().format(formatter); + } + + public String mediumDateTimeNow() { + DateTimeFormatter formatter = DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM) + .withLocale(application.getLocale()) + .withZone(application.getZoneId()); + return ZonedDateTime.now().format(formatter); + } + + public String shortDateTimeNow() { + DateTimeFormatter formatter = DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT) + .withLocale(application.getLocale()) + .withZone(application.getZoneId()); + return ZonedDateTime.now().format(formatter); + } + public String bootstrapCss() { - return contextPath("webjars/bootstrap/3.4.1/dist/css/bootstrap.min.css"); + return contextPath("webjars/bootstrap/5.2.2/dist/css/bootstrap.min.css"); } public String bootstrapJs() { - return contextPath("webjars/bootstrap/3.4.1/dist/js/bootstrap.min.js"); + return contextPath("webjars/bootstrap/5.2.2/dist/js/bootstrap.min.js"); } public String jqueryJs() { - return contextPath("webjars/jquery/3.5.1/dist/jquery.min.js"); + return contextPath("webjars/jquery/3.6.3/dist/jquery.min.js"); } public String fontawesomeCss() { - return contextPath("webjars/font-awesome/5.14.0/css/all.min.css"); + return contextPath("webjars/font-awesome/6.3.0/css/all.min.css"); } public String fontawesomeJs() { - return contextPath("webjars/font-awesome/5.14.0/js/all.min.js"); + return contextPath("webjars/font-awesome/6.3.0/js/all.min.js"); } - public String popperJs() { - return contextPath("webjars/popper.js/1.14.4/umd/popper.min.js"); - } - - public String fileinputCss() { - return contextPath("webjars/bootstrap-fileinput/4.4.8/css/fileinput.min.css"); - } - - public String fileinputJs() { - return contextPath("webjars/bootstrap-fileinput/4.4.8/js/fileinput.min.js"); - } - - public String fileinputLocale(String locale) { - return contextPath("webjars/bootstrap-fileinput/4.4.8/js/locales/" + locale + ".js"); - } - - public String fileinputTheme(String theme) { - return contextPath("webjars/bootstrap-fileinput/4.4.8/themes/" + theme + "/theme.min.js"); - } - - public String datatablesCss() { - return contextPath("webjars/datatables/1.10.19/css/jquery.dataTables.min.css"); - } - - public String datatablesJs() { - return contextPath("webjars/datatables/1.10.19/js/jquery.dataTables.min.js"); - } - - public String bootstrapTableCss() { - return contextPath("webjars/bootstrap-table/1.15.4/dist/bootstrap-table.min.css"); - } - - public String bootstrapTableLocale(String locale) { - return contextPath("webjars/bootstrap-table/1.15.4/dist/locale/bootstrap-table-${locale}.min.js"); - } - - public String bootstrapTableJs() { - return contextPath("webjars//bootstrap-table/1.15.4/dist/bootstrap-table.min.js"); - } - - public String bootstrapTableAutoRefreshJs() { - return contextPath("webjars/bootstrap-table/1.15.4/dist/extensions/auto-refresh/bootstrap-table-auto-refresh.min.js"); - } - - public String bootstrapHoverDropdownJs() { - return contextPath("webjars/bootstrap-hover-dropdown/2.2.1/bootstrap-hover-dropdown.min.js"); - } - - public String bootstrapValidatorJs() { - return contextPath("webjars/bootstrap-validator/0.11.9/js/validator.js"); - } } diff --git a/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/GroovyTemplateApplicationModule.java b/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/GroovyTemplateApplicationModule.java index ac53bde..85e9234 100644 --- a/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/GroovyTemplateApplicationModule.java +++ b/net-http-template-groovy/src/main/java/org/xbib/net/http/template/groovy/GroovyTemplateApplicationModule.java @@ -1,5 +1,6 @@ package org.xbib.net.http.template.groovy; +import groovy.text.markup.BaseTemplate; import org.xbib.net.http.server.BaseApplicationModule; import org.xbib.net.http.server.Application; import org.xbib.net.http.server.HttpRequest; @@ -7,11 +8,16 @@ import org.xbib.net.http.server.HttpServerContext; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.xbib.net.http.server.HttpService; import org.xbib.settings.Settings; public class GroovyTemplateApplicationModule extends BaseApplicationModule { + private static final Logger logger = Logger.getLogger(GroovyTemplateApplicationModule.class.getName()); + private GroovyMarkupTemplateHandler groovyMarkupTemplateHandler; private GroovyTemplateRenderer groovyTemplateRenderer; @@ -22,8 +28,29 @@ public class GroovyTemplateApplicationModule extends BaseApplicationModule { @Override public void onOpen(Application application, Settings settings) { - this.groovyMarkupTemplateHandler = new GroovyMarkupTemplateHandler(application); - this.groovyTemplateRenderer = new GroovyTemplateRenderer(); + ClassLoader classLoader = GroovyMarkupTemplateHandler.class.getClassLoader(); + String defaultMarkupTemplate = settings.get("markup.templateClass", + "org.xbib.net.http.template.DefaultMarkupTemplate"); + try { + @SuppressWarnings("unchecked") + Class defaultMarkupTemplateClass = + (Class) Class.forName(defaultMarkupTemplate, true, classLoader); + logger.log(Level.INFO, "markup template class = " + defaultMarkupTemplateClass.getName()); + this.groovyMarkupTemplateHandler = new GroovyMarkupTemplateHandler(application, + classLoader, defaultMarkupTemplateClass, application.getLocale(), + settings.getAsBoolean("markup.autoEscape", true), + settings.getAsBoolean("markup.autoIndent", false), + settings.get("markup.autoIndentString", " "), + settings.getAsBoolean("markup.autoNewline", false), + settings.getAsBoolean("markup.cacheTemplates", true), + settings.get("markup.declarationEncoding", null), + settings.getAsBoolean("markup.expandEmptyElements", true), + settings.get("markup.newLine", System.getProperty("line.separator")), + settings.getAsBoolean("markup.useDoubleQuotes", true)); + this.groovyTemplateRenderer = new GroovyTemplateRenderer(); + } catch (Exception e) { + throw new IllegalStateException(e); + } } @Override diff --git a/settings.gradle b/settings.gradle index 06df63a..164560c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,7 +3,7 @@ dependencyResolutionManagement { libs { version('gradle', '7.5.1') version('junit', '5.9.2') - version('groovy', '4.0.7') + version('groovy', '4.0.8') version('netty', '4.1.89.Final') version('netty-tcnative', '2.0.59.Final') version('datastructures', '2.0.0') @@ -14,10 +14,6 @@ dependencyResolutionManagement { library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit') library('junit4', 'junit', 'junit').version('4.13.2') library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2') - 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') - library('net-bouncycastle', 'org.xbib', 'net-bouncycastle').versionRef('net') library('netty-codec-http2', 'io.netty', 'netty-codec-http2').versionRef('netty') library('netty-handler', 'io.netty', 'netty-handler').versionRef('netty') library('netty-handler-proxy', 'io.netty', 'netty-handler-proxy').versionRef('netty') @@ -29,10 +25,14 @@ dependencyResolutionManagement { library('jackson', 'com.fasterxml.jackson.core', 'jackson-databind').version('2.12.7') library('jna', 'net.java.dev.jna', 'jna').version('5.12.1') library('oracle', 'com.oracle.database.jdbc', 'ojdbc11').version('21.7.0.0') - library('webjars-bootstrap', 'org.webjars.bower', 'bootstrap').version('3.4.1') - library('webjars-jquery', 'org.webjars.bower', 'jquery').version('3.5.1') - library('webjars-fontawesome', 'org.webjars', 'font-awesome').version('5.14.0') library('groovy-templates', 'org.apache.groovy', 'groovy-templates').versionRef('groovy') + library('webjars-bootstrap', 'org.webjars.bower', 'bootstrap').version('5.2.2') + library('webjars-jquery', 'org.webjars.bower', 'jquery').version('3.6.3') + library('webjars-fontawesome', 'org.webjars', 'font-awesome').version('6.3.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') + library('net-bouncycastle', 'org.xbib', 'net-bouncycastle').versionRef('net') library('datastructures-common', 'org.xbib', 'datastructures-common').versionRef('datastructures') library('datastructures-json-tiny', 'org.xbib', 'datastructures-json-tiny').versionRef('datastructures') library('datastructures-yaml-tiny', 'org.xbib', 'datastructures-yaml-tiny').versionRef('datastructures')