Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x
This commit is contained in:
commit
fe8b11dd48
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.thread.jmh;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
|
||||
import org.eclipse.jetty.util.thread.TryExecutor;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.TearDown;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.openjdk.jmh.runner.options.Options;
|
||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@Warmup(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
|
||||
public class ReservedThreadPoolBenchmark
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
RTP
|
||||
}
|
||||
|
||||
@Param({"RTP"})
|
||||
Type type;
|
||||
|
||||
@Param({"0", "8", "32"})
|
||||
int size;
|
||||
|
||||
QueuedThreadPool qtp;
|
||||
TryExecutor pool;
|
||||
|
||||
@Setup // (Level.Iteration)
|
||||
public void buildPool()
|
||||
{
|
||||
qtp = new QueuedThreadPool();
|
||||
switch (type)
|
||||
{
|
||||
case RTP:
|
||||
{
|
||||
ReservedThreadExecutor pool = new ReservedThreadExecutor(qtp, size);
|
||||
pool.setIdleTimeout(1, TimeUnit.SECONDS);
|
||||
this.pool = pool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
LifeCycle.start(qtp);
|
||||
LifeCycle.start(pool);
|
||||
}
|
||||
|
||||
@TearDown // (Level.Iteration)
|
||||
public void shutdownPool()
|
||||
{
|
||||
LifeCycle.stop(pool);
|
||||
LifeCycle.stop(qtp);
|
||||
pool = null;
|
||||
qtp = null;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@Threads(1)
|
||||
public void testFew() throws Exception
|
||||
{
|
||||
doJob();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@Threads(8)
|
||||
public void testSome() throws Exception
|
||||
{
|
||||
doJob();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@Threads(200)
|
||||
public void testMany() throws Exception
|
||||
{
|
||||
doJob();
|
||||
}
|
||||
|
||||
void doJob() throws Exception
|
||||
{
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Runnable task = () ->
|
||||
{
|
||||
Blackhole.consumeCPU(1);
|
||||
Thread.yield();
|
||||
Blackhole.consumeCPU(1);
|
||||
latch.countDown();
|
||||
Blackhole.consumeCPU(1);
|
||||
};
|
||||
if (!pool.tryExecute(task))
|
||||
qtp.execute(task);
|
||||
latch.await();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws RunnerException
|
||||
{
|
||||
Options opt = new OptionsBuilder()
|
||||
.include(ReservedThreadPoolBenchmark.class.getSimpleName())
|
||||
.forks(1)
|
||||
// .threads(400)
|
||||
// .syncIterations(true) // Don't start all threads at same time
|
||||
// .addProfiler(CompilerProfiler.class)
|
||||
// .addProfiler(LinuxPerfProfiler.class)
|
||||
// .addProfiler(LinuxPerfNormProfiler.class)
|
||||
// .addProfiler(LinuxPerfAsmProfiler.class)
|
||||
// .resultFormat(ResultFormatType.CSV)
|
||||
.build();
|
||||
|
||||
new Runner(opt).run();
|
||||
}
|
||||
}
|
|
@ -123,16 +123,42 @@
|
|||
<reportSets>
|
||||
<reportSet>
|
||||
<reports>
|
||||
<report>project-team</report>
|
||||
<report>mailing-list</report>
|
||||
<report>cim</report>
|
||||
<report>issue-tracking</report>
|
||||
<report>license</report>
|
||||
<report>team</report>
|
||||
<report>mailing-lists</report>
|
||||
<report>ci-management</report>
|
||||
<report>issue-management</report>
|
||||
<report>licenses</report>
|
||||
<report>scm</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
</reportSets>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-plugin-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>eclipse-release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>site-jar</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Eclipse Jetty Jspc Maven Plugin
|
||||
========================
|
||||
|
||||
This plugin will help you pre-compile your jsps and works in conjunction with the Maven war plugin to put them inside an assembled war.
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/DECORATION/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
|
||||
|
||||
<bannerLeft>
|
||||
<name>${project.name}</name>
|
||||
<src>https://www.eclipse.org/jetty/images/jetty-logo-80x22.png</src>
|
||||
<href>https://eclipse.org/jetty/</href>
|
||||
</bannerLeft>
|
||||
<bannerRight>
|
||||
<src>https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-426x100.png</src>
|
||||
</bannerRight>
|
||||
|
||||
<skin>
|
||||
<groupId>org.apache.maven.skins</groupId>
|
||||
<artifactId>maven-fluido-skin</artifactId>
|
||||
<version>1.7</version>
|
||||
</skin>
|
||||
|
||||
<custom>
|
||||
<fluidoSkin>
|
||||
<topBarEnabled>true</topBarEnabled>
|
||||
<sideBarEnabled>true</sideBarEnabled>
|
||||
<googleSearch>
|
||||
<sitesearch>${project.url}</sitesearch>
|
||||
</googleSearch>
|
||||
<gitHub>
|
||||
<projectId>eclipse/jetty.project</projectId>
|
||||
<ribbonOrientation>right</ribbonOrientation>
|
||||
<ribbonColor>gray</ribbonColor>
|
||||
</gitHub>
|
||||
</fluidoSkin>
|
||||
</custom>
|
||||
|
||||
<body>
|
||||
<menu name="Overview">
|
||||
<item name="Introduction" href="index.html"/>
|
||||
</menu>
|
||||
<menu ref="reports" inherit="bottom"/>
|
||||
|
||||
</body>
|
||||
</project>
|
|
@ -265,16 +265,42 @@
|
|||
<reportSets>
|
||||
<reportSet>
|
||||
<reports>
|
||||
<report>project-team</report>
|
||||
<report>mailing-list</report>
|
||||
<report>cim</report>
|
||||
<report>issue-tracking</report>
|
||||
<report>license</report>
|
||||
<report>team</report>
|
||||
<report>mailing-lists</report>
|
||||
<report>ci-management</report>
|
||||
<report>issue-management</report>
|
||||
<report>licenses</report>
|
||||
<report>scm</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
</reportSets>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-plugin-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>eclipse-release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>site-jar</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
Eclipse Jetty Maven Plugin
|
||||
========================
|
||||
|
||||
The Jetty Maven plugin is useful for rapid development and testing.
|
||||
|
||||
You can add it to any webapp project that is structured according to the Maven defaults.
|
||||
|
||||
The plugin can then periodically scan your project for changes and automatically redeploy the webapp if any are found.
|
||||
|
||||
This makes the development cycle more productive by eliminating the build and deploy steps: you use your IDE to make changes to the project, and the running web container automatically picks them up, allowing you to test them straight away.
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/DECORATION/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
|
||||
|
||||
<bannerLeft>
|
||||
<name>${project.name}</name>
|
||||
<src>https://www.eclipse.org/jetty/images/jetty-logo-80x22.png</src>
|
||||
<href>https://eclipse.org/jetty/</href>
|
||||
</bannerLeft>
|
||||
<bannerRight>
|
||||
<src>https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-426x100.png</src>
|
||||
</bannerRight>
|
||||
|
||||
<skin>
|
||||
<groupId>org.apache.maven.skins</groupId>
|
||||
<artifactId>maven-fluido-skin</artifactId>
|
||||
<version>1.7</version>
|
||||
</skin>
|
||||
|
||||
<custom>
|
||||
<fluidoSkin>
|
||||
<topBarEnabled>true</topBarEnabled>
|
||||
<sideBarEnabled>true</sideBarEnabled>
|
||||
<googleSearch>
|
||||
<sitesearch>${project.url}</sitesearch>
|
||||
</googleSearch>
|
||||
<gitHub>
|
||||
<projectId>eclipse/jetty.project</projectId>
|
||||
<ribbonOrientation>right</ribbonOrientation>
|
||||
<ribbonColor>gray</ribbonColor>
|
||||
</gitHub>
|
||||
</fluidoSkin>
|
||||
</custom>
|
||||
|
||||
<body>
|
||||
<menu name="Overview">
|
||||
<item name="Introduction" href="index.html"/>
|
||||
</menu>
|
||||
<menu ref="reports" inherit="bottom"/>
|
||||
|
||||
</body>
|
||||
</project>
|
|
@ -219,8 +219,18 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
_contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
|
||||
|
||||
if (!baseRequest.getHttpChannelState().isAsync())
|
||||
baseRequest.getResponse().softClose();
|
||||
// If we are not async and not closed already, then close via the possibly wrapped response.
|
||||
if (!baseRequest.getHttpChannelState().isAsync() && !baseResponse.getHttpOutput().isClosed())
|
||||
{
|
||||
try
|
||||
{
|
||||
response.getOutputStream().close();
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
response.getWriter().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -906,7 +906,7 @@ public class HttpChannelState
|
|||
throw new IllegalStateException("Response is " + _outputState);
|
||||
|
||||
response.setStatus(code);
|
||||
response.softClose();
|
||||
response.errorClose();
|
||||
|
||||
request.setAttribute(ErrorHandler.ERROR_CONTEXT, request.getErrorContext());
|
||||
request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI());
|
||||
|
|
|
@ -143,12 +143,14 @@ public class Response implements HttpServletResponse
|
|||
|
||||
public void reopen()
|
||||
{
|
||||
// Make the response mutable and reopen output.
|
||||
setErrorSent(false);
|
||||
_out.reopen();
|
||||
}
|
||||
|
||||
public void softClose()
|
||||
public void errorClose()
|
||||
{
|
||||
// Make the response immutable and soft close the output.
|
||||
setErrorSent(true);
|
||||
_out.softClose();
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
|
||||
public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context";
|
||||
|
||||
boolean _showServlet = true;
|
||||
boolean _showStacks = true;
|
||||
boolean _disableStacks = false;
|
||||
boolean _showMessageInTitle = true;
|
||||
|
@ -441,13 +442,18 @@ public class ErrorHandler extends AbstractHandler
|
|||
writer.printf("URI: %s%n", request.getRequestURI());
|
||||
writer.printf("STATUS: %s%n", code);
|
||||
writer.printf("MESSAGE: %s%n", message);
|
||||
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
if (isShowServlet())
|
||||
{
|
||||
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
}
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
while (cause != null)
|
||||
{
|
||||
writer.printf("CAUSED BY %s%n", cause);
|
||||
if (_showStacks && !_disableStacks)
|
||||
{
|
||||
cause.printStackTrace(writer);
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +467,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
json.put("url", request.getRequestURI());
|
||||
json.put("status", Integer.toString(code));
|
||||
json.put("message", message);
|
||||
if (servlet != null)
|
||||
if (isShowServlet() && servlet != null)
|
||||
{
|
||||
json.put("servlet", servlet.toString());
|
||||
}
|
||||
|
@ -539,6 +545,22 @@ public class ErrorHandler extends AbstractHandler
|
|||
_cacheControl = cacheControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the error page will show the Servlet that generated the error
|
||||
*/
|
||||
public boolean isShowServlet()
|
||||
{
|
||||
return _showServlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param showServlet True if the error page will show the Servlet that generated the error
|
||||
*/
|
||||
public void setShowServlet(boolean showServlet)
|
||||
{
|
||||
_showServlet = showServlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if stack traces are shown in the error pages
|
||||
*/
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session;
|
|||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
@ -334,12 +335,13 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
|
|||
*
|
||||
* @param id The session to retrieve
|
||||
* @param enter if true, the usage count of the session will be incremented
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @return the session if it exists, null otherwise
|
||||
* @throws Exception if the session cannot be loaded
|
||||
*/
|
||||
protected Session getAndEnter(String id, boolean enter) throws Exception
|
||||
{
|
||||
Session session = null;
|
||||
AtomicReference<Exception> exception = new AtomicReference<Exception>();
|
||||
|
||||
session = doComputeIfAbsent(id, k ->
|
||||
{
|
||||
|
@ -365,11 +367,15 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Error loading session {}", id, e);
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Exception ex = exception.get();
|
||||
if (ex != null)
|
||||
throw ex;
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
try (AutoLock lock = session.lock())
|
||||
|
@ -741,7 +747,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Passivation of idle session {} failed", session.getId(), e);
|
||||
LOG.warn("Passivation of idle session {} failed", session.getId());
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -838,7 +845,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Save of new session {} failed", id, e);
|
||||
LOG.warn("Save of new session {} failed", id);
|
||||
LOG.warn(e);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
|
|
@ -301,12 +301,13 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
|
|||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Checked {}, in use:", id, inUse);
|
||||
LOG.debug("Checked {}, in use: {}", id, inUse);
|
||||
return inUse;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Problem checking if id {} is in use", id, e);
|
||||
LOG.warn("Problem checking if id {} is in use", id);
|
||||
LOG.warn(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,7 +258,8 @@ public class FileSessionDataStore extends AbstractSessionDataStore
|
|||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
LOG.warn("Not valid session filename {}", p.getFileName(), e);
|
||||
LOG.warn("Not valid session filename {}", p.getFileName());
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +300,8 @@ public class FileSessionDataStore extends AbstractSessionDataStore
|
|||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
LOG.warn("Unable to delete unrestorable file {} for session {}", filename, id, x);
|
||||
LOG.warn("Unable to delete unrestorable file {} for session {}", filename, id);
|
||||
LOG.warn(x);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
@ -865,7 +865,8 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("{} Problem checking if potentially expired session {} exists in db", _context.getWorkerName(), k, e);
|
||||
LOG.warn("{} Problem checking if potentially expired session {} exists in db", _context.getWorkerName(), k);
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ public class NullSessionCache extends AbstractSessionCache
|
|||
@Override
|
||||
public void setEvictionPolicy(int evictionTimeout)
|
||||
{
|
||||
LOG.warn("Ignoring eviction setting:" + evictionTimeout);
|
||||
LOG.warn("Ignoring eviction setting: {}", evictionTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -900,7 +900,8 @@ public class SessionHandler extends ScopedHandler
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Invalidating session {} found to be expired when requested", id, e);
|
||||
LOG.warn("Invalidating session {} found to be expired when requested", id);
|
||||
LOG.warn(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -912,6 +913,7 @@ public class SessionHandler extends ScopedHandler
|
|||
}
|
||||
catch (UnreadableSessionDataException e)
|
||||
{
|
||||
LOG.warn("Error loading session {}", id);
|
||||
LOG.warn(e);
|
||||
try
|
||||
{
|
||||
|
@ -920,7 +922,8 @@ public class SessionHandler extends ScopedHandler
|
|||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
LOG.warn("Error cross-context invalidating unreadable session {}", id, x);
|
||||
LOG.warn("Error cross-context invalidating unreadable session {}", id);
|
||||
LOG.warn(x);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1195,7 +1198,7 @@ public class SessionHandler extends ScopedHandler
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Session listener threw exception", e);
|
||||
LOG.warn(e);
|
||||
}
|
||||
//call the attribute removed listeners and finally mark it as invalid
|
||||
session.finishInvalidate();
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
@ -32,10 +34,12 @@ import javax.servlet.RequestDispatcher;
|
|||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletRequestWrapper;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
@ -115,8 +119,9 @@ public class DispatcherTest
|
|||
String expected =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"\r\n";
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"FORWARD";
|
||||
|
||||
String responses = _connector.getResponse("GET /context/ForwardServlet?do=assertforward&do=more&test=1 HTTP/1.0\n\n");
|
||||
|
||||
|
@ -132,8 +137,9 @@ public class DispatcherTest
|
|||
String expected =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"\r\n";
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"FORWARD";
|
||||
String responses = _connector.getResponse("GET /context/ForwardServlet?do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1 HTTP/1.0\n\n");
|
||||
|
||||
assertEquals(expected, responses);
|
||||
|
@ -202,7 +208,9 @@ public class DispatcherTest
|
|||
|
||||
String expected =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"\r\n";
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"INCLUDE";
|
||||
|
||||
String responses = _connector.getResponse("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.0\n\n");
|
||||
|
||||
|
@ -218,7 +226,9 @@ public class DispatcherTest
|
|||
|
||||
String expected =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"\r\n";
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"INCLUDE";
|
||||
|
||||
String responses = _connector.getResponse("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.0\n\n");
|
||||
|
||||
|
@ -234,7 +244,9 @@ public class DispatcherTest
|
|||
|
||||
String expected =
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"\r\n";
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"FORWARD";
|
||||
|
||||
String responses = _connector.getResponse("GET /context/IncludeServlet/includepath?do=forward HTTP/1.0\n\n");
|
||||
|
||||
|
@ -374,6 +386,88 @@ public class DispatcherTest
|
|||
assertThat(rechoResponse, containsString("txeTohce"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrappedForwardCloseIntercepted() throws Exception
|
||||
{
|
||||
// Add filter that wraps response, intercepts close and writes after doChain
|
||||
_contextHandler.addFilter(WrappingFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
testForward();
|
||||
}
|
||||
|
||||
public static class WrappingFilter implements Filter
|
||||
{
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response);
|
||||
chain.doFilter(request, wrapper);
|
||||
wrapper.sendResponse(response.getOutputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResponseWrapper extends HttpServletResponseWrapper
|
||||
{
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
public ResponseWrapper(HttpServletResponse response)
|
||||
{
|
||||
super(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException
|
||||
{
|
||||
return new ServletOutputStream()
|
||||
{
|
||||
@Override
|
||||
public boolean isReady()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteListener(WriteListener writeListener)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException
|
||||
{
|
||||
buffer.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
buffer.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
buffer.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void sendResponse(OutputStream out) throws IOException
|
||||
{
|
||||
out.write(buffer.toByteArray());
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForwardServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
@ -649,6 +743,7 @@ public class DispatcherTest
|
|||
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getOutputStream().print(request.getDispatcherType().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -697,6 +792,7 @@ public class DispatcherTest
|
|||
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getOutputStream().print(request.getDispatcherType().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,6 +821,7 @@ public class DispatcherTest
|
|||
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getOutputStream().print(request.getDispatcherType().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -762,6 +859,7 @@ public class DispatcherTest
|
|||
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getOutputStream().print(request.getDispatcherType().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,6 +895,7 @@ public class DispatcherTest
|
|||
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getOutputStream().print(request.getDispatcherType().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ package org.eclipse.jetty.util.thread;
|
|||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
|
@ -217,7 +217,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
|
|||
}
|
||||
|
||||
int size = _size.decrementAndGet();
|
||||
thread.offer(task);
|
||||
if (!thread.offer(task))
|
||||
return false;
|
||||
|
||||
if (size == 0 && task != STOP)
|
||||
startReservedThread();
|
||||
|
@ -264,20 +265,25 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
|
|||
|
||||
private class ReservedThread implements Runnable
|
||||
{
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final Condition _wakeup = _lock.newCondition();
|
||||
private final SynchronousQueue<Runnable> _task = new SynchronousQueue<>();
|
||||
private boolean _starting = true;
|
||||
private Runnable _task = null;
|
||||
|
||||
public void offer(Runnable task)
|
||||
public boolean offer(Runnable task)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} offer {}", this, task);
|
||||
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try
|
||||
{
|
||||
_task = task;
|
||||
_wakeup.signal();
|
||||
_task.put(task);
|
||||
return true;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
_size.getAndIncrement();
|
||||
_stack.addFirst(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,37 +302,14 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
|
|||
if (!isRunning())
|
||||
return STOP;
|
||||
|
||||
boolean idle = false;
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try
|
||||
{
|
||||
if (_task == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_idleTime == 0)
|
||||
_wakeup.await();
|
||||
else
|
||||
idle = !_wakeup.await(_idleTime, _idleTimeUnit);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Runnable task = _task;
|
||||
_task = null;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} task={}", this, task);
|
||||
|
||||
Runnable task = _idleTime <= 0 ? _task.take() : _task.poll(_idleTime, _idleTimeUnit);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} task={}", this, task);
|
||||
if (task != null)
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
if (idle)
|
||||
{
|
||||
// Because threads are held in a stack, excess threads will be
|
||||
// idle. However, we cannot remove threads from the bottom of
|
||||
// the stack, so we submit a poison pill job to stop the thread
|
||||
|
@ -336,6 +319,10 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
|
|||
LOG.debug("{} IDLE", this);
|
||||
tryExecute(STOP);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,18 +20,24 @@ package org.eclipse.jetty.server.session;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionActivationListener;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -42,6 +48,58 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
*/
|
||||
public abstract class AbstractSessionCacheTest
|
||||
{
|
||||
public static class UnreadableSessionDataStore extends AbstractSessionDataStore
|
||||
{
|
||||
int _count;
|
||||
int _calls;
|
||||
SessionData _data;
|
||||
|
||||
public UnreadableSessionDataStore(int count, SessionData data)
|
||||
{
|
||||
_count = count;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassivating()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String id) throws Exception
|
||||
{
|
||||
return _data != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(String id) throws Exception
|
||||
{
|
||||
_data = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionData doLoad(String id) throws Exception
|
||||
{
|
||||
++_calls;
|
||||
if (_calls <= _count)
|
||||
throw new UnreadableSessionDataException(id, _context, new IllegalStateException("Throw for test"));
|
||||
else
|
||||
return _data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> doGetExpired(Set<String> candidates)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestSessionActivationListener implements HttpSessionActivationListener
|
||||
{
|
||||
|
@ -64,7 +122,58 @@ public abstract class AbstractSessionCacheTest
|
|||
public abstract AbstractSessionCacheFactory newSessionCacheFactory(int evictionPolicy, boolean saveOnCreate,
|
||||
boolean saveOnInactiveEvict, boolean removeUnloadableSessions,
|
||||
boolean flushOnResponseCommit);
|
||||
|
||||
|
||||
/**
|
||||
* Test that a session that exists in the datastore, but that cannot be
|
||||
* read will be invalidated and deleted, and thus a request to re-use that
|
||||
* same id will not succeed.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testUnreadableSession() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
server.setSessionIdManager(new DefaultSessionIdManager(server));
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/test");
|
||||
context.setServer(server);
|
||||
server.setHandler(context);
|
||||
|
||||
AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false);
|
||||
SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler());
|
||||
|
||||
//prefill the datastore with a session that will be treated as unreadable
|
||||
UnreadableSessionDataStore store = new UnreadableSessionDataStore(1, new SessionData("1234", "/test", "0.0.0.0", System.currentTimeMillis(), 0,0, -1));
|
||||
cache.setSessionDataStore(store);
|
||||
context.getSessionHandler().setSessionCache(cache);
|
||||
server.start();
|
||||
|
||||
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
|
||||
{
|
||||
//check that session 1234 cannot be read, ie returns null AND
|
||||
//that it is deleted in the datastore
|
||||
Session session = context.getSessionHandler().getSession("1234");
|
||||
assertNull(session);
|
||||
assertFalse(store.exists("1234"));
|
||||
|
||||
//now try to make a session with the same id as if from a request with
|
||||
//a SESSION_ID cookie set - the id from the cookie should not be able to
|
||||
//be re-used because we just deleted the session with that id. Ids cannot
|
||||
//be re-used (unless another context is already using that same id (ie cross
|
||||
//context dispatch), which is not the case in this test).
|
||||
Request request = new Request(null, null);
|
||||
request.setRequestedSessionId("1234");
|
||||
HttpSession newSession = context.getSessionHandler().newHttpSession(request);
|
||||
assertNotEquals("1234", newSession.getId());
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a new Session object can be created from
|
||||
* previously persisted data (SessionData).
|
||||
|
|
|
@ -32,6 +32,8 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
|||
import org.eclipse.jetty.client.api.Request;
|
||||
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;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -177,7 +179,7 @@ public class SessionEvictionFailureTest
|
|||
int port1 = server.getPort();
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
|
||||
{
|
||||
String url = "http://localhost:" + port1 + contextPath + servletMapping;
|
||||
|
||||
|
|
Loading…
Reference in New Issue