new bootstrap, allow custom Groovy Default Markup Template class

This commit is contained in:
Jörg Prante 2023-03-10 17:16:54 +01:00
parent 760e83c035
commit 21f8a03cd5
8 changed files with 111 additions and 97 deletions

View file

@ -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);
}
}

View file

@ -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<String, String> e : headers.entries()) {
sb.append(e.getKey().toLowerCase(Locale.ROOT)).append(COLON).append(SPACE).append(e.getValue()).append(CRLF);

View file

@ -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")) {

View file

@ -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() + "]";

View file

@ -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() + "]";
}

View file

@ -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");
}
}

View file

@ -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<? extends BaseTemplate> defaultMarkupTemplateClass =
(Class<? extends BaseTemplate>) 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

View file

@ -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')