working on forcing cookies, better header setting/adding, session/userprofile persisting

This commit is contained in:
Jörg Prante 2024-04-18 16:23:52 +02:00
parent e444bfca5a
commit 715ee9cd2a
49 changed files with 384 additions and 399 deletions

View file

@ -1,34 +0,0 @@
package org.xbib.net.http.j2html;
import org.xbib.net.Resource;
import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler;
import org.xbib.net.http.server.route.HttpRouterContext;
import java.io.IOException;
import java.nio.file.Path;
public class J2HtmlAuthResourceHandler extends HtmlTemplateResourceHandler {
public J2HtmlAuthResourceHandler() {
this(null, "java", "index.java");
}
public J2HtmlAuthResourceHandler(Path root, String suffix, String indexFileName) {
super(root, suffix, indexFileName);
}
@Override
protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return httpRouterContext.isAuthenticated() ?
createAuthenticatedResource(httpRouterContext) :
createUnauthenticatedResource(httpRouterContext);
}
protected Resource createAuthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new J2HtmlResource(this, httpRouterContext);
}
protected Resource createUnauthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new UnauthorizedHandler.UnauthorizedResource(this, httpRouterContext);
}
}

View file

@ -13,6 +13,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static org.xbib.j2html.TagCreator.body;
import static org.xbib.j2html.TagCreator.document;
import static org.xbib.j2html.TagCreator.h1;
import static org.xbib.j2html.TagCreator.html;
import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE;
@ -37,17 +38,13 @@ public class J2HtmlResource extends HtmlTemplateResource {
httpResponseStatus = getResponseStatus();
}
context.status(httpResponseStatus)
.header("cache-control", cacheControl()) // override default must-revalidate behavior
.header("content-length", Integer.toString(dataBuffer.writePosition()))
.header(CONTENT_TYPE, "text/html; charset=" + getCharset().displayName())
.setHeader("cache-control", "no-cache")
.setHeader("content-length", Integer.toString(dataBuffer.writePosition()))
.setHeader(CONTENT_TYPE, "text/html; charset=" + getCharset().displayName())
.body(dataBuffer)
.done();
}
protected String cacheControl() {
return "no-cache";
}
protected HttpResponseStatus getResponseStatus() {
return HttpResponseStatus.OK;
}
@ -65,6 +62,6 @@ public class J2HtmlResource extends HtmlTemplateResource {
* @return the body string fo the HTTP response
*/
protected String renderHtml(HttpRouterContext context) {
return html(body(h1("Hello World"))).render();
return document(html(body(h1("Hello World"))));
}
}

View file

@ -22,6 +22,12 @@ public class J2HtmlResourceHandler extends HtmlTemplateResourceHandler {
@Override
protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return httpRouterContext.isAuthenticated() ?
new J2HtmlResource(this, httpRouterContext) :
createUnauthenticatedResource(httpRouterContext);
}
protected Resource createUnauthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new J2HtmlResource(this, httpRouterContext);
}
}

View file

@ -61,13 +61,8 @@ public class J2HtmlServiceBuilder extends BaseHttpServiceBuilder {
public J2HtmlService build() {
if (handlers == null) {
if (securityDomain != null) {
HttpHandler httpHandler = new J2HtmlAuthResourceHandler(prefix, suffix, "index.java");
setHandler(httpHandler);
} else {
HttpHandler httpHandler = new J2HtmlResourceHandler(prefix, suffix, "index.java");
setHandler(httpHandler);
}
HttpHandler httpHandler = new J2HtmlResourceHandler(prefix, suffix, "index.java");
setHandler(httpHandler);
}
return new J2HtmlService(this);
}

View file

@ -138,7 +138,7 @@ public final class Bootstrap {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -165,14 +165,14 @@ public final class Bootstrap {
.setPath("/favicon.ico")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon)))
.done();
})
.build())
.addService(BaseHttpService.builder()
.setPath("/webjars/**")
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/"))
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/", 24 + 2600))
.build())
.addService(httpService)
.addService(GroovyTemplateService.builder()

View file

@ -11,8 +11,8 @@ import org.xbib.net.http.HttpHeaderValues;
import org.xbib.net.http.HttpResponseStatus;
import org.xbib.net.http.HttpVersion;
import org.xbib.net.http.j2html.InternalServerErrorHandler;
import org.xbib.net.http.j2html.J2HtmlAuthResourceHandler;
import org.xbib.net.http.j2html.J2HtmlResource;
import org.xbib.net.http.j2html.J2HtmlResourceHandler;
import org.xbib.net.http.j2html.J2HtmlService;
import org.xbib.net.http.server.application.web.WebApplication;
import org.xbib.net.http.server.auth.BasicAuthenticationHandler;
@ -148,20 +148,20 @@ public final class Bootstrap {
.setPath("/favicon.ico")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon)))
.done();
})
.build())
.addService(BaseHttpService.builder()
.setPath("/webjars/**")
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/"))
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/", 24 * 3600))
.build())
.addService(BaseHttpService.builder()
.setPath("/insecure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -203,10 +203,10 @@ public final class Bootstrap {
return 0;
}
static class MyResourceHandler extends J2HtmlAuthResourceHandler {
static class MyResourceHandler extends J2HtmlResourceHandler {
@Override
protected Resource createAuthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return new DemoResource(this, httpRouterContext);
}

View file

@ -62,7 +62,7 @@ public class NettyHttps2ServerMultiRequestLoadTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -62,7 +62,7 @@ public class NettyHttps2ServerTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -64,7 +64,7 @@ public class NettyHttpsServerMultiRequestLoadTest {
.setHandler(ctx -> {
logger.log(Level.INFO, "executing /secure");
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -151,7 +151,7 @@ public class NettyHttpsServerMultiRequestLoadTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +

View file

@ -68,7 +68,7 @@ public class NettyHttpsServerTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -146,7 +146,7 @@ public class NettyHttpsServerTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -223,7 +223,7 @@ public class NettyHttpsServerTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -51,7 +51,7 @@ public class NettyHttp2ServerMultiRequestLoadTest {
.setPath("/domain")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain: " +
" base URL = " + ctx.getRequest().getBaseURL() +

View file

@ -49,7 +49,7 @@ public class NettyHttp2ServerTest {
.setPath("/domain")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("Hello, here is my response: " +
ctx.getRequest().asJson())

View file

@ -48,7 +48,7 @@ public class NettyHttpServerBodyTest {
.setHandler(ctx -> {
String body = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString();
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -58,7 +58,7 @@ class NettyHttpServerByteOrderMarkTest {
String content = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString();
logger.log(Level.FINEST, "got content = " + content);
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -53,7 +53,7 @@ public class NettyHttpServerFailureTest {
.setPath("/domain")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain" +
" parameter = " + ctx.getRequest().getParameter().toString() +

View file

@ -82,7 +82,7 @@ public class NettyHttpServerFileUploadTest {
.map(m -> StandardCharsets.UTF_8.decode(m.getByteBuffer()))
.collect(Collectors.joining());
ctx.status(httpResponseStatus)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() +
@ -175,7 +175,7 @@ public class NettyHttpServerFileUploadTest {
}
}
ctx.status(httpResponseStatus)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -51,7 +51,7 @@ public class NettyHttpServerMultiRequestLoadTest {
.setPath("/domain")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain: " +
" base URL = " + ctx.getRequest().getBaseURL() +

View file

@ -55,7 +55,7 @@ public class NettyHttpServerRequestTest {
try {
String value = ctx.getRequest().getParameter().getAsString("value", Parameter.Domain.QUERY);
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("local address = " + ctx.getRequest().getLocalAddress() +
" remote address = " + ctx.getRequest().getRemoteAddress() +

View file

@ -52,7 +52,7 @@ public class NettyHttpServerTest {
.setPath("/domain")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body(ctx.getRequest().asJson())
.done();

View file

@ -35,7 +35,7 @@ public class NioHttpServerTest {
.setPath("/domain1")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain1 " +
ctx.getRequest().getParameter().toString() + " " +
@ -50,7 +50,7 @@ public class NioHttpServerTest {
.setPath("/domain2")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain2 " +
ctx.getRequest().getParameter().toString() + " " +

View file

@ -54,7 +54,7 @@ public class SimpleHttpsServerTest {
.setPath("/secure")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +

View file

@ -41,7 +41,7 @@ public class HttpRouterTest {
.setHandler(ctx -> {
Logger.getAnonymousLogger().log(Level.INFO, "got request: " + ctx.getRequestBuilder().getRequestURI());
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body(ctx.getRequestBuilder().getRequestURI());
})

View file

@ -35,7 +35,7 @@ public class SimpleHttpServerTest {
.setPath("/domain1")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain1 " +
ctx.getRequest().getParameter() + " " +
@ -54,7 +54,7 @@ public class SimpleHttpServerTest {
.setPath("/domain2")
.setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8)
.body("domain2 " +
ctx.getRequest().getParameter() + " " +

View file

@ -155,14 +155,17 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
if (done) {
return this;
}
if (name == null || value == null) {
return this;
}
if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) {
if (value.startsWith("text") && charset != null) {
value = value + "; charset=" + charset.name().toLowerCase(Locale.ROOT);
}
setContentType(value);
}
if (headers.containsHeader(name)) {
logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " overwriting with " + value);
if (headers.containsHeader(name) && !value.equals(headers.get(name))) {
logger.log(Level.WARNING, "header '" + name + "' already exist, old value = '" + headers.get(name) + "', overwriting with '" + value + "'");
}
headers.set(name, value);
return this;
@ -173,8 +176,18 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
if (done) {
return this;
}
if (name == null) {
return this;
}
if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) {
if (value.startsWith("text") && charset != null) {
value = value + "; charset=" + charset.name().toLowerCase(Locale.ROOT);
}
setContentType(value);
}
if (headers.containsHeader(name)) {
logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " adding " + value);
logger.log(Level.WARNING, "when adding '" + value + "', the header already exist: " + name + " - ignoring");
return this;
}
headers.add(name, value);
return this;
@ -315,11 +328,10 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
@Override
public BaseHttpResponseBuilder addCookie(Cookie cookie) {
if (done) {
return this;
}
Objects.requireNonNull(cookie);
headers.add(HttpHeaderNames.SET_COOKIE, CookieEncoder.STRICT.encode(cookie));
// skip done check, we force cookie add
Objects.requireNonNull(cookie, "cookie must not be null when adding");
String cookieValue = CookieEncoder.STRICT.encode(cookie);
headers.add(HttpHeaderNames.SET_COOKIE,cookieValue);
return this;
}

View file

@ -30,8 +30,8 @@ import org.xbib.net.http.server.executor.Executor;
import org.xbib.net.http.server.persist.Codec;
import org.xbib.net.http.server.render.HttpResponseRenderer;
import org.xbib.net.http.server.route.HttpRouter;
import org.xbib.net.http.server.session.IncomingSessionHandler;
import org.xbib.net.http.server.session.OutgoingSessionHandler;
import org.xbib.net.http.server.session.IncomingContextHandler;
import org.xbib.net.http.server.session.OutgoingContextHandler;
import org.xbib.net.http.server.session.PersistSessionHandler;
import org.xbib.net.http.server.session.Session;
import org.xbib.net.http.server.session.memory.MemoryPropertiesSessionCodec;
@ -171,9 +171,9 @@ public class BaseApplication implements Application {
if (builder.sessionsEnabled) {
Codec<Session> sessionCodec = newSessionCodec(httpRouterContext);
httpRouterContext.getAttributes().put("sessioncodec", sessionCodec);
httpRouterContext.addOpenHandler(newIncomingSessionHandler(sessionCodec));
httpRouterContext.addCloseHandler(newOutgoingSessionHandler());
httpRouterContext.addReleaseHandler(newPersistSessionHandler(sessionCodec));
httpRouterContext.addOpenHandler(newIncomingContextHandler(sessionCodec));
httpRouterContext.addCloseHandler(newOutgoingContextHandler());
httpRouterContext.addCloseHandler(newPersistHandler(sessionCodec));
}
httpRouterContext.addCloseHandler(newOutgoingCookieHandler());
return httpRouterContext;
@ -212,26 +212,22 @@ public class BaseApplication implements Application {
return new MemoryPropertiesSessionCodec(sessionName,this, 1024, Duration.ofDays(1));
}
protected HttpHandler newIncomingSessionHandler(Codec<Session> sessionCodec) {
return new IncomingSessionHandler(
protected HttpHandler newIncomingContextHandler(Codec<Session> sessionCodec) {
return new IncomingContextHandler(
getSecret(),
"HmacSHA1",
sessionName,
sessionCodec,
getStaticFileSuffixes(),
"user_id",
"e_user_id",
() -> RandomUtil.randomString(16));
}
protected HttpHandler newOutgoingSessionHandler() {
return new OutgoingSessionHandler(
protected HttpHandler newOutgoingContextHandler() {
return new OutgoingContextHandler(
getSecret(),
"HmacSHA1",
sessionName,
getStaticFileSuffixes(),
"user_id",
"e_user_id",
Duration.ofDays(1),
true,
false,
@ -239,7 +235,7 @@ public class BaseApplication implements Application {
);
}
protected HttpHandler newPersistSessionHandler(Codec<Session> sessionCodec) {
protected HttpHandler newPersistHandler(Codec<Session> sessionCodec) {
return new PersistSessionHandler(sessionCodec);
}

View file

@ -1,6 +1,8 @@
package org.xbib.net.http.server.auth;
import java.util.LinkedHashMap;
import java.util.Map;
import org.xbib.net.Attributes;
@SuppressWarnings("serial")
@ -10,6 +12,10 @@ public class BaseAttributes extends LinkedHashMap<String, Object> implements Att
super();
}
public BaseAttributes(Map<String, Object> map) {
super(map);
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Class<T> cl, String key) {

View file

@ -1,35 +1,33 @@
package org.xbib.net.http.server.auth;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import org.xbib.datastructures.tiny.TinyMap;
import org.xbib.net.Attributes;
import org.xbib.net.UserProfile;
public class BaseUserProfile implements UserProfile {
private final Attributes attributes;
private final Attributes effectiveAttributes;
private final List<String> roles;
private final List<String> effectiveRoles;
private final List<String> permissions;
private final List<String> effectivePermissions;
private String uid;
private String euid;
private String name;
private boolean isRemembered;
private Attributes attributes;
private Collection<String> roles;
private Collection<String> effectiveRoles;
private Collection<String> permissions;
private Collection<String> effectivePermissions;
public BaseUserProfile() {
this.attributes = new BaseAttributes();
this.effectiveAttributes = new BaseAttributes();
this.roles = new ArrayList<>();
this.effectiveRoles = new ArrayList<>();
this.permissions = new ArrayList<>();
@ -71,105 +69,64 @@ public class BaseUserProfile implements UserProfile {
roles.add(role);
}
@Override
public void setRoles(Collection<String> roles) {
this.roles = roles;
}
@Override
public Collection<String> getRoles() {
return roles;
}
@Override
public void addEffectiveRole(String role) {
effectiveRoles.add(role);
}
@Override
public List<String> getRoles() {
return roles;
public void setEffectiveRoles(Collection<String> effectiveRoles) {
this.effectiveRoles = effectiveRoles;
}
@Override
public List<String> getEffectiveRoles() {
public Collection<String> getEffectiveRoles() {
return effectiveRoles;
}
public boolean hasRole(String role) {
return roles.contains(role);
}
public boolean hasEffectiveRole(String role) {
return effectiveRoles.contains(role);
}
public boolean hasAccess(String requireAnyRole, String requireAllRoles) {
boolean access = true;
if (!requireAnyRole.isEmpty()) {
String[] expectedRoles = requireAnyRole.split(",");
if (!hasAnyRole(expectedRoles)) {
access = false;
}
} else if (!requireAllRoles.isEmpty()) {
String[] expectedRoles = requireAllRoles.split(",");
if (!hasAllRoles(expectedRoles)) {
access = false;
}
}
return access;
}
public boolean hasAnyRole(String[] expectedRoles) {
if (expectedRoles == null || expectedRoles.length == 0) {
return true;
}
for (final String role : expectedRoles) {
if (roles.contains(role)) {
return true;
}
}
return false;
}
public boolean hasAnyEffectiveRole(String[] expectedRoles) {
if (expectedRoles == null || expectedRoles.length == 0) {
return true;
}
for (final String role : expectedRoles) {
if (effectiveRoles.contains(role)) {
return true;
}
}
return false;
}
public boolean hasAllRoles(String[] expectedRoles) {
if (expectedRoles == null) {
return true;
}
for (String role : expectedRoles) {
if (!roles.contains(role)) {
return false;
}
}
return true;
}
public boolean hasAllEffectiveRoles(String[] expectedRoles) {
if (expectedRoles == null) {
return true;
}
for (String role : expectedRoles) {
if (!effectiveRoles.contains(role)) {
return false;
}
}
return true;
}
@Override
public void addPermission(String permission) {
permissions.add(permission);
}
public void removePermission(String permission) {
permissions.remove(permission);
@Override
public void setPermissions(Collection<String> permissions) {
this.permissions = permissions;
}
@Override
public void setRemembered(boolean remembered) {
this.isRemembered = remembered;
public Collection<String> getPermissions() {
return permissions;
}
@Override
public void addEffectivePermission(String permission) {
effectivePermissions.add(permission);
}
@Override
public void setEffectivePermissions(Collection<String> effectivePermissions) {
this.effectivePermissions = effectivePermissions;
}
@Override
public Collection<String> getEffectivePermissions() {
return effectivePermissions;
}
@Override
public void setAttributes(Attributes attributes) {
this.attributes = attributes;
}
@Override
@ -178,38 +135,74 @@ public class BaseUserProfile implements UserProfile {
}
@Override
public Attributes getEffectiveAttributes() {
return effectiveAttributes;
public Map<String, Object> asMap() {
TinyMap.Builder<String, Object> builder = TinyMap.builder();
builder.put("name", getName());
String userId = getUserId();
if (userId == null) {
userId = "";
}
builder.put("user_id", userId);
String eUserId = getEffectiveUserId();
if (eUserId == null) {
eUserId = "";
}
builder.put("e_user_id", eUserId);
if (getRoles() != null && !getRoles().isEmpty()) {
builder.put("roles", getRoles());
}
if (getEffectiveRoles() != null && !getEffectiveRoles().isEmpty()) {
builder.put("e_roles", getEffectiveRoles());
}
if (getPermissions() != null && !getPermissions().isEmpty()) {
builder.put("perms", getPermissions());
}
if (getEffectivePermissions() != null && !getEffectivePermissions().isEmpty()) {
builder.put("e_perms", getEffectivePermissions());
}
if (getAttributes() != null && !getAttributes().isEmpty()) {
builder.put("attrs", getAttributes());
}
return builder.build();
}
@Override
public List<String> getPermissions() {
return permissions;
}
@Override
public void addEffectivePermission(String permission) {
effectivePermissions.add(permission);
}
@Override
public List<String> getEffectivePermissions() {
return effectivePermissions;
}
@Override
public boolean isRemembered() {
return isRemembered;
}
@Override
public String toString() {
return "uid=" + uid +
",roles=" + roles +
",permissons=" + permissions +
",attributes=" + attributes +
",euid=" + euid +
",eroles=" + effectiveRoles +
",epermissions=" + effectivePermissions +
",eattributes=" + effectiveAttributes;
@SuppressWarnings("unchecked")
public static UserProfile fromMap(Map<String, Object> map) {
BaseUserProfile userProfile = new BaseUserProfile();
if (map.containsKey("name")) {
userProfile.setName((String) map.get("name"));
}
if (map.containsKey("user_id")) {
String userId = (String) map.get("user_id");
// empty user ID for better map transport, change it to null
if (userId != null && userId.isEmpty()) {
userId = null;
}
userProfile.setUserId(userId);
}
if (map.containsKey("e_user_id")) {
String eUserId = (String) map.get("e_user_id");
// empty effective user ID for better map transport, change it to null
if (eUserId != null && eUserId.isEmpty()) {
eUserId = null;
}
userProfile.setEffectiveUserId(eUserId);
}
if (map.containsKey("roles")) {
userProfile.setRoles((Collection<String>) map.get("roles"));
}
if (map.containsKey("e_roles")) {
userProfile.setEffectiveRoles((Collection<String>) map.get("e_roles"));
}
if (map.containsKey("perms")) {
userProfile.setPermissions((Collection<String>) map.get("perms"));
}
if (map.containsKey("e_perms")) {
userProfile.setEffectivePermissions((Collection<String>) map.get("e_perms"));
}
if (map.containsKey("attrs")) {
userProfile.setAttributes(new BaseAttributes((Map<String, Object>) map.get("attrs")));
}
return userProfile;
}
}

View file

@ -52,6 +52,6 @@ public class BasicAuthenticationHandler extends LoginAuthenticationHandler imple
}
logger.log(Level.INFO, "unauthenticated");
context.status(HttpResponseStatus.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"" + getSecurityRealm().getName() + "\"");
.setHeader("WWW-Authenticate", "Basic realm=\"" + getSecurityRealm().getName() + "\"");
}
}

View file

@ -26,7 +26,7 @@ public class FormAuthenticationHandler extends LoginAuthenticationHandler implem
logger.log(Level.FINE, "request = " + context.getRequest());
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
if (userProfile != null && userProfile.getUserId() != null) {
logger.log(Level.FINE, "user id already set: " + userProfile.getUserId());
logger.log(Level.FINE, "already authenticated, user id = " + userProfile.getUserId());
context.setAuthenticated(true);
return;
}

View file

@ -20,6 +20,7 @@ public class OutgoingCookieHandler implements HttpHandler {
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies");
if (cookieBox != null) {
for (Cookie cookie : cookieBox) {
// move cookie to http response
context.cookie(cookie);
logger.log(Level.FINEST, "cookie prepared for outgoing = " + cookie);
}

View file

@ -15,7 +15,7 @@ public class BadRequestHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.BAD_REQUEST)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain;charset=utf-8")
.body("Bad request")
.done();
}

View file

@ -15,7 +15,7 @@ public class ForbiddenHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.FORBIDDEN)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Forbidden")
.done();
}

View file

@ -32,7 +32,7 @@ public class InternalServerErrorHandler implements HttpErrorHandler {
message = throwable != null ? throwable.getMessage() : "";
}
context.status(status)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body(message)
.done();
}

View file

@ -15,7 +15,7 @@ public class NotFoundHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.NOT_FOUND)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Not found")
.done();
}

View file

@ -15,7 +15,7 @@ public class NotImplementedHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.NOT_IMPLEMENTED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain;charset=utf-8")
.body("Not implemented")
.done();
}

View file

@ -15,7 +15,7 @@ public class UnauthorizedHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.UNAUTHORIZED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Unauthorized")
.done();
}

View file

@ -15,7 +15,7 @@ public class VersionNotSupportedHandler implements HttpErrorHandler {
@Override
public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8")
.setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("HTTP version not supported")
.done();
}

View file

@ -41,6 +41,7 @@ public class LdapGroupsProvider extends GroupsProvider {
* Get groups, or null if not possible.
* @throws LdapException if unable to retrieve groups
*/
@Override
public Collection<String> getGroups(String username) {
if (userMappings == null) {
return null;

View file

@ -46,13 +46,13 @@ public abstract class AbstractResourceHandler implements HttpHandler {
protected abstract Resource createResource(HttpRouterContext httpRouterContext) throws IOException;
protected abstract boolean isETagResponseEnabled();
protected abstract boolean isETagEnabled();
protected abstract boolean isCacheResponseEnabled();
protected abstract boolean isRangeEnabled();
protected abstract boolean isRangeResponseEnabled();
protected abstract int getCacheMaxAgeSeconds();
protected abstract int getMaxAgeSeconds();
protected abstract String getCacheControl();
@Override
public void handle(HttpRouterContext context) throws IOException {
@ -75,7 +75,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
.build()
.toString();
logger.log(Level.FINEST, "client must add a /, external redirect to = " + loc);
context.header(HttpHeaderNames.LOCATION, loc)
context.setHeader(HttpHeaderNames.LOCATION, loc)
.status(HttpResponseStatus.TEMPORARY_REDIRECT);
} else if (resource.isExistsIndexFile()) {
// internal redirect to default index file in this directory
@ -102,7 +102,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
}
HttpHeaders headers = context.getRequestBuilder().getHeaders();
String contentType = resource.getMimeType();
context.header(CONTENT_TYPE, contentType);
context.setHeader(CONTENT_TYPE, contentType);
// heuristic for inline disposition
String disposition;
if (!contentType.startsWith("text") && !contentType.startsWith("image") && !contentType.startsWith("font")) {
@ -114,22 +114,26 @@ public abstract class AbstractResourceHandler implements HttpHandler {
if (resource.getBaseName() != null && resource.getSuffix() != null) {
String contentDisposition = disposition + ";filename=\"" + resource.getBaseName() + '.' + resource.getSuffix() + '"';
logger.log(Level.FINEST, () -> "content type = " + contentType + " content disposition = " + contentDisposition);
context.header(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition);
context.setHeader(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition);
}
long expirationMillis = System.currentTimeMillis() + 1000L * getMaxAgeSeconds();
String expires = DateTimeUtil.formatRfc1123(expirationMillis);
if (isCacheResponseEnabled()) {
String cacheControl = "public, max-age=" + getMaxAgeSeconds();
logger.log(Level.FINEST, () -> "cache response, expires = " + expires + " cache control = " + cacheControl);
context.header(HttpHeaderNames.EXPIRES, expires)
.header(HttpHeaderNames.CACHE_CONTROL, cacheControl);
if (getCacheMaxAgeSeconds() > 0) {
long expirationMillis = System.currentTimeMillis() + 1000L * getCacheMaxAgeSeconds();
String expires = DateTimeUtil.formatRfc1123(expirationMillis);
String cacheControl = "public, max-age=" + getCacheMaxAgeSeconds();
logger.log(Level.FINEST, () -> "expires = " + expires + " cache control = " + cacheControl);
context.setHeader(HttpHeaderNames.EXPIRES, expires)
.setHeader(HttpHeaderNames.CACHE_CONTROL, cacheControl);
} else {
logger.log(Level.FINEST, () -> "uncached response");
context.header(HttpHeaderNames.EXPIRES, "0")
.header(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
if (getCacheControl() == null) {
context.setHeader(HttpHeaderNames.EXPIRES, "0")
.setHeader(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
} else {
context.setHeader(HttpHeaderNames.EXPIRES, "0")
.setHeader(HttpHeaderNames.CACHE_CONTROL, getCacheControl());
}
}
boolean sent = false;
if (isETagResponseEnabled()) {
if (isETagEnabled()) {
Instant lastModifiedInstant = resource.getLastModified();
String eTag = Long.toHexString(resource.getResourcePath().hashCode() + lastModifiedInstant.toEpochMilli() + resource.getLength());
logger.log(Level.FINEST, () -> "eTag = " + eTag);
@ -149,7 +153,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
logger.log(Level.FINEST, () -> "not modified, eTag = " + eTag);
context.header(HttpHeaderNames.ETAG, eTag)
context.setHeader(HttpHeaderNames.ETAG, eTag)
.status(HttpResponseStatus.NOT_MODIFIED);
return;
}
@ -157,15 +161,15 @@ public abstract class AbstractResourceHandler implements HttpHandler {
if (ifModifiedSinceInstant != null &&
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
logger.log(Level.FINEST, () -> "not modified (after if-modified-since), eTag = " + eTag);
context.header(HttpHeaderNames.ETAG, eTag)
context.setHeader(HttpHeaderNames.ETAG, eTag)
.status(HttpResponseStatus.NOT_MODIFIED);
return;
}
String lastModified = DateTimeUtil.formatRfc1123(lastModifiedInstant);
logger.log(Level.FINEST, () -> "sending resource, lastModified = " + lastModified);
context.header(HttpHeaderNames.ETAG, eTag)
.header(HttpHeaderNames.LAST_MODIFIED, lastModified);
if (isRangeResponseEnabled()) {
context.setHeader(HttpHeaderNames.ETAG, eTag)
.setHeader(HttpHeaderNames.LAST_MODIFIED, lastModified);
if (isRangeEnabled()) {
performRangeResponse(context, resource, contentType, eTag, headers);
sent = true;
} else {
@ -176,7 +180,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
long length = resource.getLength();
if (length > 0L) {
String string = Long.toString(resource.getLength());
context.header(HttpHeaderNames.CONTENT_LENGTH, string);
context.setHeader(HttpHeaderNames.CONTENT_LENGTH, string);
logger.log(Level.FINEST, "length is known = " + resource.getLength());
send(resource, HttpResponseStatus.OK, contentType, context, 0L, resource.getLength());
} else {
@ -194,13 +198,13 @@ public abstract class AbstractResourceHandler implements HttpHandler {
HttpHeaders headers) throws IOException {
long length = resource.getLength();
logger.log(Level.FINEST, "performing range response on resource = " + resource);
context.header(HttpHeaderNames.ACCEPT_RANGES, "bytes");
context.setHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes");
Range full = new Range(0, length - 1, length);
List<Range> ranges = new ArrayList<>();
String range = headers.get(HttpHeaderNames.RANGE);
if (range != null) {
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
@ -226,7 +230,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
end = length - 1;
}
if (start > end) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
@ -235,16 +239,16 @@ public abstract class AbstractResourceHandler implements HttpHandler {
}
}
if (ranges.isEmpty() || ranges.getFirst() == full) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
.header(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
.setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
send(resource, HttpResponseStatus.OK, contentType, context, full.start, full.length);
} else if (ranges.size() == 1) {
Range r = ranges.getFirst();
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
.header(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
.setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
send(resource, HttpResponseStatus.PARTIAL_CONTENT, contentType, context, r.start, r.length);
} else {
context.header(CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY");
context.setHeader(CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY");
StringBuilder sb = new StringBuilder();
for (Range r : ranges) {
try {
@ -262,7 +266,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
}
}
context.status(HttpResponseStatus.OK)
.header(CONTENT_TYPE, contentType)
.setHeader(CONTENT_TYPE, contentType)
.body(CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1);
}
}
@ -296,7 +300,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
} else if (context.getRequestBuilder().getMethod() == HttpMethod.HEAD) {
logger.log(Level.FINEST, "HEAD request, do not send body");
context.status(HttpResponseStatus.OK)
.header(CONTENT_TYPE, contentType);
.setHeader(CONTENT_TYPE, contentType);
} else {
if ("file".equals(url.getScheme())) {
Path path = resource.getPath();
@ -338,7 +342,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
try (ReadableByteChannel channel = fileChannel) {
DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size);
context.status(httpResponseStatus)
.header(CONTENT_TYPE, contentType)
.setHeader(CONTENT_TYPE, contentType)
.body(dataBuffer);
}
}
@ -358,7 +362,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size);
context.status(httpResponseStatus)
.header(CONTENT_TYPE, contentType)
.setHeader(CONTENT_TYPE, contentType)
.body(dataBuffer);
}
}

View file

@ -19,13 +19,18 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler {
private final String resourcePrefix;
private final int cacheSeconds;
public ClassLoaderResourceHandler(ClassLoader classLoader) {
this(classLoader, null);
this(classLoader, null, 24 * 3600);
}
public ClassLoaderResourceHandler(ClassLoader classLoader, String resourcePrefix) {
public ClassLoaderResourceHandler(ClassLoader classLoader,
String resourcePrefix,
int cacheSeconds) {
this.classLoader = classLoader;
this.resourcePrefix = resourcePrefix;
this.cacheSeconds = cacheSeconds;
}
@Override
@ -34,23 +39,23 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler {
}
@Override
protected boolean isETagResponseEnabled() {
protected boolean isETagEnabled() {
return true;
}
@Override
protected boolean isCacheResponseEnabled() {
protected String getCacheControl() {
return "public";
}
@Override
protected boolean isRangeEnabled() {
return true;
}
@Override
protected boolean isRangeResponseEnabled() {
return true;
}
@Override
protected int getMaxAgeSeconds() {
return 24 * 3600;
protected int getCacheMaxAgeSeconds() {
return cacheSeconds;
}
class ClassLoaderResource implements Resource {

View file

@ -24,14 +24,20 @@ public class FileResourceHandler extends AbstractResourceHandler {
private final String pathNameOfResource;
private final int cacheSeconds;
public FileResourceHandler() {
this(null, "index.html", null);
this(null, "index.html", null, 24 * 3600);
}
public FileResourceHandler(String webRoot, String indexFileName, String pathNameOfResource) {
public FileResourceHandler(String webRoot,
String indexFileName,
String pathNameOfResource,
int cacheSeconds) {
this.webRoot = webRoot;
this.indexFileName = indexFileName;
this.pathNameOfResource = pathNameOfResource;
this.cacheSeconds = cacheSeconds;
}
@Override
@ -58,23 +64,23 @@ public class FileResourceHandler extends AbstractResourceHandler {
}
@Override
protected boolean isETagResponseEnabled() {
protected boolean isETagEnabled() {
return true;
}
@Override
protected boolean isCacheResponseEnabled() {
protected boolean isRangeEnabled() {
return true;
}
@Override
protected boolean isRangeResponseEnabled() {
return true;
protected int getCacheMaxAgeSeconds() {
return cacheSeconds;
}
@Override
protected int getMaxAgeSeconds() {
return 24 * 3600;
public String getCacheControl() {
return "public";
}
protected class FileResource implements Resource {

View file

@ -27,25 +27,26 @@ public class HtmlTemplateResourceHandler extends AbstractResourceHandler {
}
@Override
protected boolean isETagResponseEnabled() {
protected boolean isETagEnabled() {
return false;
}
@Override
protected boolean isCacheResponseEnabled() {
protected boolean isRangeEnabled() {
return false;
}
@Override
protected boolean isRangeResponseEnabled() {
return false;
}
@Override
protected int getMaxAgeSeconds() {
protected int getCacheMaxAgeSeconds() {
// disables caching
return 0;
}
@Override
protected String getCacheControl() {
return "no-cache";
}
public Path getRoot() {
return root;
}

View file

@ -56,7 +56,7 @@ public class WebRootResourceResolver implements ResourceResolver {
.build()
.toString();
httpRouterContext.status(HttpResponseStatus.TEMPORARY_REDIRECT)
.header("location", loc);
.setHeader("location", loc);
}
}
return resource;

View file

@ -230,7 +230,13 @@ public class BaseHttpRouterContext implements HttpRouterContext {
}
@Override
public BaseHttpRouterContext header(String name, String value) {
public BaseHttpRouterContext setHeader(String name, String value) {
httpResponseBuilder.setHeader(name, value);
return this;
}
@Override
public BaseHttpRouterContext addHeader(String name, String value) {
httpResponseBuilder.addHeader(name, value);
return this;
}

View file

@ -79,7 +79,9 @@ public interface HttpRouterContext {
HttpRouterContext charset(Charset charset);
HttpRouterContext header(String name, String value);
HttpRouterContext setHeader(String name, String value);
HttpRouterContext addHeader(String name, String value);
HttpRouterContext cookie(Cookie cookie);

View file

@ -24,9 +24,9 @@ import org.xbib.net.http.server.cookie.CookieSignatureException;
import org.xbib.net.http.server.cookie.CookieSignatureUtil;
import org.xbib.net.http.server.persist.Codec;
public class IncomingSessionHandler implements HttpHandler {
public class IncomingContextHandler implements HttpHandler {
private static final Logger logger = Logger.getLogger(IncomingSessionHandler.class.getName());
private static final Logger logger = Logger.getLogger(IncomingContextHandler.class.getName());
private final String sessionSecret;
@ -36,32 +36,21 @@ public class IncomingSessionHandler implements HttpHandler {
private final Codec<Session> sessionCodec;
private final String sessionUserName;
private final String sessionEffectiveUserName;
/**
* These suffixes disable incoming session creation.
*/
private final Set<String> suffixes;
Supplier<String> sessionIdGenerator;
public IncomingSessionHandler(String sessionSecret,
public IncomingContextHandler(String sessionSecret,
String sessionCookieAlgorithm,
String sessionCookieName,
Codec<Session> sessionCodec,
Set<String> suffixes,
String sessionUserName,
String sessionEffectiveUserName,
Supplier<String> sessionIdGenerator) {
this.sessionSecret = sessionSecret;
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
this.sessionCookieName = sessionCookieName;
this.sessionCodec = sessionCodec;
this.suffixes = suffixes;
this.sessionUserName = sessionUserName;
this.sessionEffectiveUserName = sessionEffectiveUserName;
this.sessionIdGenerator = sessionIdGenerator;
}
@ -73,14 +62,20 @@ public class IncomingSessionHandler implements HttpHandler {
}
Map<String, Object> payload = null;
Session session = null;
UserProfile userProfile = null;
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "incomingcookies");
if (cookieBox != null) {
for (Cookie cookie : cookieBox) {
if (cookie.name().equals(sessionCookieName)) {
logger.log(Level.FINE, "found our cookie " + sessionCookieName);
if (session == null) {
try {
payload = decodeCookie(cookie);
logger.log(Level.FINE, "payload from cookie = " + payload);
session = toSession(payload);
logger.log(Level.FINE, "session from payload = " + session);
userProfile = toUserProfile(session);
logger.log(Level.FINE, "userprofile from session = " + userProfile);
} catch (CookieSignatureException e) {
// set exception in context to discard broken cookie later and render exception message
context.getAttributes().put("_throwable", e);
@ -97,19 +92,63 @@ public class IncomingSessionHandler implements HttpHandler {
if (session == null) {
try {
session = sessionCodec.create(sessionIdGenerator.get());
logger.log(Level.FINE, "creating new session " + session.id());
} catch (IOException 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 new session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
if (payload != null) {
UserProfile userProfile = newUserProfile(payload, session);
if (userProfile != null) {
context.getAttributes().put("userprofile", userProfile);
}
if (userProfile == null) {
logger.log(Level.FINE, "creating new user profile");
userProfile = newUserProfile(payload);
}
logger.log(Level.FINEST, "incoming session ID = " + session.id() + " keys = " + session.keySet());
context.getAttributes().put("session", session);
context.getAttributes().put("userprofile", userProfile);
}
protected Session toSession(Map<String, Object> map) {
return toSession((String) map.get("id"), map);
}
protected Session toSession(String id, Map<String, Object> map) {
Session session = null;
try {
session = sessionCodec.read(id);
if (session != null && map != null) {
logger.log(Level.FINE, "session id " + id + " restored");
session.putAll(map);
}
} catch (Exception e) {
logger.log(Level.FINEST, "unable to restore session, id = " + id, e);
}
return session;
}
@SuppressWarnings("unchecked")
protected UserProfile toUserProfile(Session session) {
Map<String, Object> map = (Map<String, Object>) session.get("userprofile");
return map != null ? BaseUserProfile.fromMap(map) : null;
}
@SuppressWarnings("unchecked")
protected UserProfile newUserProfile(Map<String, Object> cookieMap) {
UserProfile userProfile = new BaseUserProfile();
// user_id, e_user_id are in cookie map
if (cookieMap != null) {
Map<String, Object> m = (Map<String, Object>) cookieMap.get("map");
if (m == null) {
// nothing found
return userProfile;
}
if (m.containsKey("user_id")) {
userProfile.setUserId((String) m.get("user_id"));
}
if (m.containsKey("e_user_id")) {
userProfile.setEffectiveUserId((String) m.get("e_user_id"));
}
// roles, permissions, attributes must be filled later
}
return userProfile;
}
private Map<String, Object> decodeCookie(Cookie cookie) throws IOException,
@ -127,48 +166,10 @@ public class IncomingSessionHandler implements HttpHandler {
String sig = s[2];
String mysig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm);
if (!sig.equals(mysig)) {
logger.log(Level.SEVERE, MessageFormat.format("signature in cookie does not match. algo={1} secret={2} payload={3} sig={4} mysig={5}",
logger.log(Level.SEVERE, () -> MessageFormat.format("signature in cookie does not match. algo={0} secret={1} payload={2} sig={3} mysig={4}",
sessionCookieAlgorithm, sessionSecret, payload, sig, mysig));
throw new CookieSignatureException("cookie security problem");
}
Map<String, Object> map = CookieSignatureUtil.toMap(payload);
return Map.of("id", id, "payload", payload, "map", map);
}
protected Session toSession(Map<String, Object> map) {
return toSession((String) map.get("id"), map);
}
protected Session toSession(String id, Map<String, Object> map) {
Session session = null;
try {
session = sessionCodec.read(id);
if (session != null && map != null) {
session.putAll(map);
}
} catch (Exception e) {
logger.log(Level.FINEST, "unable to read session, id = " + id, e);
}
return session;
}
@SuppressWarnings("unchecked")
protected UserProfile newUserProfile(Map<String, Object> map, Session session) {
UserProfile userProfile = new BaseUserProfile();
Map<String, Object> m = (Map<String, Object>) map.get("map");
if (m == null) {
return userProfile;
}
if (sessionUserName != null && m.containsKey(sessionUserName)) {
userProfile.setUserId((String) m.get(sessionUserName));
}
if (sessionEffectiveUserName != null && m.containsKey(sessionEffectiveUserName)) {
userProfile.setEffectiveUserId((String) m.get(sessionEffectiveUserName));
}
if (session != null && userProfile.getUserId() != null) {
session.put("user_id", userProfile.getUserId());
session.put("e_user_id", userProfile.getUserId());
}
return userProfile;
return Map.of("id", id, "payload", payload, "map", CookieSignatureUtil.toMap(payload));
}
}

View file

@ -5,7 +5,6 @@ import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@ -25,9 +24,9 @@ import org.xbib.net.http.server.application.Application;
import org.xbib.net.http.server.cookie.CookieSignatureException;
import org.xbib.net.http.server.cookie.CookieSignatureUtil;
public class OutgoingSessionHandler implements HttpHandler {
public class OutgoingContextHandler implements HttpHandler {
private static final Logger logger = Logger.getLogger(OutgoingSessionHandler.class.getName());
private static final Logger logger = Logger.getLogger(OutgoingContextHandler.class.getName());
private final String sessionSecret;
@ -39,22 +38,16 @@ public class OutgoingSessionHandler implements HttpHandler {
private final Set<String> suffixes;
private final String sessionUserName;
private final String sessionEffectiveUserName;
private final boolean httpOnly;
private final boolean secure;
private final SameSite sameSite;
public OutgoingSessionHandler(String sessionSecret,
public OutgoingContextHandler(String sessionSecret,
String sessionCookieAlgorithm,
String sessionCookieName,
Set<String> suffixes,
String sessionUserName,
String sessionEffectiveUserName,
Duration sessionDuration,
boolean httpOnly,
boolean secure,
@ -63,8 +56,6 @@ public class OutgoingSessionHandler implements HttpHandler {
this.sessionCookieAlgorithm = sessionCookieAlgorithm;
this.sessionCookieName = sessionCookieName;
this.suffixes = suffixes;
this.sessionUserName = sessionUserName;
this.sessionEffectiveUserName = sessionEffectiveUserName;
this.sessionDuration = sessionDuration;
this.httpOnly = httpOnly;
this.secure = secure;
@ -79,7 +70,6 @@ public class OutgoingSessionHandler implements HttpHandler {
}
String suffix = SessionUtil.extractExtension(context.getRequestBuilder().getRequestPath());
if (suffix != null && suffixes.contains(suffix)) {
logger.log(Level.FINEST, () -> "suffix " + suffix + " blocking outgoing session handling");
return;
}
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies");
@ -87,7 +77,6 @@ public class OutgoingSessionHandler implements HttpHandler {
cookieBox = new CookieBox();
}
Application application = context.getAttributes().get(Application.class, "application");
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
String host = context.getContextURL().getHost();
String path = application.getContextPath();
Throwable throwable = context.getAttributes().get(Throwable.class, "_throwable");
@ -99,16 +88,11 @@ public class OutgoingSessionHandler implements HttpHandler {
Session session = context.getAttributes().get(Session.class, "session");
if (session != null) {
try {
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
if (userProfile != null) {
logger.log(Level.FINEST, () -> "user profile present: " + userProfile);
if (sessionUserName != null) {
session.put(sessionUserName, userProfile.getUserId());
}
if (sessionEffectiveUserName != null) {
session.put(sessionEffectiveUserName, userProfile.getEffectiveUserId());
}
session.put("userprofile", userProfile.asMap());
}
Cookie cookie = encodeCookie(session, host, path);
Cookie cookie = encodeCookie(session, userProfile, host, path);
if (cookie != null) {
cookieBox.add(cookie);
}
@ -117,12 +101,18 @@ public class OutgoingSessionHandler implements HttpHandler {
throw new HttpException("unable to create session data for cookie", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
logger.log(Level.FINEST, "prepared outgoing cookies = " + cookieBox);
context.getAttributes().put("outgoingcookies", cookieBox);
}
private Cookie encodeCookie(Session session, String host, String path) throws IOException,
private Cookie encodeCookie(Session session,
UserProfile userProfile,
String host,
String path) throws IOException,
NoSuchAlgorithmException, InvalidKeyException {
if (session == null) {
logger.log(Level.WARNING, "no session, no cookie");
return null;
}
if (sessionSecret == null) {
logger.log(Level.WARNING, "no secret, no cookie");
return null;
@ -137,13 +127,10 @@ public class OutgoingSessionHandler implements HttpHandler {
session.invalidate();
return createEmptyCookie(host, path);
}
Map<String, Object> map = new HashMap<>();
if (sessionUserName != null) {
map.put(sessionUserName, session.get(sessionUserName));
}
if (sessionEffectiveUserName != null) {
map.put(sessionEffectiveUserName, session.get(sessionEffectiveUserName));
}
Map<String, Object> map = userProfile != null ?
Map.of("user_id", userProfile.getUserId() != null ? userProfile.getUserId() :"",
"e_user_id", userProfile.getEffectiveUserId() != null ? userProfile.getEffectiveUserId() : "") :
Map.of();
String payload = CookieSignatureUtil.toString(map);
String sig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm);
String cookieValue = String.join(":", id, payload, sig);

View file

@ -28,7 +28,7 @@ public class PersistSessionHandler implements HttpHandler {
sessionCodec.write(session.id(), session);
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
throw new HttpException("unable to create session data for cookie", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
throw new HttpException("unable to write session data", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
}

View file

@ -30,9 +30,9 @@ public class GroovyTemplateRenderer implements HttpHandler {
}
HttpResponseStatus httpResponseStatus = context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK);
context.status(httpResponseStatus)
.header("cache-control", "no-cache") // override default must-revalidate behavior
.header("content-length", Integer.toString(dataBuffer.writePosition()))
.header(CONTENT_TYPE, "text/html; charset=" + StandardCharsets.UTF_8.displayName())
.setHeader("cache-control", "no-cache")
.setHeader("content-length", Integer.toString(dataBuffer.writePosition()))
.setHeader(CONTENT_TYPE, "text/html; charset=" + StandardCharsets.UTF_8.displayName())
.body(dataBuffer);
}
}