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.time.Duration;
|
||||
|
||||
import org.xbib.net.http.cookie.SameSite;
|
||||
import org.xbib.net.http.server.BaseApplication;
|
||||
import org.xbib.net.http.server.HttpHandler;
|
||||
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.Session;
|
||||
import org.xbib.net.http.server.session.file.FileJsonSessionCodec;
|
||||
import org.xbib.net.util.RandomUtil;
|
||||
|
||||
public class WebApplication extends BaseApplication {
|
||||
|
||||
|
@ -40,7 +43,8 @@ public class WebApplication extends BaseApplication {
|
|||
sessionCodec,
|
||||
getStaticFileSuffixes(),
|
||||
"user_id",
|
||||
"e_user_id");
|
||||
"e_user_id",
|
||||
() -> RandomUtil.randomString(16));
|
||||
}
|
||||
|
||||
protected OutgoingSessionHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
||||
|
@ -50,10 +54,13 @@ public class WebApplication extends BaseApplication {
|
|||
getSecret(),
|
||||
"HmacSHA1",
|
||||
sessionName,
|
||||
Duration.ofDays(1),
|
||||
sessionCodec,
|
||||
getStaticFileSuffixes(),
|
||||
"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.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
public class HttpRequest extends BaseHttpRequest {
|
||||
|
@ -47,7 +48,10 @@ public class HttpRequest extends BaseHttpRequest {
|
|||
|
||||
@Override
|
||||
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() {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.xbib.net.http.server.netty;
|
|||
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import org.xbib.net.Parameter;
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.net.http.HttpAddress;
|
||||
import org.xbib.net.http.HttpMethod;
|
||||
|
@ -24,7 +25,7 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
|||
|
||||
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
|
||||
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();
|
||||
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
|
||||
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
||||
|
@ -100,6 +101,10 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected Parameter getParameter() {
|
||||
return super.parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest build() {
|
||||
return new HttpRequest(this);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.HttpAddress;
|
||||
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.validate.HttpRequestValidator;
|
||||
import org.xbib.net.util.NamedThreadFactory;
|
||||
import org.xbib.net.util.RandomUtil;
|
||||
import org.xbib.settings.Settings;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -194,7 +196,8 @@ public class BaseApplication implements Application {
|
|||
sessionCodec,
|
||||
getStaticFileSuffixes(),
|
||||
"user_id",
|
||||
"e_user_id");
|
||||
"e_user_id",
|
||||
() -> RandomUtil.randomString(16));
|
||||
}
|
||||
|
||||
protected HttpHandler buildOutgoingSessionHandler(HttpServerContext httpServerContext) {
|
||||
|
@ -204,11 +207,15 @@ public class BaseApplication implements Application {
|
|||
getSecret(),
|
||||
"HmacSHA1",
|
||||
sessionName,
|
||||
Duration.ofDays(1),
|
||||
sessionCodec,
|
||||
getStaticFileSuffixes(),
|
||||
"user_id",
|
||||
"e_user_id");
|
||||
"e_user_id",
|
||||
Duration.ofDays(1),
|
||||
true,
|
||||
false,
|
||||
SameSite.LAX
|
||||
);
|
||||
}
|
||||
|
||||
protected HttpResponseRenderer buildResponseRenderer() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.xbib.net.http.server;
|
||||
|
||||
import java.util.Map;
|
||||
import org.xbib.net.Attributes;
|
||||
import org.xbib.net.Parameter;
|
||||
import org.xbib.net.ParameterBuilder;
|
||||
|
@ -23,11 +22,15 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
|
||||
public class BaseHttpServerContext implements HttpServerContext {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BaseHttpServerContext.class.getName());
|
||||
|
||||
private static final String PATH_SEPARATOR = "/";
|
||||
|
||||
private final Application application;
|
||||
|
@ -84,16 +87,26 @@ public class BaseHttpServerContext implements HttpServerContext {
|
|||
@Override
|
||||
public void setResolverResult(HttpRouteResolver.Result<HttpService> pathResolverResult) {
|
||||
this.pathResolverResult = pathResolverResult;
|
||||
this.attributes.put("context", pathResolverResult.getContext());
|
||||
this.attributes.put("handler", pathResolverResult.getValue());
|
||||
this.attributes.put("pathparams", pathResolverResult.getParameter());
|
||||
String contextPath = pathResolverResult.getContext() != null ?
|
||||
PATH_SEPARATOR + String.join(PATH_SEPARATOR, pathResolverResult.getContext()) : null;
|
||||
setContextPath(contextPath);
|
||||
setContextURL(request().getBaseURL().resolve(contextPath != null ? contextPath + "/" : ""));
|
||||
this.httpRequest = createRequest(httpRequestBuilder);
|
||||
this.attributes.put("request", httpRequest);
|
||||
this.next = false;
|
||||
if (pathResolverResult != null) {
|
||||
attributes.put("context", pathResolverResult.getContext());
|
||||
attributes.put("handler", pathResolverResult.getValue());
|
||||
attributes.put("pathparams", pathResolverResult.getParameter());
|
||||
String contextPath = pathResolverResult.getContext() != null ?
|
||||
PATH_SEPARATOR + String.join(PATH_SEPARATOR, pathResolverResult.getContext()) : null;
|
||||
setContextPath(contextPath);
|
||||
setContextURL(request().getBaseURL().resolve(contextPath != null ? contextPath + "/" : ""));
|
||||
} else {
|
||||
// path resolver result null means "404 not found". Set default values.
|
||||
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) {
|
||||
|
@ -114,6 +127,7 @@ public class BaseHttpServerContext implements HttpServerContext {
|
|||
return contextURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path resolve(String path) {
|
||||
return application.resolve(path);
|
||||
}
|
||||
|
@ -187,8 +201,8 @@ public class BaseHttpServerContext implements HttpServerContext {
|
|||
httpResponseBuilder.write(fileChannel, bufferSize);
|
||||
}
|
||||
|
||||
protected HttpRequest createRequest(HttpRequestBuilder requestBuilder) {
|
||||
HttpHeaders headers = requestBuilder.getHeaders();
|
||||
protected HttpRequest createRequest() {
|
||||
HttpHeaders headers = httpRequestBuilder.getHeaders();
|
||||
String mimeType = headers.get(CONTENT_TYPE);
|
||||
Charset charset = StandardCharsets.UTF_8;
|
||||
if (mimeType != null) {
|
||||
|
@ -198,30 +212,40 @@ public class BaseHttpServerContext implements HttpServerContext {
|
|||
// helper URL to collect parameters in request URI
|
||||
URL url = URL.builder()
|
||||
.charset(charset, CodingErrorAction.REPLACE)
|
||||
.path(requestBuilder.getRequestURI())
|
||||
.path(httpRequestBuilder.getRequestURI())
|
||||
.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
|
||||
if (HttpMethod.POST.equals(requestBuilder.getMethod()) &&
|
||||
if (HttpMethod.POST.equals(httpRequestBuilder.getMethod()) &&
|
||||
(mimeType != null && mimeType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED))) {
|
||||
Charset htmlCharset = getCharset(mimeType, StandardCharsets.ISO_8859_1);
|
||||
CharBuffer charBuffer = requestBuilder.getBodyAsChars(htmlCharset);
|
||||
CharBuffer charBuffer = httpRequestBuilder.getBodyAsChars(htmlCharset);
|
||||
if (charBuffer != null) {
|
||||
formParameters.addPercentEncodedBody(charBuffer.toString());
|
||||
formParameterBuilder.addPercentEncodedBody(charBuffer.toString());
|
||||
}
|
||||
}
|
||||
CookieBox cookieBox = attributes.get(CookieBox.class, "incomingcookies");
|
||||
ParameterBuilder cookieParameters = Parameter.builder().domain("COOKIE");
|
||||
ParameterBuilder cookieParameterBuilder = Parameter.builder().domain("COOKIE");
|
||||
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());
|
||||
parameterBuilder.add(formParameters.build());
|
||||
parameterBuilder.add(cookieParameters.build());
|
||||
parameterBuilder.add(pathResolverResult.getParameter());
|
||||
requestBuilder.setParameter(parameterBuilder.build());
|
||||
requestBuilder.setContext(this);
|
||||
return requestBuilder.build();
|
||||
Parameter queryParameter = url.getQueryParams();
|
||||
logger.log(Level.FINER, "adding query parameters = " + queryParameter.getDomain() + " " + queryParameter.allToString());
|
||||
parameterBuilder.add(queryParameter);
|
||||
Parameter formParameter = formParameterBuilder.build();
|
||||
logger.log(Level.FINER, "adding form parameters = " + formParameter.getDomain() + " " + formParameter.allToString());
|
||||
parameterBuilder.add(formParameter);
|
||||
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) {
|
||||
|
|
|
@ -39,34 +39,37 @@ public class HtmlTemplateResource implements HttpServerResource {
|
|||
|
||||
private final String suffix;
|
||||
|
||||
protected final boolean negotiateLocale;
|
||||
|
||||
protected HtmlTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
||||
HttpServerContext httpServerContext) throws IOException {
|
||||
this.templateResourceHandler = templateResourceHandler;
|
||||
String indexFileName = templateResourceHandler.getIndexFileName();
|
||||
Application application = httpServerContext.attributes().get(Application.class, "application");
|
||||
this.negotiateLocale = application.getSettings().getAsBoolean("negotiateLocale", false);
|
||||
Path root = templateResourceHandler.getPrefix();
|
||||
root = root != null ? root : application.getHome();
|
||||
if (root == null) {
|
||||
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);
|
||||
logger.log(Level.FINE, "resource path = " + resourcePath);
|
||||
this.path = resourcePath.length() > 0 ? root.resolve(resourcePath) : root;
|
||||
logger.log(Level.FINE, "path = " + path);
|
||||
logger.log(Level.FINE, "index file name = " + indexFileName);
|
||||
logger.log(Level.FINER, "class = " + getClass().getName() +
|
||||
" root = " + root +
|
||||
" resource path = " + resourcePath +
|
||||
" path = " + path +
|
||||
" index file name = " + indexFileName);
|
||||
this.name = path.getFileName().toString();
|
||||
this.baseName = AbstractResourceHandler.basename(name);
|
||||
this.suffix = AbstractResourceHandler.suffix(name);
|
||||
if (Files.isDirectory(path)) {
|
||||
if (getIndexFileName() != null) {
|
||||
Path indexPath = path.resolve(indexFileName);
|
||||
logger.log(Level.FINE, "index path = " + indexPath);
|
||||
logger.log(Level.FINE, "resolved to index path = " + indexPath);
|
||||
if (Files.exists(indexPath)) {
|
||||
logger.log(Level.FINE, "index path exists");
|
||||
this.isExistsIndexFile = true;
|
||||
this.path = indexPath;
|
||||
logger.log(Level.FINE, "index file path found = " + path);
|
||||
this.isDirectory = false;
|
||||
} else {
|
||||
this.isExistsIndexFile = false;
|
||||
|
@ -81,15 +84,9 @@ public class HtmlTemplateResource implements HttpServerResource {
|
|||
this.isDirectory = false;
|
||||
}
|
||||
this.isExists = Files.exists(path);
|
||||
logger.log(Level.FINE, "exists = " + isExists);
|
||||
logger.log(Level.FINE, "isDirectory = " + isDirectory);
|
||||
this.url = URL.create(path.toUri().toString());
|
||||
logger.log(Level.FINE, "url = " + url);
|
||||
if (isExists) {
|
||||
this.lastModified = Files.getLastModifiedTime(path).toInstant();
|
||||
} else {
|
||||
this.lastModified = Instant.now();
|
||||
}
|
||||
logger.log(Level.FINE, "isExists = " + isExists + " isDirectory = " + isDirectory + " url = " + url);
|
||||
this.lastModified = isExists ? Files.getLastModifiedTime(path).toInstant() : Instant.now();
|
||||
// length will be computed at rendering time
|
||||
this.length = -1;
|
||||
}
|
||||
|
|
|
@ -63,10 +63,9 @@ public class BaseHttpRoute implements HttpRoute {
|
|||
|
||||
@Override
|
||||
public boolean matches(ParameterBuilder parameterBuilder, HttpRoute requestedRoute) {
|
||||
if (!(requestedRoute instanceof BaseHttpRoute)) {
|
||||
if (!(requestedRoute instanceof BaseHttpRoute baseHttpRoute)) {
|
||||
return false;
|
||||
}
|
||||
BaseHttpRoute baseHttpRoute = (BaseHttpRoute) requestedRoute;
|
||||
if (!httpAddress.equals(baseHttpRoute.getHttpAddress())) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public class BaseHttpRouteResolver<T> implements HttpRouteResolver<T> {
|
|||
@Override
|
||||
public void resolve(HttpRoute httpRoute, ResultListener<T> listener) {
|
||||
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);
|
||||
if (match && listener != null) {
|
||||
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.HttpException;
|
||||
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.HttpResponseBuilder;
|
||||
import org.xbib.net.http.server.HttpServerContext;
|
||||
import org.xbib.net.http.server.HttpService;
|
||||
import org.xbib.net.http.server.handler.InternalServerErrorHandler;
|
||||
|
||||
import static org.xbib.net.http.HttpResponseStatus.NOT_FOUND;
|
||||
|
||||
|
@ -105,6 +107,7 @@ public class BaseHttpRouter implements HttpRouter {
|
|||
}
|
||||
if (httpRouteResolverResults.isEmpty()) {
|
||||
logger.log(Level.FINE, "route resolver results is empty, generating a not found message");
|
||||
httpServerContext.setResolverResult(null);
|
||||
routeStatus(NOT_FOUND, httpServerContext);
|
||||
return;
|
||||
}
|
||||
|
@ -113,7 +116,8 @@ public class BaseHttpRouter implements HttpRouter {
|
|||
// first: create the final request
|
||||
httpServerContext.setResolverResult(httpRouteResolverResult);
|
||||
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.
|
||||
if (httpService.getSecurityDomain() != null) {
|
||||
logger.log(Level.FINEST, () -> "handling security domain service " + httpService);
|
||||
|
@ -125,11 +129,12 @@ public class BaseHttpRouter implements HttpRouter {
|
|||
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
||||
break;
|
||||
}
|
||||
// accept service and execute service
|
||||
// after security checks, accept service, open and execute service
|
||||
httpServerContext.attributes().put("service", httpService);
|
||||
application.getModules().forEach(module -> module.onOpen(application, httpServerContext, httpService));
|
||||
logger.log(Level.FINEST, () -> "handling service " + httpService);
|
||||
httpService.handle(httpServerContext);
|
||||
// if service signals that work is done, break
|
||||
if (httpServerContext.isDone() || httpServerContext.isFailed()) {
|
||||
break;
|
||||
}
|
||||
|
@ -155,10 +160,17 @@ public class BaseHttpRouter implements HttpRouter {
|
|||
|
||||
@Override
|
||||
public void routeStatus(HttpResponseStatus httpResponseStatus, HttpServerContext httpServerContext) {
|
||||
logger.log(Level.FINER, "routing status " + httpResponseStatus);
|
||||
try {
|
||||
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();
|
||||
httpHandler.handle(httpServerContext);
|
||||
httpServerContext.done();
|
||||
logger.log(Level.FINER, "routing status " + httpResponseStatus + " done");
|
||||
} catch (IOException 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.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.xbib.net.util.RandomUtil;
|
||||
|
||||
public class IncomingSessionHandler implements HttpHandler {
|
||||
|
||||
|
@ -46,13 +46,16 @@ public class IncomingSessionHandler implements HttpHandler {
|
|||
*/
|
||||
private final Set<String> suffixes;
|
||||
|
||||
Supplier<String> sessionIdGenerator;
|
||||
|
||||
public IncomingSessionHandler(String sessionSecret,
|
||||
String sessionCookieAlgorithm,
|
||||
String sessionCookieName,
|
||||
Codec<Session> sessionCodec,
|
||||
Set<String> suffixes,
|
||||
String sessionUserName,
|
||||
String sessionEffectiveUserName) {
|
||||
String sessionEffectiveUserName,
|
||||
Supplier<String> sessionIdGenerator) {
|
||||
this.sessionSecret = sessionSecret;
|
||||
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
||||
this.sessionCookieName = sessionCookieName;
|
||||
|
@ -60,6 +63,7 @@ public class IncomingSessionHandler implements HttpHandler {
|
|||
this.suffixes = suffixes;
|
||||
this.sessionUserName = sessionUserName;
|
||||
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
||||
this.sessionIdGenerator = sessionIdGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,6 +80,7 @@ public class IncomingSessionHandler implements HttpHandler {
|
|||
if (session == null) {
|
||||
try {
|
||||
Map<String, Object> payload = decodeCookie(cookie);
|
||||
logger.log(Level.FINER, "cookie decoded");
|
||||
session = toSession(payload);
|
||||
UserProfile userProfile = newUserProfile(payload, session);
|
||||
if (userProfile != null) {
|
||||
|
@ -96,14 +101,14 @@ public class IncomingSessionHandler implements HttpHandler {
|
|||
}
|
||||
if (session == null) {
|
||||
try {
|
||||
session = sessionCodec.create(RandomUtil.randomString(32));
|
||||
session = sessionCodec.create(sessionIdGenerator.get());
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
throw new HttpException("unable to create session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
context.attributes().put("session", session);
|
||||
|
||||
logger.log(Level.FINER, "incoming session " + session.id());
|
||||
}
|
||||
|
||||
private Map<String, Object> decodeCookie(Cookie cookie) throws IOException,
|
||||
|
|
|
@ -47,22 +47,34 @@ public class OutgoingSessionHandler implements HttpHandler {
|
|||
|
||||
private final String sessionEffectiveUserName;
|
||||
|
||||
private final boolean httpOnly;
|
||||
|
||||
private final boolean secure;
|
||||
|
||||
private final SameSite sameSite;
|
||||
|
||||
public OutgoingSessionHandler(String sessionSecret,
|
||||
String sessionCookieAlgorithm,
|
||||
String sessionCookieName,
|
||||
Duration sessionDuration,
|
||||
Codec<Session> sessionCodec,
|
||||
Set<String> suffixes,
|
||||
String sessionUserName,
|
||||
String sessionEffectiveUserName) {
|
||||
String sessionEffectiveUserName,
|
||||
Duration sessionDuration,
|
||||
boolean httpOnly,
|
||||
boolean secure,
|
||||
SameSite sameSite) {
|
||||
this.sessionSecret = sessionSecret;
|
||||
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
|
||||
this.sessionCookieName = sessionCookieName;
|
||||
this.sessionDuration = sessionDuration;
|
||||
this.sessionCodec = sessionCodec;
|
||||
this.suffixes = suffixes;
|
||||
this.sessionUserName = sessionUserName;
|
||||
this.sessionEffectiveUserName = sessionEffectiveUserName;
|
||||
this.sessionDuration = sessionDuration;
|
||||
this.httpOnly = httpOnly;
|
||||
this.secure = secure;
|
||||
this.sameSite = sameSite;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -137,28 +149,32 @@ public class OutgoingSessionHandler implements HttpHandler {
|
|||
PercentEncoder percentEncoder = PercentEncoders.getCookieEncoder(StandardCharsets.ISO_8859_1);
|
||||
DefaultCookie cookie = new DefaultCookie(sessionCookieName, percentEncoder.encode(cookieValue));
|
||||
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.setPath(path);
|
||||
cookie.setMaxAge(sessionDuration.toSeconds());
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(true);
|
||||
cookie.setSameSite(SameSite.LAX);
|
||||
cookie.setHttpOnly(httpOnly);
|
||||
cookie.setSecure(secure);
|
||||
cookie.setSameSite(sameSite);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
private Cookie createEmptyCookie(String host, String path) {
|
||||
DefaultCookie cookie = new DefaultCookie(sessionCookieName);
|
||||
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.setPath(path);
|
||||
cookie.setMaxAge(0L);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(true);
|
||||
cookie.setSameSite(SameSite.LAX);
|
||||
cookie.setHttpOnly(httpOnly);
|
||||
cookie.setSecure(secure);
|
||||
cookie.setSameSite(sameSite);
|
||||
logger.log(Level.FINEST, "baked empty cookie");
|
||||
return cookie;
|
||||
}
|
||||
|
|
|
@ -44,28 +44,31 @@ public abstract class DefaultMarkupTemplate extends BaseTemplate {
|
|||
super(templateEngine, model, modelTypes, configuration);
|
||||
this.application = (Application) model.get("application");
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
public void contentType(String contentType) {
|
||||
public void setContentType(String contentType) {
|
||||
responseBuilder.setHeader(HttpHeaderNames.CONTENT_TYPE, 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);
|
||||
}
|
||||
|
||||
public void contentDisposition(String contentDisposition) {
|
||||
public void setContentDisposition(String 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.Logger;
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.net.http.HttpResponseStatus;
|
||||
import org.xbib.net.http.server.Application;
|
||||
import org.xbib.net.http.server.HttpServerContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class GroovyHttpResonseStatusTemplateResource extends GroovyTemplateResource {
|
||||
|
||||
|
@ -47,36 +45,11 @@ class GroovyHttpResonseStatusTemplateResource extends GroovyTemplateResource {
|
|||
groovyTemplateRenderer.handle(httpServerContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
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
|
||||
public boolean isExists() {
|
||||
return true;
|
||||
|
|
|
@ -17,9 +17,9 @@ import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
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 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();
|
||||
|
||||
protected GroovyTemplateResource(HtmlTemplateResourceHandler templateResourceHandler,
|
||||
|
@ -39,7 +47,7 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
|||
|
||||
@Override
|
||||
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");
|
||||
if (application == null) {
|
||||
logger.log(Level.WARNING, "application is null");
|
||||
|
@ -52,33 +60,35 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
|||
}
|
||||
Path templatePath = getPath();
|
||||
HttpService service = httpServerContext.attributes().get(HttpService.class, "service");
|
||||
GroovyTemplateService groovyTemplateService = (GroovyTemplateService) service;
|
||||
if (groovyTemplateService.getTemplateName() != null) {
|
||||
templatePath = application.resolve(groovyTemplateService.getTemplateName());
|
||||
logger.log(Level.FINE, "templatePath after application.resolve() = " + templatePath);
|
||||
} else {
|
||||
logger.log(Level.FINE, "the GroovyTemplateService does not have a template name set");
|
||||
if (service instanceof GroovyTemplateService groovyTemplateService) {
|
||||
if (groovyTemplateService.getTemplateName() != null) {
|
||||
templatePath = application.resolve(groovyTemplateService.getTemplateName());
|
||||
logger.log(Level.FINER, "templatePath after application.resolve() = " + templatePath);
|
||||
} else {
|
||||
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");
|
||||
if (resource != null) {
|
||||
logger.log(Level.FINE, "Groovy HTTP status response rendering");
|
||||
String indexFileName = resource.getIndexFileName();
|
||||
if (indexFileName != null) {
|
||||
templatePath = application.resolve(indexFileName);
|
||||
}
|
||||
}
|
||||
// override if 'templatePath' attribute is set
|
||||
String overridePath = httpServerContext.attributes().get(String.class, "templatePath");
|
||||
if (overridePath != null) {
|
||||
logger.log(Level.FINE, "found override templatePath = " + overridePath);
|
||||
templatePath = application.resolve(overridePath);
|
||||
logger.log(Level.FINE, "found override templatePath, resolved to " + templatePath);
|
||||
}
|
||||
if (templatePath == null) {
|
||||
logger.log(Level.FINE, "templatePath is null, OOTB effort on " + getIndexFileName());
|
||||
// OOTB rendering via getIndexFileName(), no getPath(), no getTemplateName()
|
||||
templatePath = application.resolve(getIndexFileName());
|
||||
logger.log(Level.FINER, "rendering Groovy HTTP status response with templatePath = " + templatePath);
|
||||
} else {
|
||||
// override if 'templatePath' attribute is set
|
||||
String overridePath = httpServerContext.attributes().get(String.class, "templatePath");
|
||||
if (overridePath != null) {
|
||||
logger.log(Level.FINER, "found override templatePath = " + overridePath);
|
||||
templatePath = application.resolve(overridePath);
|
||||
logger.log(Level.FINER, "found override templatePath, resolved to " + templatePath);
|
||||
}
|
||||
if (templatePath == null) {
|
||||
logger.log(Level.FINER, "templatePath is null, OOTB effort on " + getIndexFileName());
|
||||
// OOTB rendering via getIndexFileName(), no getPath(), no getTemplateName()
|
||||
templatePath = application.resolve(getIndexFileName());
|
||||
}
|
||||
}
|
||||
if (isDirectory()) {
|
||||
if (isExistsIndexFile()) {
|
||||
|
@ -88,11 +98,15 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
|||
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 -> {
|
||||
try {
|
||||
logger.log(Level.FINEST, "groovy templatePath = " + path + " creating by template engine");
|
||||
return templateEngine.createTemplate(Files.readString(path));
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
});
|
||||
|
@ -104,35 +118,42 @@ public class GroovyTemplateResource extends HtmlTemplateResource {
|
|||
binding.setVariable("log", templateLogger);
|
||||
application.getModules().forEach(m -> binding.setVariable(m.getName(), m));
|
||||
DefaultTemplateResolver templateResolver = httpServerContext.attributes().get(DefaultTemplateResolver.class, "templateresolver");
|
||||
if (templateResolver != null) {
|
||||
// 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());
|
||||
try {
|
||||
lock.lock();
|
||||
templateResolver.setLocale(application.getLocale());
|
||||
String acceptLanguage = httpServerContext.request().getHeaders().get(HttpHeaderNames.ACCEPT_LANGUAGE);
|
||||
if (acceptLanguage != null) {
|
||||
Locale negotiatedLocale = LocaleNegotiator.findLocale(acceptLanguage);
|
||||
if (negotiatedLocale != null) {
|
||||
logger.log(Level.FINER, "negotiated locale for template = " + negotiatedLocale);
|
||||
templateResolver.setLocale(negotiatedLocale);
|
||||
}
|
||||
}
|
||||
Writable writable = template.make(binding.getVariables());
|
||||
httpServerContext.attributes().put("writable", writable);
|
||||
} catch (Exception e) {
|
||||
// in case there is not template with negotiated locale
|
||||
templateResolver.setLocale(application.getLocale());
|
||||
Writable writable = template.make(binding.getVariables());
|
||||
httpServerContext.attributes().put("writable", writable);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
// for Groovy template engines without a resolver
|
||||
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
|
||||
logger.log(Level.FINER, "application locale for template = " + application.getLocale());
|
||||
try {
|
||||
lock.lock();
|
||||
templateResolver.setLocale(application.getLocale());
|
||||
// language from request overrides application locale
|
||||
String acceptLanguage = httpServerContext.request().getHeaders().get(HttpHeaderNames.ACCEPT_LANGUAGE);
|
||||
if (acceptLanguage != null) {
|
||||
Locale negotiatedLocale = LocaleNegotiator.findLocale(acceptLanguage);
|
||||
if (negotiatedLocale != null) {
|
||||
logger.log(Level.FINER, "negotiated locale for template = " + negotiatedLocale);
|
||||
templateResolver.setLocale(negotiatedLocale);
|
||||
}
|
||||
}
|
||||
Writable writable = template.make(binding.getVariables());
|
||||
httpServerContext.attributes().put("writable", writable);
|
||||
} catch (Exception e) {
|
||||
// fail silently by ignoring negotation
|
||||
templateResolver.setLocale(application.getLocale());
|
||||
Writable writable = template.make(binding.getVariables());
|
||||
httpServerContext.attributes().put("writable", writable);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
logger.log(Level.FINER, "rendering done: " + httpServerContext.isDone());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ dependencyResolutionManagement {
|
|||
version('netty-tcnative', '2.0.59.Final')
|
||||
version('datastructures', '2.0.0')
|
||||
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-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
|
||||
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
||||
|
|
Loading…
Reference in a new issue