Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x

This commit is contained in:
Jan Bartel 2018-12-19 10:54:41 +11:00
commit 4abc4f8dd4
25 changed files with 735 additions and 209 deletions

1
Jenkinsfile vendored
View File

@ -59,7 +59,6 @@ pipeline {
}
*/
}
}
}
}

View File

@ -681,6 +681,21 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (context == null)
throw new IllegalArgumentException("WebAppContext null");
//if we don't know where its from it can't be excluded
if (sciResource == null)
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} null resource", sci);
return false;
}
//A ServletContainerInitialier that came from WEB-INF/classes or equivalent cannot be excluded by an ordering
if (isFromWebInfClasses(context, sciResource))
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} from web-inf/classes", sci);
return false;
}
//A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
//of WEB-INF/lib jars
@ -709,14 +724,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
return true;
}
if (sciResource == null)
{
//not from a jar therefore not from WEB-INF so not excludable
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} not from jar", sci);
return false;
}
//Check if it is excluded by an ordering
URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
@ -762,7 +770,46 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
if (sci == null)
return false;
return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
ClassLoader sciLoader = sci.getClass().getClassLoader();
//if loaded by bootstrap loader, then its the container classpath
if ( sciLoader == null)
return true;
//if there is no context classloader, then its the container classpath
if (context.getClassLoader() == null)
return true;
ClassLoader loader = sciLoader;
while (loader != null)
{
if (loader == context.getClassLoader())
return false; //the webapp classloader is in the ancestry of the classloader for the sci
else
loader = loader.getParent();
}
return true;
}
/**
* Test if the ServletContainerInitializer is from WEB-INF/classes
*
* @param context the webapp to test
* @param sci a Resource representing the SCI
* @return true if the sci Resource is inside a WEB-INF/classes directory, false otherwise
*/
public boolean isFromWebInfClasses (WebAppContext context, Resource sci)
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
if (dir.equals(sci))
{
return true;
}
}
return false;
}
/**
@ -849,27 +896,47 @@ public class AnnotationConfiguration extends AbstractConfiguration
//No jetty-specific ordering specified, or just the wildcard value "*" specified.
//Fallback to ordering the ServletContainerInitializers according to:
//container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar ordering)
//no web.xml ordering defined, add SCIs in any order
//First add in all SCIs that can't be excluded
int lastContainerSCI = -1;
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
{
nonExcludedInitializers.add(++lastContainerSCI, entry.getKey()); //add all container SCIs before any webapp SCIs
}
else if (entry.getValue() == null) //can't work out provenance of SCI, so can't be ordered/excluded
{
nonExcludedInitializers.add(entry.getKey()); //add at end of list
}
else
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
if (dir.equals(entry.getValue()))//from WEB-INF/classes so can't be ordered/excluded
{
nonExcludedInitializers.add(entry.getKey());
}
}
}
}
//throw out the ones we've already accounted for
for (ServletContainerInitializer s:nonExcludedInitializers)
sciResourceMap.remove(s);
if (context.getMetaData().getOrdering() == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
//add the rest of the scis
nonExcludedInitializers.addAll(sciResourceMap.keySet());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Ordering ServletContainerInitializers with ordering {}",context.getMetaData().getOrdering());
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
//add in SCIs from the container classpath
if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
nonExcludedInitializers.add(entry.getKey());
else if (entry.getValue() == null) //add in SCIs not in a jar, as they must be from WEB-INF/classes and can't be ordered
nonExcludedInitializers.add(entry.getKey());
}
//add SCIs according to the ordering of its containing jar
for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
{
@ -889,7 +956,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
ListIterator<ServletContainerInitializer> it = nonExcludedInitializers.listIterator();
while (it.hasNext())
{
ServletContainerInitializer sci = it.next();
ServletContainerInitializer sci = it.next();
if (!isFromContainerClassPath(context, sci))
{
if (LOG.isDebugEnabled())

View File

@ -23,6 +23,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -32,6 +33,7 @@ import javax.servlet.ServletContainerInitializer;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
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.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -119,56 +121,123 @@ public class TestAnnotationConfiguration
File web25 = MavenTestingUtils.getTestResourceFile("web25.xml");
File web31false = MavenTestingUtils.getTestResourceFile("web31false.xml");
File web31true = MavenTestingUtils.getTestResourceFile("web31true.xml");
Set<String> sciNames = new HashSet<>(Arrays.asList("org.eclipse.jetty.annotations.ServerServletContainerInitializer", "com.acme.initializer.FooInitializer"));
//prepare an sci that will be on the webapp's classpath
File jarDir = new File(MavenTestingUtils.getTestResourcesDir().getParentFile(), "jar");
File testSciJar = new File(jarDir, "test-sci.jar");
assertTrue(testSciJar.exists());
URLClassLoader webAppLoader = new URLClassLoader(new URL[]{testSciJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
ClassLoader orig = Thread.currentThread().getContextClassLoader();
File testContainerSciJar = new File(jarDir, "test-sci-for-container-path.jar");
URLClassLoader containerLoader = new URLClassLoader(new URL[] {testContainerSciJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader webAppLoader = new URLClassLoader(new URL[] {testSciJar.toURI().toURL()}, containerLoader);
Resource targetClasses = Resource.newResource(MavenTestingUtils.getTargetDir().toURI()).addPath("/test-classes");
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(containerLoader);
try
{
//test 3.1 webapp loads both server and app scis
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
//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(Collections.singletonList(targetClasses));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
Thread.currentThread().setContextClassLoader(webAppLoader);
List<ServletContainerInitializer> scis = config.getNonExcludedInitializers(context);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); //web-inf classes
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); //web-inf jar no web-fragment
//test a 3.1 webapp with metadata-complete=false loads both server and webapp scis
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(Collections.singletonList(targetClasses));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); //web-inf classes
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); //web-inf jar no web-fragment
//test a 3.1 webapp with RELATIVE ORDERING loads sci from equivalent of WEB-INF/classes as well as container path
File orderedFragmentJar = new File(jarDir, "test-sci-with-ordering.jar");
assertTrue(orderedFragmentJar.exists());
URLClassLoader orderedLoader = new URLClassLoader(new URL[] {orderedFragmentJar.toURI().toURL(), testSciJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(orderedLoader);
context.getMetaData().setWebXml(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(Collections.singletonList(targetClasses));
context.getMetaData().orderFragments();
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(4, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); //web-inf classes
assertEquals("com.acme.AcmeServletContainerInitializer", scis.get(2).getClass().getName()); //first in ordering
assertEquals("com.acme.initializer.FooInitializer", scis.get(3).getClass().getName()); //other in ordering
//test 3.1 webapp with a specific SCI ordering
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(Collections.singletonList(targetClasses));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
context.setAttribute("org.eclipse.jetty.containerInitializerOrder", "com.acme.initializer.FooInitializer,com.acme.ServerServletContainerInitializer, *");
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(3, scis.size());
assertEquals("com.acme.initializer.FooInitializer", scis.get(0).getClass().getName()); //web-inf jar no web-fragment
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(1).getClass().getName()); //container path
assertEquals("org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer", scis.get(2).getClass().getName()); //web-inf classes
//test 2.5 webapp with configurationDiscovered=false loads only server scis
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(Collections.singletonList(targetClasses));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(1, scis.size());
assertTrue("org.eclipse.jetty.annotations.ServerServletContainerInitializer".equals(scis.get(0).getClass().getName()));
for (ServletContainerInitializer s:scis)
{
//should not have any of the web-inf lib scis in here
assertFalse(s.getClass().getName().equals("com.acme.AcmeServletContainerInitializer"));
assertFalse(s.getClass().getName().equals("com.acme.initializer.FooInitializer"));
//NOTE: should also not have the web-inf classes scis in here either, but due to the
//way the test is set up, the sci we're pretending is in web-inf classes will actually
//NOT be loaded by the webapp's classloader, but rather by the junit classloader, so
//it looks as if it is a container class.
}
//test 2.5 webapp with configurationDiscovered=true loads both server and webapp scis
config = new AnnotationConfiguration();
@ -176,20 +245,25 @@ public class TestAnnotationConfiguration
context.setConfigurationDiscovered(true);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(Collections.singletonList(targetClasses));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); //web-inf classes
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); //web-inf jar no web-fragment
}
finally
{
Thread.currentThread().setContextClassLoader(orig);
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testGetFragmentFromJar() throws Exception
{

View File

@ -30,13 +30,13 @@ import javax.servlet.ServletException;
*
*
*/
public class ServerServletContainerInitializer implements ServletContainerInitializer
public class WebInfClassServletContainerInitializer implements ServletContainerInitializer
{
/**
*
*/
public ServerServletContainerInitializer()
public WebInfClassServletContainerInitializer()
{
// TODO Auto-generated constructor stub
}

View File

@ -1 +1 @@
org.eclipse.jetty.annotations.ServerServletContainerInitializer
org.eclipse.jetty.annotations.WebInfClassServletContainerInitializer

View File

@ -91,7 +91,29 @@ By default, log files are kept for 90 days before being deleted.
The value for `retainDays` (xml) or `setRetainDays` (Java) should be configured as _1 + n_ days.
For example, if you wanted to keep the logs for the current day and the day prior you would set the `retainDays` (or `setRetainDays`) value to 2.
To examine more configuration options, see link:{JDURL}/org/eclipse/jetty/server/NCSARequestLog.html[NCSARequestLog.java].
[[request-log-custom-writer]]
==== Introducing RequestLog.Writer
The concept of a `RequestLog.Writer`, introduced in Jetty 9.4.15, manages the writing to a log the string generated by the `RequestLog`.
This allows the `CustomRequestLog` to match the functionality of other `RequestLogger` implementations by plugging in any `RequestLog.Writer` and a custom format string.
Jetty currently has implementations of `RequestLog.Writer`, `RequestLogWriter`, `AsyncRequestLogWriter`, and `Slf4jRequestLogWriter`.
So, the way to create an asynchronous `RequestLog` using the extended NCSA format has been changed from:
`new AsyncNcsaRequestLog(filename)`
to:
`new CustomRequestLog(new AsyncRequestLogWriter(filename), CustomRequestLog.EXTENDED_NCSA_FORMAT)`
Additionally, there are now two settings for the log timezone to be configured.
There is the configuration for logging the request time, which is set in the `timeZone` parameter in the `%t` format code of the string, given in the format `%{format|timeZone|locale}t`.
The other `timeZone` parameter relates to the generation of the log file name (both at creation and roll over).
This is configured in the `requestlog` module file, or can be used as a setter on `RequestLogWriter` via XML.
Both timezones are set to GMT by default.
[[configuring-separate-request-log-for-web-application]]
==== Configuring a Separate Request Log For a Web Application

View File

@ -18,23 +18,23 @@
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.jndi.*</onlyAnalyze>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>javax.mail.*;resolution:=optional,*</Import-Package>
<Import-Package>javax.mail.*;resolution:=optional,*</Import-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.jndi.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -17,6 +17,8 @@ Running single test
You can run single or set of test as well using the command line argument: ```-Dinvoker.test=jetty-run-mojo-it,jetty-run-war*-it,!jetty-run-distro*```
The parameter supports pattern and exclusion with !
NOTE: if you use ```clean``` arg to maven, you will also need to add the test ```it-parent-pom``` first for invoker.test, eg ```-Dinvoker.test=it-parent-pom,jetty-run-mojo-it```.
Running Logs
--------------------
The output of each Maven build will be located in /target/it/${project-name}/build.log

View File

@ -3,13 +3,6 @@
<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>
<!--parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent-->
<parent>
<groupId>org.eclipse.jetty.its</groupId>
<artifactId>it-parent-pom</artifactId>
@ -20,7 +13,6 @@
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>

View File

@ -58,32 +58,7 @@ public class AsyncContextState implements AsyncContext
@Override
public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
{
AsyncListener wrap = new AsyncListener()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onError(AsyncEvent event) throws IOException
{
listener.onError(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
};
AsyncListener wrap = new WrappedAsyncListener(listener, request, response);
state().addListener(wrap);
}
@ -188,6 +163,46 @@ public class AsyncContextState implements AsyncContext
return state();
}
public static class WrappedAsyncListener implements AsyncListener
{
private final AsyncListener _listener;
private final ServletRequest _request;
private final ServletResponse _response;
public WrappedAsyncListener(AsyncListener listener, ServletRequest request, ServletResponse response)
{
_listener = listener;
_request = request;
_response = response;
}
public AsyncListener getListener()
{
return _listener;
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
_listener.onTimeout(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
_listener.onStartAsync(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onError(AsyncEvent event) throws IOException
{
_listener.onError(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
_listener.onComplete(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
}
}

View File

@ -149,6 +149,25 @@ public class HttpChannelState
}
}
public boolean hasListener(AsyncListener listener)
{
try(Locker.Lock lock= _locker.lock())
{
if (_asyncListeners==null)
return false;
for (AsyncListener l : _asyncListeners)
{
if (l==listener)
return true;
if (l instanceof AsyncContextState.WrappedAsyncListener && ((AsyncContextState.WrappedAsyncListener)l).getListener()==listener)
return true;
}
return false;
}
}
public void setTimeout(long ms)
{
try(Locker.Lock lock= _locker.lock())

View File

@ -442,7 +442,7 @@ public class LowResourceMonitor extends ContainerLifeCycle
{
}
interface LowResourceCheck
public interface LowResourceCheck
{
boolean isLowOnResources();

View File

@ -72,6 +72,7 @@ import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
@ -465,8 +466,12 @@ public class Request implements HttpServletRequest
if (MimeTypes.Type.FORM_ENCODED.is(contentType) &&
_channel.getHttpConfiguration().isFormEncodedMethod(getMethod()))
{
if (_metaData!=null && getHttpFields().contains(HttpHeader.CONTENT_ENCODING))
throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501,"Unsupported Content-Encoding");
if (_metaData!=null)
{
String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING);
if (contentEncoding!=null && !HttpHeaderValue.IDENTITY.is(contentEncoding))
throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501, "Unsupported Content-Encoding");
}
extractFormParameters(_contentParameters);
}
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) &&

View File

@ -55,7 +55,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
public final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
protected static final AtomicLong COUNTER = new AtomicLong();

View File

@ -1148,4 +1148,18 @@ public class Session implements SessionHandler.SessionIf
return _resident;
}
@Override
public String toString()
{
try (Lock lock = _lock.lock())
{
return String.format("%s@%x{id=%s,x=%s,req=%d,res=%b}",
getClass().getSimpleName(),
hashCode(),
_sessionData.getId(),
_extendedId,
_requests,
_resident);
}
}
}

View File

@ -162,8 +162,22 @@ public class SessionHandler extends ScopedHandler
@Override
public void onComplete(AsyncEvent event) throws IOException
{
//An async request has completed, so we can complete the session
complete(Request.getBaseRequest(event.getAsyncContext().getRequest()).getSession(false));
// An async request has completed, so we can complete the session,
// but we must locate the session instance for this context
Request request = Request.getBaseRequest(event.getAsyncContext().getRequest());
HttpSession session = request.getSession(false);
String id;
if (session!=null)
id = session.getId();
else
{
id = (String)request.getAttribute(DefaultSessionIdManager.__NEW_SESSION_ID);
if (id==null)
id = request.getRequestedSessionId();
}
if (id!=null)
complete(getSession(id));
}
@Override
@ -407,9 +421,12 @@ public class SessionHandler extends ScopedHandler
*/
public void complete(HttpSession session)
{
if (LOG.isDebugEnabled())
LOG.debug("Complete called with session {}", session);
if (session == null)
return;
Session s = ((SessionIf)session).getSession();
try
@ -422,23 +439,26 @@ public class SessionHandler extends ScopedHandler
LOG.warn(e);
}
}
public void complete (Session session, Request request)
@Deprecated
public void complete(Session session, Request baseRequest)
{
if (request.isAsyncStarted() && request.getDispatcherType() == DispatcherType.REQUEST)
ensureCompletion(baseRequest);
}
private void ensureCompletion(Request baseRequest)
{
if (baseRequest.isAsyncStarted())
{
request.getAsyncContext().addListener(_sessionAsyncListener);
if (LOG.isDebugEnabled())
LOG.debug("Adding AsyncListener for {}", baseRequest);
if (!baseRequest.getHttpChannelState().hasListener(_sessionAsyncListener))
baseRequest.getAsyncContext().addListener(_sessionAsyncListener);
}
else
{
complete(session);
complete(baseRequest.getSession(false));
}
//if dispatcher type is not async and not request, complete immediately (its a forward or an include)
//else if dispatcher type is request and not async, complete immediately
//else register an async callback completion listener that will complete the session
}
@ -455,7 +475,6 @@ public class SessionHandler extends ScopedHandler
_context=ContextHandler.getCurrentContext();
_loader=Thread.currentThread().getContextClassLoader();
synchronized (server)
{
//Get a SessionDataStore and a SessionDataStore, falling back to in-memory sessions only
@ -472,7 +491,6 @@ public class SessionHandler extends ScopedHandler
_sessionCache.setSessionDataStore(sds);
}
if (_sessionIdManager==null)
{
@ -1593,16 +1611,19 @@ public class SessionHandler extends ScopedHandler
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
SessionHandler old_session_manager = null;
SessionHandler old_session_handler = null;
HttpSession old_session = null;
HttpSession existingSession = null;
try
{
old_session_manager = baseRequest.getSessionHandler();
if (LOG.isDebugEnabled())
LOG.debug("SessionHandler.doScope");
old_session_handler = baseRequest.getSessionHandler();
old_session = baseRequest.getSession(false);
if (old_session_manager != this)
if (old_session_handler != this)
{
// new session context
baseRequest.setSessionHandler(this);
@ -1613,7 +1634,7 @@ public class SessionHandler extends ScopedHandler
// access any existing session for this context
existingSession = baseRequest.getSession(false);
if ((existingSession != null) && (old_session_manager != this))
if ((existingSession != null) && (old_session_handler != this))
{
HttpCookie cookie = access(existingSession,request.isSecure());
// Handle changed ID or max-age refresh, but only if this is not a redispatched request
@ -1622,10 +1643,7 @@ public class SessionHandler extends ScopedHandler
}
if (LOG.isDebugEnabled())
{
LOG.debug("sessionHandler=" + this);
LOG.debug("session=" + existingSession);
}
LOG.debug("sessionHandler={} session={}",this, existingSession);
if (_nextScope != null)
_nextScope.doScope(target,baseRequest,request,response);
@ -1637,16 +1655,18 @@ public class SessionHandler extends ScopedHandler
finally
{
//if there is a session that was created during handling this context, then complete it
HttpSession finalSession = baseRequest.getSession(false);
if (LOG.isDebugEnabled()) LOG.debug("FinalSession="+finalSession+" old_session_manager="+old_session_manager+" this="+this);
if ((finalSession != null) && (old_session_manager != this))
if (LOG.isDebugEnabled())
LOG.debug("FinalSession={}, old_session_handler={}, this={}, calling complete={}", baseRequest.getSession(false), old_session_handler, this, (old_session_handler != this));
// If we are leaving the scope of this session handler, ensure the session is completed
if (old_session_handler != this)
ensureCompletion(baseRequest);
// revert the session handler to the previous, unless it was null, in which case remember it as
// the first session handler encountered.
if (old_session_handler != null && old_session_handler != this)
{
complete((Session)finalSession, baseRequest);
}
if (old_session_manager != null && old_session_manager != this)
{
baseRequest.setSessionHandler(old_session_manager);
baseRequest.setSessionHandler(old_session_handler);
baseRequest.setSession(old_session);
}
}

View File

@ -648,18 +648,29 @@ public class RequestTest
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testIdentityParamExtraction() throws Exception
{
_handler._checker = (request, response) -> "bar".equals(request.getParameter("foo"));
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: application/x-www-form-urlencoded; charset=utf-8\n"+
"Content-Length: 7\n"+
"Content-Encoding: identity\n"+
"Connection: close\n"+
"\n"+
"foo=bar\n";
String responses=_connector.getResponse(request);
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testEncodedNotParams() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
return request.getParameter("param")==null;
}
};
_handler._checker = (request, response) -> request.getParameter("param")==null;
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
@ -675,7 +686,6 @@ public class RequestTest
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testInvalidHostHeader() throws Exception
{

View File

@ -50,7 +50,7 @@
<jetty-test-policy.version>1.2</jetty-test-policy.version>
<servlet.api.version>4.0.1</servlet.api.version>
<servlet.schema.version>4.0.3</servlet.schema.version>
<jsp.version>8.5.33</jsp.version>
<jsp.version>8.5.35.1</jsp.version>
<!-- default values are unsupported, but required to be defined for reactor sanity reasons -->
<alpn.version>undefined</alpn.version>
<conscrypt.version>1.4.1</conscrypt.version>

View File

@ -117,7 +117,7 @@ public class AttributeNameTest
//Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
//Make a request to the 2nd server which will do a refresh, use TestFooServlet to ensure that the
//Make a request to the 2nd server which will do a refresh, use TestServlet to ensure that the
//session attribute with dotted name is not removed
Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
request2.header("Cookie", sessionCookie);

View File

@ -1,59 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.session;
import java.io.IOException;
import java.lang.reflect.Proxy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestFooServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("create".equals(action))
{
HttpSession session = request.getSession(true);
TestFoo testFoo = new TestFoo();
testFoo.setInt(33);
FooInvocationHandler handler = new FooInvocationHandler(testFoo);
Foo foo = (Foo)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {Foo.class}, handler);
session.setAttribute("foo", foo);
}
else if ("test".equals(action))
{
HttpSession session = request.getSession(false);
if (session == null)
response.sendError(500, "Session not activated");
Foo foo = (Foo)session.getAttribute("foo");
if (foo == null || foo.getInt() != 33)
response.sendError(500, "Foo not deserialized");
}
}
}

View File

@ -0,0 +1,358 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.session;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.lang.reflect.Proxy;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
/**
* AsyncTest
*
* Tests async handling wrt sessions.
*/
public class AsyncTest
{
public static class LatchServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getWriter().println("Latched");
}
}
@Test
public void testSessionWithAsyncDispatch() throws Exception
{
// Test async dispatch back to same context, which then creates a session.
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
String contextPath = "";
String mapping = "/server";
ServletContextHandler contextHandler = server.addContext(contextPath);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
contextHandler.addServlet(holder, mapping);
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextHandler.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + contextPath + mapping+"?action=async";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + contextPath + "/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextHandler.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithAsyncComplete() throws Exception
{
// Test async write, which creates a session and completes outside of a dispatch
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
String contextPath = "";
String mapping = "/server";
ServletContextHandler contextHandler = server.addContext(contextPath);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
contextHandler.addServlet(holder, mapping);
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextHandler.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + contextPath + mapping+"?action=asyncComplete";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + contextPath + "/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextHandler.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithCrossContextAsync() throws Exception
{
// Test async dispatch from context A to context B then
// async dispatch back to context B, which then creates a session (in context B).
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
ServletContextHandler contextA = server.addContext("/ctxA");
CrossContextServlet ccServlet = new CrossContextServlet();
ServletHolder ccHolder = new ServletHolder(ccServlet);
contextA.addServlet(ccHolder, "/*");
ServletContextHandler contextB = server.addContext("/ctxB");
TestServlet testServlet = new TestServlet();
ServletHolder testHolder = new ServletHolder(testServlet);
contextB.addServlet(testHolder, "/*");
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextB.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + "/ctxA/test?action=async";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + "/ctxB/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextB.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextB.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithCrossContextAsyncComplete() throws Exception
{
// Test async dispatch from context A to context B, which then does an
// async write, which creates a session (in context A) and completes outside of a
// dispatch
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
ServletContextHandler contextA = server.addContext("/ctxA");
CrossContextServlet ccServlet = new CrossContextServlet();
ServletHolder ccHolder = new ServletHolder(ccServlet);
contextA.addServlet(ccHolder, "/*");
ServletContextHandler contextB = server.addContext("/ctxB");
TestServlet testServlet = new TestServlet();
ServletHolder testHolder = new ServletHolder(testServlet);
contextB.addServlet(testHolder, "/*");
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextB.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + "/ctxA/test?action=asyncComplete";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + "/ctxB/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache A after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextA.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextA.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("create".equals(action))
{
HttpSession session = request.getSession(true);
TestFoo testFoo = new TestFoo();
testFoo.setInt(33);
FooInvocationHandler handler = new FooInvocationHandler(testFoo);
Foo foo = (Foo)Proxy
.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {Foo.class}, handler);
session.setAttribute("foo", foo);
}
else if ("test".equals(action))
{
HttpSession session = request.getSession(false);
if (session == null)
response.sendError(500, "Session not activated");
Foo foo = (Foo)session.getAttribute("foo");
if (foo == null || foo.getInt() != 33)
response.sendError(500, "Foo not deserialized");
}
else if ("async".equals(action))
{
if (request.getAttribute("async-test") == null)
{
request.setAttribute("async-test", Boolean.TRUE);
AsyncContext acontext = request.startAsync();
acontext.dispatch();
return;
}
else
{
HttpSession session = request.getSession(true);
response.getWriter().println("OK");
}
}
else if ("asyncComplete".equals(action))
{
AsyncContext acontext = request.startAsync();
ServletOutputStream out = response.getOutputStream();
out.setWriteListener(new WriteListener()
{
@Override
public void onWritePossible() throws IOException
{
if (out.isReady())
{
request.getSession(true);
out.print("OK\n");
acontext.complete();
}
}
@Override
public void onError(Throwable t)
{
}
});
}
}
}
public static class CrossContextServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext acontext = request.startAsync();
acontext.dispatch(request.getServletContext().getContext("/ctxB"),"/test");
}
}
}

View File

@ -128,12 +128,7 @@ public class DeleteUnloadableSessionTest
}
}
/**
* TestFooServlet
*
*
*/
public static class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;

View File

@ -136,16 +136,9 @@ public class SessionEvictionFailureTest
}
/**
* TestFooServlet
*
*
*/
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
{