Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-5133-webappcontext-extraclasspath-cleanup

This commit is contained in:
Joakim Erdfelt 2020-09-22 12:24:01 -05:00
commit 33286db44b
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
925 changed files with 14293 additions and 8956 deletions

5
Jenkinsfile vendored
View File

@ -39,12 +39,13 @@ pipeline {
}
}
}
stage( "Build / Test - JDK14" ) {
stage("Build / Test - JDK15") {
agent { node { label 'linux' } }
steps {
container( 'jetty-build' ) {
timeout( time: 120, unit: 'MINUTES' ) {
mavenBuild( "jdk14", "-T3 clean install", "maven3", true )
mavenBuild( "jdk15", "-T3 clean install", "maven3", true )
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml'
}

View File

@ -1,7 +1,7 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables use of the apache implementation of JSP
Enables use of the apache implementation of JSP.
[lib]
lib/apache-jsp/*.jar

View File

@ -1,7 +1,7 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables the apache version of JSTL
Enables the apache version of JSTL for all webapps.
[lib]
lib/apache-jstl/*.jar

View File

@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
* <ul>
* <li>The <code>jetty.home</code> system property</li>
* <li>The <code>JETTY_HOME</code> environment variable</li>
* <li>The working directory hierarchy with subdirectory <code>jetty-distribution/target/home</code></li>
* <li>The working directory hierarchy with subdirectory <code>jetty-distribution/target/distribution</code></li>
* </ul>
*/
public class JettyDistribution
@ -41,12 +41,23 @@ public class JettyDistribution
static
{
Path distro = asJettyDistribution(System.getProperty("jetty.home"));
LOG.debug("JettyDistribution(prop(jetty.home)) = " + distro);
if (distro == null)
Path jettyHome = asDirectory(System.getProperty("jetty.home"));
LOG.debug("JettyDistribution(prop(jetty.home)) = {}", jettyHome);
if (jettyHome == null)
{
distro = asJettyDistribution(System.getenv().get("JETTY_HOME"));
LOG.debug("JettyDistribution(env(JETTY_HOME)) = " + distro);
jettyHome = asDirectory(System.getenv().get("JETTY_HOME"));
LOG.debug("JettyDistribution(env(JETTY_HOME)) = {}", jettyHome);
}
Path distro = null;
if (jettyHome != null)
{
Path parent = jettyHome.getParent();
if (hasDemoBase(parent))
{
distro = parent;
}
}
if (distro == null)
@ -54,13 +65,20 @@ public class JettyDistribution
try
{
Path working = Paths.get(System.getProperty("user.dir"));
LOG.debug("JettyDistribution(prop(user.dir)) = " + working);
while (distro == null && working != null)
Path dir = null;
LOG.debug("JettyDistribution(prop(user.dir)) = {}", working);
while (dir == null && working != null)
{
distro = asJettyDistribution(working.resolve("jetty-distribution/target/distribution").toString());
dir = asDirectory(working.resolve("jetty-distribution/target/distribution").toString());
if (dir != null && hasDemoBase(dir))
{
distro = dir;
}
// try one parent up
working = working.getParent();
}
LOG.debug("JettyDistribution(working.resolve(...)) = " + distro);
if (LOG.isDebugEnabled())
LOG.debug("JettyDistribution(working.resolve(...)) = {}", distro);
}
catch (Throwable th)
{
@ -74,47 +92,47 @@ public class JettyDistribution
}
else
{
LOG.debug("JettyDistribution() FOUND = " + distro);
if (LOG.isDebugEnabled())
LOG.debug("JettyDistribution() FOUND = {}", distro);
}
DISTRIBUTION = distro;
}
private static Path asJettyDistribution(String jettyHome)
private static boolean hasDemoBase(Path path)
{
Path demoBase = path.resolve("demo-base");
return Files.exists(demoBase) && Files.isDirectory(demoBase);
}
private static Path asDirectory(String path)
{
try
{
if (jettyHome == null)
if (path == null)
{
return null;
}
if (StringUtil.isBlank(jettyHome))
if (StringUtil.isBlank(path))
{
LOG.debug("asJettyDistribution {} is blank", jettyHome);
LOG.debug("asDirectory {} is blank", path);
return null;
}
Path dir = Paths.get(jettyHome);
Path dir = Paths.get(path);
if (!Files.exists(dir))
{
LOG.debug("asJettyDistribution {} does not exist", jettyHome);
LOG.debug("asDirectory {} does not exist", path);
return null;
}
if (!Files.isDirectory(dir))
{
LOG.debug("asJettyDistribution {} is not a directory", jettyHome);
LOG.debug("asDirectory {} is not a directory", path);
return null;
}
Path demoBase = dir.resolve("demo-base");
if (!Files.exists(demoBase) || !Files.isDirectory(demoBase))
{
LOG.debug("asJettyDistribution {} has no demo-base", jettyHome);
return null;
}
LOG.debug("asJettyDistribution {}", dir);
LOG.debug("asDirectory {}", dir);
return dir.toAbsolutePath();
}
catch (Exception e)

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
import org.eclipse.jetty.deploy.bindings.DebugListenerBinding;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
@ -45,7 +46,6 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnectionStatistics;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
@ -66,13 +66,13 @@ public class LikeJettyXml
public static Server createServer(int port, int securePort, boolean addDebugListener) throws Exception
{
// Path to as-built jetty-distribution directory
Path jettyHomeBuild = JettyDistribution.get();
Path jettyDistro = JettyDistribution.get();
// Find jetty home and base directories
String homePath = System.getProperty("jetty.home", jettyHomeBuild.toString());
String homePath = System.getProperty("jetty.home", jettyDistro.resolve("jetty-home").toString());
Path homeDir = Paths.get(homePath);
String basePath = System.getProperty("jetty.base", homeDir.resolve("demo-base").toString());
String basePath = System.getProperty("jetty.base", jettyDistro.resolve("demo-base").toString());
Path baseDir = Paths.get(basePath);
// Configure jetty.home and jetty.base system properties
@ -171,7 +171,7 @@ public class LikeJettyXml
StatisticsHandler stats = new StatisticsHandler();
stats.setHandler(server.getHandler());
server.setHandler(stats);
ServerConnectionStatistics.addToAllConnectors(server);
server.addBeanToAllConnectors(new ConnectionStatistics());
// === Rewrite Handler
RewriteHandler rewrite = new RewriteHandler();
@ -181,7 +181,7 @@ public class LikeJettyXml
rewrite.addRule(new ValidUrlRule());
// === jetty-requestlog.xml ===
AsyncRequestLogWriter logWriter = new AsyncRequestLogWriter(jettyHome + "/logs/yyyy_mm_dd.request.log");
AsyncRequestLogWriter logWriter = new AsyncRequestLogWriter(jettyBase + "/logs/yyyy_mm_dd.request.log");
logWriter.setFilenameDateFormat("yyyy_MM_dd");
logWriter.setRetainDays(90);
logWriter.setTimeZone("GMT");

View File

@ -20,9 +20,9 @@ package org.eclipse.jetty.embedded;
import java.lang.management.ManagementFactory;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnectionStatistics;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
@ -45,7 +45,7 @@ public class OneServletContextJmxStats
context.addServlet(DefaultServlet.class, "/");
// Add Connector Statistics tracking to all connectors
ServerConnectionStatistics.addToAllConnectors(server);
server.addBeanToAllConnectors(new ConnectionStatistics());
return server;
}

View File

@ -77,7 +77,7 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not initialize " + processor, x);
LOG.debug("Could not initialize {}", processor, x);
failure.addSuppressed(x);
}
});

View File

@ -112,7 +112,7 @@ public class ConscryptServerALPNProcessor implements ALPNProcessor.Server
public void handshakeFailed(Event event, Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("TLS handshake failed " + alpnConnection, failure);
LOG.debug("TLS handshake failed {}", alpnConnection, failure);
}
}
}

View File

@ -97,7 +97,7 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandsha
public void handshakeFailed(Event event, Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("TLS handshake failed " + alpnConnection, failure);
LOG.debug("TLS handshake failed {}", alpnConnection, failure);
}
}
}

View File

@ -3,6 +3,11 @@
[description]
Enables the ALPN (Application Layer Protocol Negotiation) TLS extension.
[tag]
connector
ssl
internal
[depend]
ssl
alpn-impl/alpn-${java.version.platform}

View File

@ -480,10 +480,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
long elapsedMs = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS);
LOG.info("Annotation scanning elapsed time={}ms", elapsedMs);
if (LOG.isDebugEnabled())
{
LOG.debug("Annotation scanning elapsed time={}ms", elapsedMs);
for (ParserTask p : _parserTasks)
{
LOG.debug("Scanned {} in {}ms", p.getResource(), TimeUnit.MILLISECONDS.convert(p.getStatistic().getElapsed(), TimeUnit.NANOSECONDS));
@ -612,7 +612,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (c.isAnnotation())
{
if (LOG.isDebugEnabled())
LOG.debug("Registering annotation handler for " + c.getName());
LOG.debug("Registering annotation handler for {}", c.getName());
_containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
}
}
@ -621,14 +621,14 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled())
LOG.debug("No classes in HandlesTypes on initializer " + service.getClass());
LOG.debug("No classes in HandlesTypes on initializer {}", service.getClass());
}
}
else
{
initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled())
LOG.debug("No HandlesTypes annotation on initializer " + service.getClass());
LOG.debug("No HandlesTypes annotation on initializer {}", service.getClass());
}
initializers.add(initializer);

View File

@ -70,8 +70,8 @@ import org.slf4j.LoggerFactory;
public class AnnotationParser
{
private static final Logger LOG = LoggerFactory.getLogger(AnnotationParser.class);
protected static int ASM_OPCODE_VERSION = Opcodes.ASM7; //compatibility of api
protected static String ASM_OPCODE_VERSION_STR = "ASM7";
protected static int ASM_OPCODE_VERSION = Opcodes.ASM8; //compatibility of api
protected static String ASM_OPCODE_VERSION_STR = "ASM8";
/**
* Map of classnames scanned and the first location from which scan occurred
@ -122,6 +122,11 @@ public class AnnotationParser
asmVersion = Opcodes.ASM7;
break;
}
case 8:
{
asmVersion = Opcodes.ASM8;
break;
}
default:
{
LOG.warn("Unrecognized ASM version, assuming {}", ASM_OPCODE_VERSION_STR);
@ -822,7 +827,7 @@ public class AnnotationParser
catch (Exception ex)
{
if (LOG.isDebugEnabled())
LOG.debug("Error scanning file " + file, ex);
LOG.debug("Error scanning file {}", file, ex);
me.add(new RuntimeException("Error scanning file " + file, ex));
}
}
@ -993,7 +998,7 @@ public class AnnotationParser
if (path.startsWith(".") || path.contains("/."))
{
if (LOG.isDebugEnabled())
LOG.debug("Contains hidden dirs: " + path);
LOG.debug("Contains hidden dirs: {}", path);
return false;
}

View File

@ -111,14 +111,14 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//JavaEE Spec 5.2.3: Field cannot be static
if (Modifier.isStatic(field.getModifiers()))
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + field.getName() + ": cannot be static");
LOG.warn("Skipping Resource annotation on {}.{}: cannot be static", clazz.getName(), field.getName());
return;
}
//JavaEE Spec 5.2.3: Field cannot be final
if (Modifier.isFinal(field.getModifiers()))
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + field.getName() + ": cannot be final");
LOG.warn("Skipping Resource annotation on {}.{}: cannot be final", clazz.getName(), field.getName());
return;
}
@ -179,7 +179,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//Check there is a JNDI entry for this annotation
if (bound)
{
LOG.debug("Bound " + (mappedName == null ? name : mappedName) + " as " + name);
LOG.debug("Bound {} as {}", (mappedName == null ? name : mappedName), name);
// Make the Injection for it if the binding succeeded
injection = new Injection(clazz, field, type, name, mappedName);
injections.add(injection);
@ -243,7 +243,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//JavaEE Spec 5.2.3: Method cannot be static
if (Modifier.isStatic(method.getModifiers()))
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + method.getName() + ": cannot be static");
LOG.warn("Skipping Resource annotation on {}.{}: cannot be static", clazz.getName(), method.getName());
return;
}
@ -251,19 +251,19 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
// only 1 parameter
if (!method.getName().startsWith("set"))
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + method.getName() + ": invalid java bean, does not start with 'set'");
LOG.warn("Skipping Resource annotation on {}.{}: invalid java bean, does not start with 'set'", clazz.getName(), method.getName());
return;
}
if (method.getParameterCount() != 1)
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + method.getName() + ": invalid java bean, not single argument to method");
LOG.warn("Skipping Resource annotation on {}.{}: invalid java bean, not single argument to method", clazz.getName(), method.getName());
return;
}
if (Void.TYPE != method.getReturnType())
{
LOG.warn("Skipping Resource annotation on " + clazz.getName() + "." + method.getName() + ": invalid java bean, not void");
LOG.warn("Skipping Resource annotation on {}.{}: invalid java bean, not void", clazz.getName(), method.getName());
return;
}
@ -332,7 +332,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
if (bound)
{
LOG.debug("Bound " + (mappedName == null ? name : mappedName) + " as " + name);
LOG.debug("Bound {} as {}", (mappedName == null ? name : mappedName), name);
// Make the Injection for it
injection = new Injection(clazz, method, paramType, resourceType, name, mappedName);
injections.add(injection);

View File

@ -45,7 +45,7 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
Resource[] resArray = resources.value();
if (resArray == null || resArray.length == 0)
{
LOG.warn("Skipping empty or incorrect Resources annotation on " + clazz.getName());
LOG.warn("Skipping empty or incorrect Resources annotation on {}", clazz.getName());
return;
}
@ -63,7 +63,8 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName))
LOG.warn("Skipping Resources(Resource) annotation on " + clazz.getName() + " for name " + name + ": No resource bound at " + (mappedName == null ? name : mappedName));
LOG.warn("Skipping Resources(Resource) annotation on {} for name {}: no resource bound at {}",
clazz.getName(), name, (mappedName == null ? name : mappedName));
}
catch (NamingException e)
{

View File

@ -65,17 +65,17 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
}
}
else
LOG.warn("Bad value for @RunAs annotation on class " + clazz.getName());
LOG.warn("Bad value for @RunAs annotation on class {}", clazz.getName());
}
}
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation)
{
LOG.warn("@RunAs annotation not applicable for fields: " + className + "." + fieldName);
LOG.warn("@RunAs annotation not applicable for fields: {}.{}", className, fieldName);
}
public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation)
{
LOG.warn("@RunAs annotation ignored on method: " + className + "." + methodName + " " + signature);
LOG.warn("@RunAs annotation ignored on method: {}.{} {}", className, methodName, signature);
}
}

View File

@ -55,7 +55,7 @@ public class ServletContainerInitializersStarter extends AbstractLifeCycle imple
try
{
if (LOG.isDebugEnabled())
LOG.debug("Calling ServletContainerInitializer " + i.getTarget().getClass().getName());
LOG.debug("Calling ServletContainerInitializer {}", i.getTarget().getClass().getName());
i.callStartup(_context);
}
catch (Exception e)

View File

@ -83,7 +83,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
if (constraintsExist(servletMappings, constraintMappings))
{
LOG.warn("Constraints already defined for " + clazz.getName() + ", skipping ServletSecurity annotation");
LOG.warn("Constraints already defined for {}, skipping ServletSecurity annotation", clazz.getName());
return;
}

View File

@ -62,14 +62,14 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
Class clazz = getTargetClass();
if (clazz == null)
{
LOG.warn(_className + " cannot be loaded");
LOG.warn("{} cannot be loaded", _className);
return;
}
//Servlet Spec 8.1.2
if (!Filter.class.isAssignableFrom(clazz))
{
LOG.warn(clazz.getName() + " is not assignable from javax.servlet.Filter");
LOG.warn("{} is not assignable from javax.servlet.Filter", clazz.getName());
return;
}
MetaData metaData = _context.getMetaData();
@ -78,7 +78,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
if (filterAnnotation.value().length > 0 && filterAnnotation.urlPatterns().length > 0)
{
LOG.warn(clazz.getName() + " defines both @WebFilter.value and @WebFilter.urlPatterns");
LOG.warn("{} defines both @WebFilter.value and @WebFilter.urlPatterns", clazz.getName());
return;
}

View File

@ -52,7 +52,7 @@ public class WebFilterAnnotationHandler extends AbstractDiscoverableAnnotationHa
{
if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
return;
LOG.warn("@WebFilter not applicable for fields: " + info.getClassInfo().getClassName() + "." + info.getFieldName());
LOG.warn("@WebFilter not applicable for fields: {}.{}", info.getClassInfo().getClassName(), info.getFieldName());
}
@Override
@ -60,6 +60,6 @@ public class WebFilterAnnotationHandler extends AbstractDiscoverableAnnotationHa
{
if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
return;
LOG.warn("@WebFilter not applicable for methods: " + info.getClassInfo().getClassName() + "." + info.getMethodName() + " " + info.getSignature());
LOG.warn("@WebFilter not applicable for methods: {}.{} {}", info.getClassInfo().getClassName(), info.getMethodName(), info.getSignature());
}
}

View File

@ -61,7 +61,7 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
if (clazz == null)
{
LOG.warn(_className + " cannot be loaded");
LOG.warn("{} cannot be loaded", _className);
return;
}
@ -84,7 +84,7 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
}
}
else
LOG.warn(clazz.getName() + " does not implement one of the servlet listener interfaces");
LOG.warn("{} does not implement one of the servlet listener interfaces", clazz.getName());
}
catch (Exception e)
{

View File

@ -49,7 +49,7 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
{
if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
return;
LOG.warn("@WebListener is not applicable to fields: " + info.getClassInfo().getClassName() + "." + info.getFieldName());
LOG.warn("@WebListener is not applicable to fields: {}.{}", info.getClassInfo().getClassName(), info.getFieldName());
}
@Override
@ -57,6 +57,6 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
{
if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
return;
LOG.warn("@WebListener is not applicable to methods: " + info.getClassInfo().getClassName() + "." + info.getMethodName() + " " + info.getSignature());
LOG.warn("@WebListener is not applicable to methods: {}.{} {}", info.getClassInfo().getClassName(), info.getMethodName(), info.getSignature());
}
}

View File

@ -65,14 +65,14 @@ public class WebServletAnnotation extends DiscoveredAnnotation
if (clazz == null)
{
LOG.warn(_className + " cannot be loaded");
LOG.warn("{} cannot be loaded", _className);
return;
}
//Servlet Spec 8.1.1
if (!HttpServlet.class.isAssignableFrom(clazz))
{
LOG.warn(clazz.getName() + " is not assignable from javax.servlet.http.HttpServlet");
LOG.warn("{} is not assignable from javax.servlet.http.HttpServlet", clazz.getName());
return;
}
@ -80,7 +80,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
if (annotation.urlPatterns().length > 0 && annotation.value().length > 0)
{
LOG.warn(clazz.getName() + " defines both @WebServlet.value and @WebServlet.urlPatterns");
LOG.warn("{} defines both @WebServlet.value and @WebServlet.urlPatterns", clazz.getName());
return;
}
@ -90,7 +90,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
if (urlPatterns.length == 0)
{
LOG.warn(clazz.getName() + " defines neither @WebServlet.value nor @WebServlet.urlPatterns");
LOG.warn("{} defines neither @WebServlet.value nor @WebServlet.urlPatterns", clazz.getName());
return;
}

View File

@ -49,14 +49,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>ant</groupId>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.6.5</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<groupId>org.apache.ant</groupId>
<artifactId>ant-launcher</artifactId>
<version>1.6.5</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -17,7 +17,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.0.1</version>
<version>1.2.5</version>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<flattenedPomFilename>flattened-pom.xml</flattenedPomFilename>

View File

@ -41,6 +41,12 @@
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet-core</artifactId>
<version>${weld.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,9 +1,8 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Configures Jetty to use the "CdiDecoratingListener" as the default
CDI integration mode that allows a webapp to register it's own CDI
decorator.
Configures Jetty to use the "CdiDecoratingListener" as the default CDI mode.
This mode that allows a webapp to register it's own CDI decorator.
[tag]
cdi

View File

@ -1,8 +1,8 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Configures Jetty to use the "CdiSpiDecorator" that calls the CDI SPI
as the default CDI integration mode.
Configures Jetty to use the "CdiSpiDecorator" as the default CDI mode.
This mode uses the CDI SPI to integrate an arbitrary CDI implementation.
[tag]
cdi

View File

@ -1,7 +1,7 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Support for CDI inside the webapp.
Integration of CDI within webapp to Jetty container object lifecycles.
This module does not provide CDI, but configures jetty to support various
integration modes with a CDI implementation on the webapp classpath.
CDI integration modes can be selected per webapp with the "org.eclipse.jetty.cdi"
@ -27,8 +27,3 @@ etc/cdi/jetty-cdi.xml
[lib]
lib/jetty-cdi-${jetty.version}.jar
lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar
[ini]
jetty.webapp.addSystemClasses+=,org.eclipse.jetty.cdi.CdiServletContainerInitializer
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.cdi.CdiServletContainerInitializer

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.cdi;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>CDI Configuration</p>
* <p>This configuration configures the WebAppContext server/system classes to
* be able to see the {@link CdiServletContainerInitializer}.
* </p>
*/
public class CdiConfiguration extends AbstractConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(CdiConfiguration.class);
public CdiConfiguration()
{
protectAndExpose("org.eclipse.jetty.cdi.CdiServletContainerInitializer");
addDependents(AnnotationConfiguration.class, PlusConfiguration.class);
}
}

View File

@ -24,7 +24,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
/**
* A DecoratingListener that listens for "org.eclipse.jetty.cdi.decorator"
*/
class CdiDecoratingListener extends DecoratingListener
public class CdiDecoratingListener extends DecoratingListener
{
public static final String MODE = "CdiDecoratingListener";
public static final String ATTRIBUTE = "org.eclipse.jetty.cdi.decorator";
@ -32,5 +32,6 @@ class CdiDecoratingListener extends DecoratingListener
public CdiDecoratingListener(ServletContextHandler contextHandler)
{
super(contextHandler, ATTRIBUTE);
contextHandler.setAttribute(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, MODE);
}
}

View File

@ -86,14 +86,12 @@ public class CdiServletContainerInitializer implements ServletContainerInitializ
default:
throw new IllegalStateException(mode);
}
context.setAttribute(CDI_INTEGRATION_ATTRIBUTE, mode);
LOG.info(mode + " enabled in " + ctx);
LOG.info("{} enabled in {}", mode, ctx);
}
catch (UnsupportedOperationException | ClassNotFoundException e)
{
if (LOG.isDebugEnabled())
LOG.debug("CDI not found in " + ctx, e);
LOG.debug("CDI not found in {}", ctx, e);
}
}
}

View File

@ -21,8 +21,12 @@ package org.eclipse.jetty.cdi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.Decorator;
@ -61,11 +65,15 @@ public class CdiSpiDecorator implements Decorator
private final MethodHandle _inject;
private final MethodHandle _dispose;
private final MethodHandle _release;
private final Set<String> _undecorated = new HashSet<>(Collections.singletonList("org.jboss.weld.environment.servlet.Listener"));
public CdiSpiDecorator(ServletContextHandler context) throws UnsupportedOperationException
{
_context = context;
context.setAttribute(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, MODE);
ClassLoader classLoader = _context.getClassLoader();
if (classLoader == null)
classLoader = this.getClass().getClassLoader();
try
{
@ -92,6 +100,54 @@ public class CdiSpiDecorator implements Decorator
}
}
/**
* Test if a class can be decorated.
* The default implementation checks the set from {@link #getUndecoratable()}
* on the class and all it's super classes.
* @param clazz The class to check
* @return True if the class and all it's super classes can be decorated
*/
protected boolean isDecoratable(Class<?> clazz)
{
if (Object.class == clazz)
return true;
if (getUndecoratable().contains(clazz.getName()))
return false;
return isDecoratable(clazz.getSuperclass());
}
/**
* Get the set of classes that will not be decorated. The default set includes the listener from Weld that will itself
* setup decoration.
* @return The modifiable set of class names that will not be decorated (ie {@link #isDecoratable(Class)} will return false.
* @see #isDecoratable(Class)
*/
public Set<String> getUndecoratable()
{
return _undecorated;
}
/**
* @param classnames The set of class names that will not be decorated.
* @see #isDecoratable(Class)
*/
public void setUndecoratable(Set<String> classnames)
{
_undecorated.clear();
if (classnames != null)
_undecorated.addAll(classnames);
}
/**
* @param classname A class name that will be added to the undecoratable classes set.
* @see #getUndecoratable()
* @see #isDecoratable(Class)
*/
public void addUndecoratable(String... classname)
{
_undecorated.addAll(Arrays.asList());
}
/**
* Decorate an object.
* <p>The signature of this method must match what is introspected for by the
@ -108,11 +164,12 @@ public class CdiSpiDecorator implements Decorator
if (LOG.isDebugEnabled())
LOG.debug("decorate {} in {}", o, _context);
_decorated.put(o, new Decorated(o));
if (isDecoratable(o.getClass()))
_decorated.put(o, new Decorated(o));
}
catch (Throwable th)
{
LOG.warn("Unable to decorate " + o, th);
LOG.warn("Unable to decorate {}", o, th);
}
return o;
}
@ -134,7 +191,7 @@ public class CdiSpiDecorator implements Decorator
}
catch (Throwable th)
{
LOG.warn("Unable to destroy " + o, th);
LOG.warn("Unable to destroy {}", o, th);
}
}

View File

@ -0,0 +1,2 @@
org.eclipse.jetty.cdi.CdiConfiguration

View File

@ -0,0 +1,331 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.embedded;
import java.io.IOException;
import java.util.EnumSet;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.cdi.CdiConfiguration;
import org.eclipse.jetty.cdi.CdiServletContainerInitializer;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class EmbeddedWeldTest
{
public static Server createServerWithServletContext(String mode)
{
Server server = new Server();
server.addConnector(new LocalConnector(server));
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.setResourceBase("src/test/resources/weldtest");
server.setHandler(context);
// Setup context
context.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
context.addServlet(GreetingsServlet.class, "/");
context.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
// Setup Jetty weld integration
switch (mode)
{
case "none" : // Do nothing, let weld work it out.
// Expect:INFO: WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported.
context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class));
break;
case "DecoratingListener+Listener":
// Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters.
context.addEventListener(new org.eclipse.jetty.webapp.DecoratingListener(context));
context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class));
break;
case "CdiDecoratingListener+Listener":
// Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters.
context.addEventListener(new org.eclipse.jetty.cdi.CdiDecoratingListener(context));
context.addEventListener(new org.jboss.weld.environment.servlet.Listener());
break;
case "CdiSpiDecorator+Listener":
// Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters.
context.getObjectFactory().addDecorator(new org.eclipse.jetty.cdi.CdiSpiDecorator(context));
context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class));
break;
case "CdiServletContainerInitializer+Listener":
// Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters.
context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
context.addEventListener(new org.jboss.weld.environment.servlet.Listener());
break;
case "CdiServletContainerInitializer(CdiDecoratingListener)+Listener":
// Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters
context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE);
context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
context.addEventListener(new org.jboss.weld.environment.servlet.Listener());
break;
case "CdiServletContainerInitializer+EnhancedListener":
// Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters.
context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener()));
break;
case "CdiServletContainerInitializer(CdiDecoratingListener)+EnhancedListener":
// Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters
context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE);
context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener()));
break;
case "EnhancedListener+CdiServletContainerInitializer(CdiDecoratingListener)":
// Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters
context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE);
context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener()));
context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
break;
}
return server;
}
@ParameterizedTest()
@ValueSource(strings =
{
"none",
"DecoratingListener+Listener",
"CdiDecoratingListener+Listener",
"CdiSpiDecorator+Listener",
"CdiServletContainerInitializer+Listener",
"CdiServletContainerInitializer(CdiDecoratingListener)+Listener",
"CdiServletContainerInitializer+EnhancedListener",
"CdiServletContainerInitializer(CdiDecoratingListener)+EnhancedListener"
})
public void testServletContext(String mode) throws Exception
{
Server server = createServerWithServletContext(mode);
server.start();
LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
if (mode.contains("EnhancedListener"))
assertThat(response, containsString("Listener saw Weld BeanManager"));
else
assertThat(response, containsString("Listener saw null"));
assertThat(response, containsString("Beans from Weld BeanManager for "));
server.stop();
}
@Test
public void testWebappContext() throws Exception
{
Server server = new Server(8080);
server.addConnector(new LocalConnector(server));
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setResourceBase("src/test/resources/weldtest");
server.setHandler(webapp);
webapp.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE);
webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.eclipse.jetty.cdi.CdiServletContainerInitializer()));
webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.jboss.weld.environment.servlet.EnhancedListener()));
webapp.getServerClassMatcher().add("-org.eclipse.jetty.embedded.");
webapp.getSystemClassMatcher().add("org.eclipse.jetty.embedded.");
webapp.addServlet(GreetingsServlet.class, "/");
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
server.start();
LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
assertThat(response, containsString("Listener saw Weld BeanManager"));
server.stop();
}
@Test
public void testWebappContextDiscovered() throws Exception
{
Server server = new Server(8080);
server.addConnector(new LocalConnector(server));
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setResourceBase("src/test/resources/weldtest");
server.setHandler(webapp);
// Need the AnnotationConfiguration to detect SCIs
webapp.addConfiguration(new AnnotationConfiguration());
// Need to expose our SCI. This is ugly could be made better in jetty-10 with a CdiConfiguration
webapp.addConfiguration(new CdiConfiguration());
// This is ugly but needed for maven for testing in a overlaid war pom
webapp.getServerClassMatcher().add("-org.eclipse.jetty.embedded.");
webapp.getSystemClassMatcher().add("org.eclipse.jetty.embedded.");
webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.addServlet(GreetingsServlet.class, "/");
server.start();
LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
System.err.println(response);
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
assertThat(response, containsString("Listener saw Weld BeanManager"));
server.stop();
}
public static class MyContextListener implements ServletContextListener
{
@Inject
BeanManager manager;
@Override
public void contextInitialized(ServletContextEvent sce)
{
sce.getServletContext().setAttribute("listener", manager);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
}
public static class MyFilter implements Filter
{
@Inject
BeanManager manager;
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
if (manager == null)
throw new IllegalStateException();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
// copy attribute from MyListener to see if it was decorated.
request.setAttribute("filter", manager);
chain.doFilter(request, response);
}
@Override
public void destroy()
{
}
}
public static class GreetingsServlet extends HttpServlet
{
@Inject
@Named("friendly")
public Greetings greetings;
@Inject
BeanManager manager;
@Override
public void init()
{
if (manager == null)
throw new IllegalStateException();
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
resp.getWriter().print(greetings == null ? "NULL" : greetings.getGreeting());
resp.getWriter().print(" filtered by ");
resp.getWriter().println(req.getAttribute("filter"));
resp.getWriter().println("Beans from " + manager);
resp.getWriter().println("Listener saw " + req.getServletContext().getAttribute("listener"));
}
}
public interface Greetings
{
String getGreeting();
}
public static class FriendlyGreetings
{
@Produces
@Named("friendly")
public Greetings friendly(InjectionPoint ip)
{
return () -> "Hello " + ip.getMember().getDeclaringClass().getSimpleName();
}
@Produces
@Named("old")
public Greetings old()
{
return () -> "Salutations!";
}
}
}

View File

@ -0,0 +1,4 @@
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all">
</beans>

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.Sweeper;
import org.slf4j.Logger;
@ -40,26 +41,32 @@ import org.slf4j.LoggerFactory;
import static java.util.stream.Collectors.toCollection;
@ManagedObject
public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable, Sweeper.Sweepable
public abstract class AbstractConnectionPool extends ContainerLifeCycle implements ConnectionPool, Dumpable, Sweeper.Sweepable
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractConnectionPool.class);
private final HttpDestination destination;
private final Callback requester;
private final Pool<Connection> pool;
private boolean maximizeConnections;
protected AbstractConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester)
{
this(destination, new Pool<>(Pool.StrategyType.FIRST, maxConnections, cache), requester);
}
protected AbstractConnectionPool(HttpDestination destination, Pool<Connection> pool, Callback requester)
{
this.destination = destination;
this.requester = requester;
@SuppressWarnings("unchecked")
Pool<Connection> pool = destination.getBean(Pool.class);
if (pool == null)
{
pool = new Pool<>(maxConnections, cache ? 1 : 0);
destination.addBean(pool);
}
this.pool = pool;
addBean(pool);
}
@Override
protected void doStop() throws Exception
{
pool.close();
}
@Override
@ -68,7 +75,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
CompletableFuture<?>[] futures = new CompletableFuture[connectionCount];
for (int i = 0; i < connectionCount; i++)
{
futures[i] = tryCreateReturningFuture(pool.getMaxEntries());
futures[i] = tryCreateAsync(getMaxConnectionCount());
}
return CompletableFuture.allOf(futures);
}
@ -130,16 +137,47 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
}
@Override
@ManagedAttribute("Whether this pool is closed")
public boolean isClosed()
{
return pool.isClosed();
}
@ManagedAttribute("Whether the pool tries to maximize the number of connections used")
public boolean isMaximizeConnections()
{
return maximizeConnections;
}
/**
* <p>Sets whether the number of connections should be maximized.</p>
*
* @param maximizeConnections whether the number of connections should be maximized
*/
public void setMaximizeConnections(boolean maximizeConnections)
{
this.maximizeConnections = maximizeConnections;
}
/**
* <p>Returns an idle connection, if available;
* if an idle connection is not available, and the given {@code create} parameter is {@code true}
* or {@link #isMaximizeConnections()} is {@code true},
* then schedules the opening of a new connection, if possible within the configuration of this
* connection pool (for example, if it does not exceed the max connection count);
* otherwise returns {@code null}.</p>
*
* @param create whether to schedule the opening of a connection if no idle connections are available
* @return an idle connection or {@code null} if no idle connections are available
* @see #tryCreate(int)
*/
@Override
public Connection acquire(boolean create)
{
if (LOG.isDebugEnabled())
LOG.debug("Acquiring create={} on {}", create, this);
Connection connection = activate();
if (connection == null && create)
if (connection == null && (create || isMaximizeConnections()))
{
tryCreate(destination.getQueuedRequestCount());
connection = activate();
@ -159,22 +197,21 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
*/
protected void tryCreate(int maxPending)
{
tryCreateReturningFuture(maxPending);
tryCreateAsync(maxPending);
}
private CompletableFuture<Void> tryCreateReturningFuture(int maxPending)
private CompletableFuture<Void> tryCreateAsync(int maxPending)
{
int connectionCount = getConnectionCount();
if (LOG.isDebugEnabled())
{
LOG.debug("tryCreate {}/{} connections {}/{} pending", pool.size(), pool.getMaxEntries(), getPendingConnectionCount(), maxPending);
}
LOG.debug("Try creating connection {}/{} with {}/{} pending", connectionCount, getMaxConnectionCount(), getPendingConnectionCount(), maxPending);
Pool<Connection>.Entry entry = pool.reserve(maxPending);
if (entry == null)
return CompletableFuture.completedFuture(null);
if (LOG.isDebugEnabled())
LOG.debug("newConnection {}/{} connections {}/{} pending", pool.size(), pool.getMaxEntries(), getPendingConnectionCount(), maxPending);
LOG.debug("Creating connection {}/{}", connectionCount, getMaxConnectionCount());
CompletableFuture<Void> future = new CompletableFuture<>();
destination.newConnection(new Promise<>()
@ -183,7 +220,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
public void succeeded(Connection connection)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection {}/{} creation succeeded {}", pool.size(), pool.getMaxEntries(), connection);
LOG.debug("Connection {}/{} creation succeeded {}", connectionCount, getMaxConnectionCount(), connection);
if (!(connection instanceof Attachable))
{
failed(new IllegalArgumentException("Invalid connection object: " + connection));
@ -201,7 +238,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection " + pool.size() + "/" + pool.getMaxEntries() + " creation failed", x);
LOG.debug("Connection {}/{} creation failed", connectionCount, getMaxConnectionCount(), x);
entry.remove();
future.completeExceptionally(x);
requester.failed(x);
@ -240,7 +277,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
if (entry != null)
{
if (LOG.isDebugEnabled())
LOG.debug("activated {}", entry);
LOG.debug("Activated {} {}", entry, pool);
Connection connection = entry.getPooled();
acquired(connection);
return connection;
@ -258,8 +295,6 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
Pool<Connection>.Entry entry = (Pool<Connection>.Entry)attachable.getAttachment();
if (entry == null)
return false;
if (LOG.isDebugEnabled())
LOG.debug("isActive {}", entry);
return !entry.isIdle();
}
@ -283,7 +318,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
return true;
boolean reusable = pool.release(entry);
if (LOG.isDebugEnabled())
LOG.debug("Released ({}) {}", reusable, entry);
LOG.debug("Released ({}) {} {}", reusable, entry, pool);
if (reusable)
return true;
remove(connection);
@ -308,7 +343,7 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
attachable.setAttachment(null);
boolean removed = pool.remove(entry);
if (LOG.isDebugEnabled())
LOG.debug("Removed ({}) {}", removed, entry);
LOG.debug("Removed ({}) {} {}", removed, entry, pool);
if (removed || force)
{
released(connection);

View File

@ -137,7 +137,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
if (result.getResponseFailure() != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Authentication challenge failed {}", result.getFailure());
LOG.debug("Authentication challenge failed", result.getFailure());
forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
return;
}

View File

@ -18,7 +18,9 @@
package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -27,12 +29,17 @@ public class DuplexConnectionPool extends AbstractConnectionPool
{
public DuplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester)
{
this(destination, maxConnections, true, requester);
this(destination, maxConnections, false, requester);
}
public DuplexConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester)
{
super(destination, maxConnections, cache, requester);
this(destination, new Pool<>(Pool.StrategyType.FIRST, maxConnections, cache), requester);
}
public DuplexConnectionPool(HttpDestination destination, Pool<Connection> pool, Callback requester)
{
super(destination, pool, requester);
}
@Override

View File

@ -31,7 +31,6 @@ import java.nio.channels.SocketChannel;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -563,7 +562,8 @@ public class HttpClient extends ContainerLifeCycle
@Override
public void succeeded(List<InetSocketAddress> socketAddresses)
{
Map<String, Object> context = new HashMap<>();
// Multiple threads may access the map, especially with DEBUG logging enabled.
Map<String, Object> context = new ConcurrentHashMap<>();
context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
connect(socketAddresses, 0, context);

View File

@ -370,7 +370,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{
// Aggressively send other queued requests
// in case connections are multiplexed.
return getHttpExchanges().size() > 0 ? ProcessResult.CONTINUE : ProcessResult.FINISH;
return getQueuedRequestCount() > 0 ? ProcessResult.CONTINUE : ProcessResult.FINISH;
}
if (LOG.isDebugEnabled())
@ -427,11 +427,16 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{
if (connectionPool.isActive(connection))
{
// trigger the next request after releasing the connection
// Trigger the next request after releasing the connection.
if (connectionPool.release(connection))
{
send(false);
}
else
{
connection.close();
send(true);
}
}
else
{

View File

@ -496,7 +496,7 @@ public abstract class HttpReceiver
return false;
if (LOG.isDebugEnabled())
LOG.debug("Response failure " + exchange.getResponse(), failure);
LOG.debug("Response failure {}", exchange.getResponse(), failure);
// Mark atomically the response as completed, with respect
// to concurrency between response success and response failure.

View File

@ -226,7 +226,7 @@ public abstract class HttpSender
return;
if (LOG.isDebugEnabled())
LOG.debug("Request failure " + exchange.getRequest(), failure);
LOG.debug("Request failure {}", exchange.getRequest(), failure);
// Mark atomically the request as completed, with respect
// to concurrency between request success and request failure.

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.LeakDetector;
import org.eclipse.jetty.util.component.LifeCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,40 +41,16 @@ public class LeakTrackingConnectionPool extends DuplexConnectionPool
public LeakTrackingConnectionPool(HttpDestination destination, int maxConnections, Callback requester)
{
super((HttpDestination)destination, maxConnections, requester);
start();
}
private void start()
{
try
{
leakDetector.start();
}
catch (Exception x)
{
throw new RuntimeException(x);
}
addBean(leakDetector);
}
@Override
public void close()
{
stop();
LifeCycle.stop(this);
super.close();
}
private void stop()
{
try
{
leakDetector.stop();
}
catch (Exception x)
{
throw new RuntimeException(x);
}
}
@Override
protected void acquired(Connection connection)
{
@ -90,6 +67,6 @@ public class LeakTrackingConnectionPool extends DuplexConnectionPool
protected void leaked(LeakDetector.LeakInfo leakInfo)
{
LOG.info("Connection " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
LOG.info("Connection {} leaked at:", leakInfo.getResourceDescription(), leakInfo.getStackFrames());
}
}

View File

@ -18,7 +18,9 @@
package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -27,12 +29,17 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C
{
public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex)
{
this(destination, maxConnections, true, requester, maxMultiplex);
this(destination, maxConnections, false, requester, maxMultiplex);
}
public MultiplexConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester, int maxMultiplex)
{
super(destination, maxConnections, cache, requester);
this(destination, new Pool<>(Pool.StrategyType.FIRST, maxConnections, cache), requester, maxMultiplex);
}
public MultiplexConnectionPool(HttpDestination destination, Pool<Connection> pool, Callback requester, int maxMultiplex)
{
super(destination, pool, requester);
setMaxMultiplex(maxMultiplex);
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.client;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedObject;
/**
* <p>A {@link ConnectionPool} that provides connections
* randomly among the ones that are available.</p>
*/
@ManagedObject
public class RandomConnectionPool extends MultiplexConnectionPool
{
public RandomConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex)
{
super(destination, new Pool<>(Pool.StrategyType.RANDOM, maxConnections, false), requester, maxMultiplex);
}
}

View File

@ -62,7 +62,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -92,7 +92,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -122,7 +122,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -152,7 +152,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -194,7 +194,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -224,7 +224,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -254,7 +254,7 @@ public class RequestNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
}

View File

@ -56,7 +56,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -79,7 +79,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
return false;
}
}
@ -101,7 +101,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -121,7 +121,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -156,7 +156,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -177,7 +177,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -198,7 +198,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}
@ -219,7 +219,7 @@ public class ResponseNotifier
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
LOG.info("Exception while notifying listener {}", listener, x);
}
}

View File

@ -18,23 +18,37 @@
package org.eclipse.jetty.client;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A {@link ConnectionPool} that attempts to provide connections using a round-robin algorithm.</p>
* <p>The round-robin behavior is almost impossible to achieve for several reasons:</p>
* <ul>
* <li>the server takes different times to serve different requests; if a request takes a long
* time to be processed by the server, it would be a performance penalty to stall sending requests
* waiting for that connection to be available - better skip it and try another connection</li>
* <li>connections may be closed by the client or by the server, so it would be a performance
* penalty to stall sending requests waiting for a new connection to be opened</li>
* <li>thread scheduling on both client and server may temporarily penalize a connection</li>
* </ul>
* <p>Do not expect this class to provide connections in a perfect recurring sequence such as
* {@code c0, c1, ..., cN-1, c0, c1, ..., cN-1, c0, c1, ...} because that is impossible to
* achieve in a real environment.
* This class will just attempt a best-effort to provide the connections in a sequential order,
* but most likely the order will be quasi-random.</p>
* <p>Applications using this class should {@link #preCreateConnections(int) pre-create}
* the connections to ensure that they are already opened when the application starts to requests
* them, otherwise the first connection that is opened may be used multiple times before the others
* are opened, resulting in a behavior that is more random-like than more round-robin-like (and
* that confirms that round-robin behavior is almost impossible to achieve).</p>
*
* @see RandomConnectionPool
*/
@ManagedObject
public class RoundRobinConnectionPool extends MultiplexConnectionPool
{
private static final Logger LOG = LoggerFactory.getLogger(RoundRobinConnectionPool.class);
private final AtomicInteger offset = new AtomicInteger();
private final Pool<Connection> pool;
public RoundRobinConnectionPool(HttpDestination destination, int maxConnections, Callback requester)
{
this(destination, maxConnections, requester, 1);
@ -42,31 +56,11 @@ public class RoundRobinConnectionPool extends MultiplexConnectionPool
public RoundRobinConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex)
{
super(destination, maxConnections, false, requester, maxMultiplex);
pool = destination.getBean(Pool.class);
}
@Override
protected Connection activate()
{
int offset = this.offset.get();
Connection connection = activate(offset);
if (connection != null)
this.offset.getAndIncrement();
return connection;
}
private Connection activate(int offset)
{
Pool<Connection>.Entry entry = pool.acquireAt(Math.abs(offset % pool.getMaxEntries()));
if (LOG.isDebugEnabled())
LOG.debug("activated '{}'", entry);
if (entry != null)
{
Connection connection = entry.getPooled();
acquired(connection);
return connection;
}
return null;
super(destination, new Pool<>(Pool.StrategyType.ROUND_ROBIN, maxConnections, false), requester, maxMultiplex);
// If there are queued requests and connections get
// closed due to idle timeout or overuse, we want to
// aggressively try to open new connections to replace
// those that were closed to process queued requests.
setMaximizeConnections(true);
}
}

View File

@ -51,6 +51,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
private RetainableByteBuffer networkBuffer;
private boolean shutdown;
private boolean complete;
private boolean unsolicited;
private int status;
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
{
@ -131,16 +133,18 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
protected ByteBuffer onUpgradeFrom()
{
ByteBuffer upgradeBuffer = null;
if (networkBuffer.hasRemaining())
{
HttpClient client = getHttpDestination().getHttpClient();
ByteBuffer upgradeBuffer = BufferUtil.allocate(networkBuffer.remaining(), client.isUseInputDirectByteBuffers());
upgradeBuffer = BufferUtil.allocate(networkBuffer.remaining(), client.isUseInputDirectByteBuffers());
BufferUtil.clearToFill(upgradeBuffer);
BufferUtil.put(networkBuffer.getBuffer(), upgradeBuffer);
BufferUtil.flipToFlush(upgradeBuffer, 0);
return upgradeBuffer;
}
return null;
releaseNetworkBuffer();
return upgradeBuffer;
}
private void process()
@ -159,12 +163,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
return;
}
// Connection may be closed or upgraded in a parser callback.
boolean upgraded = connection != endPoint.getConnection();
if (connection.isClosed() || upgraded)
// Connection may be closed in a parser callback.
if (connection.isClosed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} {}", upgraded ? "Upgraded" : "Closed", connection);
LOG.debug("Closed {}", connection);
releaseNetworkBuffer();
return;
}
@ -229,6 +232,14 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
if (LOG.isDebugEnabled())
LOG.debug("Parse complete={}, remaining {} {}", complete, networkBuffer.remaining(), parser);
if (complete)
{
int status = this.status;
this.status = 0;
if (status == HttpStatus.SWITCHING_PROTOCOLS_101)
return true;
}
if (networkBuffer.isEmpty())
return false;
@ -272,9 +283,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void startResponse(HttpVersion version, int status, String reason)
{
HttpExchange exchange = getHttpExchange();
unsolicited = exchange == null;
if (exchange == null)
return;
this.status = status;
String method = exchange.getRequest().getMethod();
parser.setHeadResponse(HttpMethod.HEAD.is(method) ||
(HttpMethod.CONNECT.is(method) && status == HttpStatus.OK_200));
@ -287,7 +300,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void parsedHeader(HttpField field)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
unsolicited |= exchange == null;
if (unsolicited)
return;
responseHeader(exchange, field);
@ -297,7 +311,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public boolean headerComplete()
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
unsolicited |= exchange == null;
if (unsolicited)
return false;
// Store the EndPoint is case of upgrades, tunnels, etc.
@ -309,7 +324,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public boolean content(ByteBuffer buffer)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
unsolicited |= exchange == null;
if (unsolicited)
return false;
RetainableByteBuffer networkBuffer = this.networkBuffer;
@ -331,7 +347,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void parsedTrailer(HttpField trailer)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
unsolicited |= exchange == null;
if (unsolicited)
return;
exchange.getResponse().trailer(trailer);
@ -341,8 +358,12 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public boolean messageComplete()
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
if (exchange == null || unsolicited)
{
// We received an unsolicited response from the server.
getHttpConnection().close();
return false;
}
int status = exchange.getResponse().getStatus();
if (status != HttpStatus.CONTINUE_100)
@ -359,7 +380,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
{
HttpExchange exchange = getHttpExchange();
HttpConnectionOverHTTP connection = getHttpConnection();
if (exchange == null)
if (exchange == null || unsolicited)
connection.close();
else
failAndClose(new EOFException(String.valueOf(connection)));
@ -369,7 +390,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void badMessage(BadMessageException failure)
{
HttpExchange exchange = getHttpExchange();
if (exchange != null)
if (exchange == null || unsolicited)
{
getHttpConnection().close();
}
else
{
HttpResponse response = exchange.getResponse();
response.status(failure.getCode()).reason(failure.getReason());

View File

@ -50,7 +50,6 @@ import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@ -65,11 +64,17 @@ public class ConnectionPoolTest
private HttpClient client;
public static Stream<ConnectionPoolFactory> pools()
{
return Stream.concat(poolsNoRoundRobin(),
Stream.of(new ConnectionPoolFactory("round-robin", destination -> new RoundRobinConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination))));
}
public static Stream<ConnectionPoolFactory> poolsNoRoundRobin()
{
return Stream.of(
new ConnectionPoolFactory("duplex", destination -> new DuplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination)),
new ConnectionPoolFactory("round-robin", destination -> new RoundRobinConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination)),
new ConnectionPoolFactory("multiplex", destination -> new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1))
new ConnectionPoolFactory("multiplex", destination -> new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1)),
new ConnectionPoolFactory("random", destination -> new RandomConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1))
);
}
@ -301,11 +306,11 @@ public class ConnectionPoolTest
}
@ParameterizedTest
@MethodSource("pools")
@MethodSource("poolsNoRoundRobin")
public void testConcurrentRequestsDontOpenTooManyConnections(ConnectionPoolFactory factory) throws Exception
{
// Round robin connection pool does open a few more connections than expected.
Assumptions.assumeFalse(factory.name.equals("round-robin"));
// Round robin connection pool does open a few more
// connections than expected, exclude it from this test.
startServer(new EmptyServerHandler());

View File

@ -1820,6 +1820,65 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertArrayEquals(bytes, baos.toByteArray());
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testUnsolicitedResponseBytesFromServer(Scenario scenario) throws Exception
{
String response = "" +
"HTTP/1.1 408 Request Timeout\r\n" +
"Content-Length: 0\r\n" +
"Connection: close\r\n" +
"\r\n";
testUnsolicitedBytesFromServer(scenario, response);
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testUnsolicitedInvalidBytesFromServer(Scenario scenario) throws Exception
{
String response = "ABCDEF";
testUnsolicitedBytesFromServer(scenario, response);
}
private void testUnsolicitedBytesFromServer(Scenario scenario, String bytesFromServer) throws Exception
{
try (ServerSocket server = new ServerSocket(0))
{
startClient(scenario, clientConnector ->
{
clientConnector.setSelectors(1);
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(clientConnector);
transport.setConnectionPoolFactory(destination ->
{
ConnectionPool connectionPool = new DuplexConnectionPool(destination, 1, destination);
connectionPool.preCreateConnections(1);
return connectionPool;
});
return transport;
}, null);
String host = "localhost";
int port = server.getLocalPort();
// Resolve the destination which will pre-create a connection.
HttpDestination destination = client.resolveDestination(new Origin("http", host, port));
// Accept the connection and send an unsolicited 408.
try (Socket socket = server.accept())
{
OutputStream output = socket.getOutputStream();
output.write(bytesFromServer.getBytes(StandardCharsets.UTF_8));
output.flush();
}
// Give some time to the client to process the response.
Thread.sleep(1000);
AbstractConnectionPool pool = (AbstractConnectionPool)destination.getConnectionPool();
assertEquals(0, pool.getConnectionCount());
}
}
private void assertCopyRequest(Request original)
{
Request copy = client.copyRequest((HttpRequest)original, original.getURI());

View File

@ -1,7 +1,7 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables Deployer to apply common configuration to all webapp deployments
Enables Deployer to apply common configuration to all webapp deployments.
[depend]
deploy

View File

@ -184,7 +184,7 @@ public class AppLifeCycle extends Graph
for (Binding binding : getBindings(node))
{
if (LOG.isDebugEnabled())
LOG.debug("Calling " + binding.getClass().getName() + " for " + app);
LOG.debug("Calling {} for {}", binding.getClass().getName(), app);
binding.processBinding(node, app);
}
}

View File

@ -70,7 +70,6 @@ import org.slf4j.LoggerFactory;
public class DeploymentManager extends ContainerLifeCycle
{
private static final Logger LOG = LoggerFactory.getLogger(DeploymentManager.class);
private MultiException onStartupErrors;
/**
* Represents a single tracked app within the deployment manager.
@ -127,6 +126,7 @@ public class DeploymentManager extends ContainerLifeCycle
}
private final AutoLock _lock = new AutoLock();
private MultiException _onStartupErrors;
private final List<AppProvider> _providers = new ArrayList<AppProvider>();
private final AppLifeCycle _lifecycle = new AppLifeCycle();
private final Queue<AppEntry> _apps = new ConcurrentLinkedQueue<AppEntry>();
@ -251,9 +251,10 @@ public class DeploymentManager extends ContainerLifeCycle
startAppProvider(provider);
}
if (onStartupErrors != null)
try (AutoLock l = _lock.lock())
{
onStartupErrors.ifExceptionThrow();
if (_onStartupErrors != null)
_onStartupErrors.ifExceptionThrow();
}
super.doStart();
@ -524,7 +525,7 @@ public class DeploymentManager extends ContainerLifeCycle
}
catch (Throwable t)
{
LOG.warn("Unable to reach node goal: " + nodeName, t);
LOG.warn("Unable to reach node goal: {}", nodeName, t);
// migrate to FAILED node
Node failed = _lifecycle.getNodeByName(AppLifeCycle.FAILED);
@ -546,6 +547,17 @@ public class DeploymentManager extends ContainerLifeCycle
}
}
private void addOnStartupError(Throwable cause)
{
try (AutoLock l = _lock.lock())
{
if (_onStartupErrors == null)
_onStartupErrors = new MultiException();
_onStartupErrors.add(cause);
}
}
/**
* Move an {@link App} through the {@link AppLifeCycle} to the desired {@link Node}, executing each lifecycle step
* in the process to reach the desired state.
@ -564,16 +576,6 @@ public class DeploymentManager extends ContainerLifeCycle
requestAppGoal(appentry, nodeName);
}
private void addOnStartupError(Throwable cause)
{
try (AutoLock l = _lock.lock())
{
if (onStartupErrors == null)
onStartupErrors = new MultiException();
onStartupErrors.add(cause);
}
}
/**
* Set a contextAttribute that will be set for every Context deployed by this provider.
*

View File

@ -81,7 +81,7 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding
if (LOG.isDebugEnabled())
{
LOG.debug("Binding: Configuring webapp context with global settings from: " + _jettyXml);
LOG.debug("Binding: Configuring webapp context with global settings from: {}", _jettyXml);
}
if (_jettyXml == null)
@ -111,7 +111,7 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding
}
else
{
LOG.info("Binding: Unable to locate global webapp context settings: " + _jettyXml);
LOG.info("Binding: Unable to locate global webapp context settings: {}", _jettyXml);
}
}
}

View File

@ -123,18 +123,18 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
protected void doStart() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug(this.getClass().getSimpleName() + ".doStart()");
LOG.debug("{}.doStart()", this.getClass().getSimpleName());
if (_monitored.size() == 0)
throw new IllegalStateException("No configuration dir specified");
LOG.info("Deployment monitor " + _monitored + " at interval " + _scanInterval);
LOG.info("Deployment monitor {}", _monitored);
List<File> files = new ArrayList<>();
for (Resource resource : _monitored)
{
if (resource.exists() && resource.getFile().canRead())
files.add(resource.getFile());
else
LOG.warn("Does not exist: " + resource);
LOG.warn("Does not exist: {}", resource);
}
_scanner = new Scanner();

View File

@ -101,9 +101,7 @@
<!-- handler by context path and virtual host, and the -->
<!-- DefaultHandler, which handles any requests not handled by -->
<!-- the context handlers. -->
<!-- Other handlers may be added to the "Handlers" collection, -->
<!-- for example the jetty-requestlog.xml file adds the -->
<!-- RequestLogHandler after the default handler -->
<!-- Other handlers may be added to the "Handlers" collection. -->
<!-- =========================================================== -->
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerList">

View File

@ -10,11 +10,14 @@
<packaging>pom</packaging>
<properties>
<assembly-directory>${basedir}/target/distribution</assembly-directory>
<home-directory>${basedir}/target/home</home-directory>
<assembly-directory>${project.build.directory}/distribution</assembly-directory>
<temp-home-directory>${project.build.directory}/home</temp-home-directory>
<home-directory>${assembly-directory}/jetty-home</home-directory>
<base-directory>${assembly-directory}/demo-base</base-directory>
</properties>
<build>
<plugins>
<!--
Copies the additional distribution content over the unpacked jetty-home
@ -64,9 +67,9 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<version>${project.version}</version>
<type>zip</type>
<type>tar.gz</type>
<overWrite>true</overWrite>
<outputDirectory>${home-directory}</outputDirectory>
<outputDirectory>${temp-home-directory}</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>META-INF/**</excludes>
@ -87,7 +90,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>test.war</destFileName>
</artifactItem>
<artifactItem>
@ -97,7 +100,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>test-jaas.war</destFileName>
</artifactItem>
<artifactItem>
@ -107,7 +110,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>test-jndi.war</destFileName>
</artifactItem>
<artifactItem>
@ -117,7 +120,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>test-spec.war</destFileName>
</artifactItem>
<artifactItem>
@ -127,7 +130,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>javadoc-proxy.war</destFileName>
</artifactItem>
<artifactItem>
@ -137,7 +140,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<outputDirectory>${base-directory}/webapps</outputDirectory>
<destFileName>async-rest.war</destFileName>
</artifactItem>
</artifactItems>
@ -159,7 +162,7 @@
<classifier>config</classifier>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}</outputDirectory>
<outputDirectory>${base-directory}</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>META-INF/**</excludes>
@ -181,7 +184,7 @@
<classifier>config</classifier>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}</outputDirectory>
<outputDirectory>${base-directory}</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>META-INF/**</excludes>
@ -203,7 +206,7 @@
<classifier>config</classifier>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}</outputDirectory>
<outputDirectory>${base-directory}</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>META-INF/**</excludes>
@ -225,7 +228,7 @@
<classifier>config</classifier>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}</outputDirectory>
<outputDirectory>${base-directory}</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>META-INF/**</excludes>
@ -246,7 +249,7 @@
<classifier>html</classifier>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}/demo-base/webapps/doc</outputDirectory>
<outputDirectory>${base-directory}/webapps/doc</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
@ -265,8 +268,15 @@
</goals>
<configuration>
<tasks>
<copy todir="${home-directory}">
<fileset dir="${temp-home-directory}/jetty-home-${project.version}/" />
</copy>
<copy todir="${assembly-directory}">
<fileset dir="${home-directory}/jetty-home-${project.version}/" />
<fileset dir="${home-directory}">
<include name="NOTICE.txt" />
<include name="LICENSE.txt" />
<include name="VERSION.txt" />
</fileset>
</copy>
</tasks>
</configuration>
@ -279,64 +289,16 @@
</goals>
<configuration>
<tasks>
<chmod dir="${assembly-directory}/bin" perm="755" includes="**/*.sh" />
<chmod dir="${home-directory}/bin" perm="755" includes="**/*.sh" />
</tasks>
</configuration>
</execution>
<execution>
<id>removeKeystore</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<delete file="${assembly-directory}/etc/keystore.p12" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<resourceBundles>
<resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.2</resourceBundle>
</resourceBundles>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<!--
Setup the jetty distribution as a jetty.home/jetty.base combo using start.ini configuration mechanism
-->
<execution>
<id>setup home</id>
<phase>package</phase>
<configuration>
<mainClass>org.eclipse.jetty.start.Main</mainClass>
<arguments>
<argument>jetty.home=${assembly-directory}</argument>
<argument>jetty.base=${assembly-directory}</argument>
<argument>--add-to-start=server,deploy,websocket,ext,resources,jsp,jstl,http</argument>
</arguments>
</configuration>
<goals>
<goal>java</goal>
</goals>
</execution>
<!--
Setup the demo-base using the start.d configuration mechanism
-->
@ -346,10 +308,10 @@
<configuration>
<mainClass>org.eclipse.jetty.start.Main</mainClass>
<arguments>
<argument>jetty.home=${assembly-directory}</argument>
<argument>jetty.base=${assembly-directory}/demo-base</argument>
<argument>jetty.home=${home-directory}</argument>
<argument>jetty.base=${base-directory}</argument>
<argument>--create-startd</argument>
<argument>--add-to-start=server,requestlog,deploy,websocket,ext,resources,client,annotations,jndi,servlets,jsp,jstl,http,https,demo</argument>
<argument>--add-module=server,requestlog,deploy,websocket,ext,resources,client,annotations,jndi,servlets,jsp,jstl,http,https,demo</argument>
</arguments>
</configuration>
<goals>
@ -405,9 +367,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<version>${project.version}</version>
<type>pom</type>
<type>tar.gz</type>
<optional>true</optional>
</dependency>
<!-- demo-base/webapps -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-webapp</artifactId>
@ -415,20 +378,6 @@
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-proxy-webapp</artifactId>
<type>war</type>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
<artifactId>example-async-rest-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-jaas-webapp</artifactId>
@ -450,6 +399,21 @@
<type>war</type>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-proxy-webapp</artifactId>
<type>war</type>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
<artifactId>example-async-rest-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<optional>true</optional>
</dependency>
<!-- documentation -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-documentation</artifactId>

View File

@ -1,68 +0,0 @@
JETTY
=====
The Jetty project is a 100% Java HTTP Server, HTTP Client
and Servlet Container from the eclipse foundation
http://www.eclipse.org/jetty/
Jetty is open source and is dual licensed using the Apache 2.0 and
Eclipse Public License 1.0. You may choose either license when
distributing Jetty.
RUNNING JETTY
=============
The run directory is either the top-level of a binary release
or jetty-distribution/target/distribution directory when built from
source.
To run with the default options:
$ cd demo-base
$ java -jar ../start.jar
To see the available options and the default arguments
provided by the start.ini file:
$ java -jar /path/to/start.jar --help
Many Jetty features can be enabled by using the --module command
For example:
$ cd mybase
$ java -jar /path/to/start.jar --module=https,deploy
Will enable the https and deploy modules (and their transitive
dependencies) temporarily for this specific run of Jetty.
To see what modules are available
$ java -jar /path/to/start.jar --list-modules
JETTY BASE
==========
The jetty.base property is a property that can be defined on the
command line (defaults to what your java 'user.dir' property points to)
Jetty's start.jar mechanism will configure your jetty instance from
the configuration present in this jetty.base directory.
Example setup:
# Create the base directory
$ mkdir mybase
$ cd mybase
# Initialize the base directory's start.ini and needed directories
$ java -jar /path/to/jetty-dist/start.jar --add-to-start=http,deploy
# Run this base directory configuration
$ java -jar /path/to/jetty-dist/start.jar

View File

@ -0,0 +1,49 @@
RUNNING JETTY
Assuming unix commands the general form of running jetty is:
$ JETTY_HOME=/path/to/jetty-home
$ cd /path/to/jetty-base
$ java -jar $JETTY_HOME/start.jar
To see all the options to the start command:
$ java -jar $JETTY_HOME/start.jar --help
RUNNING THE DEMO_BASE
To see the configuration of the included demo-base
$ cd demo-base
$ java -jar $JETTY_HOME/start.jar --list-config
To run the demo (from the demo-base directory):
$ java -jar $JETTY_HOME/start.jar
RUNNING A WAR
This distribution contains a jetty-base directory with a minimal configuration.
To enable http and webapp deployment for this base
$ JETTY_HOME=$PWD/jetty-home
$ cd jetty-base
$ java -jar $JETTY_HOME/start.jar --add-to-start=http,deploy
$ cp /path/to/mywebapp.war webapps
To see what other modules can be configured:
$ java -jar $JETTY_HOME/start.jar --list-modules
This war in this base can then be run with
$ java -jar $JETTY_HOME/start.jar
CREATING A NEW JETTY BASE
A new Jetty base can be created anywhere on the file system and with any name:
$ mkdir /path/to/my-jetty-base
$ cd /path/to/my-jetty-base
$ java -jar $JETTY_HOME/start.jar --create-startd --add-to-start=server,http,deploy
$ cp /path/to/mywebapp.war webapps
This base can then be run with
$ java -jar $JETTY_HOME/start.jar

View File

@ -1,22 +0,0 @@
#===========================================================
# Jetty Startup
#
# Starting Jetty from this {jetty.home} is not recommended.
#
# A proper {jetty.base} directory should be configured, instead
# of making changes to this {jetty.home} directory.
#
# See documentation about {jetty.base} at
# http://www.eclipse.org/jetty/documentation/current/startup.html
#
# A demo-base directory has been provided as an example of
# this sort of setup.
#
# $ cd demo-base
# $ java -jar ../start.jar
#
#===========================================================
# To disable the warning message, comment the following line
--module=home-base-warning

View File

@ -1,33 +0,0 @@
This directory is scanned by the WebAppDeployer provider for web
applications to deploy using the following conventions:
+ A directory called example/ will be deployed as a standard web
application if it contains a WEB-INF/ subdirectory, otherwise it will be
deployed as context of static content. The context path will be /example
(eg http://localhost:8080/example/) unless the base name is "root" (not
case sensitive), in which case the context path is /. If the directory name
ends with ".d" it is ignored (but may still be used by explicit configuration).
+ A file called example.war will be deployed as a standard web application
with the context path /example (eg http://localhost:8080/example/). If the
base name is root, then the context path is /. If example.war and example/
exist, then only the WAR is deployed (which may use the directory as an
unpack location).
+ An XML file like example.xml will be deployed as a context whose
configuration is defined by the XML. The context path must be set by
the configuration itself. If example.xml and example.war exist, then
only the XML is deployed (which may use the war in its configuration).
This directory is scanned for additions, removals and updates
for hot deployment.
To configure the deployment mechanism, edit the files:
start.d/500-deploy.ini
etc/jetty-deploy.ini
To disable the auto deploy mechanism use the command:
java -jar start.jar --disable=deploy

View File

@ -68,6 +68,11 @@
<artifactId>jetty-servlets</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util-ajax</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
@ -94,6 +99,11 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.memcached</groupId>
<artifactId>jetty-memcached-sessions</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
@ -165,9 +175,9 @@
<SRCDIR>${basedir}/..</SRCDIR>
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/master</GITBROWSEURL>
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-10.0.x-doc-refactor/jetty-documentation/src/main/asciidoc</GITDOCURL>
<DISTGUIDE>../distribution-guide/index.html</DISTGUIDE>
<EMBEDGUIDE>../embedded-guide/index.html</EMBEDGUIDE>
<QUICKGUIDE>../quickstart-guide/index.html</QUICKGUIDE>
<OPGUIDE>../operations-guide/index.html</OPGUIDE>
<PROGGUIDE>../programming-guide/index.html</PROGGUIDE>
<GSTARTGUIDE>../gettingstarted-guide/index.html</GSTARTGUIDE>
<CONTRIBGUIDE>../contribution-guide/index.html</CONTRIBGUIDE>
<MVNCENTRAL>http://central.maven.org/maven2</MVNCENTRAL>
<VERSION>${project.version}</VERSION>
@ -176,7 +186,7 @@
</configuration>
<executions>
<execution>
<id>quickstart-guide</id>
<id>operations-guide</id>
<phase>generate-resources</phase>
<goals>
<goal>process-asciidoc</goal>
@ -184,23 +194,9 @@
<configuration>
<backend>html5</backend>
<templateDir>${web.resources.directory}/asciidoc/slim-template</templateDir>
<sourceDirectory>${basedir}/src/main/asciidoc/quickstart-guide</sourceDirectory>
<sourceDirectory>src/main/asciidoc/operations-guide</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<outputDirectory>${project.build.directory}/html/quickstart-guide</outputDirectory>
</configuration>
</execution>
<execution>
<id>distribution-guide</id>
<phase>generate-resources</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html5</backend>
<templateDir>${web.resources.directory}/asciidoc/slim-template</templateDir>
<sourceDirectory>${basedir}/src/main/asciidoc/distribution-guide</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<outputDirectory>${project.build.directory}/html/distribution-guide</outputDirectory>
<outputDirectory>${project.build.directory}/html/operations-guide</outputDirectory>
</configuration>
</execution>
<execution>
@ -219,7 +215,7 @@
</configuration>
</execution>
<execution>
<id>embedded-guide</id>
<id>programming-guide</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
@ -227,9 +223,9 @@
<configuration>
<backend>html5</backend>
<templateDir>${web.resources.directory}/asciidoc/slim-template</templateDir>
<sourceDirectory>${basedir}/src/main/asciidoc/embedded-guide</sourceDirectory>
<sourceDirectory>${basedir}/src/main/asciidoc/programming-guide</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<outputDirectory>${project.build.directory}/html/embedded-guide</outputDirectory>
<outputDirectory>${project.build.directory}/html/programming-guide</outputDirectory>
<sourceHighlighter>coderay</sourceHighlighter>
<requires>
<require>asciidoctor-diagram</require>

View File

@ -163,6 +163,59 @@ Intellectual Property is a hallmark concern of the Eclipse Foundation so you are
As much as we would like to accept a tremendous pull request, without the proper chain of events being completed our hands are tied.
That being said, the steps are not particularly onerous and we are happy to work with you to get them accomplished.
==== Logging Conventions
When deciding when and what to log, bear in mind a few things:
* never use `LOG.debug` without a preceding `if (LOG.isDebugEnabled())`
* we don't want to pollute the log with very long stacktraces unless necessary
* we don't want to routinely produce logging events in response to data sent by a user
* we should not call more than one LOG method for a single event: otherwise log messages may be interleaved and more confusing
* we should never LOG.warn and then throw that exception, as that will result in double handling
* we should seldom LOG.debug and then throw as that will make debug verbose and add little information
* when interacting with a request, or information received from a client:
** no logging unless `isDebugEnabled`, in which case you output at `DEBUG` level eg:
[source, java, subs="{sub-order}"]
----
catch (Throwable t)
{
if (LOG.isDebugEnabled())
LOG.debug("Something happened {} {} {}",x, y, z, t);
}
----
* when calling into application code that throws an exception:
** use `INFO` level, and use `isDebugEnabled` to cut down on the size of the logging of stack traces:
[source, java, subs="{sub-order}"]
----
catch (Throwable t)
{
if (LOG.isDebugEnabled())
LOG.info("Something happened {} {} {}", x, y, z, t);
else
LOG.info("Something happened {} {} {} {}", x, y, z, t.toString());
}
----
* when exceptions happen in jetty code:
** mostly use `WARN` or `ERROR` level
** if the exception is not entirely unexpected, can happen relatively frequently, or can potentially have a very long stack trace and you don't want to clutter up the log, you can use `isDebugEnabled` to cut down on the size of the logging of the stacktrace:
[source, java, subs="{sub-order}"]
----
catch (Throwable t)
{
if (LOG.isDebugEnabled())
LOG.warn("Something happened {} {} {}", x, y, z, t);
else
LOG.warn("Something happened {} {} {} {}", x, y, z, t.toString());
}
----
____
[TIP]
Be aware that `LOG.warn("Something happened", t)` is the same as `LOG.warn("Something happened {}", t)`, at least for the default jetty logging.
In both cases, the full stacktrace is output. If you only want the log message, you need to do `LOG.warn("Something happened {}", t.toString())`.
____
[[cg-patches]]
=== Contributing Patches

View File

@ -1,46 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-management]]
== Session Management
Sessions are a concept within the Servlet api which allow requests to store and retrieve information across the time a user spends in an application.
Choosing the correct session manager implementation is an important consideration for every application as each can fit and perform optimally in different situations.
If you need a simple in-memory session manager that can persist to disk then session management using the local file system can be a good place to start.
If you need a session manager that can work in a clustered scenario with multiple instances of Jetty, then the JDBC session manager can be an excellent option.
Jetty also offers more niche session managers that leverage backends such as MongoDB, Inifinispan, or even Google's Cloud Data Store.
include::session-hierarchy.adoc[]
include::sessions-details.adoc[]
include::session-configuration-housekeeper.adoc[]
include::session-configuration-sessioncache.adoc[]
include::session-configuration-memory.adoc[]
include::session-configuration-file-system.adoc[]
include::session-configuration-jdbc.adoc[]
include::session-configuration-mongodb.adoc[]
include::session-configuration-infinispan.adoc[]
include::session-configuration-hazelcast.adoc[]
include::session-configuration-gcloud.adoc[]
include::session-configuration-memcachedsessiondatastore.adoc[]
include::sessions-usecases.adoc[]
//include::setting-session-characteristics.adoc[]
//include::using-persistent-sessions.adoc[]
//include::session-clustering-jdbc.adoc[]
//include::session-clustering-mongodb.adoc[]
//include::session-clustering-infinispan.adoc[]
//include::session-clustering-gcloud-datastore.adoc[]

View File

@ -1,276 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-clustering-gcloud-datastore]]
=== Session Clustering with Google Cloud Datastore
Jetty can support session clustering by persisting sessions to https://cloud.google.com/datastore/docs/concepts/overview[Google Cloud Datastore].
Each Jetty instance locally caches sessions for which it has received requests, writing any changes to the session through to the Datastore as the request exits the server.
Sessions must obey the Serialization contract, and servlets must call the `Session.setAttribute()` method to ensure that changes are persisted.
The persistent session mechanism works in conjunction with a load balancer that supports stickiness.
Stickiness can be based on various data items, such as source IP address or characteristics of the session ID or a load-balancer specific mechanism.
For those load balancers that examine the session ID, the Jetty persistent session mechanism appends a node ID to the session ID, which can be used for routing.
==== Configuration
There are two components to session management in Jetty: a session ID manager and a session manager.
- The session ID manager ensures that session IDs are unique across all webapps hosted on a Jetty instance, and thus there can only be one session ID manager per Jetty instance.
- The session manager handles the session lifecycle (create/update/invalidate/expire) on behalf of a web application, so there is one session manager per web application instance.
These managers also cooperate and collaborate with the `org.eclipse.jetty.server.session.SessionHandler` to enable cross-context dispatch.
==== The gcloud-sessions Module
When using the jetty distribution, to enable Cloud Datastore session persistence, you will first need to enable the `gcloud-sessions` link:#startup-modules[module] for your link:#creating-jetty-base[base] using the `--add-to-start` argument to the link:#startup-overview[start.jar].
As part of the module installation, the necessary jars will be dynamically downloaded and installed to your `${jetty.base}/lib/gcloud` directory.
If you need to up or downgrade the version of the jars, then you can delete the jars that were automatically installed and replace them.
Once you've done that, you will need to prevent jetty's startup checks from detecting the missing jars.
To do that, you can use `--skip-file-validation=glcoud-sessions` argument to start.jar on the command line, or place that line inside `${jetty.base}/start.ini` to ensure it is used for every start.
===== Configuring the GCloudSessionIdManager
The gcloud-sessions module will have installed file called `${jetty.home}/etc/jetty-gcloud-sessions.xml`.
This file configures an instance of the `GCloudSessionIdManager` that will be shared across all webapps deployed on that server. It looks like this:
[source, xml, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-gcloud/jetty-gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml[]
----
You configure it by setting values for properties.
The properties will either be inserted as commented out in your `start.ini`, or your `start.d/gcloud-sessions.ini` file, depending on how you enabled the module.
The only property you always need to set is the name of the node in the cluster:
jetty.gcloudSession.workerName::
The name that uniquely identifies this node in the cluster.
This value will also be used by the sticky load balancer to identify the node.
Don't forget to change the value of this property on *each* node on which you enable gcloud datastore session clustering.
===== Configuring GCloud Datastore
Things that you will need:
- a local installation of the https://cloud.google.com/sdk/[Google Cloud SDK]
- a project id referred to below as [YOUR PROJECT ID]
- to have https://cloud.google.com/datastore/docs/activate[enabled your project id] to use GCloud Datastore
====== Using GCloud Datastore from Compute/AppEngine
If you are running your webapp from within ComputeEngine or AppEngine, you do not need to do anything else in order to configure your GCloud setup. All necessary information will be inferred from the environment by the infrastrcture.
====== Using GCloud Datastore from an external server
If you are running your webapp externally to Google infrastructure, you can still interact with the remote GCloud Datastore service.
Execute the following commands:
- gcloud config set project [YOUR PROJECT ID].
- gcloud auth login
This will populate your environment with the necessary authentication information to allow you to contact the remote GCloud Datastore instance.
====== Using GCloud Datastore local development server
If you would like to locally test your application, you can use the Google Cloud SDK's https://cloud.google.com/datastore/docs/tools/datastore-emulator[GCloud Datastore emulator].
Follow the instructions on the https://cloud.google.com/datastore/docs/tools/datastore-emulator[GCloud Datastore emulator page] to set up your environment.
===== Configuring the GCloudSessionManager
As mentioned elsewhere, there must be one `SessionManager` per context (e.g. webapp).
Each SessionManager needs to reference the single `GCloudSessionIdManager`.
The way you configure a `GCloudSessionManager` depends on whether you're configuring from a context xml file, a `jetty-web.xml` file or code.
The basic difference is how you get a reference to the Jetty `org.eclipse.jetty.server.Server` instance.
From a context xml file, you reference the Server instance as a Ref:
[source, xml, subs="{sub-order}"]
----
<!-- Get a reference to the GCloudSessionIdManager -->
<Ref id="Server">
<Call id="idMgr" name="getSessionIdManager"/>
</Ref>
<!-- Use the GCloudSessionIdManager to set up the GCloudSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.gcloud.session.GCloudSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="scavengeIntervalSec">600</Set>
</New>
</Arg>
</New>
</Set>
----
From a `WEB-INF/jetty-web.xml` file, you can reference the Server instance directly:
[source, xml, subs="{sub-order}"]
----
<!-- Reference the server directly -->
<Get name="server">
<Get id="idMgr" name="sessionIdManager"/>
</Get>
<!-- Apply the SessionIdManager to the GCloudSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.gcloud.session.GCloudSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="scavengeIntervalSec">600</Set>
</New>
</Arg>
</New>
</Set>
----
The `GCloudSessionManager` supports the following configuration setters:
scavengeIntervalSec::
Time in seconds between runs of a scavenger task that looks for expired old sessions to delete.
The default is 10 minutes.
If set to 0, no scavenging is done.
staleIntervalSec::
The length of time a session can be in memory without being checked against the cluster.
A value of 0 indicates that the session is never checked against the cluster - the current node is considered to be the master for the session.
maxQueryResults::
The maximum number of results to return for a query to find expired sessions.
For efficiency it is important to limit the size of the result.
The default is 100.
If 0 or negative numbers are set, the default is used instead.
===== The gcloud-memcached-sessions module
As an optimization, you can have Jetty store your session data into GCloud Datastore but also cache it into memcached. This serves two purposes: faster read-accesses and also better support for non-sticky load balancers (although using a non-sticky load balancer is highly undesirable and not recommended).
You will need to enable the `gcloud-memcached-sessions` link:#startup-modules[module] for your link:#creating-jetty-base[base] using the `--add-to-start` argument to the link:#startup-overview[start.jar].
If you already enabled the gcloud-sessions module, that's fine as the gcloud-memcached-sessions module depends on it anyway.
Jetty uses the https://github.com/killme2008/xmemcached[Xmemcached] java client.
It depends on http://www.slf4j.org/[slf4j], so you will need to choose an http://www.slf4j.org/[slf4j logging implementation]. You can copy the chosen implementation jars into your $jetty.base/lib/ext directory.
====== Configuring the GCloudSessionIdManager
The instructions here are exactly the same as for the gcloud-sessions module.
====== Configuring GCloud Datastore
The instructions here are exactly the same as for the gcloud-sessions module.
====== Configuring Memcached
If you have installed memcached on a host and port other than the defaults of `localhost` and `11211`, then you will need to take note of these values and supply them to the configuration of the `GCloudMemcachedSessionManager`.
====== Configuring the GCloudMemcachedSessionManager
*Note that* you will be configuring a `GCloudMemcachedSessionManager` 'instead of' a `GCloudSessionManager`.
As usual, there must be only one per context (e.g. webapp).
Each GCloudMemcachedSessionManager needs to reference the single `GCloudSessionIdManager`.
The way you configure a `GCloudMemcachedSessionManager` depends on whether you're configuring from a context xml file, a `jetty-web.xml` file or code.
The basic difference is how you get a reference to the Jetty `org.eclipse.jetty.server.Server` instance.
From a context xml file, you reference the Server instance as a Ref:
[source, xml, subs="{sub-order}"]
----
<!-- Get a reference to the GCloudSessionIdManager -->
<Ref id="Server">
<Call id="idMgr" name="getSessionIdManager"/>
</Ref>
<!-- Use the GCloudSessionIdManager to set up the GCloudMemcachedSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.gcloud.memcached.session.GCloudMemcachedSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="scavengeIntervalSec">600</Set>
<Set name="host">myhost</Set>
<Set name="port">11211</Set>
<Set name="expirySec">0</Set>
</New>
</Arg>
</New>
</Set>
----
From a `WEB-INF/jetty-web.xml` file, you can reference the Server instance directly:
[source, xml, subs="{sub-order}"]
----
<!-- Reference the server directly -->
<Get name="server">
<Get id="idMgr" name="sessionIdManager"/>
</Get>
<!-- Apply the SessionIdManager to the GCloudMemcachedSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.gcloud..memcached.session.GCloudMemcachedSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="scavengeIntervalSec">600</Set>
<Set name="host">myhost</Set>
<Set name="port">11211</Set>
<Set name="expirySec">0</Set>
</New>
</Arg>
</New>
</Set>
----
The `GCloudMemcachedSessionManager` supports the following configuration setters:
scavengeIntervalSec::
Time in seconds between runs of a scavenger task that looks for expired old sessions to delete.
The default is 10 minutes.
If set to 0, no scavenging is done.
staleIntervalSec::
The length of time a session can be in memory without being checked against the cluster.
A value of 0 indicates that the session is never checked against the cluster - the current node is considered to be the master for the session.
maxQueryResults::
The maximum number of results to return for a query to find expired sessions.
For efficiency it is important to limit the size of the result.
The default is 100.
If 0 or negative numbers are set, the default is used instead.
host::
The address of the host where the memcached server is running. Defaults to "localhost".
port::
The port on the host where the memcached serer is running. Defaults to "11211".
expirySec::
The time in seconds that an entry in the memcached cache is considered valid. By default, entries are are not aged out of the cached, however they may be evicted due to memory constraints.

View File

@ -1,192 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-clustering-infinispan]]
=== Session Clustering with Infinispan
Jetty can support session clustering by persisting sessions to http://www.infinispan.org[Infinispan].
Each Jetty instance locally caches sessions for which it has received requests, writing any changes to the session through to Infinispan as the request exits the server.
Sessions must obey the Serialization contract, and servlets must call the `Session.setAttribute()` method to ensure that changes are persisted.
The persistent session mechanism works in conjunction with a load balancer that supports stickiness.
Stickiness can be based on various data items, such as source IP address or characteristics of the session ID or a load-balancer specific mechanism.
For those load balancers that examine the session ID, the Jetty persistent session mechanism appends a node ID to the session ID, which can be used for routing.
==== Configuration
There are two components to session management in Jetty: a session ID manager and a session manager.
* The session ID manager ensures that session IDs are unique across all webapps hosted on a Jetty instance, and thus there can only be one session ID manager per Jetty instance.
* The session manager handles the session lifecycle (create/update/invalidate/expire) on behalf of a web application, so there is one session manager per web application instance.
These managers also cooperate and collaborate with the `org.eclipse.jetty.server.session.SessionHandler` to enable cross-context dispatch.
==== The Infinispan Module
When using the jetty distribution, to enable Infinispan session persistence, you will first need to enable the Infinispan link:#startup-modules[module] for your link:#creating-jetty-base[base] using the `--add-to-start` argument to the link:#startup-overview[start.jar].
As part of the module installation, the necessary Infinispan jars will be dynamically downloaded and installed to your `${jetty.base}/lib/infinispan` directory.
If you need to up or downgrade the version of the Infinispan jars, then you can delete the jars that were automatically installed and replace them.
Once you've done that, you will need to prevent Jetty's startup checks from detecting the missing jars.
To do that, you can use `--skip-file-validation=infinispan` argument to start.jar on the command line, or place that line inside `${jetty.base}/start.ini` to ensure it is used for every start.
You will also find the following properties, either in your base's `start.d/infinispan.ini` file or appended to your `start.ini`, depending on how you enabled the module:
....
## Unique identifier for this node in the cluster
jetty.infinispanSession.workerName=node1
....
jetty.infinispanSession.workerName::
The name that uniquely identifies this node in the cluster.
This value will also be used by the sticky load balancer to identify the node.
Don't forget to change the value of this property on *each* node on which you enable Infinispan session clustering.
These properties are applied to the `InfinispanSessionIdManager` described below.
===== Configuring the InfinispanSessionIdManager
The Infinispan module will have installed file called `$\{jetty.home}/etc/jetty-infinispan.xml`.
This file configures an instance of the `InfinispanSessionIdManager` that will be shared across all webapps deployed on that server.
It looks like this:
[source, xml, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-infinispan/src/main/config/etc/jetty-infinispan.xml[]
----
As you can see, you configure the Infinispan http://infinispan.org/docs/7.1.x/user_guide/user_guide.html#_the_cache_apis[Cache] instance that the `InfinispanSessionIdManager` should use in this file.
By default, the Infinispan http://infinispan.org/docs/7.1.x/getting_started/getting_started.html#_running_infinispan_on_a_single_node[Default cache] instance is used (e.g. on the local node).
You can instead use a custom Cache setup - the `jetty-infinispan.xml` file shows you how to configure a remote Cache (using the http://infinispan.org/docs/7.1.x/user_guide/user_guide.html#_using_hot_rod_server[hotrod java client]).
The `InfinispanSessionIdManager` can be configured by calling setters:
idleExpiryMultiple::
Sessions that are not immortal, e.g. they have an expiry time, have their ids stored into Infinispan with an http://infinispan.org/docs/7.1.x/user_guide/user_guide.html#_expiration[idle expiry timeout] equivalent to double the session's timeout.
This should be sufficient to ensure that a session id that is in-use by a session is never accidentally removed.
However, should you wish to, you can configure this to any integral value to effectively increase the http://infinispan.org/docs/7.1.x/user_guide/user_guide.html#_expiration[idle expiry] timeout.
===== Configuring the InfinispanSessionManager
As mentioned elsewhere, there should be one `InfinispanSessionManager` per context (e.g. webapp).
It will need to reference the single `InfinispanSessionIdManager` configured previously for the Server.
The way you configure a `InfinispanSessionManager` depends on whether you're configuring from a context xml file, a `jetty-web.xml` file or code.
The basic difference is how you get a reference to the Jetty `org.eclipse.jetty.server.Server` instance.
From a context xml file, you reference the Server instance as a Ref:
[source, xml, subs="{sub-order}"]
----
<!-- Expose the jetty infinispan classes for session serialization -->
<Get name="serverClasspathPattern">
<Call name="add">
<Arg>-org.eclipse.jetty.session.infinispan.</Arg>
</Call>
</Get>
<!-- Get a reference to the InfinispanSessionIdManager -->
<Ref id="Server">
<Call id="idMgr" name="getSessionIdManager"/>
</Ref>
<!-- Get a referencee to the Cache from the InfinispanSessionIdManager -->
<Ref id="idMgr">
<Get id="cache" name="cache"/>
</Ref>
<!-- Use the InfinispanSessionIdManager and Cache to setup up the InfinispanSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.session.infinispan.InfinispanSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="cache">
<Ref id="cache">
</Ref>
</Set>
<Set name="scavengeInterval">60</Set>
</New>
</Arg>
</New>
</Set>
----
From a `WEB-INF/jetty-web.xml` file, you can reference the Server instance directly:
[source, xml, subs="{sub-order}"]
----
<!-- Expose the jetty infinispan classes for session serialization -->
<Get name="serverClasspathPattern">
<Call name="add">
<Arg>-org.eclipse.jetty.session.infinispan.</Arg>
</Call>
</Get>
<!-- Reference the server directly -->
<Get name="server">
<Get id="idMgr" name="sessionIdManager"/>
</Get>
<!-- Get a reference to the Cache via the InfinispanSessionIdManager -->
<Ref id="idMgr">
<Get id="cache" name="cache"/>
</Ref>
<!-- Apply the SessionIdManager and Cache to the InfinispanSessionManager -->
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mgr" class="org.eclipse.jetty.session.infinispan.InfinispanSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
<Set name="cache">
<Ref id="cache">
</Ref>
</Set>
<Set name="scavengeInterval">600</Set>
</New>
</Arg>
</New>
</Set>
----
The InfinispanSessionManager can be provided by calling setters:
scavengeInterval::
Time in seconds between runs of a scavenger task that looks for expired old sessions to delete.
The default is 10 minutes.
staleIntervalSec::
The length of time a session can be in memory without being checked against the cluster.
A value of 0 indicates that the session is never checked against the cluster - the current node is considered to be the master for the session.
===== Using HotRod
If you're using the hotrod client - where serialization will be required - you will need to ensure that the hotrod marshalling software works with Jetty classloading.
To do this, firstly ensure that you have included the lines containing the `getServerClasspathPattern().add(...)` to your context xml file as shown above.
Then, create the file `${jetty.base}/resources/hotrod-client.properties`.
Add the following line to this file:
....
infinispan.client.hotrod.marshaller=org.eclipse.jetty.session.infinispan.WebAppMarshaller
....

View File

@ -1,228 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-clustering-jdbc]]
=== Session Clustering with a Database
Jetty can support session clustering by persisting sessions to a shared database.
Each Jetty instance locally caches sessions for which it has received requests, writing any changes to the session through to the database as the request exits the server.
Sessions must obey the Serialization contract, and servlets must call the `Session.setAttribute()` method to ensure that changes are persisted.
The persistent session mechanism works in conjunction with a load balancer that supports stickiness.
Stickiness can be based on various data items, such as source IP address or characteristics of the session ID or a load-balancer specific mechanism.
For those load balancers that examine the session ID, the Jetty persistent session mechanism appends a node ID to the session ID, which can be used for routing.
In this type of solution, the database can become both a bottleneck and a single point of failure.
Jetty takes steps to reduce the load on the database (discussed below), but in a heavily loaded environment you might need to investigate other optimization strategies such as local caching and database replication.
You should also consult your database vendor's documentation for information on how to ensure high availability and failover of your database.
==== Configuration
There are two components to session management in Jetty: a session ID manager and a session manager.
* The session ID manager ensures that session IDs are unique across all webapps hosted on a Jetty instance, and thus there can only be one session ID manager per Jetty instance.
* The session manager handles the session lifecycle (create/update/invalidate/expire) on behalf of a web application, so there is one session manager per web application instance.
These managers also cooperate and collaborate with the `org.eclipse.jetty.server.session.SessionHandler` to enable cross-context dispatch.
==== The jdbc-session Module
When using the jetty distribution, to enable jdbc session persistence, you will first need to enable the jdbc-session link:#startup-modules[module] for your link:#creating-jetty-base[base] using the `--add-to-start` argument to the link:#startup-overview[start.jar].
You will also find the following properties, either in your base's start.d/jdbc-session.ini file or appended to your start.ini, depending on how you enabled the module:
[source, java, subs="{sub-order}"]
----
## Unique identifier for this node in the cluster
jetty.jdbcSession.workerName=node1
##Uncomment either the datasource name or driverClass and connectionURL
#jetty.jdbcSession.datasource=sessions
jetty.jdbcSession.driverClass=org.apache.derby.jdbc.EmbeddedDriver
jetty.jdbcSession.connectionURL=jdbc:derby:sessions;create=true
----
jetty.jdbcSession.workerName::
The name that uniquely identifies this node in the cluster.
This value will also be used by the sticky load balancer to identify the node.
Don't forget to change the value of this property on *each* node on which you enable jdbc session clustering.
jetty.jdbcSession.scavenge::
The time in seconds between sweeps of a task which scavenges old expired sessions.
The default is 10 minutess.
Increasing the frequency is not recommended as doing so increases the load on the database with very little gain.
jetty.jdbcSession.datasource::
The name of a `javax.sql.DataSource` that gives access to the database that holds the session information.
You should configure *either* this or the jdbc driver information described next.
jetty.jdbcSession.datasource and jetty.jdbcSession.connectionURL::
This is the name of the jdbc driver class, and a jdbc connection url suitable for that driver.
You should configure *either* this or the jdbc datasource name described above.
These properties are applied to the `JDBCSessionIdManager` described below.
===== Configuring the JDBCSessionIdManager
The jdbc-session module will have installed file called `$\{jetty.home}/etc/jetty-jdbc-sessions.xml`.
This file configures an instance of the `JDBCSessionIdManager` that will be shared across all webapps deployed on that server.
It looks like this:
[source, xml, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-server/src/main/config/etc/jetty-jdbc-sessions.xml[]
----
As well as uncommenting and setting up appropriate values for the properties discussed above, you will also need to edit this file and uncomment *either* the data source or the driver info elements.
As Jetty configuration files are direct mappings of XML to Java, it is straight forward to do this in code:
[source, java, subs="{sub-order}"]
----
Server server = new Server();
...
JDBCSessionIdManager idMgr = new JDBCSessionIdManager(server);
idMgr.setWorkerName("node1");
idMgr.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=janb");
idMgr.setScavengeInterval(600);
server.setSessionIdManager(idMgr);
----
====== Configuring the Database Schema
You may find it necessary to change the names of the tables and columns that the JDBC Session management uses to store the session information.
The defaults used are:
.Default Values for Session Id Table
[options="header"]
|===========================
|table name |JettySessionIds
|columns |id
|===========================
.Default Values for Session Table
[options="header"]
|=======================================================================
|table name |JettySessions
|columns |rowId, sessionId, contextPath, virtualHost, lastNode,
accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime,
expiryTime, maxInterval, map
|=======================================================================
To change these values, use the link:{JDURL}/org/eclipse/jetty/server/session/SessionIdTableSchema.html[org.eclipse.jetty.server.session.SessionIdTableSchema] and link:{JDURL}/org/eclipse/jetty/server/session/SessionTableSchema.html[org.eclipse.jetty.server.session.SessionTableSchema] classes.
These classes have getter/setter methods for the table name and all columns.
Here's an example of changing the name of `JettySessionsId` table and its single column.
This example will use java code, but as explained above, you may also do this via a Jetty xml configuration file:
[source, java, subs="{sub-order}"]
----
JDBCSessionIdManager idManager = new JDBCSessionIdManager(server);
SessionIdTableSchema idTableSchema = new SessionIdTableSchema();
idTableSchema.setTableName("mysessionids");
idTableSchema.setIdColumn("theid");
idManager.setSessionIdTableSchema(idTableSchema);
----
In a similar fashion, you can change the names of the table and columns for the `JettySessions` table.
*Note* that both the `SessionIdTableSchema` and the `SessionTableSchema` instances are set on the `JDBCSessionIdManager` class.
[source, java, subs="{sub-order}"]
----
JDBCSessionIdManager idManager = new JDBCSessionIdManager(server);
SessionTableSchema sessionTableSchema = new SessionTableSchema();
sessionTableSchema.setTableName("mysessions");
sessionTableSchema.setIdColumn("mysessionid");
sessionTableSchema.setAccessTimeColumn("atime");
sessionTableSchema.setContextPathColumn("cpath");
sessionTableSchema.setCookieTimeColumn("cooktime");
sessionTableSchema.setCreateTimeColumn("ctime");
sessionTableSchema.setExpiryTimeColumn("extime");
sessionTableSchema.setLastAccessTimeColumn("latime");
sessionTableSchema.setLastNodeColumn("lnode");
sessionTableSchema.setLastSavedTimeColumn("lstime");
sessionTableSchema.setMapColumn("mo");
sessionTableSchema.setMaxIntervalColumn("mi");
idManager.setSessionTableSchema(sessionTableSchema);
----
===== Configuring the JDBCSessionManager
As mentioned elsewhere, there should be one `JDBCSessionManager` per context (e.g. webapp).
It will need to reference the single `JDBCSessionIdManager` configured previously for the Server.
The way you configure a `JDBCSessionManager` depends on whether you're configuring from a context xml file, a `jetty-web.xml` file or code.
The basic difference is how you get a reference to the Jetty `org.eclipse.jetty.server.Server` instance.
From a context xml file, you reference the Server instance as a Ref:
[source, xml, subs="{sub-order}"]
----
<Ref id="Server">
<Call id="idMgr" name="getSessionIdManager"/>
</Ref>
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="jdbcmgr" class="org.eclipse.jetty.server.session.JDBCSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
</New>
</Arg>
</New>
</Set>
----
From a `WEB-INF/jetty-web.xml` file, you can reference the Server instance directly:
[source, xml, subs="{sub-order}"]
----
<Get name="server">
<Get id="idMgr" name="sessionIdManager"/>
</Get>
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New class="org.eclipse.jetty.server.session.JDBCSessionManager">
<Set name="sessionIdManager">
<Ref id="idMgr"/>
</Set>
</New>
</Arg>
</New>
</Set>
----
If you're embedding this in code:
[source, java, subs="{sub-order}"]
----
//assuming you have already set up the JDBCSessionIdManager as shown earlier
//and have a reference to the Server instance:
WebAppContext wac = new WebAppContext();
... //configure your webapp context
JDBCSessionManager jdbcMgr = new JDBCSessionManager();
jdbcMgr.setSessionIdManager(server.getSessionIdManager());
SessionHandler sessionHandler = new SessionHandler(jdbcMgr);
wac.setSessionHandler(sessionHandler);
----

View File

@ -1,240 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-clustering-mongodb]]
=== Session Clustering with MongoDB
Jetty can support session clustering by persisting sessions into http://www.mogodb.org[MongoDB].
Each Jetty instance locally caches sessions for which it has received requests, writing any changes to the session through to the cluster as the request exits the server.
Sessions must obey the Serialization contract, and servlets must call the `Session.setAttribute()` method to ensure that changes are persisted.
The session persistence mechanism works in conjunction with a load balancer that supports stickiness.
Stickiness can be based on various data items, such as source IP address or characteristics of the session ID or a load-balancer specific mechanism.
For those load balancers that examine the session ID, the Jetty persistent session mechanism appends a node ID to the session ID, which can be used for routing.
In this type of solution, the traffic on the network needs to be carefully watched and tends to be the bottleneck.
You are probably investigating this solution in order to scale to large amount of users and sessions, so careful attention should be paid to your usage scenario.
Applications with a heavy write profile to their sessions will consume more network bandwidth than profiles that are predominately read oriented.
We recommend using this session manager with largely read based session scenarios.
==== Configuration
There are two components to session management in Jetty: a session ID manager and a session manager.
* The session ID manager ensures that session IDs are unique across all webapps hosted on a Jetty instance, and thus there can only be one session ID manager per Jetty instance.
* The session manager handles the session lifecycle (create/update/invalidate/expire) on behalf of a web application, so there is one session manager per web application instance.
These managers also cooperate and collaborate with the `org.eclipse.jetty.server.session.SessionHandler` to enable cross-context dispatch.
==== The nosql Module
When using the jetty distribution, to enable the MongoDB session persistence mechanism, you will first need to enable the nosql link:#startup-modules[module] for your link:#creating-jetty-base[base] using the `--add-to-start` argument to the link:#startup-overview[start.jar].
This module will automatically download the `mongodb-java-driver` and install it to your base's `lib/nosql` directory.
As part of the module installation, the necessary mongo java driver jars will be dynamically downloaded and installed to your `${jetty.base}/lib/nosql` directory.
If you need to up or downgrade the version of these jars, then you can delete the jars that were automatically installed and replace them.
Once you've done that, you will need to prevent Jetty's startup checks from detecting the missing jars.
To do that, you can use `--skip-file-validation=nosql` argument to start.jar on the command line, or place that line inside `${jetty.base}/start.ini` to ensure it is used for every start.
You will also find the following properties, either in your base's `start.d/nosql.ini` file or appended to your `start.ini`, depending on how you enabled the module:
[source, xml, subs="{sub-order}"]
----
## Unique identifier for this node in the cluster
jetty.nosqlSession.workerName=node1
## Interval in seconds between scavenging expired sessions
jetty.nosqlSession.scavenge=1800
----
The `jetty.nosqlSession.workerName` is the unique name for this Jetty Server instance.
It will be used by the sticky load balancer to uniquely identify the node.
You should change this value on *each* node to which you install MongoDB session management.
The `jetty.nosqlSession.scavenge` property defines the time in seconds between runs of the scavenger: the scavenger is a task which runs periodically to clean out sessions that have expired but become stranded in the database for whatever reason.
These properties are substituted into the configuration of the `MongoDBSessionIdManager` and `MongoSessionManager`.
===== Configuring the MongoSessionIdManager
The nosql module will have installed file called `$\{jetty.home}/etc/jetty-nosql.xml`.
This file configures an instance of the `MongoSessionIdManager` that will be shared across all webapps deployed on that server.
It looks like this:
[source, xml, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-nosql/src/main/config/etc/jetty-nosql.xml[]
----
The `MongoSessionIdManager` needs access to a MongoDB cluster, and the `jetty-nosql.xml` file assumes the defaults of localhost and default MongoDB port.
If you need to configure something else, you will need to edit this file.
Here's an example of a more complex setup to use a remote MongoDB instance:
[source, xml, subs="{sub-order}"]
----
<New id="mongodb" class="com.mongodb.Mongo">
<Arg>
<New class="java.util.ArrayList">
<Call name="add">
<Arg>
<New class="com.mongodb.ServerAddress">
<Arg type="java.lang.String">foo.example.com</Arg>
<Arg type="int">27017</Arg>
</New>
</Arg>
</Call>
<!-- Add more Call statements here as desired --> </New>
</Arg>
<Call name="getDB">
<Arg>HttpSessions</Arg>
<Call id="sessionDocument" name="getCollection">
<Arg>sessions</Arg>
</Call>
</Call>
<!-- If you want to configure Jetty to be able to read through the slaves, call the following: -->
<Call name="slaveOk"/>
</New>
<Set name="sessionIdManager">
<New id="mongoIdMgr" class="org.eclipse.jetty.nosql.mongodb.MongoSessionIdManager">
<Arg>
<Ref id="Server"/>
</Arg>
<Arg>
<Ref id="sessionDocument"/>
</Arg>
<Set name="workerName"><Property name="jetty.nosqlSession.workerName" default="node1"/></Set>
<Set name="scavengePeriod"><Property name="jetty.nosqlSession.scavenge" default="1800"/></Set>
</New>
</Set>
----
As Jetty configuration files are direct mappings of XML to Java, it is straight forward to do this in code:
[source, java, subs="{sub-order}"]
----
Server server = new Server();
...
MongoSessionIdManager idMgr = newMongoSessionIdManager(server);
idMgr.setWorkerName("node1");
idMgr.setScavengePeriod(1800);
server.setSessionIdManager(idMgr);
----
The MongoSessionIdManager has slightly different options than some of our more traditional session options.
The `MongoDBSessionIdManager` has the same scavenge timers which govern the setting of a valid session to invalid after a certain period of inactivity.
New to this session id manager is the extra purge setting which governs removal from the MongoDB cluster.
This can be configured through the 'purge' option. Purge is by default set to true and by default runs daily for each node on the cluster.
Also able to be configured is the age in which an invalid session will be retained which is set to 1 day by default.
This means that invalid sessions will be removed after lingering in the MongoDB instance for a day.
There is also an option for purging valid sessions that have not been used recently.
The default time for this is 1 week. You can disable these behaviors by setting purge to false.
scavengeDelay::
How long to delay before periodically looking for sessions to scavenge?
scavengePeriod::
How much time after a scavenge has completed should you wait before doing it again?
scavengeBlockSize::
Number of session ids to which to limit each scavenge query.
If you have a very large number of sessions in memory then setting this to a non 0 value may help speed up scavenging by breaking the scavenge into multiple, queries.
The default is 0, which means that all session ids are considered in a single query.
purge (Boolean)::
Do you want to purge (delete) sessions that are invalid from the session store completely?
purgeDelay::
How often do you want to perform this purge operation?
purgeInvalidAge::
How old should an invalid session be before it is eligible to be purged?
purgeValidAge::
How old should a valid session be before it is eligible to be marked invalid and purged?
Should this occur at all?
purgeLimit::
Integer value that represents how many items to return from a purge query.
The default is 0, which is unlimited.
If you have a lot of old expired orphaned sessions then setting this value may speed up the purge process.
preserveOnStop::
Whether or not to retain all sessions when the session manager stops.
Default is `true`.
===== Configuring a MongoSessionManager
As mentioned elsewhere, there should be one `MongoSessionManager` per context (e.g. webapp).
It will need to reference the single `MongoSessionIdManager` configured previously for the Server.
The way you configure a link:{JDURL}/org/eclipse/jetty/nosql/MongoSessionManager.html[org.eclipse.jetty.nosql.mongodb.MongoSessionManager] depends on whether you're configuring from a link:#deployable-descriptor-file[context xml] file or a link:#jetty-web-xml-config[jetty-web.xml] file or code.
The basic difference is how you get a reference to the Jetty `org.eclipse.jetty.server.Server` instance.
From a context xml file, you reference the Server instance as a Ref:
[source, xml, subs="{sub-order}"]
----
<Ref name="Server" id="Server">
<Call id="mongoIdMgr" name="getSessionIdManager"/>
</Ref>
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New id="mongoMgr" class="org.eclipse.jetty.nosql.mongodb.MongoSessionManager">
<Set name="sessionIdManager">
<Ref id="mongoIdMgr"/>
</Set>
</New>
</Arg>
</New>
</Set>
----
From a `WEB-INF/jetty-web.xml` file, you can reference the Server instance directly:
[source, xml, subs="{sub-order}"]
----
<Get name="server">
<Get id="mongoIdMgr" name="sessionIdManager"/>
</Get>
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New class="org.eclipse.jetty.nosql.mongodb.MongoSessionManager">
<Set name="sessionIdManager">
<Ref id="mongoIdMgr"/>
</Set>
</New>
</Arg>
</New>
</Set>
----
If you're embedding this in code:
[source, java, subs="{sub-order}"]
----
//assuming you have already set up the MongoSessionIdManager as shown earlier
//and have a reference to the Server instance:
WebAppContext wac = new WebAppContext();
... //configure your webapp context
MongoSessionManager mongoMgr = new MongoSessionManager();
mongoMgr.setSessionIdManager(server.getSessionIdManager());
wac.setSessionHandler(new SessionHandler(mongoMgr));
----

View File

@ -1,281 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[setting-session-characteristics]]
=== Setting Session Characteristics
Sessions are a concept within the Servlet api which allow requests to store and retrieve information across the time a user spends in an application.
Choosing the correct session manager implementation is an important consideration for every application as each can fit and perform optimally in different situations.
If you need a simple in-memory session manager that can persist to disk then the `HashSessionManager` can be a good place to start.
If you need a session manager that can work in a clustered scenario with multiple instances of Jetty, then the JDBC session manager can be an excellent option.
Jetty also offers more niche session managers that leverage backends such as MongoDB, Inifinispan, or even Google's Cloud Data Store.
To modify the session characteristics of a web application, you can use the following parameters, applying them as in one of the example configurations:
[[using-init-parameters]]
==== Using Init Parameters
Use these parameters to set session characteristics.
.Init Parameters
[cols=",,",options="header",]
|=======================================================================
|Context Parameter |Default Value |Description
|org.eclipse.jetty.servlet.SessionCookie |JSESSIONID |Session cookie
name defaults to JSESSIONID, but can be set for a particular webapp with
this context param.
|org.eclipse.jetty.servlet.SessionIdPathParameterName |jsessionid
|Session URL parameter name. Defaults to jsessionid, but can be set for
a particular webapp with this context param. Set to "none" to disable
URL rewriting.
|org.eclipse.jetty.servlet.SessionDomain |- |Session Domain. If this
property is set as a ServletContext param, then it is used as the domain
for session cookies.If it is not set, then no domain is specified for
the session cookie.
|org.eclipse.jetty.servlet.SessionPath |- |Session Path. If this
property is set as a ServletContext param, then it is used as the path
for the session cookie. If it is not set, then the context path is used
as the path for the cookie.
|org.eclipse.jetty.servlet.MaxAge |-1 |Session Max Age. If this property
is set as a ServletContext param, then it is used as the max age for the
session cookie. If it is not set, then a max age of -1 is used.
|org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding |false |If
true, Jetty will add JSESSIONID parameter even when encoding external
urls with calls to encodeURL(). False by default.
|=======================================================================
[[applying-init-parameters]]
===== Applying Init Parameters
The following sections provide examples of how to apply the init parameters.
[[context-parameter-example]]
====== Context Parameter Example
You can set these parameters as context parameters in a web application's `WEB-INF/web.xml` file:
[source, xml, subs="{sub-order}"]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
...
<context-param>
<param-name>org.eclipse.jetty.servlet.SessionCookie</param-name>
<param-value>XSESSIONID</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.servlet.SessionIdPathParameterName</param-name>
<param-value>xsessionid</param-value>
</context-param>
...
</web-app>
----
[[web-application-examples]]
====== Web Application Examples
You can configure init parameters on a web application, either in code, or in a Jetty context xml file equivalent:
[source, xml, subs="{sub-order}"]
----
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/test</Set>
<Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test</Set>
...
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.SessionCookie</Arg>
<Arg>XSESSIONID</Arg>
</Call>
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.SessionIdPathParameterName</Arg>
<Arg>xsessionid</Arg>
</Call>
</Configure>
----
[[init-parameter-examples]]
====== SessionManager Examples
You can configure init parameters directly on a `SessionManager` instance, either in code or the equivalent in xml:
[source, xml, subs="{sub-order}"]
----
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/test</Set>
<Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test</Set>
...
<Get name="sessionHandler">
<Set name="sessionManager">
<New class="org.eclipse.jetty.server.session.HashSessionManager">
<Set name="sessionCookie">XSESSIONID</Set>
<Set name="sessionIdPathParameterName">xsessionid</Set>
</New>
</Set>
</Get>
</Configure>
----
==== Using Servlet 3.0 Session Configuration
With the advent of http://jcp.org/en/jsr/detail?id=315[Servlet Specification 3.0] there are new APIs for configuring session handling characteristics.
What was achievable before only via Jetty-specific link:#session-init-params[init-parameters] can now be achieved in a container-agnostic manner either in code, or via `web.xml`.
[[session-cookie-configuration]]
===== SessionCookieConfiguration
The http://docs.oracle.com/javaee/6/api/javax/servlet/SessionCookieConfig.html[javax.servlet.SessionCookieConfig] class can be used to set up session handling characteristics.
For full details, consult the http://docs.oracle.com/javaee/6/api/javax/servlet/SessionCookieConfig.html[javadoc].
Below is an example of this implementation: a `ServletContextListener` retrieves the `SessionCookieConfig` and sets up some new values when the context is being initialized:
[source, java, subs="{sub-order}"]
----
import javax.servlet.SessionCookieConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class TestListener implements ServletContextListener
{
public void contextInitialized(ServletContextEvent sce)
{
String comment = "This is my special cookie configuration";
String domain = "foo.com";
String path = "/my/special/path";
boolean isSecure = true;
boolean httpOnly = false;
int maxAge = 30000;
String cookieName = "FOO_SESSION";
SessionCookieConfig scf = sce.getServletContext().getSessionCookieConfig();
scf.setComment(comment);
scf.setDomain(domain);
scf.setHttpOnly(httpOnly);
scf.setMaxAge(maxAge);
scf.setPath(path);
scf.setSecure(isSecure);
scf.setName(cookieName);
}
public void contextDestroyed(ServletContextEvent sce)
{
}
}
----
You can also use `web.xml` to configure the session handling characteristics instead: here's an example doing exactly the same as above instead of using code:
[source, xml, subs="{sub-order}"]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true"
version="3.0">
<session-config>
<cookie-config>
<comment>This is my special cookie configuration</comment>
<domain>foo.com</domain>
<http-only>false</http-only>
<max-age>30000</max-age>
<path>/my/special/path</path>
<secure>true</secure>
<name>FOO_SESSION</name>
</cookie-config>
</session-config>
</web-app>
----
[[session-tracking-modes]]
===== SessionTrackingModes
In addition to the configuration of link:#session-cookie-configuration[session cookies], since Servlet 3.0 you can also use the http://docs.oracle.com/javaee/6/api/javax/servlet/SessionTrackingMode.html[javax.servlet.SessionTrackingMode] to configure session tracking.
To determine what are the _default_ session tracking characteristics used by the container, call:
[source, java, subs="{sub-order}"]
----
javax.servlet.SessionContext.getDefaultSessionTrackingModes();
----
This returns a java.util.Set of javax.servlet.SessionTrackingMode. The
_default_ session tracking modes for Jetty are:
* http://docs.oracle.com/javaee/6/api/javax/servlet/SessionTrackingMode.html#COOKIE[SessionTrackingMode.COOKIE]
* http://docs.oracle.com/javaee/6/api/javax/servlet/SessionTrackingMode.html#URL[SessionTrackingMode.URL]
To see which session tracking modes are actually in effect for this Context, the following call returns a `java.util.Set` of `javax.servlet.SessionTrackingMode`:
[source, java, subs="{sub-order}"]
----
javax.servlet.SessionContext.getEffectiveSessionTrackingModes();
----
To change the session tracking modes, call:
[source, java, subs="{sub-order}"]
----
javax.servlet.SessionContext.setSessionTrackingModes(Set<SessionTrackingMode>);
----
You may also set the tracking mode in `web.xml`, e.g.:
[source, xml, subs="{sub-order}"]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true"
version="3.0">
<session-config>
<tracking-mode>URL</tracking-mode>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
</web-app>
----

View File

@ -1,117 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[using-persistent-sessions]]
=== Using Persistent Sessions
It is sometimes useful to preserve existing Sessions across restarts of Jetty.
The link:{JDURL}/org/eclipse/jetty/server/session/HashSessionManager.html[`HashSessionManager`] supports this feature.
If you enable persistence, the `HashSessionManager` saves all existing, valid Sessions to disk before shutdown completes.
On restart, Jetty restores the saved Sessions.
[[enabling-persistence]]
==== Enabling Persistence
A `SessionManager` does just what its name suggests it manages the lifecycle and state of sessions on behalf of a webapp.
Each webapp must have its own unique `SessionManager` instance.
Enabling persistence is as simple as configuring the `HashSessionManager` as the `SessionManager` for a webapp and telling it where on disk to store the sessions:
[source, xml, subs="{sub-order}"]
----
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
.
.
.
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New class="org.eclipse.jetty.server.session.HashSessionManager">
<Set name="storeDirectory">your/chosen/directory/goes/here</Set>
</New>
</Arg>
</New>
</Set>
.
.
.
</Configure>
----
The above uses an example of a xref:intro-jetty-configuration-contexts[context configuration file].
[TIP]
====
If you want to persist the sessions from multiple webapps:
1. Configure a separate `HashSessionManager` for each.
2. Assign to each a different value for `storeDirectory`.
====
[[delaying-session-load]]
==== Delaying Session Load
You might need to ensure that the sessions are loaded AFTER the servlet environment starts up (by default, Jetty eagerly loads sessions as part of the container startup, but before it initializes the servlet environment).
For example, the Wicket web framework requires the servlet environment to be available when sessions are activated.
Using `SessionManager.setLazyLoad(true)`, Jetty loads sessions lazily either when it receives the first request for a session, or the session scavenger runs for the first time, whichever happens first.
Here's how the configuration looks in XML:
[source, xml, subs="{sub-order}"]
----
<Set name="sessionHandler">
<New class="org.eclipse.jetty.server.session.SessionHandler">
<Arg>
<New class="org.eclipse.jetty.server.session.HashSessionManager">
<Set name="lazyLoad">true</Set>
</New>
</Arg>
</New>
</Set>
----
[[enabling-persistence-for-jetty-maven-plugin]]
==== Enabling Persistence for the Jetty Maven Plugin
To enable session persistence for the Jetty Maven plugin, set up the `HashSessionManager` in the configuration section like so:
[source, xml, subs="{sub-order}"]
----
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.0.0.RC2 (or current version)</version>
<configuration>
<!-- ... -->
<webAppConfig implementation="org.eclipse.jetty.maven.plugin.JettyWebAppContext">
<defaultsDescriptor>${project.build.outputDirectory}/META-INF/webdefault.xml</defaultsDescriptor>
<contextPath>${jetty.contextRoot}</contextPath>
<sessionHandler implementation="org.eclipse.jetty.server.session.SessionHandler">
<sessionManager implementation="org.eclipse.jetty.server.session.HashSessionManager">
<storeDirectory>${project.basedir}/target/jetty-sessions</storeDirectory>
<idleSavePeriod>1</idleSavePeriod>
</sessionManager>
</sessionHandler>
</webAppConfig>
<!-- ... -->
</configuration>
</plugin>
----

View File

@ -1,89 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-file-system]]
=== Persistent Sessions: File System
Note: Persisting sessions to the local file system should *not* be used in a clustered environment.
==== Enabling File System Sessions
When using the Jetty distribution, you will first need to enable the `session-store-file` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-file
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-file initialized in ${jetty.base}/start.d/session-store-file.ini
MKDIR : ${jetty.base}/sessions
INFO : Base directory was modified
----
Doing this enables the File System Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-file` module as well as the `sessions` and `server` modules, which are required for the File System session management to operate.
Additionally a `${jetty.base}/sessions` directory was created.
By default Session files will be saved to this directory.
In addition to adding these modules to the classpath of the server, several ini configuration files were added to the `${jetty.base}/start.d` directory.
____
[NOTE]
Session data is now only loaded when requested.
Previous functionality such as `setLazyLoad` has been removed.
____
==== Configuring File System Session Properties
Opening `start.d/session-store-file.ini` will show a list of all the configurable options for the file system session module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-file
# Enables session persistent storage in files.
# ---------------------------------------
--module=session-store-file
jetty.session.file.storeDir=${jetty.base}/sessions
#jetty.session.file.deleteUnrestorableFiles=false
#jetty.session.savePeriod.seconds=0
----
jetty.session.storeDir::
This defines the location for storage of Session files.
jetty.session.file.deleteUnrestorableFiles::
Boolean.
If set to true, unreadable files will be deleted: this is useful to prevent repeated logging of the same error when the scavenger periodically (re-) attempts to load the corrupted information for a session in order to expire it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____

View File

@ -1,271 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-gcloud]]
=== Persistent Sessions: Google Cloud DataStore
==== Preparation
You will first need to create a project and enable the Google Cloud api: https://cloud.google.com/docs/authentication#preparation.
Take note of the project id that you create in this step as you need to supply it in later steps.
===== Communicating with GCloudDataStore
====== When running Jetty outside of google infrastructure
Before running Jetty, you will need to choose one of the following methods to set up the local environment to enable remote GCloud DataStore communications.
1. Using the GCloud SDK:
* Ensure you have the GCloud SDK installed: https://cloud.google.com/sdk/?hl=en.
* Use the GCloud tool to set up the project you created in the preparation step: `gcloud config set project PROJECT_ID`
* Use the GCloud tool to authenticate a google account associated with the project created in the preparation step: `gcloud auth login ACCOUNT`
2. Using environment variables
* Define the environment variable `GCLOUD_PROJECT` with the project id you created in the preparation step.
* Generate a JSON link:https://cloud.google.com/storage/docs/authentication?hl=en#service_accounts[service account key] and then define the environment variable `GOOGLE_APPLICATION_CREDENTIALS=/path/to/my/key.json`
====== When Running Jetty Inside of Google Infrastructure
The Google deployment tools will automatically configure the project and authentication information for you.
==== Configuring Indexes for Session Data
Using some special, composite indexes can speed up session search operations, although it may make write operations slower.
By default, indexes will not be used.
In order to use them, you will need to manually upload a file that defines the indexes.
This file is named `index.yaml` and you can find it in your distribution in `${jetty.base}/etc/sessions/gcloud/index.yaml`.
Follow the instructions link:https://cloud.google.com/datastore/docs/tools/#the_development_workflow_using_gcloud[here] to upload the pre-generated `index.yaml` file.
===== Communicating with the GCloudDataStore Emulator
To enable communication using the GCloud Emulator:
* Ensure you have the GCloud SDK installed: https://cloud.google.com/sdk/?hl=en
* Follow the instructions link:https://cloud.google.com/datastore/docs/tools/datastore-emulator[here] on how to start the GCloud datastore emulator, and how to propagate the environment variables that it creates to the terminal in which you run Jetty.
==== Enabling the Google Cloud DataStore Module
When using the Jetty distribution, you will first need to enable the `session-store-gcloud` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-gcloud
ALERT: There are enabled module(s) with licenses.
The following 2 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: gcloud
+ GCloudDatastore is an open source project hosted on Github and released under the Apache 2.0 license.
+ https://github.com/GoogleCloudPlatform/gcloud-java
+ http://www.apache.org/licenses/LICENSE-2.0.html
Module: slf4j-api
+ SLF4J is distributed under the MIT License.
+ Copyright (c) 2004-2013 QOS.ch
+ All rights reserved.
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Proceed (y/N)? y
INFO : webapp transitively enabled, ini template available with --add-to-start=webapp
INFO : jul-impl transitively enabled
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : servlet transitively enabled
INFO : gcloud transitively enabled, ini template available with --add-to-start=gcloud
INFO : annotations transitively enabled
INFO : plus transitively enabled
INFO : slf4j-api transitively enabled
INFO : security transitively enabled
INFO : gcloud-datastore transitively enabled
INFO : jcl-slf4j transitively enabled
INFO : session-store-gcloud initialized in ${jetty.base}/start.d/session-store-gcloud.ini
INFO : jndi transitively enabled
MKDIR : ${jetty.base}/etc
COPY : ${jetty.home}/modules/jul-impl/etc/java-util-logging.properties to ${jetty.base}/etc/java-util-logging.properties
MKDIR : ${jetty.base}/lib/slf4j
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/lib/gcloud
COPY : /Users/admin/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar to ${jetty.base}/lib/gcloud/aopalliance-1.0.jar
COPY : /Users/admin/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.1.3/jackson-core-2.1.3.jar to ${jetty.base}/lib/gcloud/jackson-core-2.1.3.jar
COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client-appengine/1.21.0/google-api-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-appengine-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client/1.20.0/google-api-client-1.20.0.jar to ${jetty.base}/lib/gcloud/google-api-client-1.20.0.jar
COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client-servlet/1.21.0/google-api-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-servlet-1.21.0.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/api/gax/0.0.21/gax-0.0.21.jar to ${jetty.base}/lib/gcloud/gax-0.0.21.jar
COPY : /Users/admin/.m2/repository/com/google/api/grpc/grpc-google-common-protos/0.1.0/grpc-google-common-protos-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-common-protos-0.1.0.jar
COPY : /Users/admin/.m2/repository/com/google/api/grpc/grpc-google-iam-v1/0.1.0/grpc-google-iam-v1-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-iam-v1-0.1.0.jar
COPY : /Users/admin/.m2/repository/com/google/auth/google-auth-library-credentials/0.3.1/google-auth-library-credentials-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-credentials-0.3.1.jar
COPY : /Users/admin/.m2/repository/com/google/auth/google-auth-library-oauth2-http/0.3.1/google-auth-library-oauth2-http-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-oauth2-http-0.3.1.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/auto/value/auto-value/1.2/auto-value-1.2.jar to ${jetty.base}/lib/gcloud/auto-value-1.2.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/cloud/datastore/datastore-v1-proto-client/1.3.0/datastore-v1-proto-client-1.3.0.jar to ${jetty.base}/lib/gcloud/datastore-v1-proto-client-1.3.0.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/cloud/datastore/datastore-v1-protos/1.3.0/datastore-v1-protos-1.3.0.jar to ${jetty.base}/lib/gcloud/datastore-v1-protos-1.3.0.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/0.5.1/google-cloud-core-0.5.1.jar to ${jetty.base}/lib/gcloud/google-cloud-core-0.5.0.jar
DOWNLD: https://repo1.maven.org/maven2/com/google/cloud/google-cloud-datastore/0.5.1/google-cloud-datastore-0.5.1.jar to ${jetty.base}/lib/gcloud/google-cloud-datastore-0.5.1.jar
COPY : /Users/admin/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar to ${jetty.base}/lib/gcloud/jsr305-1.3.9.jar
COPY : /Users/admin/.m2/repository/com/google/code/gson/gson/2.3/gson-2.3.jar to ${jetty.base}/lib/gcloud/gson-2.3.jar
COPY : /Users/admin/.m2/repository/com/google/guava/guava/19.0/guava-19.0.jar to ${jetty.base}/lib/gcloud/guava-19.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-appengine/1.21.0/google-http-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-appengine-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jackson2/1.19.0/google-http-client-jackson2-1.19.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson2-1.19.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jackson/1.21.0/google-http-client-jackson-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client/1.21.0/google-http-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jdo/1.21.0/google-http-client-jdo-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jdo-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-protobuf/1.20.0/google-http-client-protobuf-1.20.0.jar to ${jetty.base}/lib/gcloud/google-http-client-protobuf-1.20.0.jar
COPY : /Users/admin/.m2/repository/com/google/inject/guice/4.0/guice-4.0.jar to ${jetty.base}/lib/gcloud/guice-4.0.jar
COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client-appengine/1.21.0/google-oauth-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-appengine-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client/1.21.0/google-oauth-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client-servlet/1.21.0/google-oauth-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-servlet-1.21.0.jar
COPY : /Users/admin/.m2/repository/com/google/protobuf/protobuf-java/3.0.0/protobuf-java-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-3.0.0.jar
COPY : /Users/admin/.m2/repository/com/google/protobuf/protobuf-java-util/3.0.0/protobuf-java-util-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-util-3.0.0.jar
COPY : /Users/admin/.m2/repository/commons-codec/commons-codec/1.3/commons-codec-1.3.jar to ${jetty.base}/lib/gcloud/commons-codec-1.3.jar
COPY : /Users/admin/.m2/repository/io/grpc/grpc-context/1.0.1/grpc-context-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-context-1.0.1.jar
COPY : /Users/admin/.m2/repository/io/grpc/grpc-core/1.0.1/grpc-core-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-core-1.0.1.jar
COPY : /Users/admin/.m2/repository/io/grpc/grpc-protobuf/1.0.1/grpc-protobuf-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-1.0.1.jar
COPY : /Users/admin/.m2/repository/io/grpc/grpc-protobuf-lite/1.0.1/grpc-protobuf-lite-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-lite-1.0.1.jar
COPY : /Users/admin/.m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar to ${jetty.base}/lib/gcloud/javax.inject-1.jar
COPY : /Users/admin/.m2/repository/javax/jdo/jdo2-api/2.3-eb/jdo2-api-2.3-eb.jar to ${jetty.base}/lib/gcloud/jdo2-api-2.3-eb.jar
COPY : /Users/admin/.m2/repository/javax/transaction/transaction-api/1.1/transaction-api-1.1.jar to ${jetty.base}/lib/gcloud/transaction-api-1.1.jar
COPY : /Users/admin/.m2/repository/joda-time/joda-time/2.9.2/joda-time-2.9.2.jar to ${jetty.base}/lib/gcloud/joda-time-2.9.2.jar
COPY : /Users/admin/.m2/repository/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar to ${jetty.base}/lib/gcloud/httpclient-4.0.1.jar
COPY : /Users/admin/.m2/repository/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar to ${jetty.base}/lib/gcloud/httpcore-4.0.1.jar
COPY : /Users/admin/.m2/repository/org/codehaus/jackson/jackson-core-asl/1.9.11/jackson-core-asl-1.9.11.jar to ${jetty.base}/lib/gcloud/jackson-core-asl-1.9.11.jar
COPY : /Users/admin/.m2/repository/org/json/json/20151123/json-20151123.jar to ${jetty.base}/lib/gcloud/json-20151123.jar
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.21/jcl-over-slf4j-1.7.21.jar to ${jetty.base}/lib/slf4j/jcl-over-slf4j-1.7.21.jar
COPY : ${jetty.home}/modules/gcloud/index.yaml to ${jetty.base}/etc/index.yaml
INFO : Base directory was modified
ERROR : Module jcl-slf4j requires a module providing slf4j-impl from one of [slf4j-simple-impl, slf4j-logback, slf4j-jul, slf4j-log4j2, slf4j-log4j]
ERROR : Unsatisfied module dependencies: jcl-slf4j
Usage: java -jar $JETTY_HOME/start.jar [options] [properties] [configs]
java -jar $JETTY_HOME/start.jar --help # for more information
----
Doing this enables the GCloud Session module and any dependent session modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because the Google Cloud DataStore is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
You will notice, however, that the above output presented a warning: GCloud requires certain Java Commons Logging features to work correctly.
GCloud has a dependency on Java Commons Logging, and by default Jetty will route this through SLF4J.
Enabling the GCloud Sessions module will also enable the `jcl-slf4j` module, which sends JCL logging information to SLF4J.
It does *not*, however, configure a SLF4J implementation for the users.
As such, you will also need to enable one of the SLF4J implementation modules listed.
In this example, we will enable the `slf4j-simple-impl` module to provide a SLF4J implementation.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --add-to-start=slf4j-simple-impl
INFO : slf4j-simple-impl initialized in ${jetty.base}/start.d/slf4j-simple-impl.ini
INFO : resources transitively enabled
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/1.7.21/slf4j-simple-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-simple-1.7.21.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/slf4j-simple-impl/resources/simplelogger.properties to ${jetty.base}/resources/simplelogger.properties
INFO : Base directory was modified
----
When the `--add-to-start` argument was added to the command line the first time, it enabled the the `session-store-gcloud` module as well as several others, such as as `server`, `sessions`, `webapp` and others which are required for GCloud session management to operate; the `slf4j-simple-impl` and its dependent modules were added when the the command was run the second time.
In addition to adding these modules to the classpath of the server it also added the respective configuration files to the `${jetty.base}start.d` directory.
____
[NOTE]
If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `${jetty.base}/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
____
==== Configuring GCloud Session Properties
Opening the `start.d/session-store-gcloud.ini` will display a list of all the configurable properties for the Google Cloud DataStore module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-gcloud
# Enables GCloudDatastore session management.
# ---------------------------------------
--module=session-store-gcloud
## GCloudDatastore Session config
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
#jetty.session.gcloud.maxRetries=5
#jetty.session.gcloud.backoffMs=1000
#jetty.session.gcloud.namespace=
#jetty.session.gcloud.model.kind=GCloudSession
#jetty.session.gcloud.model.id=id
#jetty.session.gcloud.model.contextPath=contextPath
#jetty.session.gcloud.model.vhost=vhost
#jetty.session.gcloud.model.accessed=accessed
#jetty.session.gcloud.model.lastAccessed=lastAccessed
#jetty.session.gcloud.model.createTime=createTime
#jetty.session.gcloud.model.cookieSetTime=cookieSetTime
#jetty.session.gcloud.model.lastNode=lastNode
#jetty.session.gcloud.model.expiry=expiry
#jetty.session.gcloud.model.maxInactive=maxInactive
#jetty.session.gcloud.model.attributes=attributes
----
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
jetty.session.gcloud.maxRetries::
Maxmium number of tries to connect to GCloud DataStore to write sessions.
jetty.session.gcloud.backoffMs::
Amount of time, in milliseconds, between attempts to connect to the GCloud DataStore to write sessions.
jetty.session.gcloud.namespace::
Optional.
Sets the namespace for GCloud Datastore to use.
If set, partitions the visibility of session data between webapps, which is helpful for multi-tenant deployments.
More information can be found link:https://cloud.google.com/datastore/docs/concepts/multitenancy[here.]
The other values listed are simply the names of properties that represent stored session data, and can be changed if needed.

View File

@ -1,197 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-hazelcast]]
=== Persistent Sessions: Hazelcast
==== Enabling Hazelcast Sessions
When using the Jetty distribution, you will first need to enable the `session-store-hazelcast-remote` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
MKDIR : ${jetty.base}/start.d
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-hazelcast-remote
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: session-store-hazelcast-remote
+ Hazelcast is an open source project hosted on Github and released under the Apache 2.0 license.
+ https://hazelcast.org/
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-hazelcast-remote initialized in ${jetty.base}/start.d/session-store-hazelcast-remote.ini
MKDIR : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2
DOWNLD: https://repo1.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar
MKDIR : ${jetty.base}/lib/hazelcast
COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar
COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar
INFO : Base directory was modified
----
Doing this enables the remote Hazelcast Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because Hazelcast is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-hazelcast-remote` module as well as the `sessions` and `server` modules, which are required for Hazelcast session management to operate.
It also downloaded the needed Hazelcast-specific jar files and created a directory named `${jetty.base}/lib/hazelcast/` to house them.
In addition to adding these modules to the classpath of the server it also added several ini configuration files to the `${jetty.base}/start.d` directory.
____
[NOTE]
If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `${jetty.base}/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
____
==== Configuring Hazelcast Remote Properties
Opening the `start.d/session-store-hazelcast-remote.ini` will show a list of all the configurable options for the Hazelcast module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-hazelcast-remote
# Enables session data store in a remote Hazelcast Map
# ---------------------------------------
--module=session-store-hazelcast-remote
#jetty.session.hazelcast.mapName=jetty_sessions
#jetty.session.hazelcast.onlyClient=true
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
jetty.session.hazelcast.mapName::
Name of the Map in Hazelcast where sessions will be stored.
jetty.session.hazelcast.onlyClient::
Hazelcast instance will be configured in client mode
jetty.session.hazelcast.configurationLocation::
Path to an an Hazelcast xml configuration file
jetty.session.hazelcast.scavengeZombies::
True/False. `False` by default. If `true`, jetty will use hazelcast queries to find sessions that are no longer being used on any jetty node and whose expiry time has passed. If you enable this option, and your session stores attributes that reference classes from inside your webapp, or jetty classes, you will need to ensure that these classes are available on each of your hazelcast instances.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
Be aware using the `scavengeZombies` option that if your session attributes contain classes from inside your webapp (or jetty classes) then you will need to put these classes onto the classpath of all of your hazelcast instances.
____
==== Configuring Embedded Hazelcast Clustering
During testing, it can be helpful to run an in-process instance of Hazelcast.
To enable this you will first need to enable the `session-store-hazelcast-embedded` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
MKDIR : ${jetty.base}/start.d
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-hazelcast-embedded
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: session-store-hazelcast-embedded
+ Hazelcast is an open source project hosted on Github and released under the Apache 2.0 license.
+ https://hazelcast.org/
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-hazelcast-embedded initialized in ${jetty.base}/start.d/session-store-hazelcast-embedded.ini
MKDIR : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2
DOWNLD: https://repo1.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar
MKDIR : ${jetty.base}/lib/hazelcast
COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar
COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar
----
Doing this enables the embedded Hazelcast Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because Hazelcast is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-hazelcast-embedded` module as well as the `sessions` and `server` modules, which are required for Hazelcast session management to operate.
It also downloaded the needed Hazelcast-specific jar files and created a directory named `${jetty.base}/lib/hazelcast/` to house them.
In addition to adding these modules to the classpath of the server it also added several ini configuration files to the `${jetty.base}/start.d` directory.
==== Configuring Hazelcast Embedded Properties
Opening the `start.d/start.d/session-store-hazelcast-embedded.ini` will show a list of all the configurable options for the Hazelcast module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-hazelcast-embedded
# Enables session data store in an embedded Hazelcast Map
# ---------------------------------------
--module=session-store-hazelcast-embedded
#jetty.session.hazelcast.mapName=jetty_sessions
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
jetty.session.hazelcast.mapName::
Name of the Map in Hazelcast where sessions will be stored.
jetty.session.hazelcast.configurationLocation::
Path to an an Hazelcast xml configuration file
jetty.session.hazelcast.scavengeZombies::
True/False. `False` by default. If `true`, jetty will use hazelcast queries to find sessions that are no longer being used on any jetty node and whose expiry time has passed. If you enable this option, and your sessions contain attributes that reference classes from inside your webapp (or jetty classes) you will need to ensure that these classes are available on each of your hazelcast instances.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
Be aware using the `scavengeZombies` option that if your session attributes contain classes from inside your webapp (or jetty classes) then you will need to put these classes onto the classpath of all of your hazelcast instances. In the cast of embedded hazelcast, as it is started before your webapp, it will NOT have access to your webapp's classes - you will need to extract these classes and put them onto the jetty server's classpath.
____

View File

@ -1,56 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-configuration-housekeeper]]
=== The SessionIdManager and the Housekeeper
==== Default Settings
By default, Jetty will instantiate a single instance of the `DefaultSessionIdManager` and `HouseKeeper` at startup with default settings.
The default settings are:
DefaultSessionIdManager: worker name::
This uniquely identifies the jetty server instance within a cluster.
It is set from the value of the `JETTY_WORKER_INSTANCE` environment variable, or `node0` if the environment value is not set.
If you have more than one Jetty instance, it is *crucial* that you explicitly configure the worker name on each Jetty instance (see link:#session-idmanager-housekeeper-config[below] for how to configure).
HouseKeeper: scavenge interval::
This is the period in seconds between runs of the session scavenger, and by default is set to the equivalent of 10 minutes.
As a rule of thumb, you should ensure that the scavenge interval is shorter than the `maxInactiveInterval` of your sessions to ensure that they are promptly scavenged.
See below for instructions on how to configure this.
[[session-idmanager-housekeeper-config]]
==== Configuration
To change the default values, use the link:#startup-modules[module system] to link:#startup-modules[enable] the `sessions` module.
This will enable the `$jetty.home/etc/sessions/id-manager.xml` file and generate a `$jetty.base/start.d/sessions.ini` file.
The `id-manager.xml` file instantiates a single `DefaultSessionIdManager` and `HouseKeeper` and configures them using the properties from the `sessions.ini` file.
Edit the ini file to change the properties to easily customize the `DefaultSessionIdManager` and `HouseKeeper`:
jetty.sessionIdManager.workerName::
By default it is `node1`.
This uniquely identifies the Jetty server instance within a cluster.
If you have more than one Jetty instance, it is crucial that you configure the worker name differently on each jetty instance.
jetty.sessionScavengeInterval.seconds::
This is the period in seconds between runs of the session scavenger.
By default it will run every 600 secs (ie 10 mins).
As a rule of thumb, you should ensure that the scavenge interval is shorter than the maxInactiveInterval of your sessions to ensure that they are promptly scavenged.

View File

@ -1,248 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-infinispan]]
=== Persistent Sessions: Inifinspan
==== Enabling Infinispan Sessions
When using the Jetty distribution, you will first need to enable the `session-store-infinispan-remote` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-infinispan-remote
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: session-store-infinispan-remote
+ Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
+ http://infinispan.org/
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-infinispan-remote initialized in ${jetty.base}/start.d/session-store-infinispan-remote.ini
MKDIR : ${jetty.base}/lib/infinispan
DOWNLD: https://repo1.maven.org/maven2/org/infinispan/infinispan-remote-it/9.4.8.Final/infinispan-remote-it-9.4.8.Final.jar to ${jetty.base}/lib/infinispan/infinispan-remote-it-9.4.8.Final.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/session-store-infinispan-remote/resources/hotrod-client.properties to ${jetty.base}/resources/hotrod-client.properties
INFO : Base directory was modified
----
Doing this enables the remote Infinispan Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because Infinispan is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-infinispan-remote` module as well as the `sessions` and `server` modules, which are required for Infinispan session management to operate.
It also downloaded the needed Infinispan-specific jar files and created a directory named `${jetty.base}/lib/infinispan/` to house them.
In addition to adding these modules to the classpath of the server it also added several ini configuration files to the `${jetty.base}/start.d` directory.
____
[NOTE]
If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `${jetty.base}/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
____
==== Configuring Inifinspan Remote Properties
Opening the `start.d/session-store-infinispan-remote.ini` will show a list of all the configurable options for the JDBC module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-infinispan-remote
# Enables session data store in a remote Infinispan cache
# ---------------------------------------
--module=session-store-infinispan-remote
#jetty.session.infinispan.remoteCacheName=sessions
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
jetty.session.infinispan.remoteCacheName::
Name of the cache in Infinispan where sessions will be stored.
jetty.session.infinispan.idleTimeout.seconds::
Amount of time, in seconds, that a session entry in infinispan can be idle (ie not read or written) before infinispan will delete its entry.
Usually, you do *not* want to set a value for this, as you want jetty to handle all session expiration (and call any SessionListeners).
However, if there is the possibility that sessions can be left in infinispan but no longer referenced by any jetty node (so called "zombie" or "orphan" sessions), then you might want to use this feature.
You should make sure that the number of seconds you specify is sufficiently large to avoid the situation where a session is still being referenced by jetty, but is rarely accessed and thus deleted by infinispan.
Alternatively, you can enable the `infinispan-remote-query` module, which will allow jetty to search the infinispan session cache to proactively find and properly (ie calling any SessionListeners) scavenge defunct sessions.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
==== Configuring the Remote Infinispan Query Module
Enabling this module allows jetty to search infinispan for expired sessions that are no longer being referenced by any jetty node.
Note that this is an *additional* module, to be used in conjuction with the `session-store-infinispan-remote` module.
[source, screen, subs="{sub-order}"]
----
java -jar ../start.jar --add-to-start=infinispan-remote-query
----
There are no configuration properties associated with this module.
==== Configuring Embedded Inifinspan Clustering
During testing, it can be helpful to run an in-process instance of Infinispan.
To enable this you will first need to enable the `session-store-infinispan-embedded` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
____
[IMPORTANT]
If you are running Jetty with JDK 9 or greater, enable `session-store-infinispan-embedded-910.mod` instead.
____
[source, screen, subs="{sub-order}"]
----
java -jar ../start.jar --add-to-start=session-store-infinispan-embedded
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: session-store-infinispan-embedded
+ Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
+ http://infinispan.org/
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-infinispan-embedded initialised in ${jetty.base}/start.d/session-store-infinispan-embedded.ini
DOWNLOAD: https://repo1.maven.org/maven2/org/infinispan/infinispan-embedded-it/9.4.8.Final/infinispan-embedded-it-9.4.8.Final.jar to ${jetty.base}/lib/infinispan/infinispan-embedded-it-9.4.8.Final.jar
INFO : Base directory was modified
----
Doing this enables the embedded Infinispan Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because Infinispan is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-infinispan-embedded` module as well as the `sessions` and `server` modules, which are required for Infinispan session management to operate.
It also downloaded the needed Infinispan-specific jar files and created a directory named `${jetty.base}/lib/infinispan/` to house them.
In addition to adding these modules to the classpath of the server it also added several ini configuration files to the `${jetty.base}/start.d` directory.
==== Configuring Inifinspan Embedded Properties
Opening the `start.d/session-store-infinispan-remote.ini` will show a list of all the configurable options for the JDBC module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-infinispan-embedded
# Enables session data store in a local Infinispan cache
# ---------------------------------------
--module=session-store-infinispan-embedded
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
==== Configuring Inifinspan Embedded Query
Similarly to the `session-store-infinispan-remote` module, the `session-store-infinispan-embedded` module has an adjunct module `infinispan-embedded-query`, which when enabled, will allow jetty to detect and properly scavenge defunct sessions stranded in infinispan.
[source, screen, subs="{sub-order}"]
----
java -jar ../start.jar --add-to-start=infinispan-embedded-query
----
There are no configuration properties associated with this module.
==== Converting Session Format for Jetty-9.4.13
From Jetty-9.4.13 onwards, we have changed the format of the serialized session when using a remote cache (ie using hotrod).
Prior to release 9.4.13 we used the default Infinispan serialization, however this was not able to store sufficient information to allow jetty to properly deserialize session attributes in all circumstances.
See issue https://github.com/eclipse/jetty.project/issues/2919 for more background.
We have provided a conversion program which will convert any sessions stored in Infinispan to the new format.
____
[IMPORTANT]
We recommend that you backup your stored sessions before running the conversion program.
____
How to use the converter:
[source, screen, subs="{sub-order}"]
----
java -cp jetty-servlet-api-4.0.2.jar:jetty-util-{VERSION}.jar:jetty-server-{VERSION}.jar:infinispan-remote-9.1.0.Final.jar:jetty-infinispan-{VERSION}.jar:[other classpath] org.eclipse.jetty.session.infinispan.InfinispanSessionLegacyConverter
Usage: InfinispanSessionLegacyConverter [-Dhost=127.0.0.1] [-Dverbose=true|false] <cache-name> [check]
----
The classpath::
Must contain the servlet-api, jetty-util, jetty-server, jetty-infinispan and infinispan-remote jars. If your sessions contain attributes that use application classes, you will also need to also put those classes onto the classpath. If your session has been authenticated, you may also need to include the jetty-security and jetty-http jars on the classpath.
Parameters::
When used with no arguments the usage message is printed. When used with the `cache-name` parameter the conversion is performed. When used with both `cache-name` and `check` parameters, sessions are checked for whether or not they are converted.
-Dhost::: you can optionally provide a system property with the address of your remote Infinispan server. Defaults to the localhost.
-Dverbose::: defaults to false. If true, prints more comprehensive stacktrace information about failures. Useful to diagnose why a session is not converted.
cache-name::: the name of the remote cache containing your sessions. This is mandatory.
check::: the optional check command will verify sessions have been converted. Use it _after_ doing the conversion.
To perform the conversion, run the InfinispanSessionLegacyConverter with just the `cache-name`, and optionally the `host` system property.
The following command will attempt to convert all sessions in the cached named `my-remote-cache` on the machine `myhost`, ensuring that application classes in the `/my/custom/classes` directory are on the classpath:
[source, screen, subs="{sub-order}"]
----
java -cp jetty-servlet-api-4.0.2.jar:jetty-util-{VERSION}.jar:jetty-server-{VERSION}.jar:infinispan-remote-9.1.0.Final.jar:jetty-infinispan-{VERSION}.jar:/my/custom/classes org.eclipse.jetty.session.infinispan.InfinispanSessionLegacyConverter -Dhost=myhost my-remote-cache
----
If the converter fails to convert a session, an error message and stacktrace will be printed and the conversion will abort. The failed session should be untouched, however _it is prudent to take a backup of your cache before attempting the conversion_.

View File

@ -1,134 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-jdbc]]
=== Persistent Sessions: JDBC
==== Enabling JDBC Sessions
When using the Jetty distribution, you will first need to enable the `session-store-jdbc` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-jdbc
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : sessions/jdbc/datasource dynamic dependency of session-store-jdbc
INFO : session-store-jdbc initialized in ${jetty.base}/start.d/session-store-jdbc.ini
INFO : Base directory was modified
----
Doing this enables the JDBC Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-jdbc` module as well as the `sessions` and `server` modules, which are required for JDBC session management to operate.
In addition to adding these modules to the classpath of the server, several ini configuration files were added to the `${jetty.base}/start.d` directory.
==== Configuring JDBC Session Properties
Opening the `start.d/session-store-jdbc.ini` will show a list of all the configurable options for the JDBC module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-jdbc
# Enables JDBC persistent/distributed session storage.
# ---------------------------------------
--module=session-store-jdbc
##
##JDBC Session properties
##
#jetty.session.gracePeriod.seconds=3600
## Connection type:Datasource
db-connection-type=datasource
#jetty.session.jdbc.datasourceName=/jdbc/sessions
## Connection type:driver
#db-connection-type=driver
#jetty.session.jdbc.driverClass=
#jetty.session.jdbc.driverUrl=
## Session table schema
#jetty.session.jdbc.schema.accessTimeColumn=accessTime
#jetty.session.jdbc.schema.contextPathColumn=contextPath
#jetty.session.jdbc.schema.cookieTimeColumn=cookieTime
#jetty.session.jdbc.schema.createTimeColumn=createTime
#jetty.session.jdbc.schema.expiryTimeColumn=expiryTime
#jetty.session.jdbc.schema.lastAccessTimeColumn=lastAccessTime
#jetty.session.jdbc.schema.lastSavedTimeColumn=lastSavedTime
#jetty.session.jdbc.schema.idColumn=sessionId
#jetty.session.jdbc.schema.lastNodeColumn=lastNode
#jetty.session.jdbc.schema.virtualHostColumn=virtualHost
#jetty.session.jdbc.schema.maxIntervalColumn=maxInterval
#jetty.session.jdbc.schema.mapColumn=map
#jetty.session.jdbc.schema.table=JettySessions
# Optional name of the schema used to identify where the session table is defined in the database:
# "" - empty string, no schema name
# "INFERRED" - special string meaning infer from the current db connection
# name - a string defined by the user
#jetty.session.jdbc.schema.schemaName
# Optional name of the catalog used to identify where the session table is defined in the database:
# "" - empty string, no catalog name
# "INFERRED" - special string meaning infer from the current db connection
# name - a string defined by the user
#jetty.session.jdbc.schema.catalogName
----
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
db-connection-type::
Set to either `datasource` or `driver` depending on the type of connection being used.
jetty.session.jdbc.datasourceName::
Name of the remote datasource.
jetty.session.jdbc.driverClass::
Name of the JDBC driver that controls access to the remote database, such as `com.mysql.jdbc.Driver`
jetty.session.jdbc.driverUrl::
Url of the database which includes the driver type, host name and port, service name and any specific attributes unique to the database, such as a username.
As an example, here is a mysql connection with the username appended: `jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin`.
The `jetty.session.jdbc.schema.*` values represent the names of the table and columns in the JDBC database used to store sessions and can be changed to suit your environment.
There are also two special, optional properties: `jetty.session.jdbc.schema.schemaName` and `jetty.session.jdbc.schema.catalogName`.
The exact meaning of these two properties is dependent on your database vendor, but can broadly be described as further scoping for the session table name.
See https://en.wikipedia.org/wiki/Database_schema and https://en.wikipedia.org/wiki/Database_catalog.
These extra scoping names can come into play at startup time when jetty determines if the session table already exists, or otherwise creates it on-the-fly.
If you have employed either of these concepts when you pre-created the session table, or you want to ensure that jetty uses them when it auto-creates the session table, then you have two options: either set them explicitly, or let jetty infer them from a database connection (obtained using either a Datasource or Driver according to the `db-connection-type` you have configured).
To set them explicitly, uncomment and supply appropriate values for the `jetty.session.jdbc.schema.schemaName` and/or `jetty.session.jdbc.schema.catalogName` properties.
To allow jetty to infer them from a database connection, use the special string `INFERRED` instead.
If you leave them blank or commented out, then the sessions table will not be scoped by schema or catalog name.

View File

@ -1,33 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-memory]]
=== Non-Persistent Sessions
Non-clustered, non-persistent, in-memory-only is the default style of session management.
In previous versions of Jetty this was referred to as "hash" sessions, as they were stored in a `HashMap` in memory.
This is delivered by a combination of the `DefaultSessionCache` (to keep sessions in memory) and a `NullSessionDataStore` (to avoid session persistence).
If you do nothing, Jetty will instantiate one of each of these objects for each context at startup time using hard-coded defaults.
To explicitly set up non-persisted sessions using modules, use both the `session-cache-hash` and the `session-store-null` modules.
Enabling the modules allows you to configure behavior - see link:#session-configuration-sessioncache[the L1 Session Cache] for detailed information on configuration options for the `DefaultSessionCache`.
The `NullSessionDataStore` has no customizable options.

View File

@ -1,131 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[configuring-sessions-mongo]]
=== Persistent Sessions: MongoDB
==== Enabling MongoDB Sessions
When using the Jetty distribution, you will first need to enable the `session-store-mongo` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
[source, screen, subs="{sub-order}"]
----
$ java -jar ../start.jar --create-startd
INFO : Base directory was modified
$ java -jar ../start.jar --add-to-start=session-store-mongo
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: session-store-mongo
+ The java driver for the MongoDB document-based database system is hosted on GitHub and released under the Apache 2.0 license.
+ http://www.mongodb.org/
+ http://www.apache.org/licenses/LICENSE-2.0.html
Proceed (y/N)? y
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-mongo initialized in ${jetty.base}/start.d/session-store-mongo.ini
INFO : sessions/mongo/address dynamic dependency of session-store-mongo
MKDIR : ${jetty.base}/lib/nosql
DOWNLD: https://repo1.maven.org/maven2/org/mongodb/mongo-java-driver/2.13.2/mongo-java-driver-2.13.2.jar to ${jetty.base}/lib/nosql/mongo-java-driver-2.13.2.jar
INFO : Base directory was modified
----
Doing this enables the MongoDB Session module and any dependent modules or files needed for it to run on the server.
The example above is using a fresh `${jetty.base}` with nothing else enabled.
Because MongoDB is not a technology provided by the Eclipse Foundation, users are prompted to assent to the licenses of the external vendor (Apache in this case).
When the `--add-to-start` argument was added to the command line, it enabled the the `session-store-mongo` module as well as the `sessions` and `server` modules, which are required for MongoDB session management to operate..
It also downloaded the needed Mongo-specific jar file and created a directory named `${jetty.base}/lib/nosql/` to house it.
In addition to adding these modules to the classpath of the server, several ini configuration files were added to the `${jetty.base}/start.d` directory.
____
[NOTE]
If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `${jetty.base}/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
____
==== Configuring MongoDB Session Properties
Opening the `start.d/session-store-mongo.ini` will show a list of all the configurable options for the MongoDB module:
[source, screen, subs="{sub-order}"]
----
# ---------------------------------------
# Module: session-store-mongo
# Enables NoSql session management with a MongoDB driver.
# ---------------------------------------
--module=session-store-mongo
#jetty.session.mongo.dbName=HttpSessions
#jetty.session.mongo.collectionName=jettySessions
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
connection-type=address
#jetty.session.mongo.host=localhost
#jetty.session.mongo.port=27017
#connection-type=uri
#jetty.session.mongo.connectionString=mongodb://localhost
----
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
jetty.session.mongo.dbName::
Name of the database in Mongo used to store the Session collection.
jetty.session.mongo.collectionName::
Name of the collection in Mongo used to keep all of the Sessions.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
connection-type=address::
Used when utilizing a direct connection to the Mongo server.
jetty.session.mongo.host;;
Host name or address for the remote Mongo instance.
jetty.session.mongo.port;;
Port number for the remote Mongo instance.
connection-type=uri::
Used when utilizing MongoURI for secured connections.
jetty.session.mongo.connectionString;;
The string defining the MongoURI value, such as `mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]`.
More information on how to format the MongoURI string can be found in the https://docs.mongodb.com/manual/reference/connection-string/[official documentation for mongo.]
+
____
[NOTE]
You will only use *one* `connection-type` at a time, `address` or `uri`.
If both are utilized in your `session-store-mongo.ini`, only the last `connection-type` configured in the file will be used.
By default, the `connection-type` of `address` is used.
____

View File

@ -1,99 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[session-configuration-sessioncache]]
=== The L1 Session Cache
==== The DefaultSessionCache
In the absence of any explicit configuration, Jetty will instantiate an instance of the `DefaultSessionCache` per context.
If you wish to change any of the default values, you need to enable the `session-cache-hash` link:#startup-modules[module].
Once the `session-cache-hash` module has been enabled, you can view a list of all the configurable values by opening `start.d/session-cache-hash.ini`:
[source, screen, subs="{sub-order}"]
----
--module=session-cache-hash
#jetty.session.evictionPolicy=-1
#jetty.session.saveOnInactiveEvict=false
#jetty.session.saveOnCreate=false
#jetty.session.removeUnloadableSessions=false
#jetty.session.flushOnResponseCommit=false
----
jetty.session.evictionPolicy::
Integer.
Controls whether session objects that are held in memory are subject to eviction from the memory cache.
Evicting sessions can reduce the memory footprint of the cache.
Eviction is usually used in conjunction with a `SessionDataStore` that persists sessions.
Values are:
* -1 : sessions are never evicted from the cache
* 0 : sessions are evicted from the cache as soon as the last active request for it finishes
* >= 1 : any positive number is the time in seconds after which a session that is in the cache but has not experienced any activity will be evicted
____
[NOTE]
If you are not using a `SessionDataStore` that persists sessions, be aware that evicted sessions will be lost.
____
jetty.session.saveOnInactiveEvict::
Boolean, default `false`.
Controls whether a session will be saved to the `SessionDataStore` just prior to its eviction.
jetty.session.saveOnCreate::
Boolean, default `false`.
Controls whether a session that is newly created will be immediately saved to the `SessionDataStore` or lazily saved as the last request for the session exits.
jetty.session.removeUnloadableSessions::
Boolean, default `false`.
Controls whether a session that cannot be restored - for example because it is corrupted - from the `SessionDataStore` is deleted by the `SessionDataStore`.
jetty.session.flushOnResponseCommit::
Boolean, default `false`.
If true, if a session is "dirty" - ie its attributes have changed - it will be written to the backing store as the response is about to commit.
This ensures that all subsequent requests whether to the same or different node will see the updated session data.
If false, a dirty session will only be written to the backing store when the last simultaneous request for it leaves the session.
For more general information on the uses of these configuration properties, see link:#sessions-details[Session Components].
==== The NullSessionCache
The `NullSessionCache` is a trivial implementation of the `SessionCache` that does not cache any session information.
You may need to use it if your clustering setup does not have a sticky load balancer, or if you want absolutely minimal support for sessions.
If you use this in conjunction with the `NullSessionDataStore`, then sessions will neither be retained in memory nor persisted.
To enable the `NullSessionCache`, enable the `sesssion-cache-null` link:#startup-modules[module].
Configuration options are:
jetty.session.saveOnCreate::
Boolean, default `false`.
Controls whether a session that is newly created will be immediately saved to the `SessionDataStore` or lazily saved as the last request for the session exits.
jetty.session.removeUnloadableSessions::
Boolean, default `false`.
Controls whether a session that cannot be restored - for example because it is corrupted - from the `SessionDataStore` is deleted by the `SessionDataStore`.
jetty.session.flushOnResponseCommit::
Boolean, default `false`.
If true, if a session is "dirty" - ie its attributes have changed - it will be written to the backing store as the response is about to commit.
This ensures that all subsequent requests whether to the same or different node will see the updated session data.
If false, a dirty session will only be written to the backing store when the last simultaneous request for it leaves the session.
For more general information on the uses of these configuration properties, see link:#sessions-details[Session Components].

View File

@ -1,46 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[jetty-sessions-architecture]]
=== Session Architecture
==== Session Architecture Hierarchy
Each Jetty instance has a singular `SessionIdManager` to handle all session requests, regardless of clustering technology.
For each context on the server there is one (1) `SessionCache` which contains all of the Session objects for the given context.
The benefit of the `SessionCache` is to ensure that simultaneous requests accessing the same Session Id in the same context always operate on the same Session object.
The SessionCache implementation supplied with the Jetty distribution does just that: keeps Session objects in memory so that they can be shared between simultaneous requests.
However, it is possible to provide your own implementation that never shares Session objects should you require it.
Where the `SessionCache` handles Session information, Session data is stored in a `SessionDataStore` that is specific to the clustering technology being implemented.
There is only one (1) `SessionDataStore` per `SessionCache`.
Visually the session architecture can be represented like this:
image::SessionsHierarchy.png[]
==== Configuring Sessions in the Jetty Distribution
Configuring session management involves selecting a link:#startup-modules[module] for the desired type of link:#session-configuration-sessioncache[session caching] behavior, and a module for the type of session persistence.
Jetty provides two different session caches: the `DefaultSessionCache` which holds sessions in memory, and the `NullSessionCache` which does not.
There is more information on both of these types of session caching and the circumstances which would lead you to select one or the other in the link:#sessions-details[Session Components] section, and more information on the configuration options of each in link:#session-configuration-sessioncache[the L1 Session Cache] section.
For session persistence, Jetty provides a number of different implementations from which to choose including link:#configuring-sessions-memory[non-persistence], link:#configuring-sessions-file-system[local file storage], clustered technologies such as link:#configuring-sessions-jdbc[JDBC], link:#configuring-sessions-mongo[MongoDB], link:#configuring-sessions-infinispan[Inifinispan], link:#configuring-sessions-gcloud[Google Cloud Datastore], and link:#configuring-sessions-hazelcast[Hazelcast].
Depending on your persistence technology, to enhance performance, you may want to use an L2 cache for session data, in which case Jetty provides the link:#session-configuration-memcachedsessiondatastore[memcached L2 session data cache].

View File

@ -1,103 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[sessions-details]]
=== Session Components
==== SessionIdManager
There is a maximum of one (1) `SessionIdManager` per Jetty Server instance.
Its purpose is to generate fresh, unique session ids and to coordinate the re-use of session ids amongst co-operating contexts.
Unlike in previous versions of Jetty, the `SessionIdManager` is agnostic with respect to the type of clustering technology chosen.
Jetty provides a default implementation - the `DefaultSessionIdManager` - which should meet the needs of most users.
If you do not explicitly enable one of the session modules or otherwise configure a `SessionIdManager`, the `DefaultSessionIdManager` will be used.
If the `DefaultSessionIdManager` does not meet your needs, you can extend the `org.eclipse.jetty.server.session.AbstractSessionIdManager` or do a fresh implementation of the `org.eclipse.jetty.server.session.SessionIdManager` interface.
See link:#session-configuration-housekeeper[Configuring the SessionIdManager and HouseKeeper] for details on configuration.
==== HouseKeeper
There is a maximum of one (1) `HouseKeeper` per `SessionIdManager`.
Its purpose is to periodically poll the `SessionHandlers` to clean out expired sessions.
By default the `HouseKeeper` will poll the `SessionHandlers` every 10 mins to find and delete expired sessions, although this interval is configurable.
See link:#session-configuration-housekeeper[Configuring the SessionIdManager and HouseKeeper] for details on configuration.
==== SessionCache
There is one (1) `SessionCache` *per context.*
Its purpose is to provide an L1 cache of Session objects.
Having a working set of Session objects in memory allows multiple simultaneous requests for the same session to share the same Session object.
Jetty provides two (2) `SessionCache` implementations: the `DefaultSessionCache` and the `NullSessionCache`.
The `DefaultSessionCache` retains Session objects in memory in a cache and has a number of link:#session-configuration-sessioncache[configuration options] to control cache behavior.
It is the default that is used if no other `SessionCache` has been configured.
It is suitable for non-clustered and clustered deployments with a sticky load balancer, as well as clustered deployments with a non-sticky load balancer, with some caveats.
The `NullSessionCache` does not actually cache any objects: each request uses a fresh Session object.
It is suitable for clustered deployments without a sticky load balancer and non-clustered deployments when purely minimal support for sessions is needed.
`SessionCaches` always write out a Session to the `SessionDataStore` whenever the last request for the Session exits.
They can also be configured to do an immediate, eager write of a freshly created session.
This can be useful if you are likely to experience multiple, near simultaneous requests referencing the same session, e.g. with HTTP/2 and you don't have a sticky load balancer.
Alternatively, if the eager write is not done, application paths which create and then invalidate a session within a single request never incur the cost of writing to persistent storage.
Additionally, if the `EVICT_ON_INACTIVITY` eviction policy is in use, you can link:#session-configuration-sessioncache[configure] the `DefaultSessionCache` to force a write of the Session to the `SessionDataStore` just before the Session is evicted.
See link:#session-configuration-sessioncache[the L1 Session Cache] for more information.
==== SessionDataStore
There is one (1) `SessionDataStore` per context.
Its purpose is to handle all persistence related operations on sessions.
The common characteristics for all `SessionDataStores` are whether or not they support passivation, and the length of the grace period.
Supporting passivation means that session data is serialized.
Some persistence mechanisms serialize, such as JDBC, GCloud Datastore etc, whereas others may store an object in shared memory, e.g. Infinispan, when configured with a local cache.
Whether or not a clustering technology entails passivation controls whether or not the session passivation/activation listeners will be called.
The grace period is an interval, configured in seconds, that attempts to deal with the non-transactional nature of sessions with regard to finding sessions that have expired.
Due to the lack of transactionality, in a clustered configuration, even with a sticky load balancer, it is always possible that a Session is live on a node but has not yet been updated in the persistent store.
When `SessionDataStores` search their persistent store to find sessions that have expired, they typically perform a few sequential searches:
* The first verifies the expiration of a list of candidate session ids suggested by the SessionCache
* The second finds sessions in the store that have expired which were last live on the current node
* The third finds sessions that expired a "while" ago, irrespective of on which node they were last used: the definition of "a while" is based on the grace period.
Jetty instantiates the trivial `NullSessionDataStore` - which does not persist sessions - as the default.
The distribution provides a number of alternative `SessionDataStore` implementations such as link:#configuring-sessions-file-system[FileSessionDataStore], link:#configuring-sessions-gcloud[GCloudSessionDataStore], link:#configuring-sessions-jdbc[JDBCSessionDataStore], link:#configuring-sessions-mongodb[MongoSessionDataStore], link:#configuring-sessions-infinispan[InfinispanSessionDataStore], link:#configuring-sessions-hazelcast[HazelcastSessionDataStore].
==== CachingSessionDataStore
The `CachingSessionDataStore` is a special type of `SessionDataStore` that inserts an L2 cache of Session data - the `SessionDataMap` - in front of a delegate `SessionDataStore`.
The `SessionDataMap` is preferentially consulted before the actual SessionDataStore on reads.
This can improve the performance of slow stores.
Jetty provides one implementation of the this L2 cache based on `Memcached`, the `MemcachedSessionDataMap`.
See link:#session-configuration-memcachedsessiondatastore[the L2 SessionData Cache]for additional information.

View File

@ -1,95 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[sessions-usecases]]
=== Session Use Cases
==== Clustering with a Sticky Load Balancer
Preferably, your cluster will utilize a sticky load balancer.
This will route requests for the same Session to the same Jetty instance.
In this case, the `DefaultSessionCache` can be used to keep in-use Session objects in memory.
You can fine-tune the cache by controlling how long Session objects remain in memory with the eviction policy settings.
If you have a large number of Sessions or very large Session objects, then you may want to manage your memory allocation by controlling the amount of time Session objects spend in the cache.
The `EVICT_ON_SESSION_EXIT` eviction policy will remove a Session object from the cache as soon as the last simultaneous request referencing it exits.
Alternatively, the `EVICT_ON_INACTIVITY` policy will remove a Session object from the cache after a configurable amount of time has passed without a request referencing it.
If your Sessions are very long lived and infrequently referenced, you might use the `EVICT_ON_INACTIVITY_POLICY` to control the size of the cache.
If your Sessions are small, or relatively few or stable in number or they are read-mostly, then you might select the `NEVER_EVICT` policy.
With this policy, Session objects will remain in the cache until they either expire or are explicitly invalidated.
If you have a high likelihood of simultaneous requests for the same session object, then the `EVICT_ON_SESSION_EXIT` policy will ensure the Session object stays in the cache as long as it is needed.
==== Clustering Without a Sticky Load Balancer
Without a sticky load balancer requests for the same session may arrive on any node in the cluster.
This means it is likely that the copy of the Session object in any `SessionCache` is likely to be out-of-date, as the Session was probably last accessed on a different node.
In this case, your `choices` are to use either the `NullSessionCache` or to de-tune the `DefaultSessionCache`.
If you use the NullSessionCache all Session object caching is avoided.
This means that every time a request references a session it must be brought in from persistent storage.
It also means that there can be no sharing of Session objects for multiple requests for the same session: each will have their own Session object.
Furthermore, the outcome of session writes are indeterminate because the Servlet Specification does not mandate ACID transactions for sessions.
If you use the `DefaultSessionCache`, there is a risk that the caches on some nodes will contain out-of-date Session information as simultaneous requests for the same session are scattered over the cluster.
To mitigate this somewhat you can use the `EVICT_ON_SESSION_EXIT` eviction policy: this will ensure that the Session is removed from the cache as soon as the last simultaneous request for it exits.
Again, due to the lack of Session transactionality, the ordering outcome of write operations cannot be guaranteed.
As the Session is cached while at least one request is accessing it, it is possible for multiple simultaneous requests to share the same Session object.
==== Handling corrupted or unloadable session data
For various reasons it might not be possible for the `SessionDataStore` to re-read a stored session.
One scenario is that the session stores a serialized object in it's attributes, and after a redeployment there in an incompatible class change.
Using the setter `SessionCache.setRemoveUnloadableSessions(true)` will allow the `SessionDataStore` to delete the unreadable session from persistent storage.
This can be useful from preventing the scavenger from continually generating errors on the same expired, but un-restorable, session.
==== Configuring Sessions via Jetty XML
With the provided session modules, there is no need to configure a context xml or `jetty-web.xml` file for sessions.
That said, if a user wishes to configure sessions this way, it is possible using link:#jetty-xml-syntax[Jetty IoC XML format.]
Below is an example of how you could configure a the link:#configuring-sessions-file-system[`FileSessionDataStore`], but the same concept would apply to any of the *SessionDataStores discussed in this chapter:
[source, xml, subs="{sub-order}"]
----
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Call id="sh" name="getSessionHandler">
<Set name="sessionCache">
<New class="org.eclipse.jetty.server.session.DefaultSessionCache">
<Arg><Ref id="sh"/></Arg>
<Set name="sessionDataStore">
<New class="org.eclipse.jetty.server.session.FileSessionDataStore">
<Set name="storeDir">/tmp/sessions</Set>
</New>
</Set>
</New>
</Set>
</Call>
</Configure>
----
The example above functions in either a `jetty-web.xml` file or a link:#using-basic-descriptor-files[context xml descriptor file.]
____
[NOTE]
If you explicitly configure the `SessionCache` and `SessionDataStore` for a `SessionHandler` in a context xml file or `jetty-web.xml` file, any session modules you already have enabled are ignored.
So, for example, if you had enabled the `session-store-gcloud module` for your sever, you could force a particular webapp to use the `FileSessionDataStore` by explicitly configuring it in either a context xml file or a `jetty-web.xml` file as shown above.
____

View File

@ -1,178 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[basic-architecture]]
=== Jetty Architecture
==== View from 20,000 feet
The Jetty link:{JDURL}/org/eclipse/jetty/server/Server.html[Server] is the plumbing between
a collection of `Connector`s that accept connections and a collection of `Handler`s that
service requests from the connections and produce responses, with threads from a thread pool doing the work.
image:jetty-high-level-architecture.png[image,width=576]
While the Jetty request/responses are derived from the Servlet API, the full features of the Servlet API
are only available if you configure the appropriate handlers.
For example, the session API on the request is inactive unless the request has been passed to a `SessionHandler`.
The concept of a Servlet itself is implemented by a `ServletHandler`.
If Servlets are not required, there is very little overhead in the use of the servlet request/response APIs.
Thus you can build a Jetty server using only connectors and handlers, without using Servlets.
The job of configuring Jetty is building a tree of connectors and handlers and providing their individual configurations.
As Jetty components are simply Plain Old Java Objects (POJOs), you can accomplish this assembly
and configuration of components by a variety of techniques:
* In code, see the examples in the Jetty Source XRef.
* Using Jetty XML, a dependency injection style in XML format.
* With your dependency injection framework of choice, Spring or XBean.
* Using Jetty WebApp and Context Deployers.
==== Patterns
The implementation of Jetty follows some fairly standard patterns.
Most abstract concepts such as `Connector`s and `Handler`s are captured by interfaces.
Generic handling for those interfaces is then provided in an abstract implementation
such as `AbstractConnector` and `AbstractHandler`.
image:basic-architecture-patterns.png[image,width=576]
The JSR77 inspired life cycle of most Jetty components is represented by the `LifeCycle`
interface and the `AbstractLifeCycle` implementation used as the base of many Jetty components.
==== Connectors
A `Connector` is the component that accepts TCP connections.
For each accepted TCP connection, the `Connector` asks a `ConnectionFactory` to create
a `Connection` object that handles the network traffic on that TCP connection, parsing
and generating bytes for a specific protocol.
A `ServerConnector` can therefore be configured with one or more `ConnectionFactory`.
The simplest case is a single `ConnectionFactory` such as `HttpConnectionFactory`, that
creates `HttpConnection` objects that parse and generate bytes for the HTTP/1.1 protocol.
A more complex case can be a `ServerConnector` configured with three factories:
`ProxyConnectionFactory`, `SslConnectionFactory` and `HttpConnectionFactory`.
Such connector will be able to handle PROXY protocol bytes coming from a load balancer
such as HAProxy (with the `ProxyConnectionFactory`), then handle TLS bytes (with
`SslConnectionFactory`) and therefore decrypting/encrypting the bytes from/to a remote
client, and finally handling HTTP/1.1 bytes (with `HttpConnectionFactory`).
Each `ConnectionFactory` is asked to create a `Connection` object for each TCP connection;
the `Connection` objects will be chained together to handle the bytes, each for its
own protocol.
Therefore the `ProxyConnection` will handle the PROXY protocol bytes, `SslConnection`
will handle the encryption/decryption of the bytes, and `HttpConnection` will handle
the HTTP/1.1 bytes producing a request and response object that will be processed by
applications.
Advanced usages of Jetty will allow users to write their own `ConnectionFactory` to
handle custom protocols that are not implemented directly by the Jetty project,
therefore using Jetty as a generic network server.
==== Handlers
A `Handler` is the component that deals with HTTP requests and responses.
The core API of a handler is the handle method:
image:basic-architecture-handlers.png[image,width=576]
[source, java, subs="{sub-order}"]
----
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
----
Parameters:
* `target` the target of the request, either a URI or a name.
* `baseRequest` the original unwrapped request object.
* `request` the request object, either as the `baseRequest` object or a wrapper of `baseRequest`.
You can use the HttpConnection.getCurrentConnection() method to access the Request object if required.
* response the response object, either unwrapped as `Response` or a wrapper of that response.
You can use the HttpConnection.getCurrentConnection() method to access the `Response` object if required.
An implementation of this method can handle the request, pass the request onto another handler (or servlet)
or it might modify and/or wrap the request and then pass it on.
This gives three styles of Handler:
* Coordinating handlers handlers that route requests to other handlers (`HandlerCollection`, `ContextHandlerCollection`)
* Filtering handlers handlers that augment a request and pass it on to other handlers (`HandlerWrapper`, `ContextHandler`, `SessionHandler`)
* Generating handlers handlers that produce content (`ResourceHandler` and `ServletHandler`)
===== Nested Handlers and Handlers Called Sequentially
You can combine handlers to handle different aspects of a request by nesting them,
calling them in sequence, or by combining the two models.
image:basic-architecture-nested-handlers.png[image,width=576]
Handlers called in sequence perform actions that do not depend on the next invocation, nor on the handler order.
They handle a request and generate the response without interacting with other handlers.
The main class for this model is `HandlerCollection`.
Nested handlers are called according to a before/invokeNext/after pattern.
The main class for nested handlers is `HandlerWrapper`.
Nested handlers are much more common than those called in sequence.
See also xref:writing-custom-handlers[].
===== Servlet Handler
The `ServletHandler` is a `Handler` that generates content by passing the request to any
configured Servlet Filters and then to a Servlet mapped by a URI pattern.
image:basic-architecture-servlet-handler.png[image,width=576]
A `ServletHandler` is normally deployed within the scope of a `ServletContext`, which is a
`ContextHandler` that provides convenience methods for mapping URIs to servlets.
Filters and Servlets can also use a `RequestDispatcher` to reroute a request to another context
or another Servlet in the current context.
[[what-is-a-context]]
==== Contexts
Contexts are handlers that group other handlers below a particular URI context path or a virtual host.
Typically a context can have:
* A context path that defines which requests are handled by the context (e.g. `/myapp`)
* A resource base for static content (a document root)
* A class loader to obtain classes specific to the context (typically from `/WEB-INF/classes` and `/WEB-INF/lib`)
* Virtual host names
Contexts implementations include:
* `ContextHandler`
* `ServletContextHandler`
* `WebAppContext`
A web application context combines handlers for security, session and servlets in a single unit
that you can configure with a `web.xml` descriptor.
==== Web Application
A `WebAppContext` is a derivation of `ServletContextHandler` that supports the standardized layout
of a web application and configuration of session, security, listeners, filter, servlets, and JSP
via a `web.xml` descriptor normally found in the `/WEB-INF` directory of a web application.
image:basic-architecture-web-application.png[image,width=576]
Essentially `WebAppContext` is a convenience class that assists the construction and configuration
of other handlers to achieve a standard web application configuration.
Configuration is actually done by pluggable implementations of the Configuration class and the
prime among these is `WebXmlConfiguration.`

View File

@ -1,99 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[creating-custom-protocol]]
=== Creating a Custom Protocol
You can create custom protocols with Jetty. This page provides an example of how to do so, with Telnet as the protocol.
To create a custom Telnet protocol, complete the following tasks:
* Implement a `TelnetServerConnectionFactory`.
* Implement a `TelnetServerConnection` by extending `o.e.j.io.AbstractConnection`.
* Create a parser/interpreter for the bytes you receive (this is totally independent from Jetty).
* If needed, design an API for the application to use to process the bytes received (also independent from Jetty).
The API likely has a _respond back_ primitive that uses a Jetty provided `EndPoint` and `EndPoint.write(Callback, Buffer...)` to write the response bytes.
[[server-connection-factory]]
==== Implementing a TelnetServerConnectionFactory
Begin with an `org.eclipse.jetty.server.ServerConnector`, which you can use as is. `ServerConnector` takes a `o.e.j.server.ConnectionFactory`, which creates `o.e.j.io.Connection` objects that interpret the bytes the connector receives.
You must implement `ConnectionFactory` with a `TelnetServerConnectionFactory`, where you return a Connection implementation (for example, `TelnetServerConnection`).
[[telnet-server-connection]]
==== Implementing the TelnetServerConnection
For the Connection implementation you need to extend from `o.e.j.io.AbstractConnection` because it provides many facilities that you would otherwise need to re-implement from scratch.
For each Connection instance there is associated an `o.e.j.io.EndPoint` instance.
Think of `EndPoint` as a specialized version of JDKs `SocketChannel`.
You use the `EndPoint` to read, write, and close.
You dont need to implement `EndPoint`, because Jetty provides concrete
classes for you to use.
The Connection is the _passive_ side (that is, Jetty calls it when there is data to read), while the `EndPoint` is the active part (that is, applications call it to write data to the other end).
When there is data to read, Jetty calls `AbstractConnection.onFillable()`, which you must implement in your `TelnetServerConnection`.
A typical implementation reads bytes from the `EndPoint` by calling `EndPoint.fill(ByteBuffer)`.
For examples, look at both the simpler `SPDYConnection` (in the SPDY client package, but server also uses it), and the slightly more complex `HttpConnection`.
[[parser-interpreter]]
==== Parsing the Bytes Received
After you read the bytes, you need to parse them.
For the Telnet protocol there is not much to parse, but perhaps you have your own commands that you want to interpret and execute.
Therefore typically every connection has an associated parser instance.
In turn, a parser usually emits parse events that a parser listener interprets, as the following examples illustrate:
* In HTTP, the Jetty HTTP parser parses the request line (and emits a parser event), then parses the headers (and emits a parser event for each) until it recognizes the end of the headers (and emits another parser event).
At that point, the _interpreter_ or parser listener (which for HTTP is `o.e.j.server.HttpChannel`) has all the information necessary to build a `HttpServletRequest` object and can call the user code (the web application, that is, servlets/filters).
* In SPDY, the Jetty SPDY parser parses a SPDY frame (and emits a parser event), and the parser listener (an instance of o.e.j.spdy.StandardSession) interprets the parser events and calls user code (application-provided listeners).
With `ConnectionFactory`, Connection, parser, and parser listeners in place, you have configured the read side.
[[api-byte-processor]]
==== Designing an API to Process Bytes
At this point, server applications typically write data back to the client.
The Servlet API (for HTTP) or application-provided listeners (for SPDY) expose an interface to web applications so that they can write data back to the client.
The implementation of those interfaces must link back to the `EndPoint` instance associated with the Connection instance so that it can write data via `EndPoint.write(Callback, ByteBuffer...)`.
This is an asynchronous call, and it notifies the callback when all the buffers have been fully written.
For example, in the Servlet API, applications use a `ServletOutputStream` to write the response content.
`ServletOutputStream` is an abstract class that Jetty implements, enabling Jetty to handle the writes from the web application; the writes eventually end up in an `EndPoint.write(...)` call.
[[api-tips]]
===== Tips for Designing an API
If you want to write a completely asynchronous implementation, your API to write data to the client must have a callback/promise concept: “Call me back when you are done, and (possibly) give me the result of the computation."
SPDYs Stream class is a typical example.
Notice how the methods there exist in two versions, a synchronous (blocking) one, and an asynchronous one that takes as last parameter a Callback (if no result is needed), or a Promise (if a result is needed).
It is trivial to write the synchronous version in terms of the asynchronous version.
You can use `EndPoint.write(Callback, ByteBuffer...)` in a blocking way as follows:
[source, java, subs="{sub-order}"]
----
FutureCallback callback = new FutureCallback();
endPoint.write(callback, buffers);
callback.get();
----
With the snippet above your API can be synchronous or asynchronous (your choice), but implemented synchronously.

View File

@ -1,166 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[framework-weld]]
=== Weld
http://seamframework.org/Weld[Weld] can be used to add support for CDI (Contexts and Dependency Injection) to Servlets, Listeners and Filters.
It is easily configured with Jetty 9.
[[weld-setup-distro]]
==== Weld Setup
The easiest way to configure weld is within the Jetty distribution itself.
This can be accomplished either by enabling one of the startup link:#startup-modules[modules] for Weld, or by creating/editing a `jetty-web.xml` descriptor (see also https://www.eclipse.org/jetty/documentation/current/jetty-web-xml-config.html[Jetty XML Reference]).
===== Jetty Weld Modules
====== Jetty `cdi-decorate` Module
Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty `cdi-decorate` module.
To activate this module in Jetty the `cdi-decorate` module needs activated on the command line, which can be done as follows:
-------------------------
cd $JETTY_BASE
java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate
-------------------------
====== Jetty `cdi2` Module
For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application.
This can be done using the deprecated `cdi2` module either by activating the `cdi2` module:
-------------------------
cd $JETTY_BASE
java -jar $JETTY_HOME/start.jar --add-to-start=cdi2
-------------------------
===== Jetty-Web XML
[source.XML, xml]
-------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Call name="prependServerClass">
<Arg>-org.eclipse.jetty.util.Decorator</Arg>
</Call>
<Call name="prependServerClass">
<Arg>-org.eclipse.jetty.util.DecoratedObjectFactory</Arg>
</Call>
<Call name="prependServerClass">
<Arg>-org.eclipse.jetty.server.handler.ContextHandler.</Arg>
</Call>
<Call name="prependServerClass">
<Arg>-org.eclipse.jetty.server.handler.ContextHandler</Arg>
</Call>
<Call name="prependServerClass">
<Arg>-org.eclipse.jetty.servlet.ServletContextHandler</Arg>
</Call>
</Configure>
-------------------------------------------------------------
//TODO Fix for 10
____
[TIP]
Directly modifying the web application classpath via `jetty-web.xml` will not work for Jetty 10.0.0 and later.
____
===== Jetty `cdi-spi` Module
Since Jetty 9.4.20 the Jetty `cdi-spi` module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI.
Since Weld support specific Jetty integration, it is not recommended to use this module with Weld.
When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration):
[source, screen, subs="{sub-order}"]
....
2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms
2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-{VERSION}
2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1
2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup
INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 2.2.9 (Final)
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.deployment.WebAppBeanArchiveScanner scan
WARN: WELD-ENV-001004: Found both WEB-INF/beans.xml and WEB-INF/classes/META-INF/beans.xml. It's not portable to use both locations at the same time. Weld is going to use file:/tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/WEB-INF/beans.xml.
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PostActivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PrePassivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:56 PM org.jboss.weld.bootstrap.MissingDependenciesRegistry handleResourceLoadingException
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.WeldServletLifecycle findContainer
INFO: WELD-ENV-001002: Container detection skipped - custom container class loaded: org.jboss.weld.environment.jetty.JettyContainer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.jetty.JettyContainer initialize
INFO: WELD-ENV-001200: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners should work on Jetty 9.1.1 and newer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.Listener contextInitialized
INFO: WELD-ENV-001006: org.jboss.weld.environment.servlet.EnhancedListener used for ServletContext notifications
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.EnhancedListener contextInitialized
INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for ServletRequest and HttpSession notifications
2015-06-18 12:13:56.535:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@6574b225{/cdi-webapp,file:///tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/,AVAILABLE}{/cdi-webapp.war}
2015-06-18 12:13:56.554:INFO:oejs.ServerConnector:main: Started ServerConnector@7112f81c{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-06-18 12:13:56.587:INFO:oejus.SslContextFactory:main: x509={jetty.eclipse.org=jetty} wild={} alias=null for SslContextFactory@3214ee6(file:///tmp/cdi-demo/etc/keystore,file:///tmp/cdi-demo/etc/keystore)
2015-06-18 12:13:56.821:INFO:oejs.ServerConnector:main: Started ServerConnector@69176a9b{SSL,[ssl, http/1.1]}{0.0.0.0:8443}
2015-06-18 12:13:56.822:INFO:oejs.Server:main: Started @2383ms
....
For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency).
[[weld-embedded]]
==== Embedded Jetty
When starting embedded Jetty programmatically from the `main` method it is necessary to register Weld's listener:
[source.JAVA, java]
----
public class Main {
public static void main(String[] args) throws Exception {
Server jetty = new Server(8080);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setResourceBase("src/main/resources");
jetty.setHandler(context);
context.addServlet(HelloWorldServlet.class, "/*");
context.addEventListener(new DecoratingListener()); # <1>
context.addEventListener(new Listener()); # <2>
jetty.start();
jetty.join();
}
public static class HelloWorldServlet extends HttpServlet {
@Inject BeanManager manager;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().append("Hello from " + manager);
}
}
}
<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20)
<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically
----

View File

@ -16,19 +16,20 @@
// ========================================================================
//
:doctitle: Eclipse Jetty: Distribution Guide
:doctitle: Eclipse Jetty: Operations Guide
:author: Jetty Developers
:email: jetty-dev@eclipse.org
:revnumber: 1.0
:revnumber: {version}
:revdate: {TIMESTAMP}
:toc: left
:toc-title: Distribution Guide
:toc-title: Operations Guide
:toc-style:
:header-style: eclipse-thin
:breadcrumb-style: eclipse-thin
:footer-style: default
:breadcrumb: Home:../index.html | Distribution Guide:./index.html
:breadcrumb: Home:../index.html | Operations Guide:./index.html
// docinfo lets you pull in shared content and/or influence via render type
//:docinfodir: {DOCINFODIR}/documentation
@ -55,21 +56,35 @@ endif::[]
// suppress automatic id generation
:sectids!:
// download and install
include::introduction.adoc[]
// TODO: jetty.home vs jetty.base
include::startup/chapter.adoc[]
include::contexts/chapter.adoc[]
include::deploying/chapter.adoc[]
include::connectors/chapter.adoc[]
include::http2/chapter.adoc[]
include::logging/chapter.adoc[]
include::security/chapter.adoc[]
include::sessions/chapter.adoc[]
include::jsp/chapter.adoc[]
include::annotations/chapter.adoc[]
include::jmx/chapter.adoc[]
include::jndi/chapter.adoc[]
include::alpn/chapter.adoc[]
include::fastcgi/chapter.adoc[]
include::extras/chapter.adoc[]
include::runner/chapter.adoc[]
include::tuning/chapter.adoc[]
// TODO: how jetty standalone work what start.jar does, command line options, modules, etc.
// TODO: this is mostly in old_docs/startup.
// TODO: [[quick-start-configure]]
// TODO: list of modules, how to write a custom module, examples e.g. http2, etc.
== OLD DOCUMENTATION
include::old_docs/gettingstarted/introduction/chapter.adoc[]
include::old_docs/gettingstarted/getting-started/chapter.adoc[]
// TODO: how about other directories under old_docs/gettingstarted/?
include::old_docs/startup/chapter.adoc[]
include::old_docs/contexts/chapter.adoc[]
include::old_docs/deploying/chapter.adoc[]
include::old_docs/connectors/chapter.adoc[]
include::old_docs/http2/chapter.adoc[]
include::old_docs/logging/chapter.adoc[]
include::old_docs/security/chapter.adoc[]
include::old_docs/sessions/sessions.adoc[]
include::old_docs/jsp/chapter.adoc[]
include::old_docs/annotations/chapter.adoc[]
include::old_docs/jmx/chapter.adoc[]
include::old_docs/jndi/chapter.adoc[]
include::old_docs/alpn/chapter.adoc[]
include::old_docs/fastcgi/chapter.adoc[]
include::old_docs/extras/chapter.adoc[]
include::old_docs/runner/chapter.adoc[]
include::old_docs/tuning/chapter.adoc[]

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[og-guide]]
== Eclipse Jetty Operations Guide
The Eclipse Jetty Operations Guide targets sysops, devops, and developers who want to install Eclipse Jetty as a standalone server to deploy web applications.
[[og-download]]
=== Downloading Eclipse Jetty
The Eclipse Jetty distribution is available for download from link:https://www.eclipse.org/jetty/download.html[]
The Eclipse Jetty distribution is available in both `zip` and `gzip` formats; download the one most appropriate for your system, typically `zip` for Windows and `gzip` for other operative systems.
[[og-install]]
=== Installing Eclipse Jetty
After the download, unpacking the Eclipse Jetty distribution will extract the files into a directory called `jetty-distribution-VERSION`, where `VERSION` is the version that you downloaded, for example `10.0.0`, so that the directory is called `jetty-distribution-10.0.0`.
Unpack the Eclipse Jetty distribution compressed file in a convenient location, for example under `/opt`.
The rest of the instructions in this documentation will refer to this location as `$JETTY_HOME`.
IMPORTANT: It is important that *only* stable release versions are used in production environments.
Versions that have been deprecated or are released as Milestones (M), Alpha, Beta or Release Candidates (RC) are *not* suitable for production as they may contain security flaws or incomplete/non-functioning feature sets.
[[og-running]]
=== Running Eclipse Jetty
Eclipse Jetty as a standalone server has no graphical user interface, so configuring and running the server is done from the command line.
TODO: section about general architecture - modules etc.

View File

@ -34,9 +34,9 @@ Additional settings, including construction your own constructor Jetty XML files
Out of the box, Jetty provides several link:#startup-modules[modules] for enabling different types of connectors, from HTTP to HTTPS, HTTP/2, and others.
If you startup Jetty with the `--list-modules=connector` command, you can see a list of all available connector modules:
[source, screen, subs="{sub-order}"]
[source,screen,subs="{sub-order}"]
....
[mybase]$ java -jar $JETTY_HOME/start.jar --list-modules=connector
[my-base]$ java -jar /path/to/jetty-home/start.jar --list-modules=connector
Available Modules:
==================
@ -276,9 +276,9 @@ jetty.http.port=5231
Now when the server is started, HTTP connections will enter on port 5231:
[source, screen, subs="{sub-order}"]
[source,screen,subs="{sub-order}"]
....
[mybase] java -jar ../start.jar
[my-base]$ java -jar /path/to/jetty-home/start.jar
2017-08-31 10:31:32.955:INFO::main: Logging initialized @366ms to org.eclipse.jetty.util.log.StdErrLog
2017-08-31 10:31:33.109:INFO:oejs.Server:main: jetty-{VERSION}
2017-08-31 10:31:33.146:INFO:oejs.AbstractConnector:main: Started ServerConnector@2ef9b8bc{HTTP/1.1,[http/1.1]}{0.0.0.0:5231}

View File

@ -63,10 +63,10 @@ jetty.sslContext.keyStorePassword::
Enabling Conscrypt SSL is just as easy as default SSL - enable both the `conscrypt` and `ssl` link:#startup-modules[modules]:
[source, plain, subs="{sub-order}"]
[source,plain,subs="{sub-order}"]
----
$ cd ${JETTY_HOME}
$ java -jar ../start.jar --add-to-start=ssl,conscrypt
$ cd ${JETTY_BASE}
$ java -jar /path/to/jetty-home/start.jar --add-to-start=ssl,conscrypt
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):

Some files were not shown because too many files have changed in this diff Show More