working on form requests and Groovy templates, update to xbib net 3.0.4
This commit is contained in:
parent
5803e57072
commit
1ea725cbc2
15 changed files with 231 additions and 158 deletions
|
@ -2,6 +2,8 @@ package org.xbib.net.http.server.application.web;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.xbib.net.http.cookie.SameSite;
|
||||||
import org.xbib.net.http.server.BaseApplication;
|
import org.xbib.net.http.server.BaseApplication;
|
||||||
import org.xbib.net.http.server.HttpHandler;
|
import org.xbib.net.http.server.HttpHandler;
|
||||||
import org.xbib.net.http.server.HttpServerContext;
|
import org.xbib.net.http.server.HttpServerContext;
|
||||||
|
@ -10,6 +12,7 @@ import org.xbib.net.http.server.session.IncomingSessionHandler;
|
||||||
import org.xbib.net.http.server.session.OutgoingSessionHandler;
|
import org.xbib.net.http.server.session.OutgoingSessionHandler;
|
||||||
import org.xbib.net.http.server.session.Session;
|
import org.xbib.net.http.server.session.Session;
|
||||||
import org.xbib.net.http.server.session.file.FileJsonSessionCodec;
|
import org.xbib.net.http.server.session.file.FileJsonSessionCodec;
|
||||||
|
import org.xbib.net.util.RandomUtil;
|
||||||
|
|
||||||
public class WebApplication extends BaseApplication {
|
public class WebApplication extends BaseApplication {
|
||||||
|
|
||||||
|
@ -40,7 +43,8 @@ public class WebApplication extends BaseApplication {
|
||||||
sessionCodec,
|
sessionCodec,
|
||||||
getStaticFileSuffixes(),
|
getStaticFileSuffixes(),
|
||||||
"user_id",
|
"user_id",
|
||||||
"e_user_id");
|
"e_user_id",
|
||||||
|
() -> RandomUtil.randomString(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OutgoingSessionHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
protected OutgoingSessionHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
||||||
|
@ -50,10 +54,13 @@ public class WebApplication extends BaseApplication {
|
||||||
getSecret(),
|
getSecret(),
|
||||||
"HmacSHA1",
|
"HmacSHA1",
|
||||||
sessionName,
|
sessionName,
|
||||||
Duration.ofDays(1),
|
|
||||||
sessionCodec,
|
sessionCodec,
|
||||||
getStaticFileSuffixes(),
|
getStaticFileSuffixes(),
|
||||||
"user_id",
|
"user_id",
|
||||||
"e_user_id");
|
"e_user_id",
|
||||||
|
Duration.ofDays(1),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
SameSite.LAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class HttpRequest extends BaseHttpRequest {
|
public class HttpRequest extends BaseHttpRequest {
|
||||||
|
@ -47,7 +48,10 @@ public class HttpRequest extends BaseHttpRequest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "HttpRequest[request=" + builder.fullHttpRequest + "]";
|
return "HttpRequest[request=" + builder.fullHttpRequest +
|
||||||
|
",parameter=" + builder.getParameter() +
|
||||||
|
",body=" + builder.fullHttpRequest.content().toString(StandardCharsets.UTF_8) +
|
||||||
|
"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBuf getByteBuf() {
|
public ByteBuf getByteBuf() {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.xbib.net.http.server.netty;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
|
import org.xbib.net.Parameter;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.net.http.HttpAddress;
|
import org.xbib.net.http.HttpAddress;
|
||||||
import org.xbib.net.http.HttpMethod;
|
import org.xbib.net.http.HttpMethod;
|
||||||
|
@ -24,7 +25,7 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
|
|
||||||
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
|
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
|
||||||
if (fullHttpRequest != null) {
|
if (fullHttpRequest != null) {
|
||||||
// retain request so we can read the body later without refCnt=0 error
|
// retain request, so we can read the body later without refCnt=0 error
|
||||||
this.fullHttpRequest = fullHttpRequest.retain();
|
this.fullHttpRequest = fullHttpRequest.retain();
|
||||||
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
|
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
|
||||||
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
||||||
|
@ -100,6 +101,10 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Parameter getParameter() {
|
||||||
|
return super.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest build() {
|
public HttpRequest build() {
|
||||||
return new HttpRequest(this);
|
return new HttpRequest(this);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.xbib.net.http.server;
|
package org.xbib.net.http.server;
|
||||||
|
|
||||||
|
import org.xbib.net.http.cookie.SameSite;
|
||||||
import org.xbib.net.http.server.route.HttpRouter;
|
import org.xbib.net.http.server.route.HttpRouter;
|
||||||
import org.xbib.net.http.HttpAddress;
|
import org.xbib.net.http.HttpAddress;
|
||||||
import org.xbib.net.http.HttpResponseStatus;
|
import org.xbib.net.http.HttpResponseStatus;
|
||||||
|
@ -14,6 +15,7 @@ import org.xbib.net.http.server.session.Session;
|
||||||
import org.xbib.net.http.server.util.BlockingThreadPoolExecutor;
|
import org.xbib.net.http.server.util.BlockingThreadPoolExecutor;
|
||||||
import org.xbib.net.http.server.validate.HttpRequestValidator;
|
import org.xbib.net.http.server.validate.HttpRequestValidator;
|
||||||
import org.xbib.net.util.NamedThreadFactory;
|
import org.xbib.net.util.NamedThreadFactory;
|
||||||
|
import org.xbib.net.util.RandomUtil;
|
||||||
import org.xbib.settings.Settings;
|
import org.xbib.settings.Settings;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
@ -194,7 +196,8 @@ public class BaseApplication implements Application {
|
||||||
sessionCodec,
|
sessionCodec,
|
||||||
getStaticFileSuffixes(),
|
getStaticFileSuffixes(),
|
||||||
"user_id",
|
"user_id",
|
||||||
"e_user_id");
|
"e_user_id",
|
||||||
|
() -> RandomUtil.randomString(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
protected HttpHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
||||||
|
@ -204,11 +207,15 @@ public class BaseApplication implements Application {
|
||||||
getSecret(),
|
getSecret(),
|
||||||
"HmacSHA1",
|
"HmacSHA1",
|
||||||
sessionName,
|
sessionName,
|
||||||
Duration.ofDays(1),
|
|
||||||
sessionCodec,
|
sessionCodec,
|
||||||
getStaticFileSuffixes(),
|
getStaticFileSuffixes(),
|
||||||
"user_id",
|
"user_id",
|
||||||
"e_user_id");
|
"e_user_id",
|
||||||
|
Duration.ofDays(1),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
SameSite.LAX
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpResponseRenderer buildResponseRenderer() {
|
protected HttpResponseRenderer buildResponseRenderer() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.xbib.net.http.server;
|
package org.xbib.net.http.server;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import org.xbib.net.Attributes;
|
import org.xbib.net.Attributes;
|
||||||
import org.xbib.net.Parameter;
|
import org.xbib.net.Parameter;
|
||||||
import org.xbib.net.ParameterBuilder;
|
import org.xbib.net.ParameterBuilder;
|
||||||
|
@ -23,11 +22,15 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.charset.UnsupportedCharsetException;
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE;
|
import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE;
|
||||||
|
|
||||||
public class BaseHttpServerContext implements HttpServerContext {
|
public class BaseHttpServerContext implements HttpServerContext {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(BaseHttpServerContext.class.getName());
|
||||||
|
|
||||||
private static final String PATH_SEPARATOR = "/";
|
private static final String PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
private final Application application;
|
private final Application application;
|
||||||
|
@ -84,16 +87,26 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
@Override
|
@Override
|
||||||
public void setResolverResult(HttpRouteResolver.Result<HttpService> pathResolverResult) {
|
public void setResolverResult(HttpRouteResolver.Result<HttpService> pathResolverResult) {
|
||||||
this.pathResolverResult = pathResolverResult;
|
this.pathResolverResult = pathResolverResult;
|
||||||
this.attributes.put("context", pathResolverResult.getContext());
|
if (pathResolverResult != null) {
|
||||||
this.attributes.put("handler", pathResolverResult.getValue());
|
attributes.put("context", pathResolverResult.getContext());
|
||||||
this.attributes.put("pathparams", pathResolverResult.getParameter());
|
attributes.put("handler", pathResolverResult.getValue());
|
||||||
|
attributes.put("pathparams", pathResolverResult.getParameter());
|
||||||
String contextPath = pathResolverResult.getContext() != null ?
|
String contextPath = pathResolverResult.getContext() != null ?
|
||||||
PATH_SEPARATOR + String.join(PATH_SEPARATOR, pathResolverResult.getContext()) : null;
|
PATH_SEPARATOR + String.join(PATH_SEPARATOR, pathResolverResult.getContext()) : null;
|
||||||
setContextPath(contextPath);
|
setContextPath(contextPath);
|
||||||
setContextURL(request().getBaseURL().resolve(contextPath != null ? contextPath + "/" : ""));
|
setContextURL(request().getBaseURL().resolve(contextPath != null ? contextPath + "/" : ""));
|
||||||
this.httpRequest = createRequest(httpRequestBuilder);
|
} else {
|
||||||
this.attributes.put("request", httpRequest);
|
// path resolver result null means "404 not found". Set default values.
|
||||||
this.next = false;
|
attributes.put("context", null);
|
||||||
|
attributes.put("handler", null);
|
||||||
|
attributes.put("pathparams", null);
|
||||||
|
setContextPath(PATH_SEPARATOR);
|
||||||
|
setContextURL(request().getBaseURL());
|
||||||
|
}
|
||||||
|
httpRequest = createRequest();
|
||||||
|
logger.log(Level.FINER, "request = " + httpRequest);
|
||||||
|
attributes.put("request", httpRequest);
|
||||||
|
next = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContextPath(String contextPath) {
|
public void setContextPath(String contextPath) {
|
||||||
|
@ -114,6 +127,7 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
return contextURL;
|
return contextURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Path resolve(String path) {
|
public Path resolve(String path) {
|
||||||
return application.resolve(path);
|
return application.resolve(path);
|
||||||
}
|
}
|
||||||
|
@ -187,8 +201,8 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
httpResponseBuilder.write(fileChannel, bufferSize);
|
httpResponseBuilder.write(fileChannel, bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpRequest createRequest(HttpRequestBuilder requestBuilder) {
|
protected HttpRequest createRequest() {
|
||||||
HttpHeaders headers = requestBuilder.getHeaders();
|
HttpHeaders headers = httpRequestBuilder.getHeaders();
|
||||||
String mimeType = headers.get(CONTENT_TYPE);
|
String mimeType = headers.get(CONTENT_TYPE);
|
||||||
Charset charset = StandardCharsets.UTF_8;
|
Charset charset = StandardCharsets.UTF_8;
|
||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
|
@ -198,30 +212,40 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
// helper URL to collect parameters in request URI
|
// helper URL to collect parameters in request URI
|
||||||
URL url = URL.builder()
|
URL url = URL.builder()
|
||||||
.charset(charset, CodingErrorAction.REPLACE)
|
.charset(charset, CodingErrorAction.REPLACE)
|
||||||
.path(requestBuilder.getRequestURI())
|
.path(httpRequestBuilder.getRequestURI())
|
||||||
.build();
|
.build();
|
||||||
ParameterBuilder formParameters = Parameter.builder().domain("FORM");
|
ParameterBuilder formParameterBuilder = Parameter.builder().domain("FORM");
|
||||||
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
|
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
|
||||||
if (HttpMethod.POST.equals(requestBuilder.getMethod()) &&
|
if (HttpMethod.POST.equals(httpRequestBuilder.getMethod()) &&
|
||||||
(mimeType != null && mimeType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED))) {
|
(mimeType != null && mimeType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED))) {
|
||||||
Charset htmlCharset = getCharset(mimeType, StandardCharsets.ISO_8859_1);
|
Charset htmlCharset = getCharset(mimeType, StandardCharsets.ISO_8859_1);
|
||||||
CharBuffer charBuffer = requestBuilder.getBodyAsChars(htmlCharset);
|
CharBuffer charBuffer = httpRequestBuilder.getBodyAsChars(htmlCharset);
|
||||||
if (charBuffer != null) {
|
if (charBuffer != null) {
|
||||||
formParameters.addPercentEncodedBody(charBuffer.toString());
|
formParameterBuilder.addPercentEncodedBody(charBuffer.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CookieBox cookieBox = attributes.get(CookieBox.class, "incomingcookies");
|
CookieBox cookieBox = attributes.get(CookieBox.class, "incomingcookies");
|
||||||
ParameterBuilder cookieParameters = Parameter.builder().domain("COOKIE");
|
ParameterBuilder cookieParameterBuilder = Parameter.builder().domain("COOKIE");
|
||||||
if (cookieBox != null) {
|
if (cookieBox != null) {
|
||||||
cookieBox.forEach(c -> cookieParameters.add(c.name(), c.value()));
|
cookieBox.forEach(c -> cookieParameterBuilder.add(c.name(), c.value()));
|
||||||
}
|
}
|
||||||
parameterBuilder.add(url.getQueryParams());
|
Parameter queryParameter = url.getQueryParams();
|
||||||
parameterBuilder.add(formParameters.build());
|
logger.log(Level.FINER, "adding query parameters = " + queryParameter.getDomain() + " " + queryParameter.allToString());
|
||||||
parameterBuilder.add(cookieParameters.build());
|
parameterBuilder.add(queryParameter);
|
||||||
parameterBuilder.add(pathResolverResult.getParameter());
|
Parameter formParameter = formParameterBuilder.build();
|
||||||
requestBuilder.setParameter(parameterBuilder.build());
|
logger.log(Level.FINER, "adding form parameters = " + formParameter.getDomain() + " " + formParameter.allToString());
|
||||||
requestBuilder.setContext(this);
|
parameterBuilder.add(formParameter);
|
||||||
return requestBuilder.build();
|
Parameter cookieParameter = cookieParameterBuilder.build();
|
||||||
|
logger.log(Level.FINER, "adding cookie parameters = " + cookieParameter.getDomain() + " " + cookieParameter.allToString());
|
||||||
|
parameterBuilder.add(cookieParameter);
|
||||||
|
if (pathResolverResult != null) {
|
||||||
|
Parameter pathParameter = pathResolverResult.getParameter();
|
||||||
|
logger.log(Level.FINER, "adding path parameters = " + pathParameter.getDomain() + " " + pathParameter.allToString());
|
||||||
|
parameterBuilder.add(pathParameter);
|
||||||
|
}
|
||||||
|
httpRequestBuilder.setParameter(parameterBuilder.build());
|
||||||
|
httpRequestBuilder.setContext(this);
|
||||||
|
return httpRequestBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Charset getCharset(String contentTypeValue, Charset defaultCharset) {
|
private static Charset getCharset(String contentTypeValue, Charset defaultCharset) {
|
||||||
|
|
|
@ -39,34 +39,37 @@ public class HtmlTemplateResource implements HttpServerResource {
|
||||||
|
|
||||||
private final String suffix;
|
private final String suffix;
|
||||||
|
|
||||||
|
protected final boolean negotiateLocale;
|
||||||
|
|
||||||
protected HtmlTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
protected HtmlTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
||||||
HttpServerContext httpServerContext) throws IOException {
|
HttpServerContext httpServerContext) throws IOException {
|
||||||
this.templateResourceHandler = templateResourceHandler;
|
this.templateResourceHandler = templateResourceHandler;
|
||||||
String indexFileName = templateResourceHandler.getIndexFileName();
|
String indexFileName = templateResourceHandler.getIndexFileName();
|
||||||
Application application = httpServerContext.attributes().get(Application.class, "application");
|
Application application = httpServerContext.attributes().get(Application.class, "application");
|
||||||
|
this.negotiateLocale = application.getSettings().getAsBoolean("negotiateLocale", false);
|
||||||
Path root = templateResourceHandler.getPrefix();
|
Path root = templateResourceHandler.getPrefix();
|
||||||
root = root != null ? root : application.getHome();
|
root = root != null ? root : application.getHome();
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
throw new IllegalArgumentException("no home path set for template resource resolving");
|
throw new IllegalArgumentException("no home path set for template resource resolving");
|
||||||
}
|
}
|
||||||
logger.log(Level.FINE, "class = " + getClass().getName());
|
|
||||||
logger.log(Level.FINE, "root = " + root);
|
|
||||||
this.resourcePath = httpServerContext.request().getRequestPath().substring(1);
|
this.resourcePath = httpServerContext.request().getRequestPath().substring(1);
|
||||||
logger.log(Level.FINE, "resource path = " + resourcePath);
|
|
||||||
this.path = resourcePath.length() > 0 ? root.resolve(resourcePath) : root;
|
this.path = resourcePath.length() > 0 ? root.resolve(resourcePath) : root;
|
||||||
logger.log(Level.FINE, "path = " + path);
|
logger.log(Level.FINER, "class = " + getClass().getName() +
|
||||||
logger.log(Level.FINE, "index file name = " + indexFileName);
|
" root = " + root +
|
||||||
|
" resource path = " + resourcePath +
|
||||||
|
" path = " + path +
|
||||||
|
" index file name = " + indexFileName);
|
||||||
this.name = path.getFileName().toString();
|
this.name = path.getFileName().toString();
|
||||||
this.baseName = AbstractResourceHandler.basename(name);
|
this.baseName = AbstractResourceHandler.basename(name);
|
||||||
this.suffix = AbstractResourceHandler.suffix(name);
|
this.suffix = AbstractResourceHandler.suffix(name);
|
||||||
if (Files.isDirectory(path)) {
|
if (Files.isDirectory(path)) {
|
||||||
if (getIndexFileName() != null) {
|
if (getIndexFileName() != null) {
|
||||||
Path indexPath = path.resolve(indexFileName);
|
Path indexPath = path.resolve(indexFileName);
|
||||||
logger.log(Level.FINE, "index path = " + indexPath);
|
logger.log(Level.FINE, "resolved to index path = " + indexPath);
|
||||||
if (Files.exists(indexPath)) {
|
if (Files.exists(indexPath)) {
|
||||||
|
logger.log(Level.FINE, "index path exists");
|
||||||
this.isExistsIndexFile = true;
|
this.isExistsIndexFile = true;
|
||||||
this.path = indexPath;
|
this.path = indexPath;
|
||||||
logger.log(Level.FINE, "index file path found = " + path);
|
|
||||||
this.isDirectory = false;
|
this.isDirectory = false;
|
||||||
} else {
|
} else {
|
||||||
this.isExistsIndexFile = false;
|
this.isExistsIndexFile = false;
|
||||||
|
@ -81,15 +84,9 @@ public class HtmlTemplateResource implements HttpServerResource {
|
||||||
this.isDirectory = false;
|
this.isDirectory = false;
|
||||||
}
|
}
|
||||||
this.isExists = Files.exists(path);
|
this.isExists = Files.exists(path);
|
||||||
logger.log(Level.FINE, "exists = " + isExists);
|
|
||||||
logger.log(Level.FINE, "isDirectory = " + isDirectory);
|
|
||||||
this.url = URL.create(path.toUri().toString());
|
this.url = URL.create(path.toUri().toString());
|
||||||
logger.log(Level.FINE, "url = " + url);
|
logger.log(Level.FINE, "isExists = " + isExists + " isDirectory = " + isDirectory + " url = " + url);
|
||||||
if (isExists) {
|
this.lastModified = isExists ? Files.getLastModifiedTime(path).toInstant() : Instant.now();
|
||||||
this.lastModified = Files.getLastModifiedTime(path).toInstant();
|
|
||||||
} else {
|
|
||||||
this.lastModified = Instant.now();
|
|
||||||
}
|
|
||||||
// length will be computed at rendering time
|
// length will be computed at rendering time
|
||||||
this.length = -1;
|
this.length = -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,9 @@ public class BaseHttpRoute implements HttpRoute {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(ParameterBuilder parameterBuilder, HttpRoute requestedRoute) {
|
public boolean matches(ParameterBuilder parameterBuilder, HttpRoute requestedRoute) {
|
||||||
if (!(requestedRoute instanceof BaseHttpRoute)) {
|
if (!(requestedRoute instanceof BaseHttpRoute baseHttpRoute)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BaseHttpRoute baseHttpRoute = (BaseHttpRoute) requestedRoute;
|
|
||||||
if (!httpAddress.equals(baseHttpRoute.getHttpAddress())) {
|
if (!httpAddress.equals(baseHttpRoute.getHttpAddress())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class BaseHttpRouteResolver<T> implements HttpRouteResolver<T> {
|
||||||
@Override
|
@Override
|
||||||
public void resolve(HttpRoute httpRoute, ResultListener<T> listener) {
|
public void resolve(HttpRoute httpRoute, ResultListener<T> listener) {
|
||||||
for (Map.Entry<HttpRoute, T> entry : builder.routes) {
|
for (Map.Entry<HttpRoute, T> entry : builder.routes) {
|
||||||
ParameterBuilder parameterBuilder = Parameter.builder();
|
ParameterBuilder parameterBuilder = Parameter.builder().domain("PATH");
|
||||||
boolean match = entry.getKey().matches(parameterBuilder, httpRoute);
|
boolean match = entry.getKey().matches(parameterBuilder, httpRoute);
|
||||||
if (match && listener != null) {
|
if (match && listener != null) {
|
||||||
List<String> list = Arrays.stream(httpRoute.getPath().replaceFirst(builder.prefix, "").split("/"))
|
List<String> list = Arrays.stream(httpRoute.getPath().replaceFirst(builder.prefix, "").split("/"))
|
||||||
|
|
|
@ -20,10 +20,12 @@ import org.xbib.net.http.server.Application;
|
||||||
import org.xbib.net.http.server.HttpDomain;
|
import org.xbib.net.http.server.HttpDomain;
|
||||||
import org.xbib.net.http.server.HttpException;
|
import org.xbib.net.http.server.HttpException;
|
||||||
import org.xbib.net.http.server.HttpHandler;
|
import org.xbib.net.http.server.HttpHandler;
|
||||||
|
import org.xbib.net.http.server.HttpRequest;
|
||||||
import org.xbib.net.http.server.HttpRequestBuilder;
|
import org.xbib.net.http.server.HttpRequestBuilder;
|
||||||
import org.xbib.net.http.server.HttpResponseBuilder;
|
import org.xbib.net.http.server.HttpResponseBuilder;
|
||||||
import org.xbib.net.http.server.HttpServerContext;
|
import org.xbib.net.http.server.HttpServerContext;
|
||||||
import org.xbib.net.http.server.HttpService;
|
import org.xbib.net.http.server.HttpService;
|
||||||
|
import org.xbib.net.http.server.handler.InternalServerErrorHandler;
|
||||||
|
|
||||||
import static org.xbib.net.http.HttpResponseStatus.NOT_FOUND;
|
import static org.xbib.net.http.HttpResponseStatus.NOT_FOUND;
|
||||||
|
|
||||||
|
@ -105,6 +107,7 @@ public class BaseHttpRouter implements HttpRouter {
|
||||||
}
|
}
|
||||||
if (httpRouteResolverResults.isEmpty()) {
|
if (httpRouteResolverResults.isEmpty()) {
|
||||||
logger.log(Level.FINE, "route resolver results is empty, generating a not found message");
|
logger.log(Level.FINE, "route resolver results is empty, generating a not found message");
|
||||||
|
httpServerContext.setResolverResult(null);
|
||||||
routeStatus(NOT_FOUND, httpServerContext);
|
routeStatus(NOT_FOUND, httpServerContext);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +116,8 @@ public class BaseHttpRouter implements HttpRouter {
|
||||||
// first: create the final request
|
// first: create the final request
|
||||||
httpServerContext.setResolverResult(httpRouteResolverResult);
|
httpServerContext.setResolverResult(httpRouteResolverResult);
|
||||||
HttpService httpService = httpRouteResolverResult.getValue();
|
HttpService httpService = httpRouteResolverResult.getValue();
|
||||||
application.getModules().forEach(module -> module.onOpen(application, httpServerContext, httpService, httpServerContext.httpRequest()));
|
HttpRequest httpRequest = httpServerContext.httpRequest();
|
||||||
|
application.getModules().forEach(module -> module.onOpen(application, httpServerContext, httpService, httpRequest));
|
||||||
// second: security check, authentication etc.
|
// second: security check, authentication etc.
|
||||||
if (httpService.getSecurityDomain() != null) {
|
if (httpService.getSecurityDomain() != null) {
|
||||||
logger.log(Level.FINEST, () -> "handling security domain service " + httpService);
|
logger.log(Level.FINEST, () -> "handling security domain service " + httpService);
|
||||||
|
@ -125,11 +129,12 @@ public class BaseHttpRouter implements HttpRouter {
|
||||||
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// accept service and execute service
|
// after security checks, accept service, open and execute service
|
||||||
httpServerContext.attributes().put("service", httpService);
|
httpServerContext.attributes().put("service", httpService);
|
||||||
application.getModules().forEach(module -> module.onOpen(application, httpServerContext, httpService));
|
application.getModules().forEach(module -> module.onOpen(application, httpServerContext, httpService));
|
||||||
logger.log(Level.FINEST, () -> "handling service " + httpService);
|
logger.log(Level.FINEST, () -> "handling service " + httpService);
|
||||||
httpService.handle(httpServerContext);
|
httpService.handle(httpServerContext);
|
||||||
|
// if service signals that work is done, break
|
||||||
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -155,10 +160,17 @@ public class BaseHttpRouter implements HttpRouter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void routeStatus(HttpResponseStatus httpResponseStatus, HttpServerContext httpServerContext) {
|
public void routeStatus(HttpResponseStatus httpResponseStatus, HttpServerContext httpServerContext) {
|
||||||
|
logger.log(Level.FINER, "routing status " + httpResponseStatus);
|
||||||
try {
|
try {
|
||||||
HttpHandler httpHandler = getHandler(httpResponseStatus);
|
HttpHandler httpHandler = getHandler(httpResponseStatus);
|
||||||
|
if (httpHandler == null) {
|
||||||
|
logger.log(Level.FINER, "handler for " + httpResponseStatus + " not present, using default error handler");
|
||||||
|
httpHandler = new InternalServerErrorHandler();
|
||||||
|
}
|
||||||
httpServerContext.response().reset();
|
httpServerContext.response().reset();
|
||||||
httpHandler.handle(httpServerContext);
|
httpHandler.handle(httpServerContext);
|
||||||
|
httpServerContext.done();
|
||||||
|
logger.log(Level.FINER, "routing status " + httpResponseStatus + " done");
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new IllegalStateException("unable to route response status, reason: " + ioe.getMessage(), ioe);
|
throw new IllegalStateException("unable to route response status, reason: " + ioe.getMessage(), ioe);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,9 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.xbib.net.util.RandomUtil;
|
|
||||||
|
|
||||||
public class IncomingSessionHandler implements HttpHandler {
|
public class IncomingSessionHandler implements HttpHandler {
|
||||||
|
|
||||||
|
@ -46,13 +46,16 @@ public class IncomingSessionHandler implements HttpHandler {
|
||||||
*/
|
*/
|
||||||
private final Set<String> suffixes;
|
private final Set<String> suffixes;
|
||||||
|
|
||||||
|
Supplier<String> sessionIdGenerator;
|
||||||
|
|
||||||
public IncomingSessionHandler(String sessionSecret,
|
public IncomingSessionHandler(String sessionSecret,
|
||||||
String sessionCookieAlgorithm,
|
String sessionCookieAlgorithm,
|
||||||
String sessionCookieName,
|
String sessionCookieName,
|
||||||
Codec<Session> sessionCodec,
|
Codec<Session> sessionCodec,
|
||||||
Set<String> suffixes,
|
Set<String> suffixes,
|
||||||
String sessionUserName,
|
String sessionUserName,
|
||||||
String sessionEffectiveUserName) {
|
String sessionEffectiveUserName,
|
||||||
|
Supplier<String> sessionIdGenerator) {
|
||||||
this.sessionSecret = sessionSecret;
|
this.sessionSecret = sessionSecret;
|
||||||
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
||||||
this.sessionCookieName = sessionCookieName;
|
this.sessionCookieName = sessionCookieName;
|
||||||
|
@ -60,6 +63,7 @@ public class IncomingSessionHandler implements HttpHandler {
|
||||||
this.suffixes = suffixes;
|
this.suffixes = suffixes;
|
||||||
this.sessionUserName = sessionUserName;
|
this.sessionUserName = sessionUserName;
|
||||||
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
||||||
|
this.sessionIdGenerator = sessionIdGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,6 +80,7 @@ public class IncomingSessionHandler implements HttpHandler {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> payload = decodeCookie(cookie);
|
Map<String, Object> payload = decodeCookie(cookie);
|
||||||
|
logger.log(Level.FINER, "cookie decoded");
|
||||||
session = toSession(payload);
|
session = toSession(payload);
|
||||||
UserProfile userProfile = newUserProfile(payload, session);
|
UserProfile userProfile = newUserProfile(payload, session);
|
||||||
if (userProfile != null) {
|
if (userProfile != null) {
|
||||||
|
@ -96,14 +101,14 @@ public class IncomingSessionHandler implements HttpHandler {
|
||||||
}
|
}
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
try {
|
try {
|
||||||
session = sessionCodec.create(RandomUtil.randomString(32));
|
session = sessionCodec.create(sessionIdGenerator.get());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
throw new HttpException("unable to create session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
throw new HttpException("unable to create session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.attributes().put("session", session);
|
context.attributes().put("session", session);
|
||||||
|
logger.log(Level.FINER, "incoming session " + session.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> decodeCookie(Cookie cookie) throws IOException,
|
private Map<String, Object> decodeCookie(Cookie cookie) throws IOException,
|
||||||
|
|
|
@ -47,22 +47,34 @@ public class OutgoingSessionHandler implements HttpHandler {
|
||||||
|
|
||||||
private final String sessionEffectiveUserName;
|
private final String sessionEffectiveUserName;
|
||||||
|
|
||||||
|
private final boolean httpOnly;
|
||||||
|
|
||||||
|
private final boolean secure;
|
||||||
|
|
||||||
|
private final SameSite sameSite;
|
||||||
|
|
||||||
public OutgoingSessionHandler(String sessionSecret,
|
public OutgoingSessionHandler(String sessionSecret,
|
||||||
String sessionCookieAlgorithm,
|
String sessionCookieAlgorithm,
|
||||||
String sessionCookieName,
|
String sessionCookieName,
|
||||||
Duration sessionDuration,
|
|
||||||
Codec<Session> sessionCodec,
|
Codec<Session> sessionCodec,
|
||||||
Set<String> suffixes,
|
Set<String> suffixes,
|
||||||
String sessionUserName,
|
String sessionUserName,
|
||||||
String sessionEffectiveUserName) {
|
String sessionEffectiveUserName,
|
||||||
|
Duration sessionDuration,
|
||||||
|
boolean httpOnly,
|
||||||
|
boolean secure,
|
||||||
|
SameSite sameSite) {
|
||||||
this.sessionSecret = sessionSecret;
|
this.sessionSecret = sessionSecret;
|
||||||
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
||||||
this.sessionCookieName = sessionCookieName;
|
this.sessionCookieName = sessionCookieName;
|
||||||
this.sessionDuration = sessionDuration;
|
|
||||||
this.sessionCodec = sessionCodec;
|
this.sessionCodec = sessionCodec;
|
||||||
this.suffixes = suffixes;
|
this.suffixes = suffixes;
|
||||||
this.sessionUserName = sessionUserName;
|
this.sessionUserName = sessionUserName;
|
||||||
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
||||||
|
this.sessionDuration = sessionDuration;
|
||||||
|
this.httpOnly = httpOnly;
|
||||||
|
this.secure = secure;
|
||||||
|
this.sameSite = sameSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,28 +149,32 @@ public class OutgoingSessionHandler implements HttpHandler {
|
||||||
PercentEncoder percentEncoder = PercentEncoders.getCookieEncoder(StandardCharsets.ISO_8859_1);
|
PercentEncoder percentEncoder = PercentEncoders.getCookieEncoder(StandardCharsets.ISO_8859_1);
|
||||||
DefaultCookie cookie = new DefaultCookie(sessionCookieName, percentEncoder.encode(cookieValue));
|
DefaultCookie cookie = new DefaultCookie(sessionCookieName, percentEncoder.encode(cookieValue));
|
||||||
String domain = extractDomain(host);
|
String domain = extractDomain(host);
|
||||||
if (!"localhost".equals(domain)) {
|
if ("localhost".equals(domain)) {
|
||||||
|
logger.log(Level.WARNING, "localhost not set as a cookie domain");
|
||||||
|
} else {
|
||||||
cookie.setDomain('.' + domain);
|
cookie.setDomain('.' + domain);
|
||||||
}
|
}
|
||||||
cookie.setPath(path);
|
cookie.setPath(path);
|
||||||
cookie.setMaxAge(sessionDuration.toSeconds());
|
cookie.setMaxAge(sessionDuration.toSeconds());
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(httpOnly);
|
||||||
cookie.setSecure(true);
|
cookie.setSecure(secure);
|
||||||
cookie.setSameSite(SameSite.LAX);
|
cookie.setSameSite(sameSite);
|
||||||
return cookie;
|
return cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cookie createEmptyCookie(String host, String path) {
|
private Cookie createEmptyCookie(String host, String path) {
|
||||||
DefaultCookie cookie = new DefaultCookie(sessionCookieName);
|
DefaultCookie cookie = new DefaultCookie(sessionCookieName);
|
||||||
String domain = extractDomain(host);
|
String domain = extractDomain(host);
|
||||||
if (!"localhost".equals(domain)) {
|
if ("localhost".equals(domain)) {
|
||||||
|
logger.log(Level.WARNING, "localhost not set as a cookie domain");
|
||||||
|
} else {
|
||||||
cookie.setDomain('.' + domain);
|
cookie.setDomain('.' + domain);
|
||||||
}
|
}
|
||||||
cookie.setPath(path);
|
cookie.setPath(path);
|
||||||
cookie.setMaxAge(0L);
|
cookie.setMaxAge(0L);
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(httpOnly);
|
||||||
cookie.setSecure(true);
|
cookie.setSecure(secure);
|
||||||
cookie.setSameSite(SameSite.LAX);
|
cookie.setSameSite(sameSite);
|
||||||
logger.log(Level.FINEST, "baked empty cookie");
|
logger.log(Level.FINEST, "baked empty cookie");
|
||||||
return cookie;
|
return cookie;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,28 +44,31 @@ public abstract class DefaultMarkupTemplate extends BaseTemplate {
|
||||||
super(templateEngine, model, modelTypes, configuration);
|
super(templateEngine, model, modelTypes, configuration);
|
||||||
this.application = (Application) model.get("application");
|
this.application = (Application) model.get("application");
|
||||||
Objects.requireNonNull(this.application, "application must not be null");
|
Objects.requireNonNull(this.application, "application must not be null");
|
||||||
this.session = (Session) model.get("session");
|
|
||||||
Objects.requireNonNull(this.session, "session must not be null");
|
|
||||||
this.request = (HttpRequest) model.get("request");
|
|
||||||
Objects.requireNonNull(this.request, "request must not be null");
|
|
||||||
this.responseBuilder = (HttpResponseBuilder) model.get("responsebuilder");
|
this.responseBuilder = (HttpResponseBuilder) model.get("responsebuilder");
|
||||||
Objects.requireNonNull(this.responseBuilder, "response must not be null");
|
Objects.requireNonNull(this.responseBuilder, "response must not be null");
|
||||||
|
this.request = (HttpRequest) model.get("request");
|
||||||
|
// request can be null in error templates
|
||||||
|
this.session = (Session) model.get("session");
|
||||||
|
// session can be null in error templates
|
||||||
}
|
}
|
||||||
|
|
||||||
public void responseStatus(HttpResponseStatus responseStatus) {
|
public void setResponseStatus(HttpResponseStatus responseStatus) {
|
||||||
responseBuilder.setResponseStatus(responseStatus);
|
responseBuilder.setResponseStatus(responseStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void contentType(String contentType) {
|
public void setContentType(String contentType) {
|
||||||
responseBuilder.setHeader(HttpHeaderNames.CONTENT_TYPE, contentType);
|
responseBuilder.setHeader(HttpHeaderNames.CONTENT_TYPE, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isContentType(String contentType) {
|
public boolean isContentType(String contentType) {
|
||||||
return request.getHeaders().containsHeader(HttpHeaderNames.CONTENT_TYPE) && contentType != null &&
|
return request != null &&
|
||||||
|
request.getHeaders() != null &&
|
||||||
|
request.getHeaders().containsHeader(HttpHeaderNames.CONTENT_TYPE) &&
|
||||||
|
contentType != null &&
|
||||||
request.getHeaders().get(HttpHeaderNames.CONTENT_TYPE).startsWith(contentType);
|
request.getHeaders().get(HttpHeaderNames.CONTENT_TYPE).startsWith(contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void contentDisposition(String contentDisposition) {
|
public void setContentDisposition(String contentDisposition) {
|
||||||
responseBuilder.setHeader(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition);
|
responseBuilder.setHeader(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ package org.xbib.net.http.template.groovy;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.xbib.net.URL;
|
|
||||||
import org.xbib.net.http.HttpResponseStatus;
|
import org.xbib.net.http.HttpResponseStatus;
|
||||||
import org.xbib.net.http.server.Application;
|
import org.xbib.net.http.server.Application;
|
||||||
import org.xbib.net.http.server.HttpServerContext;
|
import org.xbib.net.http.server.HttpServerContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
class GroovyHttpResonseStatusTemplateResource extends GroovyTemplateResource {
|
class GroovyHttpResonseStatusTemplateResource extends GroovyTemplateResource {
|
||||||
|
|
||||||
|
@ -47,36 +45,11 @@ class GroovyHttpResonseStatusTemplateResource extends GroovyTemplateResource {
|
||||||
groovyTemplateRenderer.handle(httpServerContext);
|
groovyTemplateRenderer.handle(httpServerContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path getPath() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "status-resource";
|
return "status-resource";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBaseName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSuffix() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getResourcePath() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getURL() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isExists() {
|
public boolean isExists() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -17,9 +17,9 @@ import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -28,8 +28,16 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GroovyTemplateResource.class.getName());
|
private static final Logger logger = Logger.getLogger(GroovyTemplateResource.class.getName());
|
||||||
|
|
||||||
private static final Map<Path, Template> templates = new HashMap<>();
|
/**
|
||||||
|
* This class might be reused by mtultiple handlers.
|
||||||
|
* We use a concurrent hash map here because in case of "not found" error templating,
|
||||||
|
* the "computeIfAbsent" can be called in a nested way, and would throw a concurrent modification exception.
|
||||||
|
*/
|
||||||
|
private static final Map<Path, Template> templates = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This lock ensures that changing locale and rendering is executed in a pairwisely manner.
|
||||||
|
*/
|
||||||
private static final ReentrantLock lock = new ReentrantLock();
|
private static final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
protected GroovyTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
protected GroovyTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
||||||
|
@ -39,7 +47,7 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(HttpServerContext httpServerContext) throws IOException {
|
public void render(HttpServerContext httpServerContext) throws IOException {
|
||||||
logger.log(Level.FINE, "rendering groovy template, path = " + getPath() + " isExists = " + isExists() + " isDirectory =" + isDirectory() );
|
logger.log(Level.FINER, "rendering groovy template, path = " + getPath() + " isExists = " + isExists() + " isDirectory =" + isDirectory() );
|
||||||
Application application = httpServerContext.attributes().get(Application.class, "application");
|
Application application = httpServerContext.attributes().get(Application.class, "application");
|
||||||
if (application == null) {
|
if (application == null) {
|
||||||
logger.log(Level.WARNING, "application is null");
|
logger.log(Level.WARNING, "application is null");
|
||||||
|
@ -52,34 +60,36 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
}
|
}
|
||||||
Path templatePath = getPath();
|
Path templatePath = getPath();
|
||||||
HttpService service = httpServerContext.attributes().get(HttpService.class, "service");
|
HttpService service = httpServerContext.attributes().get(HttpService.class, "service");
|
||||||
GroovyTemplateService groovyTemplateService = (GroovyTemplateService) service;
|
if (service instanceof GroovyTemplateService groovyTemplateService) {
|
||||||
if (groovyTemplateService.getTemplateName() != null) {
|
if (groovyTemplateService.getTemplateName() != null) {
|
||||||
templatePath = application.resolve(groovyTemplateService.getTemplateName());
|
templatePath = application.resolve(groovyTemplateService.getTemplateName());
|
||||||
logger.log(Level.FINE, "templatePath after application.resolve() = " + templatePath);
|
logger.log(Level.FINER, "templatePath after application.resolve() = " + templatePath);
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.FINE, "the GroovyTemplateService does not have a template name set");
|
logger.log(Level.FINER, "the GroovyTemplateService does not have a templateName");
|
||||||
}
|
}
|
||||||
logger.log(Level.FINE, "templatePath = " + templatePath);
|
}
|
||||||
|
// status response handlers have priority
|
||||||
GroovyHttpResonseStatusTemplateResource resource = httpServerContext.attributes().get(GroovyHttpResonseStatusTemplateResource.class, "_resource");
|
GroovyHttpResonseStatusTemplateResource resource = httpServerContext.attributes().get(GroovyHttpResonseStatusTemplateResource.class, "_resource");
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
logger.log(Level.FINE, "Groovy HTTP status response rendering");
|
|
||||||
String indexFileName = resource.getIndexFileName();
|
String indexFileName = resource.getIndexFileName();
|
||||||
if (indexFileName != null) {
|
if (indexFileName != null) {
|
||||||
templatePath = application.resolve(indexFileName);
|
templatePath = application.resolve(indexFileName);
|
||||||
}
|
}
|
||||||
}
|
logger.log(Level.FINER, "rendering Groovy HTTP status response with templatePath = " + templatePath);
|
||||||
|
} else {
|
||||||
// override if 'templatePath' attribute is set
|
// override if 'templatePath' attribute is set
|
||||||
String overridePath = httpServerContext.attributes().get(String.class, "templatePath");
|
String overridePath = httpServerContext.attributes().get(String.class, "templatePath");
|
||||||
if (overridePath != null) {
|
if (overridePath != null) {
|
||||||
logger.log(Level.FINE, "found override templatePath = " + overridePath);
|
logger.log(Level.FINER, "found override templatePath = " + overridePath);
|
||||||
templatePath = application.resolve(overridePath);
|
templatePath = application.resolve(overridePath);
|
||||||
logger.log(Level.FINE, "found override templatePath, resolved to " + templatePath);
|
logger.log(Level.FINER, "found override templatePath, resolved to " + templatePath);
|
||||||
}
|
}
|
||||||
if (templatePath == null) {
|
if (templatePath == null) {
|
||||||
logger.log(Level.FINE, "templatePath is null, OOTB effort on " + getIndexFileName());
|
logger.log(Level.FINER, "templatePath is null, OOTB effort on " + getIndexFileName());
|
||||||
// OOTB rendering via getIndexFileName(), no getPath(), no getTemplateName()
|
// OOTB rendering via getIndexFileName(), no getPath(), no getTemplateName()
|
||||||
templatePath = application.resolve(getIndexFileName());
|
templatePath = application.resolve(getIndexFileName());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (isDirectory()) {
|
if (isDirectory()) {
|
||||||
if (isExistsIndexFile()) {
|
if (isExistsIndexFile()) {
|
||||||
templatePath = getPath().resolve(getIndexFileName());
|
templatePath = getPath().resolve(getIndexFileName());
|
||||||
|
@ -88,11 +98,15 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
throw new HttpException("forbidden", httpServerContext, HttpResponseStatus.FORBIDDEN);
|
throw new HttpException("forbidden", httpServerContext, HttpResponseStatus.FORBIDDEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.log(Level.FINE, "rendering groovy template " + templatePath);
|
if (templatePath == null) {
|
||||||
|
logger.log(Level.WARNING, "unable to render a null path");
|
||||||
|
throw new HttpException("internal path error", httpServerContext, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
templates.computeIfAbsent(templatePath, path -> {
|
templates.computeIfAbsent(templatePath, path -> {
|
||||||
try {
|
try {
|
||||||
|
logger.log(Level.FINEST, "groovy templatePath = " + path + " creating by template engine");
|
||||||
return templateEngine.createTemplate(Files.readString(path));
|
return templateEngine.createTemplate(Files.readString(path));
|
||||||
} catch (ClassNotFoundException | IOException e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -104,12 +118,25 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
binding.setVariable("log", templateLogger);
|
binding.setVariable("log", templateLogger);
|
||||||
application.getModules().forEach(m -> binding.setVariable(m.getName(), m));
|
application.getModules().forEach(m -> binding.setVariable(m.getName(), m));
|
||||||
DefaultTemplateResolver templateResolver = httpServerContext.attributes().get(DefaultTemplateResolver.class, "templateresolver");
|
DefaultTemplateResolver templateResolver = httpServerContext.attributes().get(DefaultTemplateResolver.class, "templateresolver");
|
||||||
if (templateResolver != null) {
|
if (templateResolver == null) {
|
||||||
|
// for Groovy template engines without a resolver, no need to set a locale
|
||||||
|
Writable writable = template.make(binding.getVariables());
|
||||||
|
httpServerContext.attributes().put("writable", writable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!negotiateLocale) {
|
||||||
|
// if no locale negotiation configured, set always the applicaiton locale. This constant value never changes.
|
||||||
|
templateResolver.setLocale(application.getLocale());
|
||||||
|
Writable writable = template.make(binding.getVariables());
|
||||||
|
httpServerContext.attributes().put("writable", writable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// handle programmatic locale change plus template making under lock so no other request/response can interrupt us
|
// handle programmatic locale change plus template making under lock so no other request/response can interrupt us
|
||||||
logger.log(Level.FINER, "application locale for template = " + application.getLocale());
|
logger.log(Level.FINER, "application locale for template = " + application.getLocale());
|
||||||
try {
|
try {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
templateResolver.setLocale(application.getLocale());
|
templateResolver.setLocale(application.getLocale());
|
||||||
|
// language from request overrides application locale
|
||||||
String acceptLanguage = httpServerContext.request().getHeaders().get(HttpHeaderNames.ACCEPT_LANGUAGE);
|
String acceptLanguage = httpServerContext.request().getHeaders().get(HttpHeaderNames.ACCEPT_LANGUAGE);
|
||||||
if (acceptLanguage != null) {
|
if (acceptLanguage != null) {
|
||||||
Locale negotiatedLocale = LocaleNegotiator.findLocale(acceptLanguage);
|
Locale negotiatedLocale = LocaleNegotiator.findLocale(acceptLanguage);
|
||||||
|
@ -121,18 +148,12 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
||||||
Writable writable = template.make(binding.getVariables());
|
Writable writable = template.make(binding.getVariables());
|
||||||
httpServerContext.attributes().put("writable", writable);
|
httpServerContext.attributes().put("writable", writable);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// in case there is not template with negotiated locale
|
// fail silently by ignoring negotation
|
||||||
templateResolver.setLocale(application.getLocale());
|
templateResolver.setLocale(application.getLocale());
|
||||||
Writable writable = template.make(binding.getVariables());
|
Writable writable = template.make(binding.getVariables());
|
||||||
httpServerContext.attributes().put("writable", writable);
|
httpServerContext.attributes().put("writable", writable);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// for Groovy template engines without a resolver
|
|
||||||
Writable writable = template.make(binding.getVariables());
|
|
||||||
httpServerContext.attributes().put("writable", writable);
|
|
||||||
}
|
|
||||||
logger.log(Level.FINER, "rendering done: " + httpServerContext.isDone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ dependencyResolutionManagement {
|
||||||
version('netty-tcnative', '2.0.59.Final')
|
version('netty-tcnative', '2.0.59.Final')
|
||||||
version('datastructures', '2.0.0')
|
version('datastructures', '2.0.0')
|
||||||
version('config', '5.0.2')
|
version('config', '5.0.2')
|
||||||
version('net', '3.0.3')
|
version('net', '3.0.4')
|
||||||
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
|
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
|
||||||
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
|
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
|
||||||
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
||||||
|
|
Loading…
Reference in a new issue