Merge branch jetty-10.0.x into jetty-10.0.x-4538-MessageReaderWriter

This commit is contained in:
Lachlan Roberts 2020-03-11 14:23:47 +11:00
commit fef25e7825
148 changed files with 6272 additions and 1976 deletions

View File

@ -355,6 +355,40 @@ jetty-10.0.0-alpha0 - 11 July 2019
+ 3849 ClosedChannelException from jetty-test-webapp javax websocket chat
example
jetty-9.4.27.v20200227 - 27 February 2020
+ 3247 Generate jetty-maven-plugin website
+ 4247 Cookie security attributes are going to mandated by Google Chrome
+ 4360 Upgrade to Apache Jasper 8.5.49
+ 4475 WebSocket JSR356 implementation not honoring javadoc of MessageHandler
on Whole<Reader>
+ 4495 Review ReservedThreadExecutor's concurrency model
+ 4504 X-Forwarded-Server header overwrites X-Forwarded-Host
+ 4520 Jetty jdbc session manager causing exceptions for violating primary key
in inserting session in the table
+ 4529 ErrorHandler showing servlet info, can not be disabled unless
overriding most of its functionality
+ 4533 Reinstate hard close in dispatcher
+ 4537 High CPU on Jetty Websocket thread
+ 4541 Jetty server always allocates maximum response header size
+ 4550 XmlConfiguration constructor selection based on number of arguments
+ 4567 Jetty logging supporting Throwable as last argument
+ 4573 Order dependency of X-Forwarded-Host and X-Forwarded-Port
+ 4575 Stopping ReservedThreadExecutor may hang
+ 4577 request getPathInfo returns null
+ 4594 ServletContextListeners added to destroyServletContextListeners in
ContextHandler::startContext() are not removed by
ContextHandler::removeEventListener()
+ 4606 DateCache.formatNow(long now) does not honor the passed in long
+ 4612 ReservedThreadExecutor hangs when the last reserved thread idles out
jetty-9.4.26.v20200117 - 17 January 2020
+ 2620 Exception from user endpoint onClose results in unclosed
WebSocketSession
+ 4383 Errors deleting multipart tmp files java.lang.NullPointerException
under heavy load
+ 4444 TLS Connection Timeout Intermittently
+ 4461 IllegalStateException in HttpOutput with Jersey
jetty-9.4.25.v20191220 - 20 December 2019
+ 995 UrlEncoded.encodeString should skip more characters
+ 2195 Add parameter expansion to start.jar --exec parameters

View File

@ -39,6 +39,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.annotation.HandlesTypes;
@ -58,7 +59,6 @@ import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaDataComplete;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
@ -335,11 +335,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
@Override
public void configure(WebAppContext context) throws Exception
{
//handle introspectable annotations (postconstruct,predestroy, multipart etc etc)
context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
if (!context.getMetaData().isMetaDataComplete())
{
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
//If web.xml not metadata-complete, if this is a servlet 3 webapp or above
//or configDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
_discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
@ -408,7 +410,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Perform scanning of classes for annotations
* Perform scanning of classes for discoverable
* annotations such as WebServlet/WebFilter/WebListener
*
* @param context the context for the scan
* @throws Exception if unable to scan
@ -431,6 +434,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
isUseMultiThreading(context),
getMaxScanWait(context));
//scan selected jars on the container classpath first
parseContainerPath(context, parser);
//email from Rajiv Mordani jsrs 315 7 April 2010
// If there is a <others/> then the ordering should be
@ -438,6 +442,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
// In case there is no others then it is
// WEB-INF/classes + order of the elements.
parseWebInfClasses(context, parser);
//scan non-excluded, non medatadata-complete jars in web-inf lib
parseWebInfLib(context, parser);
long start = System.nanoTime();
@ -691,14 +696,14 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
//If no ordering, nothing is excluded
if (context.getMetaData().getOrdering() == null)
if (!context.getMetaData().isOrdered())
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} no ordering", sci);
return false;
}
List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
List<Resource> orderedJars = context.getMetaData().getWebInfResources(true);
//there is an ordering, but there are no jars resulting from the ordering, everything excluded
if (orderedJars.isEmpty())
@ -710,17 +715,17 @@ public class AnnotationConfiguration extends AbstractConfiguration
//Check if it is excluded by an ordering
URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
while (!found && itor.hasNext())
boolean included = false;
for (Resource r : orderedJars)
{
Resource r = itor.next();
found = r.getURI().equals(loadingJarURI);
included = r.getURI().equals(loadingJarURI);
if (included)
break;
}
if (LOG.isDebugEnabled())
LOG.debug("{}Excluded {} found={}", found ? "!" : "", sci, found);
return !found;
LOG.debug("{}Excluded {} found={}", included ? "!" : "", sci, included);
return !included;
}
/**
@ -785,7 +790,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
*/
public boolean isFromWebInfClasses(WebAppContext context, Resource sci)
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (dir.equals(sci))
{
@ -831,7 +836,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
catch (Error e)
{
// Probably a SCI discovered on the system classpath that is hidden by the context classloader
LOG.info("Error: " + e.getMessage() + " for " + context);
LOG.info("Error: {} for {}", e.getMessage(), context);
LOG.debug(e);
continue;
}
@ -867,7 +872,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
{
if (LOG.isDebugEnabled())
LOG.debug("Ordering ServletContainerInitializers with " + initializerOrdering);
LOG.debug("Ordering ServletContainerInitializers with {}", initializerOrdering);
//There is an ordering that is not just "*".
//Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
@ -894,7 +899,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
else
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (dir.equals(entry.getValue()))//from WEB-INF/classes so can't be ordered/excluded
{
@ -923,7 +928,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
LOG.debug("Ordering ServletContainerInitializers with ordering {}", context.getMetaData().getOrdering());
//add SCIs according to the ordering of its containing jar
for (Resource webInfJar : context.getMetaData().getOrderedWebInfJars())
for (Resource webInfJar : context.getMetaData().getWebInfResources(true))
{
for (Map.Entry<ServletContainerInitializer, Resource> entry : sciResourceMap.entrySet())
{
@ -977,7 +982,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
return null;
String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
if (tmp == null || "".equals(tmp.trim()))
if (StringUtil.isBlank(tmp))
return null;
return new ServletContainerInitializerOrdering(tmp);
@ -1002,9 +1007,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
_containerPathStats = new CounterStatistic();
//scan the container classpath jars that were selected by
//filtering in MetaInfConfiguration
for (Resource r : context.getMetaData().getContainerResources())
{
//queue it up for scanning if using multithreaded mode
if (_parserTasks != null)
{
ParserTask task = new ParserTask(parser, handlers, r);
@ -1019,7 +1025,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Scan jars in WEB-INF/lib
* Scan jars in WEB-INF/lib.
*
* Only jars selected by MetaInfConfiguration, and that are not excluded
* by an ordering will be considered.
*
* @param context the context for the scan
* @param parser the annotation parser to use
@ -1027,20 +1036,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
*/
public void parseWebInfLib(final WebAppContext context, final AnnotationParser parser) throws Exception
{
List<FragmentDescriptor> frags = context.getMetaData().getFragments();
//email from Rajiv Mordani jsrs 315 7 April 2010
//jars that do not have a web-fragment.xml are still considered fragments
//they have to participate in the ordering
ArrayList<URI> webInfUris = new ArrayList<URI>();
List<Resource> jars = null;
if (context.getMetaData().getOrdering() != null)
jars = context.getMetaData().getOrderedWebInfJars();
else
//No ordering just use the jars in any order
jars = context.getMetaData().getWebInfJars();
//if there is an ordering, the ordered jars should be used.
//If there is no ordering, jars will be unordered.
List<Resource> jars = context.getMetaData().getWebInfResources(context.getMetaData().isOrdered());
if (LOG.isDebugEnabled())
{
@ -1053,13 +1055,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
//for each jar, we decide which set of annotations we need to parse for
final Set<Handler> handlers = new HashSet<Handler>();
FragmentDescriptor f = getFragmentFromJar(r, frags);
FragmentDescriptor f = context.getMetaData().getFragmentDescriptorForJar(r);
//if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
// but yet we still need to do the scanning for the classes on behalf of the servletcontainerinitializers
//if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
//or if it has a fragment we scan it if it is not metadata complete
if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
if (f == null || !WebDescriptor.isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{
//register the classinheritance handler if there is one
if (_classInheritanceHandler != null)
@ -1069,7 +1071,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
handlers.addAll(_containerInitializerAnnotationHandlers);
//only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
if (f == null || !isMetaDataComplete(f))
if (f == null || !WebDescriptor.isMetaDataComplete(f))
handlers.addAll(_discoverableAnnotationHandlers);
if (_parserTasks != null)
@ -1087,7 +1089,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Scan classes in WEB-INF/classes
* Scan classes in WEB-INF/classes.
*
* @param context the context for the scan
* @param parser the annotation parser to use
@ -1105,7 +1107,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
_webInfClassesStats = new CounterStatistic();
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (_parserTasks != null)
{
@ -1120,39 +1122,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
}
/**
* Get the web-fragment.xml from a jar
*
* @param jar the jar to look in for a fragment
* @param frags the fragments previously found
* @return true if the fragment if found, or null of not found
* @throws Exception if unable to determine the the fragment contains
*/
public FragmentDescriptor getFragmentFromJar(Resource jar, List<FragmentDescriptor> frags)
throws Exception
{
//check if the jar has a web-fragment.xml
FragmentDescriptor d = null;
for (FragmentDescriptor frag : frags)
{
Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
if (Resource.isContainedIn(fragResource, jar))
{
d = frag;
break;
}
}
return d;
}
public boolean isMetaDataComplete(WebDescriptor d)
{
return (d != null && d.getMetaDataComplete() == MetaDataComplete.True);
}
public static class ClassInheritanceMap extends ConcurrentHashMap<String, Set<String>>
{
@Override
public String toString()
{

View File

@ -18,6 +18,9 @@
package org.eclipse.jetty.annotations;
import java.util.Objects;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.Decorator;
import org.eclipse.jetty.webapp.WebAppContext;
@ -26,23 +29,26 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
public class AnnotationDecorator implements Decorator
{
protected AnnotationIntrospector _introspector = new AnnotationIntrospector();
protected AnnotationIntrospector _introspector;
protected WebAppContext _context;
public AnnotationDecorator(WebAppContext context)
{
registerHandlers(context);
_context = Objects.requireNonNull(context);
_introspector = new AnnotationIntrospector(_context);
registerHandlers();
}
public void registerHandlers(WebAppContext context)
private void registerHandlers()
{
_introspector.registerHandler(new ResourceAnnotationHandler(context));
_introspector.registerHandler(new ResourcesAnnotationHandler(context));
_introspector.registerHandler(new RunAsAnnotationHandler(context));
_introspector.registerHandler(new PostConstructAnnotationHandler(context));
_introspector.registerHandler(new PreDestroyAnnotationHandler(context));
_introspector.registerHandler(new DeclareRolesAnnotationHandler(context));
_introspector.registerHandler(new MultiPartConfigAnnotationHandler(context));
_introspector.registerHandler(new ServletSecurityAnnotationHandler(context));
_introspector.registerHandler(new ResourceAnnotationHandler(_context));
_introspector.registerHandler(new ResourcesAnnotationHandler(_context));
_introspector.registerHandler(new RunAsAnnotationHandler(_context));
_introspector.registerHandler(new PostConstructAnnotationHandler(_context));
_introspector.registerHandler(new PreDestroyAnnotationHandler(_context));
_introspector.registerHandler(new DeclareRolesAnnotationHandler(_context));
_introspector.registerHandler(new MultiPartConfigAnnotationHandler(_context));
_introspector.registerHandler(new ServletSecurityAnnotationHandler(_context));
}
/**
@ -50,22 +56,28 @@ public class AnnotationDecorator implements Decorator
* <ul>
* <li> Resource </li>
* <li> Resources </li>
* <li> RunAs </li>
* <li> PostConstruct </li>
* <li> PreDestroy </li>
* <li> ServletSecurity? </li>
* <li> DeclareRoles </li>
* <li> MultiPart </li>
* <li> ServletSecurity</li>
* </ul>
*
* @param o the object ot introspect
* @param o the object to introspect
* @param metaInfo information about the object to introspect
*/
protected void introspect(Object o)
protected void introspect(Object o, Object metaInfo)
{
_introspector.introspect(o.getClass());
if (o == null)
return;
_introspector.introspect(o, metaInfo);
}
@Override
public Object decorate(Object o)
{
introspect(o);
introspect(o, DecoratedObjectFactory.getAssociatedInfo());
return o;
}

View File

@ -18,11 +18,21 @@
package org.eclipse.jetty.annotations;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jetty.servlet.BaseHolder;
import org.eclipse.jetty.servlet.Source.Origin;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
/**
* AnnotationIntrospector
* Introspects a class to find various types of
@ -30,8 +40,10 @@ import java.util.Set;
*/
public class AnnotationIntrospector
{
private static final Logger LOG = Log.getLogger(AnnotationIntrospector.class);
private final Set<Class<?>> _introspectedClasses = new HashSet<>();
private final List<IntrospectableAnnotationHandler> _handlers = new ArrayList<IntrospectableAnnotationHandler>();
private final WebAppContext _context;
/**
* IntrospectableAnnotationHandler
@ -51,12 +63,14 @@ public class AnnotationIntrospector
*/
public abstract static class AbstractIntrospectableAnnotationHandler implements IntrospectableAnnotationHandler
{
private boolean _introspectAncestors;
protected boolean _introspectAncestors;
protected WebAppContext _context;
public abstract void doHandle(Class<?> clazz);
public AbstractIntrospectableAnnotationHandler(boolean introspectAncestors)
public AbstractIntrospectableAnnotationHandler(boolean introspectAncestors, WebAppContext context)
{
_context = Objects.requireNonNull(context);
_introspectAncestors = introspectAncestors;
}
@ -75,6 +89,16 @@ public class AnnotationIntrospector
c = c.getSuperclass();
}
}
public WebAppContext getContext()
{
return _context;
}
}
public AnnotationIntrospector(WebAppContext context)
{
_context = Objects.requireNonNull(context);
}
public void registerHandler(IntrospectableAnnotationHandler handler)
@ -82,13 +106,96 @@ public class AnnotationIntrospector
_handlers.add(handler);
}
public void introspect(Class<?> clazz)
/**
* Test if an object should be introspected for some specific types of annotations
* like PostConstruct/PreDestroy/MultiPart etc etc.
*
* According to servlet 4.0, these types of annotations should only be evaluated iff any
* of the following are true:
* <ol>
* <li>the object was created by the javax.servlet.ServletContext.createServlet/Filter/Listener method</li>
* <li>the object comes either from a discovered annotation (WebServlet/Filter/Listener) or a declaration
* in a descriptor AND web.xml is NOT metadata-complete AND any web-fragment.xml associated with the location of
* the class is NOT metadata-complete</li>
* </ol>
*
* We also support evaluations of these types of annotations for objects that were created directly
* by the jetty api.
*
* @param o the object to check for its ability to be introspected for annotations
* @param metaInfo meta information about the object to be introspected
* @return true if it can be introspected according to servlet 4.0 rules
*/
public boolean isIntrospectable(Object o, Object metaInfo)
{
if (_handlers == null)
return;
if (clazz == null)
if (o == null)
return false; //nothing to introspect
if (metaInfo == null)
return true; //no information about the object to introspect, assume introspectable
@SuppressWarnings("rawtypes")
BaseHolder holder = null;
try
{
holder = (BaseHolder)metaInfo;
}
catch (ClassCastException e)
{
LOG.warn(e);
return true; //not the type of information we were expecting, assume introspectable
}
Origin origin = (holder.getSource() == null ? null : holder.getSource().getOrigin());
if (origin == null)
return true; //assume introspectable
switch (origin)
{
case EMBEDDED:
case JAVAX_API:
{
return true; //objects created from the jetty or servlet api are always introspectable
}
case ANNOTATION:
{
return true; //we will have discovered annotations only if metadata-complete==false
}
default:
{
//must be from a descriptor. Only introspect if the descriptor with which it was associated
//is not metadata-complete
if (_context.getMetaData().isMetaDataComplete())
return false;
String descriptorLocation = holder.getSource().getResource();
if (descriptorLocation == null)
return true; //no descriptor, can't be metadata-complete
try
{
return !WebDescriptor.isMetaDataComplete(_context.getMetaData().getFragmentDescriptor(Resource.newResource(descriptorLocation)));
}
catch (IOException e)
{
LOG.warn("Unable to get Resource for descriptor {}", descriptorLocation, e);
return false; //something wrong with the descriptor
}
}
}
}
/**
* @param o
* @param metaInfo
*/
public void introspect(Object o, Object metaInfo)
{
if (!isIntrospectable(o, metaInfo))
return;
Class<?> clazz = o.getClass();
synchronized (_introspectedClasses)
{
//Synchronize on the set of already introspected classes.

View File

@ -35,12 +35,9 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat
{
private static final Logger LOG = Log.getLogger(DeclareRolesAnnotationHandler.class);
protected WebAppContext _context;
public DeclareRolesAnnotationHandler(WebAppContext context)
{
super(false);
_context = context;
super(false, context);
}
@Override

View File

@ -33,13 +33,10 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
public class MultiPartConfigAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
protected WebAppContext _context;
public MultiPartConfigAnnotationHandler(WebAppContext context)
{
//TODO verify that MultipartConfig is not inheritable
super(false);
_context = context;
super(false, context);
}
@Override

View File

@ -31,12 +31,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
protected WebAppContext _context;
public PostConstructAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
@Override

View File

@ -31,12 +31,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
WebAppContext _context;
public PreDestroyAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
@Override

View File

@ -48,12 +48,9 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
Float.class
});
protected WebAppContext _context;
public ResourceAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
/**

View File

@ -31,12 +31,9 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
{
private static final Logger LOG = Log.getLogger(ResourcesAnnotationHandler.class);
protected WebAppContext _wac;
public ResourcesAnnotationHandler(WebAppContext wac)
{
super(true);
_wac = wac;
super(true, wac);
}
@Override
@ -64,8 +61,8 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
{
//TODO don't ignore the shareable, auth etc etc
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name, mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.getServer(), name, mappedName))
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));
}
catch (NamingException e)

View File

@ -33,14 +33,11 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
{
private static final Logger LOG = Log.getLogger(RunAsAnnotationHandler.class);
protected WebAppContext _context;
public RunAsAnnotationHandler(WebAppContext wac)
{
//Introspect only the given class for a RunAs annotation, as it is a class level annotation,
//and according to Common Annotation Spec p2-6 a class-level annotation is not inheritable.
super(false);
_context = wac;
super(false, wac);
}
@Override

View File

@ -57,12 +57,9 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
{
private static final Logger LOG = Log.getLogger(ServletSecurityAnnotationHandler.class);
private WebAppContext _context;
public ServletSecurityAnnotationHandler(WebAppContext wac)
{
super(false);
_context = wac;
super(false, wac);
}
@Override

View File

@ -0,0 +1,30 @@
//
// ========================================================================
// 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.annotations;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServlet;
public class ServletE extends HttpServlet
{
@PreDestroy
public void preDestroy()
{
}
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.RelativeOrdering;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -125,7 +126,7 @@ public class TestAnnotationConfiguration
context25.setClassLoader(Thread.currentThread().getContextClassLoader());
context25.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context25.getMetaData().setWebXml(Resource.newResource(web25));
context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25.getServletContext().setEffectiveMajorVersion(2);
context25.getServletContext().setEffectiveMinorVersion(5);
config25.configure(context25);
@ -138,7 +139,7 @@ public class TestAnnotationConfiguration
context25b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context25b.setConfigurationDiscovered(true);
context25b.getMetaData().setWebXml(Resource.newResource(web25));
context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25b.getServletContext().setEffectiveMajorVersion(2);
context25b.getServletContext().setEffectiveMinorVersion(5);
config25b.configure(context25b);
@ -150,7 +151,7 @@ public class TestAnnotationConfiguration
context31.setClassLoader(Thread.currentThread().getContextClassLoader());
context31.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context31.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context31.getMetaData().setWebXml(Resource.newResource(web31true));
context31.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
context31.getServletContext().setEffectiveMajorVersion(3);
context31.getServletContext().setEffectiveMinorVersion(1);
config31.configure(context31);
@ -162,7 +163,7 @@ public class TestAnnotationConfiguration
context31b.setClassLoader(Thread.currentThread().getContextClassLoader());
context31b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context31b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context31b.getMetaData().setWebXml(Resource.newResource(web31false));
context31b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31false)));
context31b.getServletContext().setEffectiveMajorVersion(3);
context31b.getServletContext().setEffectiveMinorVersion(1);
config31b.configure(context31b);
@ -183,9 +184,9 @@ public class TestAnnotationConfiguration
//test 3.1 webapp loads both server and app scis
context.setClassLoader(webAppLoader);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebXml(Resource.newResource(web31true));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
context.getMetaData().setWebInfClassesResources(classes);
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
@ -215,9 +216,9 @@ public class TestAnnotationConfiguration
// test a 3.1 webapp with metadata-complete=false loads both server
// and webapp scis
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31false)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
@ -260,12 +261,12 @@ public class TestAnnotationConfiguration
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(orderedLoader);
context.getMetaData().setWebXml(Resource.newResource(web31true));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
RelativeOrdering ordering = new RelativeOrdering(context.getMetaData());
context.getMetaData().setOrdering(ordering);
context.getMetaData().addWebInfJar(Resource.newResource(orderedFragmentJar.toURI().toURL()));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfResource(Resource.newResource(orderedFragmentJar.toURI().toURL()));
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().orderFragments();
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
@ -295,9 +296,9 @@ public class TestAnnotationConfiguration
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
@ -332,9 +333,9 @@ public class TestAnnotationConfiguration
List<ServletContainerInitializer> scis;
context.setConfigurationDiscovered(true);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
@ -349,24 +350,4 @@ public class TestAnnotationConfiguration
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testGetFragmentFromJar() throws Exception
{
String dir = MavenTestingUtils.getTargetTestingDir("getFragmentFromJar").getAbsolutePath();
File file = new File(dir);
file = new File(file.getCanonicalPath());
URL url = file.toURI().toURL();
Resource jar1 = Resource.newResource(url + "file.jar");
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext wac = new WebAppContext();
List<FragmentDescriptor> frags = new ArrayList<FragmentDescriptor>();
frags.add(new FragmentDescriptor(Resource.newResource("jar:" + url + "file.jar!/fooa.props")));
frags.add(new FragmentDescriptor(Resource.newResource("jar:" + url + "file2.jar!/foob.props")));
assertNotNull(config.getFragmentFromJar(jar1, frags));
}
}

View File

@ -0,0 +1,127 @@
//
// ========================================================================
// 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.annotations;
import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.eclipse.jetty.xml.XmlParser;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAnnotationDecorator
{
public class TestWebDescriptor extends WebDescriptor
{
public TestWebDescriptor(MetaData.Complete metadata)
{
super(EmptyResource.INSTANCE);
_metaDataComplete = metadata;
}
@Override
public void parse(XmlParser parser) throws Exception
{
}
@Override
public void processVersion()
{
}
@Override
public void processOrdering()
{
}
@Override
public void processDistributable()
{
}
@Override
public int getMajorVersion()
{
return 4;
}
@Override
public int getMinorVersion()
{
return 0;
}
}
@Test
public void testAnnotationDecorator() throws Exception
{
assertThrows(NullPointerException.class, () ->
{
new AnnotationDecorator(null);
});
WebAppContext context = new WebAppContext();
AnnotationDecorator decorator = new AnnotationDecorator(context);
ServletE servlet = new ServletE();
//test without BaseHolder metadata
decorator.decorate(servlet);
LifeCycleCallbackCollection callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNotNull(callbacks);
assertFalse(callbacks.getPreDestroyCallbacks().isEmpty());
//reset
context.removeAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
//test with BaseHolder metadata, should not introspect with metdata-complete==true
context.getMetaData().setWebDescriptor(new TestWebDescriptor(MetaData.Complete.True));
assertTrue(context.getMetaData().isMetaDataComplete());
ServletHolder holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, ""));
holder.setHeldClass(ServletE.class);
context.getServletHandler().addServlet(holder);
DecoratedObjectFactory.associateInfo(holder);
decorator = new AnnotationDecorator(context);
decorator.decorate(servlet);
DecoratedObjectFactory.disassociateInfo();
callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNull(callbacks);
//reset
context.removeAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
//test with BaseHolder metadata, should introspect with metadata-complete==false
context.getMetaData().setWebDescriptor(new TestWebDescriptor(MetaData.Complete.False));
DecoratedObjectFactory.associateInfo(holder);
decorator = new AnnotationDecorator(context);
decorator.decorate(servlet);
DecoratedObjectFactory.disassociateInfo();
callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNotNull(callbacks);
assertFalse(callbacks.getPreDestroyCallbacks().isEmpty());
}
}

View File

@ -0,0 +1,98 @@
//
// ========================================================================
// 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.annotations;
import java.io.File;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAnnotationIntrospector
{
@Test
public void testIsIntrospectable() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(AnnotationIntrospector.class))
{
WebAppContext wac = new WebAppContext();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
//can't introspect nothing
assertFalse(introspector.isIntrospectable(null, null));
//can introspect if no metadata to say otherwise
assertTrue(introspector.isIntrospectable(new Object(), null));
//can introspect if metdata isn't a BaseHolder
assertTrue(introspector.isIntrospectable(new Object(), new Object()));
//an EMBEDDED sourced servlet can be introspected
ServletHolder holder = new ServletHolder();
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a JAVAX API sourced servlet can be introspected
holder = new ServletHolder(Source.JAVAX_API);
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//an ANNOTATION sourced servlet can be introspected
holder = new ServletHolder(new Source(Source.Origin.ANNOTATION, ServletE.class.getName()));
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet can be introspected if web.xml metdata-complete==false
File file = MavenTestingUtils.getTestResourceFile("web31false.xml");
Resource resource = Resource.newResource(file);
wac.getMetaData().setWebDescriptor(new WebDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet can be introspected if web-fragment.xml medata-complete==false && web.xml metadata-complete==false
file = MavenTestingUtils.getTestResourceFile("web-fragment4false.xml");
resource = Resource.newResource(file);
wac.getMetaData().addFragmentDescriptor(Resource.newResource(file.getParentFile()), new FragmentDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet cannot be introspected if web-fragment.xml medata-complete==true (&& web.xml metadata-complete==false)
file = MavenTestingUtils.getTestResourceFile("web-fragment4true.xml");
resource = Resource.newResource(file);
wac.getMetaData().addFragmentDescriptor(Resource.newResource(file.getParentFile()), new FragmentDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertFalse(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet cannot be introspected if web.xml medata-complete==true
file = MavenTestingUtils.getTestResourceFile("web31true.xml");
resource = Resource.newResource(file);
wac.getMetaData().setWebDescriptor(new WebDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertFalse(introspector.isIntrospectable(new ServletE(), holder));
}
}
}

View File

@ -89,7 +89,7 @@ public class TestSecurityAnnotationConversions
//Assume we found 1 servlet with a @HttpConstraint with value=EmptyRoleSemantic.DENY security annotation
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes:
@ -108,7 +108,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[1].setConstraint(expectedConstraint);
expectedMappings[1].setPathSpec("*.foo");
introspector.introspect(DenyServlet.class);
introspector.introspect(new DenyServlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -122,15 +122,15 @@ public class TestSecurityAnnotationConversions
});
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes - no constraints at all as per Servlet Spec 3.1 pg 129
//1 ConstraintMapping per ServletMapping pathSpec
ConstraintMapping[] expectedMappings = new ConstraintMapping[]{};
introspector.introspect(PermitServlet.class);
PermitServlet permit = new PermitServlet();
introspector.introspect(permit, null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -146,7 +146,7 @@ public class TestSecurityAnnotationConversions
});
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes:compareResults
@ -164,8 +164,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[1] = new ConstraintMapping();
expectedMappings[1].setConstraint(expectedConstraint);
expectedMappings[1].setPathSpec("*.foo");
introspector.introspect(RolesServlet.class);
introspector.introspect(new RolesServlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -211,10 +210,10 @@ public class TestSecurityAnnotationConversions
expectedMappings[3].setPathSpec("*.foo");
expectedMappings[3].setMethod("GET");
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
introspector.registerHandler(annotationHandler);
introspector.introspect(Method1Servlet.class);
introspector.introspect(new Method1Servlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -227,7 +226,7 @@ public class TestSecurityAnnotationConversions
"/foo/*", "*.foo"
});
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
introspector.registerHandler(annotationHandler);
@ -263,7 +262,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[3].setPathSpec("*.foo");
expectedMappings[3].setMethod("GET");
introspector.introspect(Method2Servlet.class);
introspector.introspect(new Method2Servlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}

View File

@ -73,11 +73,14 @@ public class TestResourceAnnotations
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resA", objA, false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resB", objB, false);
AnnotationIntrospector parser = new AnnotationIntrospector();
AnnotationIntrospector parser = new AnnotationIntrospector(wac);
ResourceAnnotationHandler handler = new ResourceAnnotationHandler(wac);
parser.registerHandler(handler);
parser.introspect(ResourceA.class);
parser.introspect(ResourceB.class);
ResourceA resourceA = new ResourceA();
ResourceB resourceB = new ResourceB();
parser.introspect(resourceA, null);
parser.introspect(resourceB, null);
//processing classA should give us these jndi name bindings:
// java:comp/env/myf
@ -155,11 +158,13 @@ public class TestResourceAnnotations
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resA", objA, false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resB", objB, false);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ResourcesAnnotationHandler handler = new ResourcesAnnotationHandler(wac);
introspector.registerHandler(handler);
introspector.introspect(ResourceA.class);
introspector.introspect(ResourceB.class);
ResourceA resourceA = new ResourceA();
ResourceB resourceB = new ResourceB();
introspector.introspect(resourceA, null);
introspector.introspect(resourceB, null);
assertEquals(objA, env.lookup("peach"));
assertEquals(objB, env.lookup("pear"));

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment
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/web-fragment_4_0.xsd"
version="4.0">
<name>ardvaark</name>
</web-fragment>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment
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/web-fragment_4_0.xsd"
metadata-complete="true"
version="4.0">
<name>badger</name>
</web-fragment>

View File

@ -228,6 +228,12 @@ public class HttpSenderOverHTTP extends HttpSender
headerBuffer = byteBufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers);
break;
}
case HEADER_OVERFLOW:
{
httpClient.getByteBufferPool().release(headerBuffer);
headerBuffer = null;
throw new IllegalArgumentException("Request header too large");
}
case NEED_CHUNK:
{
chunkBuffer = byteBufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers);

View File

@ -45,6 +45,7 @@ import org.junit.jupiter.api.Test;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -165,6 +166,7 @@ public class HttpClientProxyProtocolTest
@Test
public void testClientProxyProtocolV2WithVectors() throws Exception
{
int typeTLS = 0x20;
String tlsVersion = "TLSv1.3";
byte[] tlsVersionBytes = tlsVersion.getBytes(StandardCharsets.US_ASCII);
startServer(new EmptyServerHandler()
@ -176,7 +178,10 @@ public class HttpClientProxyProtocolTest
assertTrue(endPoint instanceof ProxyConnectionFactory.ProxyEndPoint);
ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint;
if (target.equals("/tls_version"))
{
assertNotNull(proxyEndPoint.getTLV(typeTLS));
assertEquals(tlsVersion, proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION));
}
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
response.getOutputStream().print(request.getRemotePort());
}
@ -186,7 +191,6 @@ public class HttpClientProxyProtocolTest
int serverPort = connector.getLocalPort();
int clientPort = ThreadLocalRandom.current().nextInt(1024, 65536);
int typeTLS = 0x20;
byte[] dataTLS = new byte[1 + 4 + (1 + 2 + tlsVersionBytes.length)];
dataTLS[0] = 0x01; // CLIENT_SSL
dataTLS[5] = 0x21; // SUBTYPE_SSL_VERSION

View File

@ -74,4 +74,5 @@ include::jndi/chapter.adoc[]
include::alpn/chapter.adoc[]
include::fastcgi/chapter.adoc[]
include::extras/chapter.adoc[]
include::runner/chapter.adoc[]
include::tuning/chapter.adoc[]

View File

@ -0,0 +1,24 @@
//
// ========================================================================
// 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
// ========================================================================
//
[[runner]]
== Jetty Runner
This chapter explains how to use the `jetty-runner` to run your webapps without needing an installation of Jetty.
include::jetty-runner.adoc[]

View File

@ -0,0 +1,349 @@
//
// ========================================================================
// 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-runner]]
=== Use Jetty Without an Installed Distribution
The idea of the `jetty-runner` is extremely simple run a webapp directly from the command line using a single jar file and as much default configuration as possible.
Of course, if your webapp is not as straightforward, the `jetty-runner` has command line options which allow you to customize the execution environment.
[[jetty-runner-preparation]]
==== Preparation
You will need the `jetty-runner` jar:
1. Download the `jetty-runner` jar available at https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-runner/[Maven Central].
==== Deploying a Simple Context
Let's assume we have a very simple webapp that does not need any resources from its environment, nor any configuration apart from the defaults.
Starting it is as simple as performing the following:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar simple.war
....
This will start Jetty on port 8080, and deploy the webapp to `/`.
Your webapp does not have to be packed into a war, you can deploy a webapp that is a directory instead in the same way:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar simple
....
In fact, the webapp does not have to be a war or even a directory, it can simply be a Jetty link:#using-context-provider[context xml] file that describes your webapp:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar simple-context.xml
....
____
[NOTE]
When using a context xml file, the application being deployed is not even required to be a fully-fledged webapp.
It can simply be a Jetty link:#what-is-a-context[context].
____
By default, `jetty-runner` implements all Configuration Classes so that users can set up and deploy new instances with as little configuration as possible.
If you wish to only implement certain Configuration Classes, they will need to be defined in the context xml for the webapp/context.
The default Configuration Classes are:
`org.eclipse.jetty.webapp.WebInfConfiguration`
`org.eclipse.jetty.webapp.WebXmlConfiguration`
`org.eclipse.jetty.webapp.MetaInfConfiguration`
`org.eclipse.jetty.webapp.FragmentConfiguration`
`org.eclipse.jetty.webapp.JettyWebXmlConfiguration`
`org.eclipse.jetty.plus.webapp.EnvConfiguration`
`org.eclipse.jetty.plus.webapp.PlusConfiguration`
`org.eclipse.jetty.annotations.AnnotationConfiguration`
You can learn more about implementing specific Configuration Classes link:https://www.eclipse.org/jetty/documentation/current/configuring-webapps.html#webapp-configurations[here.]
==== Deploying Multiple Contexts
If you have more than one webapp that must be deployed, simply provide them all on the command line.
You can control the context paths for them using the `--path` parameter.
Here's an example of deploying 2 wars (although either or both of them could be unpacked directories instead):
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --path /one my1.war --path /two my2.war
....
If you have context xml files that describe your webapps, you can fully configure your webapps in them and hence you won't need to use the command line switches.
Just provide the list of context files like so:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar my-first-context.xml my-second-context.xml my-third-context.xml
....
____
[NOTE]
Switched used on the command line override configuration file settings.
So, for example, you could set the context path for the webapp inside the context xml file, and use the `--path` switch to override it on the command line.
____
===== Changing the Default Port
By default the `jetty-runner` will listen on port 8080.
You can easily change this on the command line using the `--port` command.
Here's an example that runs our simple.war on port 9090:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --port 9090 simple.war
....
===== Using jetty.xml Files
Instead of, or in addition to, using command line switches, you can use one or more `jetty.xml` files to configure the environment for your webapps.
Here's an example where we apply two different `jetty.xml` files:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --config jetty.xml --config jetty-https.xml simple.war
....
[[runner-configuration-reference]]
==== Full Configuration Reference
You can see the fill set of configuration options using the `--help` switch:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --help
....
Here's what the output will look like:
[source, plain, subs="{sub-order}"]
----
Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...]
Server opts:
--version - display version and exit
--log file - request log filename (with optional 'yyyy_mm_dd' wildcard
--out file - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard
--host name|ip - interface to listen on (default is all interfaces)
--port n - port to listen on (default 8080)
--stop-port n - port to listen for stop command (or -DSTOP.PORT=n)
--stop-key n - security string for stop command (required if --stop-port is present) (or -DSTOP.KEY=n)
[--jar file]*n - each tuple specifies an extra jar to be added to the classloader
[--lib dir]*n - each tuple specifies an extra directory of jars to be added to the classloader
[--classes dir]*n - each tuple specifies an extra directory of classes to be added to the classloader
--stats [unsecure|realm.properties] - enable stats gathering servlet context
[--config file]*n - each tuple specifies the name of a jetty xml config file to apply (in the order defined)
Context opts:
[[--path /path] context]*n - WAR file, web app dir or context xml file, optionally with a context path
----
===== Printing the Version
Print out the version of Jetty and then exit immediately.
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --version
....
===== Configuring a Request Log
Cause Jetty to write a request log with the given name.
If the file is prefixed with `yyyy_mm_dd` then the file will be automatically rolled over.
Note that for finer grained configuration of the link:{JDURL}/org/eclipse/jetty/server/NCSARequestLog.html[request log], you will need to use a Jetty xml file instead.
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --log yyyy_mm_dd-requests.log my.war
....
===== Configuring the Output Log
Redirect the output of jetty logging to the named file.
If the file is prefixed with `yyyy_mm_dd` then the file will be automatically rolled over.
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --out yyyy_mm_dd-output.log my.war
....
===== Configuring the Interface for HTTP
Like Jetty standalone, the default is for the connectors to listen on all interfaces on a machine.
You can control that by specifying the name or ip address of the particular interface you wish to use with the `--host` argument:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --host 192.168.22.19 my.war
....
===== Configuring the Port for HTTP
The default port number is 8080.
To link:#how-to-configure-connectors[configure a https connector], use a Jetty xml config file instead.
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --port 9090 my.war
....
===== Configuring Stop
You can configure a port number for Jetty to listen on for a stop command, so you are able to stop it from a different terminal.
This requires the use of a "secret" key, to prevent malicious or accidental termination.
Use the `--stop-port` and `--stop-key` (or `-DSTOP.PORT=` and `-DSTOP.KEY=`, respectively) parameters as arguments to the `jetty-runner`:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --stop-port 8181 --stop-key abc123
....
Then, to stop Jetty from a different terminal, you need to supply the same port and key information.
For this you'll either need a local installation of Jetty, the link:#jetty-maven-plugin[jetty-maven-plugin], the link:#jetty-ant[jetty-ant plugin], or a custom class.
Here's how to use a Jetty installation to perform a stop:
[source, screen, subs="{sub-order}"]
....
> java -jar start.jar -DSTOP.PORT=8181 -DSTOP.KEY=abc123 --stop
....
===== Configuring the Container Classpath
With a local installation of Jetty, you add jars and classes to the container's classpath by putting them in the `{$jetty.base}/lib` directory.
With the `jetty-runner`, you can use the `--lib`, `--jar` and `--classes` arguments instead to achieve the same thing.
`--lib` adds the location of a directory which contains jars to add to the container classpath.
You can add 1 or more.
Here's an example of configuring 2 directories:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --lib /usr/local/external/lib --lib $HOME/external-other/lib my.war
....
`--jar` adds a single jar file to the container classpath.
You can add 1 or more.
Here's an example of configuring 3 extra jars:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --jar /opt/stuff/jars/jar1.jar --jar $HOME/jars/jar2.jar --jar /usr/local/proj/jars/jar3.jar my.war
....
`--classes` add the location of a directory containing classes to add to the container classpath.
You can add 1 or more.
Here's an example of configuring a single extra classes dir:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --classes /opt/stuff/classes my.war
....
____
[NOTE]
When using the `--jar` and/or `--lib` arguments, by default these will *not* be inspected for `META-INF` information such as `META-INF/resources`, `META-INF/web-fragment.xml`, or `META-INF/taglib.tld`.
If you require these jar files inspected you will need to define the link:https://www.eclipse.org/jetty/documentation/current/configuring-webapps.html#webapp-context-attributes[jar pattern in your context xml file].
Jetty-Runner automatically provides and appends a suitable pattern for jtsl taglibs (this pattern is different than the one in the standard Jetty distribution).
____
===== Gathering Statistics
If statistics gathering is enabled, then they are viewable by surfing to the context `/stats`.
You may optionally protect access to that context with a password.
Here's an example of enabling statistics, with no password protection:
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --stats unsecure my.war
....
If we wished to protect access to the `/stats` context, we would provide the location of a Jetty realm configuration file containing authentication and authorization information.
For example, we could use the following example realm file from the Jetty distribution:
[source, screen, subs="{sub-order}"]
....
jetty: MD5:164c88b302622e17050af52c89945d44,user
admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin
other: OBF:1xmk1w261u9r1w1c1xmq,user
plain: plain,user
user: password,user
# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password
digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
....
Assuming we've copied it into the local directory, we would apply it like so
[source, screen, subs="{sub-order}"]
....
> java -jar jetty-runner.jar --stats realm.properties my.war
....
After navigating to http://localhost:8080/ a few times, we can point to the stats servlet on http://localhost:8080/stats to see the output:
....
Statistics:
Statistics gathering started 1490627ms ago
Requests:
Total requests: 9
Active requests: 1
Max active requests: 1
Total requests time: 63
Mean request time: 7.875
Max request time: 26
Request time standard deviation: 8.349764752888037
Dispatches:
Total dispatched: 9
Active dispatched: 1
Max active dispatched: 1
Total dispatched time: 63
Mean dispatched time: 7.875
Max dispatched time: 26
Dispatched time standard deviation: 8.349764752888037
Total requests suspended: 0
Total requests expired: 0
Total requests resumed: 0
Responses:
1xx responses: 0
2xx responses: 7
3xx responses: 1
4xx responses: 0
5xx responses: 0
Bytes sent total: 1453
Connections:
org.eclipse.jetty.server.ServerConnector@203822411
Protocols:http/1.1
Statistics gathering started 1490606ms ago
Total connections: 7
Current connections open: 1
Max concurrent connections open: 2
Total connections duration: 72883
Mean connection duration: 12147.166666666666
Max connection duration: 65591
Connection duration standard deviation: 23912.40292977684
Total messages in: 7
Total messages out: 7
Memory:
Heap memory usage: 49194840 bytes
Non-heap memory usage: 12611696 bytes
....

View File

@ -26,3 +26,4 @@ include::secure-passwords.adoc[]
include::setting-port80-access-for-non-root-user.adoc[]
include::jaas-support.adoc[]
include::spnego-support.adoc[]
include::openid-support.adoc[]

View File

@ -0,0 +1,139 @@
//
// ========================================================================
// 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
// ========================================================================
//
[[openid-support]]
=== OpenID Support
==== External Setup
===== Registering an App with OpenID Provider
You must register the app with an OpenID Provider such as link:https://developers.google.com/identity/protocols/OpenIDConnect#authenticatingtheuser[Google] or link:https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf[Amazon.]
This will give you a Client ID and Client Secret.
Once set up you must also register all the possible URI's for your webapp with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp.
These may look like
* `http://localhost:8080/openid-webapp/j_security_check`
* `https://example.com/j_security_check`
==== Distribution Configuration
===== OpenID Provider Configuration
To enable OpenID support, you first need to activate the `openid` module in your implementation.
[source, screen, subs="{sub-order}"]
----
java -jar {JETTY_HOME}/start.jar --add-to-start=openid
----
To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case sensitive URL using the `https` scheme) and the OAuth 2.0 Client ID and Client Secret.
If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider.
These can be set as properties in the `start.ini` or `start.d/openid.ini` files.
===== WebApp Specific Configuration in web.xml
The `web.xml` file needs some specific configuration to use OpenID.
There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider.
To set the error page, an init param is set at `"org.eclipse.jetty.security.openid.error_page"`, its value should be a path relative to the webapp where authentication errors should be redirected.
Example:
[source, xml, subs="{sub-order}"]
----
<login-config>
<auth-method>OPENID</auth-method>
<realm-name>https://accounts.google.com</realm-name>
</login-config>
<context-param>
<param-name>org.eclipse.jetty.security.openid.error_page</param-name>
<param-value>/error</param-value>
</context-param>
----
==== Embedded Configuration
===== Define the `OpenIdConfiguration` for a specific OpenID Provider.
If the OpenID Provider allows metadata discovery then you can use.
[source, java, subs="{sub-order}"]
----
OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, CLIENT_ID, CLIENT_SECRET);
----
Otherwise you can manually enter the necessary information:
[source, java, subs="{sub-order}"]
----
OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, TOKEN_ENDPOINT, AUTH_ENDPOINT, CLIENT_ID, CLIENT_SECRET);
----
===== Configuring an `OpenIdLoginService`
[source, java, subs="{sub-order}"]
----
LoginService loginService = new OpenIdLoginService(openIdConfig);
securityHandler.setLoginService(loginService);
----
===== Configuring an `OpenIdAuthenticator` with `OpenIdConfiguration` and Error Page Redirect
[source, java, subs="{sub-order}"]
----
Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, "/error");
securityHandler.setAuthenticator(authenticator);
servletContextHandler.setSecurityHandler(securityHandler);
----
===== Usage
====== Claims and Access Token
Claims about the user can be found using attributes on the session attribute `"org.eclipse.jetty.security.openid.claims"`, and the full response containing the OAuth 2.0 Access Token can be found with the session attribute `"org.eclipse.jetty.security.openid.response"`.
Example:
[source, java, subs="{sub-order}"]
----
Map<String, Object> claims = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.claims");
String userId = claims.get("sub");
Map<String, Object> response = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.response");
String accessToken = response.get("access_token");
----
==== Scopes
The OpenID scope is always used but additional scopes can be requested which can give you additional resources or privileges.
For the Google OpenID Provider it can be useful to request the scopes `profile` and `email` which will give you additional user claims.
Additional scopes can be requested through the `start.ini` or `start.d/openid.ini` files, or with `OpenIdConfiguration.addScopes(...);` in embedded code.
==== Roles
If security roles are required they can be configured through a wrapped `LoginService` which is deferred to for role information by the `OpenIdLoginService`.
This can be configured in XML through `etc/openid-baseloginservice.xml` in the Distribution, or in embedded code using the constructor for the `OpenIdLoginService`.
[source, java, subs="{sub-order}"]
----
LoginService wrappedLoginService = ...; // Optional LoginService for Roles
LoginService loginService = new OpenIdLoginService(openIdConfig, wrappedLoginService);
----
When using authorization roles, the setting `authenticateNewUsers` becomes significant.
If set to `true` users not found by the wrapped `LoginService` will still be authenticated but will have no roles.
If set to `false` those users will be not be allowed to authenticate and are redirected to the error page.
This setting is configured through the property `jetty.openid.authenticateNewUsers` in the `start.ini` or `start.d/openid.ini` file, or with `OpenIdLoginService.setAuthenticateNewUsers(...);` in embedded code.

View File

@ -11,7 +11,7 @@
<name>Jetty :: Hazelcast Session Manager</name>
<properties>
<hazelcast.version>3.9.4</hazelcast.version>
<hazelcast.version>3.12.6</hazelcast.version>
<bundle-symbolic-name>${project.groupId}.hazelcast</bundle-symbolic-name>
</properties>

View File

@ -5,7 +5,7 @@
<!-- ===================================================================== -->
<!-- Configure a factory for HazelcastSessionDataStore using -->
<!-- an embedded Hazelcast Instance -->
<!-- an remote Hazelcast Instance -->
<!-- ===================================================================== -->
<Call name="addBean">
<Arg>
@ -16,6 +16,7 @@
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="onlyClient"><Property name="jetty.session.hazelcast.onlyClient" default="true" /></Set>
<Set name="addresses"><Property name="jetty.session.hazelcast.addresses" default="" /></Set>
</New>
</Arg>
</Call>

View File

@ -13,7 +13,7 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
maven://com.hazelcast/hazelcast/3.12.6|lib/hazelcast/hazelcast-3.12.6.jar
[xml]
etc/sessions/hazelcast/default.xml
@ -31,7 +31,6 @@ http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0

View File

@ -13,8 +13,8 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
maven://com.hazelcast/hazelcast-client/3.9.4|lib/hazelcast/hazelcast-client-3.9.4.jar
maven://com.hazelcast/hazelcast/3.12.6|lib/hazelcast/hazelcast-3.12.6.jar
maven://com.hazelcast/hazelcast-client/3.12.6|lib/hazelcast/hazelcast-client-3.12.6.jar
[xml]
etc/sessions/hazelcast/remote.xml
@ -34,6 +34,6 @@ jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
jetty.session.hazelcast.onlyClient=true
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.hazelcast.configurationLocation=
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0
#jetty.session.hazelcast.addresses=

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.hazelcast.session;
import java.io.IOException;
import java.util.Arrays;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
@ -29,6 +30,7 @@ import com.hazelcast.config.SerializerConfig;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStore;
@ -57,6 +59,8 @@ public class HazelcastSessionDataStoreFactory
private boolean scavengeZombies = false;
private String addresses;
public boolean isScavengeZombies()
{
return scavengeZombies;
@ -82,6 +86,12 @@ public class HazelcastSessionDataStoreFactory
if (configurationLocation == null)
{
ClientConfig config = new ClientConfig();
if (addresses != null && !addresses.isEmpty())
{
config.getNetworkConfig().setAddresses(Arrays.asList(addresses.split(",")));
}
SerializerConfig sc = new SerializerConfig()
.setImplementation(new SessionDataSerializer())
.setTypeClass(SessionData.class);
@ -202,4 +212,14 @@ public class HazelcastSessionDataStoreFactory
{
this.hazelcastInstanceName = hazelcastInstanceName;
}
public String getAddresses()
{
return addresses;
}
public void setAddresses(String addresses)
{
this.addresses = addresses;
}
}

View File

@ -13,6 +13,9 @@
<Set name="umaskOctal"><Property name="jetty.setuid.umask" default="002"/></Set>
<Set name="username"><Property name="jetty.setuid.userName" default="jetty"/></Set>
<Set name="groupname"><Property name="jetty.setuid.groupName" default="jetty"/></Set>
<Set name="clearSupplementalGroups">
<Property name="jetty.setuid.clearSupplementalGroups" default="false" />
</Set>
<!-- uncomment to change the limits on number of open file descriptors for root -->
<!--
<Call name="setRLimitNoFiles">

View File

@ -1,4 +1,4 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables the unix setUID configuration so that the server
@ -9,7 +9,7 @@ changing to a restricted user (eg jetty).
server
[lib]
lib/setuid/jetty-setuid-java-1.0.3.jar
lib/setuid/jetty-setuid-java-1.0.4.jar
[xml]
etc/jetty-setuid.xml
@ -20,3 +20,4 @@ etc/jetty-setuid.xml
# jetty.setuid.userName=jetty
# jetty.setuid.groupName=jetty
# jetty.setuid.umask=002
# jetty.setuid.clearSupplementalGroups=false

View File

@ -74,6 +74,7 @@ public class HttpGenerator
NEED_CHUNK, // Need a small chunk buffer of CHUNK_SIZE
NEED_INFO, // Need the request/response metadata info
NEED_HEADER, // Need a buffer to build HTTP headers into
HEADER_OVERFLOW, // The header buffer overflowed
NEED_CHUNK_TRAILER, // Need a large chunk buffer for last chunk and trailers
FLUSH, // The buffers previously generated should be flushed
CONTINUE, // Continue generating the message
@ -250,7 +251,8 @@ public class HttpGenerator
}
catch (BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Request header too large", e);
LOG.ignore(e);
return Result.HEADER_OVERFLOW;
}
catch (Exception e)
{
@ -427,7 +429,8 @@ public class HttpGenerator
}
catch (BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Response header too large", e);
LOG.ignore(e);
return Result.HEADER_OVERFLOW;
}
catch (Exception e)
{

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class HttpGeneratorClientTest
@ -122,6 +123,42 @@ public class HttpGeneratorClientTest
assertThat(out, Matchers.not(Matchers.containsString("Null:")));
}
@Test
public void testHeaderOverflow() throws Exception
{
HttpGenerator gen = new HttpGenerator();
Info info = new Info("GET", "/index.html");
info.getFields().add("Host", "localhost");
info.getFields().add("Field", "SomeWhatLongValue");
info.setHttpVersion(HttpVersion.HTTP_1_0);
HttpGenerator.Result result = gen.generateRequest(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
ByteBuffer header = BufferUtil.allocate(16);
result = gen.generateRequest(info, header, null, null, true);
assertEquals(HttpGenerator.Result.HEADER_OVERFLOW, result);
header = BufferUtil.allocate(2048);
result = gen.generateRequest(info, header, null, null, true);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
assertFalse(gen.isChunking());
String out = BufferUtil.toString(header);
BufferUtil.clear(header);
result = gen.generateResponse(null, false, null, null, null, false);
assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertFalse(gen.isChunking());
assertEquals(0, gen.getContentPrepared());
assertThat(out, Matchers.containsString("GET /index.html HTTP/1.0"));
assertThat(out, Matchers.not(Matchers.containsString("Content-Length")));
assertThat(out, Matchers.containsString("Field: SomeWhatLongValue"));
}
@Test
public void testPOSTRequestNoContent() throws Exception
{

View File

@ -156,6 +156,12 @@ public class HttpGeneratorServerHTTPTest
header = BufferUtil.allocate(2048);
continue;
case HEADER_OVERFLOW:
if (header.capacity() >= 8192)
throw new BadMessageException(500, "Header too large");
header = BufferUtil.allocate(8192);
continue;
case NEED_CHUNK:
chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
continue;

View File

@ -110,6 +110,38 @@ public class HttpGeneratorServerTest
assertThat(response, containsString("\r\n0123456789"));
}
@Test
public void testHeaderOverflow() throws Exception
{
HttpGenerator gen = new HttpGenerator();
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 302, null, new HttpFields(), 0);
info.getFields().add("Location", "http://somewhere/else");
HttpGenerator.Result result = gen.generateResponse(info, false, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
ByteBuffer header = BufferUtil.allocate(16);
result = gen.generateResponse(info, false, header, null, null, true);
assertEquals(HttpGenerator.Result.HEADER_OVERFLOW, result);
header = BufferUtil.allocate(8096);
result = gen.generateResponse(info, false, header, null, null, true);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
String response = BufferUtil.toString(header);
BufferUtil.clear(header);
result = gen.generateResponse(null, false, null, null, null, false);
assertEquals(HttpGenerator.Result.DONE, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertEquals(0, gen.getContentPrepared());
assertThat(response, containsString("HTTP/1.1 302 Found"));
assertThat(response, containsString("Location: http://somewhere/else"));
}
@Test
public void test204() throws Exception
{

View File

@ -25,9 +25,11 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -39,6 +41,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -59,6 +62,7 @@ import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.Graceful;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -66,6 +70,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -466,7 +471,7 @@ public class HTTP2Test extends AbstractTest
// The third stream must not be created.
MetaData.Request request3 = newRequest("GET", new HttpFields());
CountDownLatch maxStreamsLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(request3, null, false), new Promise.Adapter<Stream>()
session.newStream(new HeadersFrame(request3, null, false), new Promise.Adapter<>()
{
@Override
public void failed(Throwable x)
@ -494,7 +499,7 @@ public class HTTP2Test extends AbstractTest
// Create a fourth stream.
MetaData.Request request4 = newRequest("GET", new HttpFields());
CountDownLatch exchangeLatch4 = new CountDownLatch(2);
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<Stream>()
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>()
{
@Override
public void succeeded(Stream result)
@ -893,6 +898,125 @@ public class HTTP2Test extends AbstractTest
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testGracefulServerGoAway() throws Exception
{
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverSessionLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
{
@Override
public void onAccept(Session session)
{
serverSessionRef.set(session);
serverSessionLatch.countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
dataLatch.countDown();
if (frame.isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}
};
}
});
// Avoid aggressive idle timeout to allow the test verifications.
connector.setShutdownIdleTimeout(connector.getIdleTimeout());
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
clientCloseLatch.countDown();
}
});
assertTrue(serverSessionLatch.await(5, TimeUnit.SECONDS));
Session serverSession = serverSessionRef.get();
// Start 2 requests without completing them yet.
CountDownLatch responseLatch = new CountDownLatch(2);
MetaData.Request metaData1 = newRequest("GET", new HttpFields());
HeadersFrame request1 = new HeadersFrame(metaData1, null, false);
FuturePromise<Stream> promise1 = new FuturePromise<>();
Stream.Listener.Adapter listener = new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(HttpStatus.OK_200, response.getStatus());
responseLatch.countDown();
}
}
};
clientSession.newStream(request1, promise1, listener);
Stream stream1 = promise1.get(5, TimeUnit.SECONDS);
stream1.data(new DataFrame(stream1.getId(), ByteBuffer.allocate(1), false), Callback.NOOP);
MetaData.Request metaData2 = newRequest("GET", new HttpFields());
HeadersFrame request2 = new HeadersFrame(metaData2, null, false);
FuturePromise<Stream> promise2 = new FuturePromise<>();
clientSession.newStream(request2, promise2, listener);
Stream stream2 = promise2.get(5, TimeUnit.SECONDS);
stream2.data(new DataFrame(stream2.getId(), ByteBuffer.allocate(1), false), Callback.NOOP);
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
// Both requests are now on the server, shutdown gracefully the server session.
int port = connector.getLocalPort();
CompletableFuture<Void> shutdown = Graceful.shutdown(server);
// GOAWAY should not arrive to the client yet.
assertFalse(clientCloseLatch.await(1, TimeUnit.SECONDS));
// New requests should be immediately rejected.
HostPortHttpField authority3 = new HostPortHttpField("localhost" + ":" + port);
MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP, authority3, servletPath, HttpVersion.HTTP_2, new HttpFields());
HeadersFrame request3 = new HeadersFrame(metaData3, null, false);
FuturePromise<Stream> promise3 = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1);
clientSession.newStream(request3, promise3, new Stream.Listener.Adapter()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
{
resetLatch.countDown();
}
});
Stream stream3 = promise3.get(5, TimeUnit.SECONDS);
stream3.data(new DataFrame(stream3.getId(), ByteBuffer.allocate(1), true), Callback.NOOP);
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
// Finish the previous requests and expect the responses.
stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
stream2.data(new DataFrame(stream2.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
assertNull(shutdown.get(5, TimeUnit.SECONDS));
// Now GOAWAY should arrive to the client.
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
// Wait to process the GOAWAY frames and close the EndPoints.
Thread.sleep(1000);
assertFalse(((HTTP2Session)clientSession).getEndPoint().isOpen());
assertFalse(((HTTP2Session)serverSession).getEndPoint().isOpen());
}
private static void sleep(long time)
{
try

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@ -77,6 +78,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private static final Logger LOG = Log.getLogger(HTTP2Session.class);
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
private final AtomicBiInteger streamCount = new AtomicBiInteger(); // Hi = closed, Lo = stream count
private final AtomicInteger localStreamIds = new AtomicInteger();
private final AtomicInteger lastRemoteStreamId = new AtomicInteger();
private final AtomicInteger localStreamCount = new AtomicInteger();
@ -100,6 +102,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private boolean connectProtocolEnabled;
private long idleTime;
private GoAwayFrame closeFrame;
private Callback.Completable shutdownCallback;
public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId)
{
@ -436,14 +439,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
if (LOG.isDebugEnabled())
LOG.debug("Received {}", frame);
while (true)
{
CloseState current = closed.get();
switch (current)
{
case NOT_CLOSED:
{
if (closed.compareAndSet(current, CloseState.REMOTELY_CLOSED))
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.REMOTELY_CLOSED))
{
// We received a GO_AWAY, so try to write
// what's in the queue and then disconnect.
@ -451,16 +447,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
notifyClose(this, frame, new DisconnectCallback());
return;
}
break;
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("Ignored {}, already closed", frame);
return;
}
}
}
}
@Override
@ -537,7 +526,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void onConnectionFailure(int error, String reason, Callback callback)
{
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason, callback));
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new FailureCallback(error, reason, callback));
}
@Override
@ -672,30 +661,42 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
@Override
public boolean close(int error, String reason, Callback callback)
{
while (true)
{
CloseState current = closed.get();
switch (current)
{
case NOT_CLOSED:
{
if (closed.compareAndSet(current, CloseState.LOCALLY_CLOSED))
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{
if (LOG.isDebugEnabled())
LOG.debug("Closing {}/{}", error, reason);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason);
control(null, callback, closeFrame);
return true;
}
break;
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring close {}/{}, already closed", error, reason);
callback.succeeded();
return false;
}
@Override
public CompletableFuture<Void> shutdown()
{
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{
if (LOG.isDebugEnabled())
LOG.debug("Shutting down {}", this);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, ErrorCode.NO_ERROR.code, "shutdown");
shutdownCallback = new Callback.Completable();
// Only send the close frame when we can flip Hi and Lo = 0, see onStreamClosed().
if (streamCount.compareAndSet(0, 1, 0, 0))
control(null, shutdownCallback, closeFrame);
return shutdownCallback;
}
}
if (LOG.isDebugEnabled())
LOG.debug("Ignoring shutdown, already closed");
Callback.Completable result = shutdownCallback;
// Result may be null if the shutdown is in progress,
// don't wait and return a completed CompletableFuture.
return result != null ? result : CompletableFuture.completedFuture(null);
}
private GoAwayFrame newGoAwayFrame(CloseState closeState, int error, String reason)
@ -1041,10 +1042,28 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void onStreamOpened(IStream stream)
{
streamCount.addAndGetLo(1);
}
protected void onStreamClosed(IStream stream)
{
Callback callback = null;
while (true)
{
long encoded = streamCount.get();
int closed = AtomicBiInteger.getHi(encoded);
int streams = AtomicBiInteger.getLo(encoded) - 1;
if (streams == 0 && closed == 0)
{
callback = shutdownCallback;
closed = 1;
}
if (streamCount.compareAndSet(encoded, closed, streams))
break;
}
// Only send the close frame if we can flip Hi and Lo = 0, see shutdown().
if (callback != null)
control(null, callback, closeFrame);
}
@Override
@ -1321,8 +1340,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
{
case HEADERS:
{
onStreamOpened(stream);
HeadersFrame headersFrame = (HeadersFrame)frame;
if (headersFrame.getMetaData().isRequest())
onStreamOpened(stream);
if (stream.updateClose(headersFrame.isEndStream(), CloseState.Event.AFTER_SEND))
removeStream(stream);
break;
@ -1598,12 +1618,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
}
private class CloseCallback extends Callback.Nested
private class FailureCallback extends Callback.Nested
{
private final int error;
private final String reason;
private CloseCallback(int error, String reason, Callback callback)
private FailureCallback(int error, String reason, Callback callback)
{
super(callback);
this.error = error;

View File

@ -322,7 +322,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
{
// It's a bad client, it does not deserve to be
// treated gently by just resetting the stream.
session.close(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", Callback.NOOP);
((HTTP2Session)session).onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded");
callback.failed(new IOException("stream_window_exceeded"));
return;
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http2;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
@ -151,4 +152,14 @@ public interface ISession extends Session
* @param callback the callback to notify when the frame has been processed
*/
public void onData(DataFrame frame, Callback callback);
/**
* <p>Gracefully closes the session, returning a {@code CompletableFuture} that
* is completed when all the streams currently being processed are completed.</p>
* <p>Implementation is idempotent, i.e. calling this method a second time
* or concurrently results in a no-operation.</p>
*
* @return a {@code CompletableFuture} that is completed when all the streams are completed
*/
public CompletableFuture<Void> shutdown();
}

View File

@ -24,11 +24,14 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
@ -46,6 +49,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
@ManagedObject
@ -296,22 +300,25 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
}
@ManagedObject("The container of HTTP/2 sessions")
public static class HTTP2SessionContainer implements Connection.Listener, Dumpable
public static class HTTP2SessionContainer implements Connection.Listener, Graceful, Dumpable
{
private final Set<Session> sessions = ConcurrentHashMap.newKeySet();
private final Set<ISession> sessions = ConcurrentHashMap.newKeySet();
private final AtomicReference<CompletableFuture<Void>> shutdown = new AtomicReference<>();
@Override
public void onOpened(Connection connection)
{
Session session = ((HTTP2Connection)connection).getSession();
ISession session = ((HTTP2Connection)connection).getSession();
sessions.add(session);
LifeCycle.start(session);
if (isShutdown())
shutdown(session);
}
@Override
public void onClosed(Connection connection)
{
Session session = ((HTTP2Connection)connection).getSession();
ISession session = ((HTTP2Connection)connection).getSession();
if (sessions.remove(session))
LifeCycle.stop(session);
}
@ -327,6 +334,39 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
return sessions.size();
}
@Override
public CompletableFuture<Void> shutdown()
{
CompletableFuture<Void> result = new CompletableFuture<>();
if (shutdown.compareAndSet(null, result))
{
CompletableFuture.allOf(sessions.stream().map(this::shutdown).toArray(CompletableFuture[]::new))
.whenComplete((v, x) ->
{
if (x == null)
result.complete(v);
else
result.completeExceptionally(x);
});
return result;
}
else
{
return shutdown.get();
}
}
@Override
public boolean isShutdown()
{
return shutdown.get() != null;
}
private CompletableFuture<Void> shutdown(ISession session)
{
return session.shutdown();
}
@Override
public String dump()
{

View File

@ -103,6 +103,12 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
onConnectionFailure(ErrorCode.STREAM_CLOSED_ERROR.code, "unexpected_headers_frame");
}
else
{
if (isClosed())
{
reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
}
else
{
stream = createRemoteStream(streamId, (MetaData.Request)metaData);
if (stream != null)
@ -124,6 +130,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
}
}
}
}
else
{
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream");

View File

@ -95,6 +95,16 @@ import org.eclipse.jetty.util.thread.Invocable;
*/
public interface EndPoint extends Closeable
{
/**
* Marks an <code>EndPoint</code> that wraps another <code>EndPoint</code>.
*/
public interface Wrapper
{
/**
* @return The wrapped <code>EndPoint</code>
*/
EndPoint unwrap();
}
/**
* @return The local Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>

View File

@ -457,7 +457,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
return getEndPoint().flush(output);
}
public class DecryptedEndPoint extends AbstractEndPoint
public class DecryptedEndPoint extends AbstractEndPoint implements EndPoint.Wrapper
{
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
private Throwable _failure;
@ -469,6 +469,12 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
super.setIdleTimeout(-1);
}
@Override
public EndPoint unwrap()
{
return getEndPoint();
}
@Override
public long getIdleTimeout()
{
@ -682,7 +688,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
try
{
_underflown = false;
unwrapResult = unwrap(_sslEngine, _encryptedInput, appIn);
unwrapResult = SslConnection.this.unwrap(_sslEngine, _encryptedInput, appIn);
}
finally
{
@ -1554,5 +1560,6 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
return String.format("SSL@%h.DEP.writeCallback", SslConnection.this);
}
}
}
}

View File

@ -126,9 +126,9 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
continue;
Resource bundleRes = oparser.indexBundle(bundle);
if (!context.getMetaData().getWebInfJars().contains(bundleRes))
if (!context.getMetaData().getWebInfResources(false).contains(bundleRes))
{
context.getMetaData().addWebInfJar(bundleRes);
context.getMetaData().addWebInfResource(bundleRes);
}
if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)

View File

@ -70,7 +70,7 @@
<Call class="org.eclipse.jetty.webapp.Configurations" name="setServerDefault">
<Arg><Ref refid="Server"/></Arg>
<Call name="add">
<Arg>
<Arg name="configClass">
<Array type="String">
<Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
<Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>

View File

@ -48,6 +48,16 @@
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId>

View File

@ -748,8 +748,9 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
injections.add(injection);
//Record which was the first descriptor to declare an injection for this name
if (context.getMetaData().getOriginDescriptor(node.getTag() + "." + jndiName + ".injection") == null)
context.getMetaData().setOrigin(node.getTag() + "." + jndiName + ".injection", descriptor);
String name = node.getTag() + "." + jndiName + ".injection";
if (context.getMetaData().getOriginDescriptor(name) == null)
context.getMetaData().setOrigin(name, descriptor);
}
catch (ClassNotFoundException e)
{

View File

@ -74,20 +74,20 @@ public class PlusDescriptorProcessorTest
URL webXml = Thread.currentThread().getContextClassLoader().getResource("web.xml");
webDescriptor = new WebDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(webXml));
webDescriptor.parse();
webDescriptor.parse(WebDescriptor.getParser(false));
URL frag1Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-1.xml");
fragDescriptor1 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag1Xml));
fragDescriptor1.parse();
fragDescriptor1.parse(WebDescriptor.getParser(false));
URL frag2Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-2.xml");
fragDescriptor2 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag2Xml));
fragDescriptor2.parse();
fragDescriptor2.parse(WebDescriptor.getParser(false));
URL frag3Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-3.xml");
fragDescriptor3 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag3Xml));
fragDescriptor3.parse();
fragDescriptor3.parse(WebDescriptor.getParser(false));
URL frag4Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-4.xml");
fragDescriptor4 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag4Xml));
fragDescriptor4.parse();
fragDescriptor4.parse(WebDescriptor.getParser(false));
}
@AfterEach

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.StandardDescriptorProcessor;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
@ -211,9 +212,9 @@ public class QuickStartConfiguration extends AbstractConfiguration
context.setConfigurations(context.getConfigurations().stream()
.filter(c -> !__replacedConfigurations.contains(c.replaces()) && !__replacedConfigurations.contains(c.getClass()))
.collect(Collectors.toList()).toArray(new Configuration[]{}));
context.getMetaData().setWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion());
context.getMetaData().setWebDescriptor(new WebDescriptor((Resource)context.getAttribute(QUICKSTART_WEB_XML)));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebDescriptor().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebDescriptor().getMinorVersion());
}
/**

135
jetty-runner/pom.xml Normal file
View File

@ -0,0 +1,135 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-runner</artifactId>
<name>Jetty :: Runner</name>
<properties>
<assembly-directory>target/distribution</assembly-directory>
<bundle-symbolic-name>${project.groupId}.runner</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includes>**</includes>
<excludes>**/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF,module-info.class</excludes>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
<executions>
<execution>
<id>integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>install</goal>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<scriptVariables>
<maven.dependency.plugin.version>${maven.dependency.plugin.version}</maven.dependency.plugin.version>
</scriptVariables>
<goals>
<goal>clean</goal>
</goals>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.neo4j.build.plugins</groupId>
<artifactId>clirr-maven-plugin</artifactId>
<configuration>
<!-- Clirr fails with "org.apache.tools.ant.Task not found" on this project -->
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<!-- jetty-runner is not an OSGi component -->
<skip>true</skip>
<!-- there is no way to skip MANIFEST creation by the plugin so just configure dummy location -->
<manifestLocation>${project.build.directory}/NON_USED_MANIFEST</manifestLocation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/resources/MANIFEST.MF</manifestFile>
<manifest>
<mainClass>org.eclipse.jetty.runner.Runner</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jaas</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jstl</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<profiles>
<profile>
<id>it-repo</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>

View File

@ -0,0 +1 @@
invoker.goals = generate-resources

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.its</groupId>
<artifactId>jetty-runner-it-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-runner</artifactId>
<version>@project.version@</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>@maven.dependency.plugin.version@</version>
<executions>
<execution>
<id>copy-jetty-runner</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-runner</artifactId>
<version>@project.version@</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/</outputDirectory>
<destFileName>jetty-runner.jar</destFileName>
</artifactItem>
</artifactItems>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,10 @@
import java.util.jar.*
File artifact = new File( basedir, "target/jetty-runner.jar" )
assert artifact.exists()
JarFile jar = new JarFile( artifact );
Attributes manifest = jar.getManifest().getMainAttributes();
assert manifest.getValue( new Attributes.Name( "Main-Class" ) ).equals( "org.eclipse.jetty.runner.Runner" )

View File

@ -0,0 +1,595 @@
//
// ========================================================================
// 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.runner;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.ShutdownMonitor;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.StatisticsServlet;
import org.eclipse.jetty.util.RolloverFileOutputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
* Runner
* <p>
* Combine jetty classes into a single executable jar and run webapps based on the args to it.
*
* @deprecated No replacement provided or available. Migrate to jetty-home (and use {@code ${jetty.base}} directory).
*/
@Deprecated
public class Runner
{
private static final Logger LOG = Log.getLogger(Runner.class);
public static final String[] PLUS_CONFIGURATION_CLASSES =
{
org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
};
public static final String CONTAINER_INCLUDE_JAR_PATTERN = ".*/jetty-runner-[^/]*\\.jar$";
public static final String DEFAULT_CONTEXT_PATH = "/";
public static final int DEFAULT_PORT = 8080;
protected Server _server;
protected URLClassLoader _classLoader;
protected Classpath _classpath = new Classpath();
protected ContextHandlerCollection _contexts;
protected String _logFile;
protected ArrayList<String> _configFiles;
protected boolean _enableStats = false;
protected String _statsPropFile;
/**
* Classpath
*/
public class Classpath
{
private List<URI> _classpath = new ArrayList<>();
public void addJars(Resource lib) throws IOException
{
if (lib == null || !lib.exists())
throw new IllegalStateException("No such lib: " + lib);
String[] list = lib.list();
if (list == null)
return;
for (String path : list)
{
if (".".equals(path) || "..".equals(path))
continue;
try (Resource item = lib.addPath(path))
{
if (item.isDirectory())
addJars(item);
else
{
String lowerCasePath = path.toLowerCase(Locale.ENGLISH);
if (lowerCasePath.endsWith(".jar") ||
lowerCasePath.endsWith(".zip"))
{
_classpath.add(item.getURI());
}
}
}
}
}
public void addPath(Resource path)
{
if (path == null || !path.exists())
throw new IllegalStateException("No such path: " + path);
_classpath.add(path.getURI());
}
public URI[] asArray()
{
return _classpath.toArray(new URI[0]);
}
}
public Runner()
{
}
/**
* Generate helpful usage message and exit
*
* @param error the error header
*/
public void usage(String error)
{
if (error != null)
System.err.println("ERROR: " + error);
System.err.println("Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
System.err.println("Server opts:");
System.err.println(" --version - display version and exit");
System.err.println(" --log file - request log filename (with optional 'yyyy_mm_dd' wildcard");
System.err.println(" --out file - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
System.err.println(" --host name|ip - interface to listen on (default is all interfaces)");
System.err.println(" --port n - port to listen on (default 8080)");
System.err.println(" --stop-port n - port to listen for stop command (or -DSTOP.PORT=n)");
System.err.println(" --stop-key n - security string for stop command (required if --stop-port is present) (or -DSTOP.KEY=n)");
System.err.println(" [--jar file]*n - each tuple specifies an extra jar to be added to the classloader");
System.err.println(" [--lib dir]*n - each tuple specifies an extra directory of jars to be added to the classloader");
System.err.println(" [--classes dir]*n - each tuple specifies an extra directory of classes to be added to the classloader");
System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
System.err.println(" [--config file]*n - each tuple specifies the name of a jetty xml config file to apply (in the order defined)");
System.err.println("Context opts:");
System.err.println(" [[--path /path] context]*n - WAR file, web app dir or context xml file, optionally with a context path");
System.exit(1);
}
/**
* Generate version message and exit
*/
public void version()
{
System.err.println("org.eclipse.jetty.runner.Runner: " + Server.getVersion());
System.exit(1);
}
/**
* Configure a jetty instance and deploy the webapps presented as args
*
* @param args the command line arguments
* @throws Exception if unable to configure
*/
public void configure(String[] args) throws Exception
{
// handle classpath bits first so we can initialize the log mechanism.
for (int i = 0; i < args.length; i++)
{
if ("--lib".equals(args[i]))
{
try (Resource lib = Resource.newResource(args[++i]))
{
if (!lib.exists() || !lib.isDirectory())
usage("No such lib directory " + lib);
_classpath.addJars(lib);
}
}
else if ("--jar".equals(args[i]))
{
try (Resource jar = Resource.newResource(args[++i]))
{
if (!jar.exists() || jar.isDirectory())
usage("No such jar " + jar);
_classpath.addPath(jar);
}
}
else if ("--classes".equals(args[i]))
{
try (Resource classes = Resource.newResource(args[++i]))
{
if (!classes.exists() || !classes.isDirectory())
usage("No such classes directory " + classes);
_classpath.addPath(classes);
}
}
else if (args[i].startsWith("--"))
i++;
}
initClassLoader();
LOG.info("Runner");
LOG.debug("Runner classpath {}", _classpath);
String contextPath = DEFAULT_CONTEXT_PATH;
boolean contextPathSet = false;
int port = DEFAULT_PORT;
String host = null;
int stopPort = Integer.getInteger("STOP.PORT", 0);
String stopKey = System.getProperty("STOP.KEY", null);
boolean runnerServerInitialized = false;
for (int i = 0; i < args.length; i++)
{
switch (args[i])
{
case "--port":
port = Integer.parseInt(args[++i]);
break;
case "--host":
host = args[++i];
break;
case "--stop-port":
stopPort = Integer.parseInt(args[++i]);
break;
case "--stop-key":
stopKey = args[++i];
break;
case "--log":
_logFile = args[++i];
break;
case "--out":
String outFile = args[++i];
PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile, true, -1));
LOG.info("Redirecting stderr/stdout to " + outFile);
System.setErr(out);
System.setOut(out);
break;
case "--path":
contextPath = args[++i];
contextPathSet = true;
break;
case "--config":
if (_configFiles == null)
_configFiles = new ArrayList<>();
_configFiles.add(args[++i]);
break;
case "--lib":
++i;//skip
break;
case "--jar":
++i; //skip
break;
case "--classes":
++i;//skip
break;
case "--stats":
_enableStats = true;
_statsPropFile = args[++i];
_statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
break;
default:
// process system property type argument so users can use in second args part
if (args[i].startsWith("-D"))
{
String[] sysProps = args[i].substring(2).split("=", 2);
if ("STOP.KEY".equals(sysProps[0]))
{
stopKey = sysProps[1];
break;
}
else if ("STOP.PORT".equals(sysProps[0]))
{
stopPort = Integer.parseInt(sysProps[1]);
break;
}
}
// process contexts
if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
{
if (_server == null) // server not initialized yet
{
// build the server
_server = new Server();
}
//apply jetty config files if there are any
if (_configFiles != null)
{
for (String cfg : _configFiles)
{
try (Resource resource = Resource.newResource(cfg))
{
XmlConfiguration xmlConfiguration = new XmlConfiguration(resource);
xmlConfiguration.configure(_server);
}
}
}
//check that everything got configured, and if not, make the handlers
HandlerCollection handlers = (HandlerCollection)_server.getChildHandlerByClass(HandlerCollection.class);
if (handlers == null)
{
handlers = new HandlerCollection();
_server.setHandler(handlers);
}
//check if contexts already configured
_contexts = (ContextHandlerCollection)handlers.getChildHandlerByClass(ContextHandlerCollection.class);
if (_contexts == null)
{
_contexts = new ContextHandlerCollection();
prependHandler(_contexts, handlers);
}
if (_enableStats)
{
//if no stats handler already configured
if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null)
{
StatisticsHandler statsHandler = new StatisticsHandler();
Handler oldHandler = _server.getHandler();
statsHandler.setHandler(oldHandler);
_server.setHandler(statsHandler);
ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
statsContext.setSessionHandler(new SessionHandler());
if (_statsPropFile != null)
{
final HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
Constraint constraint = new Constraint();
constraint.setName("Admin Only");
constraint.setRoles(new String[]{"admin"});
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setLoginService(loginService);
securityHandler.setConstraintMappings(Collections.singletonList(cm));
securityHandler.setAuthenticator(new BasicAuthenticator());
statsContext.setSecurityHandler(securityHandler);
}
}
}
//ensure a DefaultHandler is present
if (handlers.getChildHandlerByClass(DefaultHandler.class) == null)
{
handlers.addHandler(new DefaultHandler());
}
//check a connector is configured to listen on
Connector[] connectors = _server.getConnectors();
if (connectors == null || connectors.length == 0)
{
ServerConnector connector = new ServerConnector(_server);
connector.setPort(port);
if (host != null)
connector.setHost(host);
_server.addConnector(connector);
if (_enableStats)
connector.addBean(new ConnectionStatistics());
}
else
{
if (_enableStats)
{
for (Connector connector : connectors)
{
((AbstractConnector)connector).addBean(new ConnectionStatistics());
}
}
}
runnerServerInitialized = true;
}
// Create a context
try (Resource ctx = Resource.newResource(args[i]))
{
if (!ctx.exists())
usage("Context '" + ctx + "' does not exist");
if (contextPathSet && !(contextPath.startsWith("/")))
contextPath = "/" + contextPath;
// Configure the context
if (!ctx.isDirectory() && ctx.toString().toLowerCase(Locale.ENGLISH).endsWith(".xml"))
{
// It is a context config file
XmlConfiguration xmlConfiguration = new XmlConfiguration(ctx);
xmlConfiguration.getIdMap().put("Server", _server);
ContextHandler handler = (ContextHandler)xmlConfiguration.configure();
if (contextPathSet)
handler.setContextPath(contextPath);
_contexts.addHandler(handler);
String containerIncludeJarPattern = (String)handler.getAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN);
if (containerIncludeJarPattern == null)
containerIncludeJarPattern = CONTAINER_INCLUDE_JAR_PATTERN;
else
{
if (!containerIncludeJarPattern.contains(CONTAINER_INCLUDE_JAR_PATTERN))
{
containerIncludeJarPattern = containerIncludeJarPattern + (StringUtil.isBlank(containerIncludeJarPattern) ? "" : "|") + CONTAINER_INCLUDE_JAR_PATTERN;
}
}
handler.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, containerIncludeJarPattern);
//check the configurations, if not explicitly set up, then configure all of them
if (handler instanceof WebAppContext)
{
WebAppContext wac = (WebAppContext)handler;
if (wac.getConfigurationClasses() == null || wac.getConfigurationClasses().length == 0)
wac.setConfigurationClasses(PLUS_CONFIGURATION_CLASSES);
}
}
else
{
// assume it is a WAR file
WebAppContext webapp = new WebAppContext(_contexts, ctx.toString(), contextPath);
webapp.setConfigurationClasses(PLUS_CONFIGURATION_CLASSES);
webapp.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN,
CONTAINER_INCLUDE_JAR_PATTERN);
}
}
//reset
contextPathSet = false;
contextPath = DEFAULT_CONTEXT_PATH;
break;
}
}
if (_server == null)
usage("No Contexts defined");
_server.setStopAtShutdown(true);
switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
{
case 1:
usage("Must specify --stop-key when --stop-port is specified");
break;
case 2:
usage("Must specify --stop-port when --stop-key is specified");
break;
case 3:
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setPort(stopPort);
monitor.setKey(stopKey);
monitor.setExitVm(true);
break;
default:
break;
}
if (_logFile != null)
{
CustomRequestLog requestLog = new CustomRequestLog(_logFile);
_server.setRequestLog(requestLog);
}
}
protected void prependHandler(Handler handler, HandlerCollection handlers)
{
if (handler == null || handlers == null)
return;
Handler[] existing = handlers.getChildHandlers();
Handler[] children = new Handler[existing.length + 1];
children[0] = handler;
System.arraycopy(existing, 0, children, 1, existing.length);
handlers.setHandlers(children);
}
public void run() throws Exception
{
_server.start();
_server.join();
}
private URL toURL(URI uri)
{
try
{
return uri.toURL();
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}
/**
* Establish a classloader with custom paths (if any)
*/
protected void initClassLoader()
{
URL[] paths = Arrays.stream(_classpath.asArray()).map(this::toURL).toArray(URL[]::new);
if (_classLoader == null && paths.length > 0)
{
ClassLoader context = Thread.currentThread().getContextClassLoader();
if (context == null)
{
_classLoader = new URLClassLoader(paths);
}
else
{
_classLoader = new URLClassLoader(paths, context);
}
Thread.currentThread().setContextClassLoader(_classLoader);
}
}
public static void main(String[] args)
{
System.err.println("WARNING: jetty-runner is deprecated.");
System.err.println(" See Jetty Documentation for startup options");
System.err.println(" https://www.eclipse.org/jetty/documentation/");
Runner runner = new Runner();
try
{
if (args.length > 0 && args[0].equalsIgnoreCase("--help"))
{
runner.usage(null);
}
else if (args.length > 0 && args[0].equalsIgnoreCase("--version"))
{
runner.version();
}
else
{
runner.configure(args);
runner.run();
}
}
catch (Exception e)
{
e.printStackTrace();
runner.usage(null);
}
}
}

View File

@ -16,9 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.webapp;
/**
* Jetty Runner : Embedded Jetty Tool for running webapps directly
*/
package org.eclipse.jetty.runner;
public enum MetaDataComplete
{
NotSet, True, False
}

View File

@ -0,0 +1 @@
Comment: Jetty Runner

View File

@ -60,6 +60,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getAttribute(RequestDispatcher.FORWARD_MAPPING));
}
else
{
@ -68,6 +69,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getServletPath());
baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getPathInfo());
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getQueryString());
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getHttpServletMapping());
}
}
}

View File

@ -27,6 +27,7 @@ import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -108,7 +109,7 @@ public class Dispatcher implements RequestDispatcher
attr._servletPath = null; // set by ServletHandler
attr._pathInfo = _pathInContext;
attr._query = _uri.getQuery();
attr._mapping = null; //set by ServletHandler
if (attr._query != null)
baseRequest.mergeQueryParameters(baseRequest.getQueryString(), attr._query, false);
baseRequest.setAttributes(attr);
@ -147,6 +148,7 @@ public class Dispatcher implements RequestDispatcher
final String old_context_path = baseRequest.getContextPath();
final String old_servlet_path = baseRequest.getServletPath();
final String old_path_info = baseRequest.getPathInfo();
final HttpServletMapping old_mapping = baseRequest.getHttpServletMapping();
final MultiMap<String> old_query_params = baseRequest.getQueryParameters();
final Attributes old_attr = baseRequest.getAttributes();
@ -175,6 +177,7 @@ public class Dispatcher implements RequestDispatcher
attr._requestURI = (String)old_attr.getAttribute(FORWARD_REQUEST_URI);
attr._contextPath = (String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
attr._servletPath = (String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
attr._mapping = (HttpServletMapping)old_attr.getAttribute(FORWARD_MAPPING);
}
else
{
@ -183,6 +186,7 @@ public class Dispatcher implements RequestDispatcher
attr._requestURI = old_uri.getPath();
attr._contextPath = old_context_path;
attr._servletPath = old_servlet_path;
attr._mapping = old_mapping;
}
HttpURI uri = new HttpURI(old_uri.getScheme(), old_uri.getHost(), old_uri.getPort(),
@ -261,6 +265,7 @@ public class Dispatcher implements RequestDispatcher
String _servletPath;
String _pathInfo;
String _query;
HttpServletMapping _mapping;
ForwardAttributes(Attributes attributes)
{
@ -282,6 +287,8 @@ public class Dispatcher implements RequestDispatcher
return _contextPath;
if (key.equals(FORWARD_QUERY_STRING))
return _query;
if (key.equals(FORWARD_MAPPING))
return _mapping;
}
if (key.startsWith(__INCLUDE_PREFIX))
@ -312,6 +319,7 @@ public class Dispatcher implements RequestDispatcher
set.add(FORWARD_REQUEST_URI);
set.add(FORWARD_SERVLET_PATH);
set.add(FORWARD_CONTEXT_PATH);
set.add(FORWARD_MAPPING);
if (_query != null)
set.add(FORWARD_QUERY_STRING);
else
@ -336,7 +344,8 @@ public class Dispatcher implements RequestDispatcher
_contextPath = (String)value;
else if (key.equals(FORWARD_QUERY_STRING))
_query = (String)value;
else if (key.equals(FORWARD_MAPPING))
_mapping = (HttpServletMapping)value;
else if (value == null)
_attr.removeAttribute(key);
else
@ -376,6 +385,7 @@ public class Dispatcher implements RequestDispatcher
String _servletPath;
String _pathInfo;
String _query;
HttpServletMapping _mapping;
IncludeAttributes(Attributes attributes)
{
@ -397,6 +407,8 @@ public class Dispatcher implements RequestDispatcher
return _query;
if (key.equals(INCLUDE_REQUEST_URI))
return _requestURI;
if (key.equals(INCLUDE_MAPPING))
return _mapping;
}
else if (key.startsWith(__INCLUDE_PREFIX))
return null;
@ -425,6 +437,7 @@ public class Dispatcher implements RequestDispatcher
set.add(INCLUDE_REQUEST_URI);
set.add(INCLUDE_SERVLET_PATH);
set.add(INCLUDE_CONTEXT_PATH);
set.add(INCLUDE_MAPPING);
if (_query != null)
set.add(INCLUDE_QUERY_STRING);
else
@ -449,6 +462,8 @@ public class Dispatcher implements RequestDispatcher
_contextPath = (String)value;
else if (key.equals(INCLUDE_QUERY_STRING))
_query = (String)value;
else if (key.equals(INCLUDE_MAPPING))
_mapping = (HttpServletMapping)value;
else if (value == null)
_attr.removeAttribute(key);
else

View File

@ -25,6 +25,7 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpGenerator;
@ -33,6 +34,7 @@ import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpParser.RequestHandler;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.AbstractConnection;
@ -781,6 +783,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
case NEED_HEADER:
{
_header = _bufferPool.acquire(Math.min(_config.getResponseHeaderSize(), _config.getOutputBufferSize()), useDirectByteBuffers);
continue;
}
case HEADER_OVERFLOW:
{
int capacity = _header.capacity();
_bufferPool.release(_header);
if (capacity >= _config.getResponseHeaderSize())
throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Response header too large");
_header = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers);
continue;
}

View File

@ -27,6 +27,8 @@ import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
@ -366,6 +368,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
{
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A
};
private final String _nextProtocol;
private int _maxProxyHeader = 1024;
@ -545,7 +548,6 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
private void parseBodyAndUpgrade() throws IOException
{
// stop reading when bufferRemainingReserve bytes are remaining in the buffer
int nonProxyRemaining = _buffer.remaining() - _length;
if (LOG.isDebugEnabled())
LOG.debug("Proxy v2 parsing body, length = {}, buffer = {}", _length, BufferUtil.toHexSummary(_buffer));
@ -609,14 +611,14 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
if (LOG.isDebugEnabled())
LOG.debug(String.format("Proxy v2 T=%x L=%d V=%s for %s", type, length, TypeUtil.toHexString(value), this));
switch (type)
{
case 0x20: // PP2_TYPE_SSL
// PP2_TYPE_NOOP is only used for byte alignment, skip them.
if (type != ProxyEndPoint.PP2_TYPE_NOOP)
proxyEndPoint.putTLV(type, value);
if (type == ProxyEndPoint.PP2_TYPE_SSL)
{
int client = value[0] & 0xFF;
switch (client)
{
case 0x01: // PP2_CLIENT_SSL
if (client == ProxyEndPoint.PP2_TYPE_SSL_PP2_CLIENT_SSL)
{
int i = 5; // Index of the first sub_tlv, after verify.
while (i < length)
@ -626,36 +628,13 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
byte[] subValue = new byte[subLength];
System.arraycopy(value, i, subValue, 0, subLength);
i += subLength;
switch (subType)
if (subType == ProxyEndPoint.PP2_SUBTYPE_SSL_VERSION)
{
case 0x21: // PP2_SUBTYPE_SSL_VERSION
String tlsVersion = new String(subValue, StandardCharsets.US_ASCII);
proxyEndPoint.setAttribute(TLS_VERSION, tlsVersion);
break;
case 0x22: // PP2_SUBTYPE_SSL_CN
case 0x23: // PP2_SUBTYPE_SSL_CIPHER
case 0x24: // PP2_SUBTYPE_SSL_SIG_ALG
case 0x25: // PP2_SUBTYPE_SSL_KEY_ALG
default:
break;
}
}
break;
}
case 0x02: // PP2_CLIENT_CERT_CONN
case 0x04: // PP2_CLIENT_CERT_SESS
default:
break;
}
break;
}
case 0x01: // PP2_TYPE_ALPN
case 0x02: // PP2_TYPE_AUTHORITY
case 0x03: // PP2_TYPE_CRC32C
case 0x04: // PP2_TYPE_NOOP
case 0x30: // PP2_TYPE_NETNS
default:
break;
}
}
@ -751,71 +730,106 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
}
}
public static class ProxyEndPoint extends AttributesMap implements EndPoint
public static class ProxyEndPoint extends AttributesMap implements EndPoint, EndPoint.Wrapper
{
private final EndPoint _endp;
private static final int PP2_TYPE_NOOP = 0x04;
private static final int PP2_TYPE_SSL = 0x20;
private static final int PP2_TYPE_SSL_PP2_CLIENT_SSL = 0x01;
private static final int PP2_SUBTYPE_SSL_VERSION = 0x21;
private final EndPoint _endPoint;
private final InetSocketAddress _remote;
private final InetSocketAddress _local;
private Map<Integer, byte[]> _tlvs;
public ProxyEndPoint(EndPoint endp, InetSocketAddress remote, InetSocketAddress local)
public ProxyEndPoint(EndPoint endPoint, InetSocketAddress remote, InetSocketAddress local)
{
_endp = endp;
_endPoint = endPoint;
_remote = remote;
_local = local;
}
public EndPoint unwrap()
{
return _endPoint;
}
/**
* <p>Sets a TLV vector, see section 2.2.7 of the PROXY protocol specification.</p>
*
* @param type the TLV type
* @param value the TLV value
*/
private void putTLV(int type, byte[] value)
{
if (_tlvs == null)
_tlvs = new HashMap<>();
_tlvs.put(type, value);
}
/**
* <p>Gets a TLV vector, see section 2.2.7 of the PROXY protocol specification.</p>
*
* @param type the TLV type
* @return the TLV value or null if not present.
*/
public byte[] getTLV(int type)
{
return _tlvs != null ? _tlvs.get(type) : null;
}
@Override
public void close(Throwable cause)
{
_endp.close(cause);
_endPoint.close(cause);
}
@Override
public int fill(ByteBuffer buffer) throws IOException
{
return _endp.fill(buffer);
return _endPoint.fill(buffer);
}
@Override
public void fillInterested(Callback callback) throws ReadPendingException
{
_endp.fillInterested(callback);
_endPoint.fillInterested(callback);
}
@Override
public boolean flush(ByteBuffer... buffer) throws IOException
{
return _endp.flush(buffer);
return _endPoint.flush(buffer);
}
@Override
public Connection getConnection()
{
return _endp.getConnection();
return _endPoint.getConnection();
}
@Override
public void setConnection(Connection connection)
{
_endp.setConnection(connection);
_endPoint.setConnection(connection);
}
@Override
public long getCreatedTimeStamp()
{
return _endp.getCreatedTimeStamp();
return _endPoint.getCreatedTimeStamp();
}
@Override
public long getIdleTimeout()
{
return _endp.getIdleTimeout();
return _endPoint.getIdleTimeout();
}
@Override
public void setIdleTimeout(long idleTimeout)
{
_endp.setIdleTimeout(idleTimeout);
_endPoint.setIdleTimeout(idleTimeout);
}
@Override
@ -833,49 +847,49 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
@Override
public Object getTransport()
{
return _endp.getTransport();
return _endPoint.getTransport();
}
@Override
public boolean isFillInterested()
{
return _endp.isFillInterested();
return _endPoint.isFillInterested();
}
@Override
public boolean isInputShutdown()
{
return _endp.isInputShutdown();
return _endPoint.isInputShutdown();
}
@Override
public boolean isOpen()
{
return _endp.isOpen();
return _endPoint.isOpen();
}
@Override
public boolean isOutputShutdown()
{
return _endp.isOutputShutdown();
return _endPoint.isOutputShutdown();
}
@Override
public void onClose(Throwable cause)
{
_endp.onClose(cause);
_endPoint.onClose(cause);
}
@Override
public void onOpen()
{
_endp.onOpen();
_endPoint.onOpen();
}
@Override
public void shutdownOutput()
{
_endp.shutdownOutput();
_endPoint.shutdownOutput();
}
@Override
@ -886,25 +900,25 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
hashCode(),
_remote,
_local,
_endp);
_endPoint);
}
@Override
public boolean tryFillInterested(Callback callback)
{
return _endp.tryFillInterested(callback);
return _endPoint.tryFillInterested(callback);
}
@Override
public void upgrade(Connection newConnection)
{
_endp.upgrade(newConnection);
_endPoint.upgrade(newConnection);
}
@Override
public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
{
_endp.write(callback, buffers);
_endPoint.write(callback, buffers);
}
}
}

View File

@ -190,6 +190,85 @@ public class Request implements HttpServletRequest
return null;
}
public static HttpServletMapping getServletMapping(PathSpec pathSpec, String servletPath, String servletName)
{
final MappingMatch match;
final String mapping;
if (pathSpec instanceof ServletPathSpec)
{
switch (((ServletPathSpec)pathSpec).getGroup())
{
case ROOT:
match = MappingMatch.CONTEXT_ROOT;
mapping = "";
break;
case DEFAULT:
match = MappingMatch.DEFAULT;
mapping = "/";
break;
case EXACT:
match = MappingMatch.EXACT;
mapping = servletPath.startsWith("/") ? servletPath.substring(1) : servletPath;
break;
case SUFFIX_GLOB:
match = MappingMatch.EXTENSION;
int dot = servletPath.lastIndexOf('.');
mapping = servletPath.substring(0, dot);
break;
case PREFIX_GLOB:
match = MappingMatch.PATH;
mapping = servletPath;
break;
default:
match = null;
mapping = servletPath;
break;
}
}
else
{
match = null;
mapping = servletPath;
}
return new HttpServletMapping()
{
@Override
public String getMatchValue()
{
return (mapping == null ? "" : mapping);
}
@Override
public String getPattern()
{
if (pathSpec != null)
return pathSpec.getDeclaration();
return "";
}
@Override
public String getServletName()
{
return (servletName == null ? "" : servletName);
}
@Override
public MappingMatch getMappingMatch()
{
return match;
}
@Override
public String toString()
{
return "HttpServletMapping{matchValue=" + getMatchValue() +
", pattern=" + getPattern() + ", servletName=" + getServletName() +
", mappingMatch=" + getMappingMatch() + "}";
}
};
}
private final HttpChannel _channel;
private final List<ServletRequestAttributeListener> _requestAttributeListeners = new ArrayList<>();
private final HttpInput _input;
@ -2354,73 +2433,6 @@ public class Request implements HttpServletRequest
@Override
public HttpServletMapping getHttpServletMapping()
{
final PathSpec pathSpec = _pathSpec;
final MappingMatch match;
final String mapping;
if (pathSpec instanceof ServletPathSpec)
{
switch (((ServletPathSpec)pathSpec).getGroup())
{
case ROOT:
match = MappingMatch.CONTEXT_ROOT;
mapping = "";
break;
case DEFAULT:
match = MappingMatch.DEFAULT;
mapping = "/";
break;
case EXACT:
match = MappingMatch.EXACT;
mapping = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
break;
case SUFFIX_GLOB:
match = MappingMatch.EXTENSION;
int dot = _servletPath.lastIndexOf('.');
mapping = _servletPath.substring(0, dot);
break;
case PREFIX_GLOB:
match = MappingMatch.PATH;
mapping = _servletPath;
break;
default:
match = null;
mapping = _servletPath;
break;
}
}
else
{
match = null;
mapping = _servletPath;
}
return new HttpServletMapping()
{
@Override
public String getMatchValue()
{
return mapping;
}
@Override
public String getPattern()
{
if (pathSpec != null)
pathSpec.toString();
return null;
}
@Override
public String getServletName()
{
return Request.this.getServletName();
}
@Override
public MappingMatch getMappingMatch()
{
return match;
}
};
return Request.getServletMapping(_pathSpec, _servletPath, getServletName());
}
}

View File

@ -52,6 +52,7 @@ public class AsyncDelayHandler extends HandlerWrapper
Object asyncQueryString = null;
Object asyncRequestUri = null;
Object asyncServletPath = null;
Object asyncHttpServletMapping = null;
// Is this request a restarted one?
boolean restart = false;
@ -72,6 +73,8 @@ public class AsyncDelayHandler extends HandlerWrapper
baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, null);
asyncServletPath = baseRequest.getAttribute(AsyncContext.ASYNC_SERVLET_PATH);
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, null);
asyncHttpServletMapping = baseRequest.getAttribute(AsyncContext.ASYNC_MAPPING);
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, null);
}
// Should we handle this request now?
@ -101,6 +104,7 @@ public class AsyncDelayHandler extends HandlerWrapper
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, asyncQueryString);
baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, asyncRequestUri);
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, asyncServletPath);
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, asyncHttpServletMapping);
}
// signal the request is leaving the handler

View File

@ -199,7 +199,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private final List<EventListener> _programmaticListeners = new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _servletContextListeners = new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _destroySerletContextListeners = new ArrayList<>();
private final List<ServletContextListener> _destroyServletContextListeners = new ArrayList<>();
private final List<ServletContextAttributeListener> _servletContextAttributeListeners = new CopyOnWriteArrayList<>();
private final List<ServletRequestListener> _servletRequestListeners = new CopyOnWriteArrayList<>();
private final List<ServletRequestAttributeListener> _servletRequestAttributeListeners = new CopyOnWriteArrayList<>();
@ -646,7 +646,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_contextListeners.remove(listener);
if (listener instanceof ServletContextListener)
{
_servletContextListeners.remove(listener);
_destroyServletContextListeners.remove(listener);
}
if (listener instanceof ServletContextAttributeListener)
_servletContextAttributeListeners.remove(listener);
@ -838,14 +841,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
super.doStart();
// Call context listeners
_destroySerletContextListeners.clear();
_destroyServletContextListeners.clear();
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
{
callContextInitialized(listener, event);
_destroySerletContextListeners.add(listener);
_destroyServletContextListeners.add(listener);
}
}
}
@ -854,9 +857,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
// Call the context listeners
ServletContextEvent event = new ServletContextEvent(_scontext);
Collections.reverse(_destroySerletContextListeners);
Collections.reverse(_destroyServletContextListeners);
MultiException ex = new MultiException();
for (ServletContextListener listener : _destroySerletContextListeners)
for (ServletContextListener listener : _destroyServletContextListeners)
{
try
{

View File

@ -26,16 +26,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import static org.eclipse.jetty.server.handler.InetAccessSet.AccessTuple;
import static org.eclipse.jetty.server.handler.InetAccessSet.PatternTuple;
/**
* InetAddress Access Handler
* <p>
@ -43,18 +47,13 @@ import org.eclipse.jetty.util.log.Logger;
* provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This
* handler uses the real internet address of the connection, not one reported in
* the forwarded for headers, as this cannot be as easily forged.
* <p>
* Additionally, there may be times when you want to only apply this handler to
* a subset of your connectors. In this situation you can use
* <b>connectorNames</b> to specify the connector names that you want this IP
* access filter to apply to.
* </p>
*/
public class InetAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
private final IncludeExcludeSet<String, InetAddress> _addrs = new IncludeExcludeSet<>(InetAddressSet.class);
private final IncludeExclude<String> _names = new IncludeExclude<>();
private final IncludeExcludeSet<PatternTuple, AccessTuple> _set = new IncludeExcludeSet<>(InetAccessSet.class);
/**
* Clears all the includes, excludes, included connector names and excluded
@ -62,92 +61,150 @@ public class InetAccessHandler extends HandlerWrapper
*/
public void clear()
{
_addrs.clear();
_names.clear();
_set.clear();
}
/**
* Includes an InetAddress pattern
* Includes an InetAccess pattern with an optional connector name, address and URI mapping.
*
* @param pattern InetAddress pattern to include
* <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
*
* @param pattern InetAccess pattern to include
* @see InetAddressSet
*/
public void include(String pattern)
{
_addrs.include(pattern);
_set.include(PatternTuple.from(pattern));
}
/**
* Includes InetAddress patterns
* Includes InetAccess patterns
*
* @param patterns InetAddress patterns to include
* @see InetAddressSet
*/
public void include(String... patterns)
{
_addrs.include(patterns);
for (String pattern : patterns)
include(pattern);
}
/**
* Excludes an InetAddress pattern
* Includes an InetAccess entry.
* @param connectorName optional name of a connector to include.
* @param addressPattern optional InetAddress pattern to include.
* @param pathSpec optional pathSpec to include.
*/
public void include(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.include(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
}
/**
* Excludes an InetAccess entry pattern with an optional connector name, address and URI mapping.
*
* <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
*
* @param pattern InetAddress pattern to exclude
* @see InetAddressSet
*/
public void exclude(String pattern)
{
_addrs.exclude(pattern);
_set.exclude(PatternTuple.from(pattern));
}
/**
* Excludes InetAddress patterns
* Excludes InetAccess patterns
*
* @param patterns InetAddress patterns to exclude
* @see InetAddressSet
*/
public void exclude(String... patterns)
{
_addrs.exclude(patterns);
for (String pattern : patterns)
exclude(pattern);
}
/**
* Excludes an InetAccess entry.
* @param connectorName optional name of a connector to exclude.
* @param addressPattern optional InetAddress pattern to exclude.
* @param pathSpec optional pathSpec to exclude.
*/
public void exclude(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.exclude(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
}
/**
* Includes a connector name.
*
* @param name Connector name to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnector(String name)
{
_names.include(name);
throw new UnsupportedOperationException();
}
/**
* Excludes a connector name.
*
* @param name Connector name to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void excludeConnector(String name)
{
_names.exclude(name);
_set.exclude(new PatternTuple(name, null, null));
}
/**
* Includes connector names.
*
* @param names Connector names to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnectors(String... names)
{
_names.include(names);
throw new UnsupportedOperationException();
}
/**
* Excludes connector names.
*
* @param names Connector names to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void excludeConnectors(String... names)
{
_names.exclude(names);
for (String name : names)
excludeConnector(name);
}
/**
@ -187,26 +244,16 @@ public class InetAccessHandler extends HandlerWrapper
*/
protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request)
{
String name = baseRequest.getHttpChannel().getConnector().getName();
boolean filterAppliesToConnector = _names.test(name);
boolean allowedByAddr = _addrs.test(addr);
if (LOG.isDebugEnabled())
{
LOG.debug("name = {}/{} addr={}/{} appliesToConnector={} allowedByAddr={}",
name, _names, addr, _addrs, filterAppliesToConnector, allowedByAddr);
}
if (!filterAppliesToConnector)
return true;
return allowedByAddr;
String connectorName = baseRequest.getHttpChannel().getConnector().getName();
String path = baseRequest.getMetaData().getURI().getDecodedPath();
return _set.test(new AccessTuple(connectorName, addr, path));
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent,
new DumpableCollection("included", _addrs.getIncluded()),
new DumpableCollection("excluded", _addrs.getExcluded()),
new DumpableCollection("includedConnector", _names.getIncluded()),
new DumpableCollection("excludedConnector", _names.getExcluded()));
new DumpableCollection("included", _set.getIncluded()),
new DumpableCollection("excluded", _set.getExcluded()));
}
}

View File

@ -0,0 +1,158 @@
//
// ========================================================================
// 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.server.handler;
import java.net.InetAddress;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.StringUtil;
public class InetAccessSet extends AbstractSet<InetAccessSet.PatternTuple> implements Set<InetAccessSet.PatternTuple>, Predicate<InetAccessSet.AccessTuple>
{
private ArrayList<PatternTuple> tuples = new ArrayList<>();
@Override
public boolean add(PatternTuple storageTuple)
{
return tuples.add(storageTuple);
}
@Override
public boolean remove(Object o)
{
return tuples.remove(o);
}
@Override
public Iterator<PatternTuple> iterator()
{
return tuples.iterator();
}
@Override
public int size()
{
return tuples.size();
}
@Override
public boolean test(AccessTuple entry)
{
if (entry == null)
return false;
for (PatternTuple tuple : tuples)
{
if (tuple.test(entry))
return true;
}
return false;
}
static class PatternTuple implements Predicate<AccessTuple>
{
private final String connector;
private final InetAddressPattern address;
private final PathSpec pathSpec;
public static PatternTuple from(String pattern)
{
String path = null;
int pathIndex = pattern.indexOf('|');
if (pathIndex >= 0)
path = pattern.substring(pathIndex + 1);
String connector = null;
int connectorIndex = pattern.indexOf('@');
if (connectorIndex >= 0)
connector = pattern.substring(0, connectorIndex);
String addr = null;
int addrStart = (connectorIndex < 0) ? 0 : connectorIndex + 1;
int addrEnd = (pathIndex < 0) ? pattern.length() : pathIndex;
if (addrStart != addrEnd)
addr = pattern.substring(addrStart, addrEnd);
return new PatternTuple(connector, InetAddressPattern.from(addr),
StringUtil.isEmpty(path) ? null : new ServletPathSpec(path));
}
public PatternTuple(String connector, InetAddressPattern address, PathSpec pathSpec)
{
this.connector = connector;
this.address = address;
this.pathSpec = pathSpec;
}
@Override
public boolean test(AccessTuple entry)
{
// Match for connector.
if ((connector != null) && !connector.equals(entry.getConnector()))
return false;
// If we have a path we must must be at this path to match for an address.
if ((pathSpec != null) && !pathSpec.matches(entry.getPath()))
return false;
// Match for InetAddress.
if ((address != null) && !address.test(entry.getAddress()))
return false;
return true;
}
}
static class AccessTuple
{
private final String connector;
private final InetAddress address;
private final String path;
public AccessTuple(String connector, InetAddress address, String path)
{
this.connector = connector;
this.address = address;
this.path = path;
}
public String getConnector()
{
return connector;
}
public InetAddress getAddress()
{
return address;
}
public String getPath()
{
return path;
}
}
}

View File

@ -155,8 +155,9 @@ public class HouseKeeper extends AbstractLifeCycle
LOG.info("{} Stopped scavenging", _sessionIdManager.getWorkerName());
}
_task = null;
if (_ownScheduler)
if (_ownScheduler && _scheduler != null)
{
_ownScheduler = false;
_scheduler.stop();
_scheduler = null;
}

View File

@ -30,6 +30,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@ -1266,6 +1267,48 @@ public class HttpConnectionTest
}
}
@Test
public void testAllowedLargeResponse() throws Exception
{
connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setResponseHeaderSize(16 * 1024);
connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setOutputBufferSize(8 * 1024);
byte[] bytes = new byte[12 * 1024];
Arrays.fill(bytes, (byte)'X');
final String longstr = "thisisastringthatshouldreachover12kbytes-" + new String(bytes, StandardCharsets.ISO_8859_1) + "_Z_";
final CountDownLatch checkError = new CountDownLatch(1);
server.stop();
server.setHandler(new AbstractHandler()
{
@SuppressWarnings("unused")
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setHeader(HttpHeader.CONTENT_TYPE.toString(), MimeTypes.Type.TEXT_HTML.toString());
response.setHeader("LongStr", longstr);
PrintWriter writer = response.getWriter();
writer.write("<html><h1>FOO</h1></html>");
writer.flush();
if (writer.checkError())
checkError.countDown();
response.flushBuffer();
}
});
server.start();
String response = null;
response = connector.getResponse("GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n"
);
checkContains(response, 0, "HTTP/1.1 200");
checkContains(response, 0, "LongStr: thisisastringthatshouldreachover12kbytes");
checkContains(response, 0, "XXX_Z_");
assertThat(checkError.getCount(), is(1L));
}
@Test
public void testAsterisk() throws Exception
{

View File

@ -25,10 +25,13 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ProxyConnectionFactory.ProxyEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.AfterEach;
@ -118,15 +121,29 @@ public class ProxyProtocolTest
{
final String remoteAddr = "192.168.0.1";
final int remotePort = 12345;
final byte[] customE0 = new byte[] {1, 2};
final byte[] customE1 = new byte[] {-1, -1, -1};
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (remoteAddr.equals(request.getRemoteAddr()) &&
if (validateEndPoint(baseRequest) &&
remoteAddr.equals(request.getRemoteAddr()) &&
remotePort == request.getRemotePort())
baseRequest.setHandled(true);
}
private boolean validateEndPoint(Request request)
{
HttpConnection con = (HttpConnection)request.getAttribute(HttpConnection.class.getName());
EndPoint endPoint = con.getEndPoint();
ProxyEndPoint proxyEndPoint = (ProxyEndPoint)endPoint;
return Arrays.equals(customE0, proxyEndPoint.getTLV(0xE0)) &&
Arrays.equals(customE1, proxyEndPoint.getTLV(0xE1)) &&
proxyEndPoint.getTLV(0xE2) == null;
}
});
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
@ -141,8 +158,8 @@ public class ProxyProtocolTest
// 0x1 : AF_INET 0x1 : STREAM. Address length is 2*4 + 2*2 = 12 bytes.
"11" +
// length of remaining header (4+4+2+2+6+3 = 21)
"0015" +
// length of remaining header (4+4+2+2+3+6+5+6 = 32)
"0020" +
// uint32_t src_addr; uint32_t dst_addr; uint16_t src_port; uint16_t dst_port;
"C0A80001" +
@ -154,7 +171,13 @@ public class ProxyProtocolTest
"040000" +
// NOOP value ABCDEF
"040003ABCDEF";
"040003ABCDEF" +
// Custom 0xEO {0x01,0x02}
"E000020102" +
// Custom 0xE1 {0xFF,0xFF,0xFF}
"E10003FFFFFF";
String request1 =
"GET /1 HTTP/1.1\r\n" +
@ -193,4 +216,5 @@ public class ProxyProtocolTest
}
}
}
}

View File

@ -33,6 +33,7 @@ import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@ -42,6 +43,7 @@ import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@ -49,6 +51,7 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.http.tools.HttpTester;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
@ -1410,6 +1413,69 @@ public class RequestTest
assertThat(response, containsString("Hello World"));
}
@Test
public void testHttpServletMapping() throws Exception
{
String request = "GET / HTTP/1.1\n" +
"Host: whatever\n" +
"Connection: close\n" +
"\n";
_server.stop();
PathMappingHandler handler = new PathMappingHandler(null, null, null);
_server.setHandler(handler);
_server.start();
String response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=, mappingMatch=null}"));
_server.stop();
ServletPathSpec spec = new ServletPathSpec("");
handler = new PathMappingHandler(spec, spec.getPathMatch("foo"), "Something");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=Something, mappingMatch=CONTEXT_ROOT}"));
_server.stop();
spec = new ServletPathSpec("/");
handler = new PathMappingHandler(spec, "", "Default");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/, pattern=/, servletName=Default, mappingMatch=DEFAULT}"));
_server.stop();
spec = new ServletPathSpec("/foo/*");
handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar"), "BarServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo, pattern=/foo/*, servletName=BarServlet, mappingMatch=PATH}"));
_server.stop();
spec = new ServletPathSpec("*.jsp");
handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar.jsp"), "JspServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo/bar, pattern=*.jsp, servletName=JspServlet, mappingMatch=EXTENSION}"));
_server.stop();
spec = new ServletPathSpec("/catalog");
handler = new PathMappingHandler(spec, spec.getPathMatch("/catalog"), "CatalogServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=catalog, pattern=/catalog, servletName=CatalogServlet, mappingMatch=EXACT}"));
_server.stop();
}
@Test
public void testCookies() throws Exception
{
@ -1915,4 +1981,68 @@ public class RequestTest
}
}
}
private class TestUserIdentityScope implements UserIdentity.Scope
{
private ContextHandler _handler;
private String _contextPath;
private String _name;
public TestUserIdentityScope(ContextHandler handler, String contextPath, String name)
{
_handler = handler;
_contextPath = contextPath;
_name = name;
}
@Override
public ContextHandler getContextHandler()
{
return _handler;
}
@Override
public String getContextPath()
{
return _contextPath;
}
@Override
public String getName()
{
return _name;
}
@Override
public Map<String, String> getRoleRefMap()
{
return null;
}
}
private class PathMappingHandler extends AbstractHandler
{
private ServletPathSpec _spec;
private String _servletPath;
private String _servletName;
public PathMappingHandler(ServletPathSpec spec, String servletPath, String servletName)
{
_spec = spec;
_servletPath = servletPath;
_servletName = servletName;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
baseRequest.setServletPath(_servletPath);
baseRequest.setPathSpec(_spec);
if (_servletName != null)
baseRequest.setUserIdentityScope(new TestUserIdentityScope(null, null, _servletName));
HttpServletMapping mapping = baseRequest.getHttpServletMapping();
response.getWriter().println(mapping);
}
}
}

View File

@ -25,7 +25,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -36,6 +35,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
@ -67,7 +67,6 @@ public class InetAccessHandlerTest
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.OK_200);
@ -85,7 +84,7 @@ public class InetAccessHandlerTest
@ParameterizedTest
@MethodSource("data")
public void testHandler(String include, String exclude, String includeConnectors, String excludeConnectors, String code)
public void testHandler(String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code)
throws Exception
{
_handler.clear();
@ -107,14 +106,14 @@ public class InetAccessHandlerTest
{
if (inc.length() > 0)
{
_handler.includeConnector(inc);
_handler.include(inc + "@");
}
}
for (String exc : excludeConnectors.split(";", -1))
{
if (exc.length() > 0)
{
_handler.excludeConnector(exc);
_handler.exclude(exc + "@");
}
}
@ -127,11 +126,11 @@ public class InetAccessHandlerTest
}
}
testConnector(_connector1.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
testConnector(_connector2.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
testConnector(_connector1.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
testConnector(_connector2.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
}
private void testConnector(int port, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException
private void testConnector(int port, String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException
{
try (Socket socket = new Socket("127.0.0.1", port);)
{
@ -139,7 +138,7 @@ public class InetAccessHandlerTest
HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
request.setURI("/path");
request.setURI(StringUtil.isEmpty(path) ? "/" : path);
request.setHeader("Host", "127.0.0.1");
request.setVersion(HttpVersion.HTTP_1_0);
@ -166,60 +165,81 @@ public class InetAccessHandlerTest
Object[][] data = new Object[][]
{
// Empty lists 1
{"", "", "", "", "200;200"},
{"", "", "", "", "", "200;200"},
// test simple filters
{"127.0.0.1", "", "", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"192.0.0.1", "", "", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "", "403;403"},
{"", "127.0.0.1", "", "", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"", "192.0.0.1", "", "", "", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test includeConnector
{"127.0.0.1", "", "http_connector1", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"192.0.0.1", "", "http_connector1", "", "403;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector1", "", "403;200"},
{"192.0.0.1", "", "http_connector2", "", "200;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector2", "", "200;403"},
{"", "127.0.0.1", "", "http_connector1", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"", "192.0.0.1", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1", "", "http_connector2", "", "403;200"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector2", "", "403;200"},
// test includeConnector names where none of them match
{"127.0.0.1", "", "nothttp", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
{"192.0.0.1", "", "nothttp", "", "200;200"},
{"192.0.0.1-192.0.0.254", "", "nothttp", "", "200;200"},
{"", "127.0.0.1", "", "nothttp", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
{"", "192.0.0.1", "", "nothttp", "", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "nothttp", "", "403;403"},
// text excludeConnector
{"127.0.0.1", "", "", "http_connector1", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1", "200;200"},
{"192.0.0.1", "", "", "http_connector1", "200;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1", "200;403"},
{"192.0.0.1", "", "", "http_connector2", "403;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;200"},
{"", "127.0.0.1", "", "", "http_connector1", "403;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1", "403;200"},
{"", "192.0.0.1", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1", "", "", "http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;403"},
// test excludeConnector where none of them match.
{"127.0.0.1", "", "", "nothttp", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
{"192.0.0.1", "", "", "nothttp", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
{"", "127.0.0.1", "", "", "nothttp", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
{"", "192.0.0.1", "", "", "nothttp", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
// both connectors are excluded
{"127.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
{"", "127.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
// both connectors are included
{"127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"192.0.0.1", "", "http_connector1;http_connector2", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "403;403"},
{"", "127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"", "192.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"", "", "127.0.0.1", "http_connector1;http_connector2", "", "403;403"},
// exclude takes precedence over include
{"127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"}
{"", "127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
// Test path specific include/exclude.
{"/testPath", "", "", "http_connector1", "", "200;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/notTestPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.0.13|/testPath;127.0.0.1|/testPath", "", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/a/b", "", "127.0.0.1|/a/*", "", "", "403;403"},
{"/b/a", "", "127.0.0.1|/a/*", "", "", "200;200"},
{"/org/eclipse/jetty/test.html", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "200;200"},
{"/org/eclipse/jetty/test.xml", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/org/eclipse/jetty/test.pdf", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/a/test.html", "", "127.0.0.1|*.html;127.0.0.10|/a/*", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "200;200"},
{"/foo/bar/test.html", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/bar/*", "127.0.0.1|/foo/*", "", "", "403;403"},
};
return Arrays.asList(data).stream().map(Arguments::of);
return Arrays.stream(data).map(Arguments::of);
}
}

View File

@ -19,10 +19,12 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.UnavailableException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@ -177,6 +179,40 @@ public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpabl
return _instance;
}
protected synchronized T createInstance() throws Exception
{
ServletContext ctx = getServletContext();
if (ctx == null)
return getHeldClass().getDeclaredConstructor().newInstance();
if (ServletContextHandler.Context.class.isAssignableFrom(ctx.getClass()))
return ((ServletContextHandler.Context)ctx).createInstance(this);
return null;
}
public ServletContext getServletContext()
{
ServletContext scontext = null;
//try the ServletHandler first
if (getServletHandler() != null)
scontext = getServletHandler().getServletContext();
if (scontext != null)
return scontext;
//try the ContextHandler next
Context ctx = ContextHandler.getCurrentContext();
if (ctx != null)
{
ContextHandler contextHandler = ctx.getContextHandler();
if (contextHandler != null)
return contextHandler.getServletContext();
}
return null;
}
/**
* @return True if this holder was created for a specific instance.
*/

View File

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
@ -113,10 +114,7 @@ public class FilterHolder extends Holder<Filter>
{
try
{
ServletContext context = getServletHandler().getServletContext();
_filter = (context != null)
? context.createFilter(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
_filter = createInstance();
}
catch (ServletException ex)
{
@ -135,6 +133,19 @@ public class FilterHolder extends Holder<Filter>
}
}
@Override
protected synchronized Filter createInstance() throws Exception
{
Filter filter = super.createInstance();
if (filter == null)
{
ServletContext context = getServletContext();
if (context != null)
filter = context.createFilter(getHeldClass());
}
return filter;
}
@Override
public void doStop()
throws Exception

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.servlet;
import java.util.EventListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -88,10 +89,7 @@ public class ListenerHolder extends BaseHolder<EventListener>
//create an instance of the listener and decorate it
try
{
ServletContext context = contextHandler.getServletContext();
_listener = (context != null)
? context.createListener(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
_listener = createInstance();
}
catch (ServletException ex)
{
@ -107,6 +105,20 @@ public class ListenerHolder extends BaseHolder<EventListener>
}
}
@Override
protected synchronized EventListener createInstance() throws Exception
{
EventListener listener = super.createInstance();
if (listener == null)
{
ServletContext ctx = getServletContext();
if (ctx != null)
listener = ctx.createListener(getHeldClass());
}
return listener;
}
@Override
public void doStop() throws Exception
{

View File

@ -1261,6 +1261,21 @@ public class ServletContextHandler extends ContextHandler
return _objFactory.decorate(super.createInstance(clazz));
}
public <T> T createInstance(BaseHolder<T> holder) throws ServletException
{
try
{
//set a thread local
DecoratedObjectFactory.associateInfo(holder);
return createInstance(holder.getHeldClass());
}
finally
{
//unset the thread local
DecoratedObjectFactory.disassociateInfo();
}
}
public <T extends Filter> void destroyFilter(T f)
{
_objFactory.destroy(f);

View File

@ -449,6 +449,7 @@ public class ServletHandler extends ScopedHandler
{
baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPath);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, pathInfo);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, Request.getServletMapping(pathSpec, servletPath, servletHolder.getName()));
}
else
{

View File

@ -33,6 +33,7 @@ import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
import javax.servlet.GenericServlet;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
@ -1110,30 +1111,22 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
* @throws NoSuchMethodException if creating new instance resulted in error
* @throws InvocationTargetException If creating new instance throws an exception
*/
protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException,
NoSuchMethodException, InvocationTargetException
protected Servlet newInstance() throws Exception
{
try
{
ServletContext ctx = getServletHandler().getServletContext();
if (ctx != null)
return ctx.createServlet(getHeldClass());
return getHeldClass().getDeclaredConstructor().newInstance();
return createInstance();
}
}
catch (ServletException ex)
@Override
protected synchronized Servlet createInstance() throws Exception
{
Throwable cause = ex.getRootCause();
if (cause instanceof InstantiationException)
throw (InstantiationException)cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException)cause;
if (cause instanceof NoSuchMethodException)
throw (NoSuchMethodException)cause;
if (cause instanceof InvocationTargetException)
throw (InvocationTargetException)cause;
throw ex;
Servlet servlet = super.createInstance();
if (servlet == null)
{
ServletContext ctx = getServletContext();
if (ctx != null)
servlet = ctx.createServlet(getHeldClass());
}
return servlet;
}
@Override

View File

@ -33,7 +33,6 @@ public class Source
EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION
}
;
public Origin _origin;
public String _resource;

View File

@ -27,6 +27,7 @@ import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
@ -224,6 +225,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -257,6 +262,10 @@ public class AsyncContextTest
assertThat("async run attr query string", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("async run context path", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/test/hello%2fthere"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:/test"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/test/*"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:PATH"));
}
@Test
@ -296,6 +305,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/path%20with%20spaces/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:path with spaces/servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/path with spaces/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -338,6 +351,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -687,6 +704,14 @@ public class AsyncContextTest
_context.getResponse().getOutputStream().print("async:run:attr:queryString:" + req.getAttribute(AsyncContext.ASYNC_QUERY_STRING) + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:contextPath:" + req.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH) + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:requestURI:" + req.getAttribute(AsyncContext.ASYNC_REQUEST_URI) + "\n");
HttpServletMapping mapping = (HttpServletMapping)req.getAttribute(AsyncContext.ASYNC_MAPPING);
if (mapping != null)
{
_context.getResponse().getOutputStream().print("async:run:attr:mapping:matchValue:" + mapping.getMatchValue() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:pattern:" + mapping.getPattern() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:servletName:" + mapping.getServletName() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:mappingMatch:" + mapping.getMappingMatch() + "\n");
}
}
catch (IOException e)
{

View File

@ -41,6 +41,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@ -728,9 +729,12 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=assertforward&do=more&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
List<String> expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH,
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List<String> requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -760,9 +764,12 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
List<String> expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH,
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List<String> requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -806,9 +813,12 @@ public class DispatcherTest
assertEquals("/AssertIncludeServlet", request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals("do=end&do=the", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING);
assertNotNull(incMapping);
assertEquals("/AssertIncludeServlet", incMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.INCLUDE_REQUEST_URI, Dispatcher.INCLUDE_CONTEXT_PATH,
Dispatcher.INCLUDE_SERVLET_PATH, Dispatcher.INCLUDE_QUERY_STRING);
Dispatcher.INCLUDE_SERVLET_PATH, Dispatcher.INCLUDE_QUERY_STRING, Dispatcher.INCLUDE_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -836,17 +846,23 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals("/forwardpath", request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=include", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
assertEquals("/context/AssertForwardIncludeServlet/assertpath", request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI));
assertEquals("/context", request.getAttribute(Dispatcher.INCLUDE_CONTEXT_PATH));
assertEquals("/AssertForwardIncludeServlet", request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals("/assertpath", request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals("do=end", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING);
assertNotNull(incMapping);
assertEquals("/AssertForwardIncludeServlet", incMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING,
Dispatcher.INCLUDE_REQUEST_URI, Dispatcher.INCLUDE_CONTEXT_PATH, Dispatcher.INCLUDE_SERVLET_PATH,
Dispatcher.INCLUDE_PATH_INFO, Dispatcher.INCLUDE_QUERY_STRING);
Dispatcher.INCLUDE_PATH_INFO, Dispatcher.INCLUDE_QUERY_STRING, Dispatcher.INCLUDE_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -874,15 +890,19 @@ public class DispatcherTest
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_MAPPING));
assertEquals("/context/IncludeServlet/includepath", request.getAttribute(Dispatcher.FORWARD_REQUEST_URI));
assertEquals("/context", request.getAttribute(Dispatcher.FORWARD_CONTEXT_PATH));
assertEquals("/IncludeServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals("/includepath", request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=forward", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/IncludeServlet", fwdMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));

View File

@ -27,10 +27,12 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/**
@ -38,6 +40,22 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
public class FilterHolderTest
{
public static class DummyFilter implements Filter
{
public DummyFilter()
{
}
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
}
}
@Test
public void testInitialize()
@ -95,4 +113,28 @@ public class FilterHolderTest
fh.initialize();
assertEquals(2, counter.get());
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
FilterHolder holder = new FilterHolder();
holder.setName("foo");
holder.setHeldClass(DummyFilter.class);
Filter filter = holder.createInstance();
assertNotNull(filter);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addFilter(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getFilter());
}
}
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// 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.servlet;
import java.util.EventListener;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ListenerHolderTest
{
public static class DummyListener implements EventListener
{
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
ListenerHolder holder = new ListenerHolder();
holder.setHeldClass(DummyListener.class);
EventListener listener = holder.createInstance();
assertNotNull(listener);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addListener(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getListener());
}
}
}

View File

@ -20,10 +20,13 @@ package org.eclipse.jetty.servlet;
import java.util.Collections;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletRegistration;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.log.StacklessLogging;
@ -33,6 +36,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -114,6 +118,30 @@ public class ServletHolderTest
assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("a/b/c/blah.jsp"));
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ContextHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
ServletHolder holder = new ServletHolder();
holder.setName("foo");
holder.setHeldClass(FakeServlet.class);
Servlet servlet = holder.createInstance();
assertNotNull(servlet);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addServlet(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getServlet());
}
}
@Test
public void testNoClassName() throws Exception
{

View File

@ -154,12 +154,12 @@ public class Module implements Comparable<Module>
private final List<String> _license = new ArrayList<>();
/**
* Dependencies
* Dependencies from {@code [depends]} section
*/
private final List<String> _depends = new ArrayList<>();
/**
* Optional
* Optional dependencies from {@code [optional]} section are structural in nature.
*/
private final Set<String> _optional = new HashSet<>();
@ -196,6 +196,18 @@ public class Module implements Comparable<Module>
process(basehome);
}
public static boolean isRequiredDependency(String depends)
{
return (depends != null) && (depends.charAt(0) != '?');
}
public static String normalizeModuleName(String name)
{
if (!isRequiredDependency(name))
return name.substring(1);
return name;
}
public String getName()
{
return _name;

View File

@ -247,6 +247,7 @@ public class ModuleGraphWriter
{
for (String depends : module.getDepends())
{
depends = Module.normalizeModuleName(depends);
out.printf(" \"%s\" -> \"%s\";%n", module.getName(), depends);
}
for (String optional : module.getOptional())

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.start;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@ -132,7 +133,12 @@ public class Modules implements Iterable<Module>
label = " Depend: %s";
for (String parent : module.getDepends())
{
parent = Module.normalizeModuleName(parent);
System.out.printf(label, parent);
if (!Module.isRequiredDependency(parent))
{
System.out.print(" [not-required]");
}
label = ", %s";
}
System.out.println();
@ -353,45 +359,59 @@ public class Modules implements Iterable<Module>
// Process module dependencies (always processed as may be dynamic)
StartLog.debug("Enabled module %s depends on %s", module.getName(), module.getDepends());
for (String dependsOn : module.getDepends())
for (String dependsOnRaw : module.getDepends())
{
// Look for modules that provide that dependency
Set<Module> providers = getAvailableProviders(dependsOn);
boolean isRequired = Module.isRequiredDependency(dependsOnRaw);
// Final to allow lambda's below to use name
final String dependentModule = Module.normalizeModuleName(dependsOnRaw);
StartLog.debug("Module %s depends on %s provided by %s", module, dependsOn, providers);
// Look for modules that provide that dependency
Set<Module> providers = getAvailableProviders(dependentModule);
StartLog.debug("Module %s depends on %s provided by %s", module, dependentModule, providers);
// If there are no known providers of the module
if (providers.isEmpty())
{
// look for a dynamic module
if (dependsOn.contains("/"))
if (dependentModule.contains("/"))
{
Path file = _baseHome.getPath("modules/" + dependentModule + ".mod");
if (isRequired || Files.exists(file))
{
Path file = _baseHome.getPath("modules/" + dependsOn + ".mod");
registerModule(file).expandDependencies(_args.getProperties());
providers = _provided.get(dependsOn);
providers = _provided.get(dependentModule);
if (providers == null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s", _baseHome.toShortForm(file), dependsOn);
throw new UsageException("Module %s does not provide %s", _baseHome.toShortForm(file), dependentModule);
enable(newlyEnabled, providers.stream().findFirst().get(), "dynamic dependency of " + module.getName(), true);
continue;
}
throw new UsageException("No module found to provide %s for %s", dependsOn, module);
}
// is this a non-required module
if (!isRequired)
{
StartLog.debug("Skipping non-required module [%s]: doesn't exist", dependentModule);
continue;
}
// throw an exception (not a dynamic module and a required dependency)
throw new UsageException("No module found to provide %s for %s", dependentModule, module);
}
// If a provider is already enabled, then add a transitive enable
if (providers.stream().filter(Module::isEnabled).count() > 0)
providers.stream().filter(m -> m.isEnabled() && !m.equals(module)).forEach(m -> enable(newlyEnabled, m, "transitive provider of " + dependsOn + " for " + module.getName(), true));
if (providers.stream().anyMatch(Module::isEnabled))
providers.stream().filter(m -> m.isEnabled() && !m.equals(module)).forEach(m -> enable(newlyEnabled, m, "transitive provider of " + dependentModule + " for " + module.getName(), true));
else
{
// Is there an obvious default?
Optional<Module> dftProvider = (providers.size() == 1)
? providers.stream().findFirst()
: providers.stream().filter(m -> m.getName().equals(dependsOn)).findFirst();
: providers.stream().filter(m -> m.getName().equals(dependentModule)).findFirst();
if (dftProvider.isPresent())
enable(newlyEnabled, dftProvider.get(), "transitive provider of " + dependsOn + " for " + module.getName(), true);
enable(newlyEnabled, dftProvider.get(), "transitive provider of " + dependentModule + " for " + module.getName(), true);
else if (StartLog.isDebugEnabled())
StartLog.debug("Module %s requires a %s implementation from one of %s", module, dependsOn, providers);
StartLog.debug("Module %s requires a %s implementation from one of %s", module, dependentModule, providers);
}
}
}

View File

@ -36,6 +36,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
@ExtendWith(WorkDirExtension.class)
public class ModulesTest
@ -202,6 +204,152 @@ public class ModulesTest
assertThat("Resolved XMLs: " + actualXmls, actualXmls, contains(expectedXmls.toArray()));
}
@Test
public void testResolveNotRequiredModuleNotFound() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=cannot-find-me"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is(nullValue()));
}
@Test
public void testResolveNotRequiredModuleFound() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=dive"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
expectedNames.add("bar-dive");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is("dive"));
}
@Test
public void testResolveNotRequiredModuleFoundDynamic() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=dynamic"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
expectedNames.add("impls/bar-dynamic");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is("dynamic"));
}
private List<String> normalizeLibs(List<Module> active)
{
List<String> libs = new ArrayList<>();

View File

@ -0,0 +1,2 @@
[ini]
bar.name=dive

View File

@ -0,0 +1,6 @@
# Top level mod
[depends]
foo
?bar-${bar.type}
?impls/bar-${bar.type}

View File

@ -0,0 +1 @@
# nothing here

View File

@ -0,0 +1,2 @@
[ini]
bar.name=dynamic

View File

@ -239,7 +239,7 @@ public class DateCache
// recheck the tick, to save multiple formats
if (tick == null || tick._seconds != seconds)
{
String s = ZonedDateTime.ofInstant(Instant.now(), _zoneId).format(_tzFormat);
String s = ZonedDateTime.ofInstant(Instant.ofEpochMilli(now), _zoneId).format(_tzFormat);
_tick = new Tick(seconds, s);
tick = _tick;
}

View File

@ -46,8 +46,25 @@ public class DecoratedObjectFactory implements Iterable<Decorator>
*/
public static final String ATTR = DecoratedObjectFactory.class.getName();
private static final ThreadLocal<Object> decoratorInfo = new ThreadLocal<>();
private List<Decorator> decorators = new ArrayList<>();
public static void associateInfo(Object info)
{
decoratorInfo.set(info);
}
public static void disassociateInfo()
{
decoratorInfo.set(null);
}
public static Object getAssociatedInfo()
{
return decoratorInfo.get();
}
public void addDecorator(Decorator decorator)
{
LOG.debug("Adding Decorator: {}", decorator);
@ -70,7 +87,7 @@ public class DecoratedObjectFactory implements Iterable<Decorator>
{
if (LOG.isDebugEnabled())
{
LOG.debug("Creating Instance: " + clazz);
LOG.debug("Creating Instance: {}", clazz);
}
T o = clazz.getDeclaredConstructor().newInstance();
return decorate(o);

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -145,10 +146,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void include(T... element)
{
for (T e : element)
{
_includes.add(e);
}
_includes.addAll(Arrays.asList(element));
}
public void exclude(T element)
@ -158,10 +156,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void exclude(T... element)
{
for (T e : element)
{
_excludes.add(e);
}
_excludes.addAll(Arrays.asList(element));
}
@Override
@ -233,34 +228,4 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
{
return _includes.isEmpty() && _excludes.isEmpty();
}
/**
* Match items in combined IncludeExcludeSets.
* @param item1 The item to match against set1
* @param set1 A IncludeExcludeSet to match item1 against
* @param item2 The item to match against set2
* @param set2 A IncludeExcludeSet to match item2 against
* @param <T1> The type of item1
* @param <T2> The type of item2
* @return True IFF <ul>
* <li>Neither item is excluded from their respective sets</li>
* <li>Both sets have no includes OR at least one of the items is included in its respective set</li>
* </ul>
*/
public static <T1,T2> boolean matchCombined(T1 item1, IncludeExcludeSet<?,T1> set1, T2 item2, IncludeExcludeSet<?,T2> set2)
{
Boolean match1 = set1.isIncludedAndNotExcluded(item1);
Boolean match2 = set2.isIncludedAndNotExcluded(item2);
// if we are excluded from either set, then we do not match
if (match1 == Boolean.FALSE || match2 == Boolean.FALSE)
return false;
// If either set has any includes, then we must be included by one of them
if (set1.hasIncludes() || set2.hasIncludes())
return match1 == Boolean.TRUE || match2 == Boolean.TRUE;
// If not excluded and no includes, then we match
return true;
}
}

View File

@ -0,0 +1,282 @@
//
// ========================================================================
// 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.util;
import java.net.InetAddress;
import java.util.function.Predicate;
/**
* A pattern representing a single or range of {@link InetAddress}. To create a pattern use
* the {@link InetAddressPattern#from(String)} method, which will create a pattern given a
* string conforming to one of the following formats.
*
* <dl>
* <dt>InetAddress</dt>
* <dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt>
* <dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt>
* <dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* <dt>Legacy format</dt>
* <dd>The legacy format used for IPv4 only.
* eg. "10.10.10-14.0-128"</dd>
* </dl>
*/
public abstract class InetAddressPattern implements Predicate<InetAddress>
{
protected final String _pattern;
public static InetAddressPattern from(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetAddressRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetAddressRange(pattern);
}
catch (Exception ex2)
{
e.addSuppressed(ex2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
}
public InetAddressPattern(String pattern)
{
_pattern = pattern;
}
@Override
public String toString()
{
return _pattern;
}
static class SingletonInetAddressRange extends InetAddressPattern
{
final InetAddress _address;
public SingletonInetAddressRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address)
{
return _address.equals(address);
}
}
static class MinMaxInetAddressRange extends InetAddressPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetAddressRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetAddressRange extends InetAddressPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetAddressRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
return _mask == 0 || (raw[_octets] & _mask) == _masked;
}
}
static class LegacyInetAddressRange extends InetAddressPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetAddressRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}

View File

@ -30,62 +30,20 @@ import java.util.function.Predicate;
* A set of InetAddress patterns.
* <p>This is a {@link Set} of String patterns that are used to match
* a {@link Predicate} over InetAddress for containment semantics.
* The patterns that may be set are:
* The patterns that may be set are defined in {@link InetAddressPattern}.
* </p>
* <dl>
* <dt>InetAddress</dt><dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt><dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt><dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* </dl>
* <p>This class is designed to work with {@link IncludeExcludeSet}</p>
*
* @see IncludeExcludeSet
*/
public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress>
{
private Map<String, InetPattern> _patterns = new HashMap<>();
private Map<String, InetAddressPattern> _patterns = new HashMap<>();
@Override
public boolean add(String pattern)
{
return _patterns.put(pattern, newInetRange(pattern)) == null;
}
private InetPattern newInetRange(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetRange(pattern);
}
catch (Exception e2)
{
e.addSuppressed(e2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
return _patterns.put(pattern, InetAddressPattern.from(pattern)) == null;
}
@Override
@ -111,219 +69,11 @@ public class InetAddressSet extends AbstractSet<String> implements Set<String>,
{
if (address == null)
return false;
byte[] raw = address.getAddress();
for (InetPattern pattern : _patterns.values())
for (InetAddressPattern pattern : _patterns.values())
{
if (pattern.test(address, raw))
if (pattern.test(address))
return true;
}
return false;
}
abstract static class InetPattern
{
final String _pattern;
InetPattern(String pattern)
{
_pattern = pattern;
}
abstract boolean test(InetAddress address, byte[] raw);
@Override
public String toString()
{
return _pattern;
}
}
static class SingletonInetRange extends InetPattern
{
final InetAddress _address;
public SingletonInetRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address, byte[] raw)
{
return _address.equals(address);
}
}
static class MinMaxInetRange extends InetPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetRange extends InetPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
if (_mask != 0 && (raw[_octets] & _mask) != _masked)
return false;
return true;
}
}
static class LegacyInetRange extends InetPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}

View File

@ -63,14 +63,13 @@ import org.eclipse.jetty.util.Loader;
*/
public class JavaUtilLog extends AbstractLogger
{
private static final String THIS_CLASS = JavaUtilLog.class.getName();
private static final boolean __source =
Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
Log.__props.getProperty("org.eclipse.jetty.util.log.javautil.SOURCE", "true")));
private static final boolean SOURCE =
Boolean.parseBoolean(Log.getProperty("org.eclipse.jetty.util.log.SOURCE",
Log.getProperty("org.eclipse.jetty.util.log.javautil.SOURCE", "true")));
private static boolean _initialized = false;
private static boolean __initialized = false;
private Level configuredLevel;
private Level _configuredLevel;
private java.util.logging.Logger _logger;
public JavaUtilLog()
@ -82,11 +81,11 @@ public class JavaUtilLog extends AbstractLogger
{
synchronized (JavaUtilLog.class)
{
if (!_initialized)
if (!__initialized)
{
_initialized = true;
__initialized = true;
final String properties = Log.__props.getProperty("org.eclipse.jetty.util.log.javautil.PROPERTIES", null);
final String properties = Log.getProperty("org.eclipse.jetty.util.log.javautil.PROPERTIES", null);
if (properties != null)
{
AccessController.doPrivileged(new PrivilegedAction<Object>()
@ -115,7 +114,7 @@ public class JavaUtilLog extends AbstractLogger
_logger = java.util.logging.Logger.getLogger(name);
switch (lookupLoggingLevel(Log.__props, name))
switch (lookupLoggingLevel(Log.getProperties(), name))
{
case LEVEL_ALL:
_logger.setLevel(Level.ALL);
@ -137,7 +136,7 @@ public class JavaUtilLog extends AbstractLogger
break;
}
configuredLevel = _logger.getLevel();
_configuredLevel = _logger.getLevel();
}
@Override
@ -152,13 +151,13 @@ public class JavaUtilLog extends AbstractLogger
if (thrown != null)
record.setThrown(thrown);
record.setLoggerName(_logger.getName());
if (__source)
if (SOURCE)
{
StackTraceElement[] stack = new Throwable().getStackTrace();
for (int i = 0; i < stack.length; i++)
{
StackTraceElement e = stack[i];
if (!e.getClassName().equals(THIS_CLASS))
if (!e.getClassName().equals(JavaUtilLog.class.getName()))
{
record.setSourceClassName(e.getClassName());
record.setSourceMethodName(e.getMethodName());
@ -222,12 +221,12 @@ public class JavaUtilLog extends AbstractLogger
{
if (enabled)
{
configuredLevel = _logger.getLevel();
_configuredLevel = _logger.getLevel();
_logger.setLevel(Level.FINE);
}
else
{
_logger.setLevel(configuredLevel);
_logger.setLevel(_configuredLevel);
}
}

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