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

This commit is contained in:
Greg Wilkins 2018-08-23 17:17:46 +10:00
commit 5afe2e215f
33 changed files with 983 additions and 386 deletions

View File

@ -179,6 +179,11 @@ public class HttpClient extends ContainerLifeCycle
public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory)
{
this.transport = transport;
if (sslContextFactory == null)
{
sslContextFactory = new SslContextFactory(false);
sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
}
this.sslContextFactory = sslContextFactory;
}

View File

@ -460,21 +460,6 @@ public class WebAppProvider extends ScanningAppProvider
{
File file = new File(filename);
//is the file that was removed a directory?
if (file.isDirectory())
{
//is there a .xml file of the same name?
if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
return; //assume we will get removed events for the xml file
//is there .war file of the same name?
if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
return; //assume we will get removed events for the war file
super.fileRemoved(filename);
return;
}
//is the file that was removed a .war file?
String lowname = file.getName().toLowerCase(Locale.ENGLISH);
if (lowname.endsWith(".war"))
@ -491,6 +476,19 @@ public class WebAppProvider extends ScanningAppProvider
//is the file that was removed an .xml file?
if (lowname.endsWith(".xml"))
{
super.fileRemoved(filename);
return;
}
//is there a .xml file of the same name?
if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
return; //assume we will get removed events for the xml file
//is there .war file of the same name?
if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
return; //assume we will get removed events for the war file
super.fileRemoved(filename);
}

View File

@ -45,6 +45,13 @@
<onlyAnalyze>org.eclipse.jetty.http.spi.*</onlyAnalyze>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@ -67,4 +74,32 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jdk9</id>
<activation>
<jdk>[1.9,)</jdk>
</activation>
<dependencies>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.0EA3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View File

@ -57,9 +57,9 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
private InetSocketAddress _addr;
private Map<String, JettyHttpContext> _contexts = new HashMap<String, JettyHttpContext>();
private Map<String, JettyHttpContext> _contexts = new HashMap<>();
private Map<String, Connector> _connectors = new HashMap<String, Connector>();
private Map<String, Connector> _connectors = new HashMap<>();
public JettyHttpServer(Server server, boolean shared)
@ -82,13 +82,14 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
@Override
public void bind(InetSocketAddress addr, int backlog) throws IOException
{
this._addr = addr;
// check if there is already a connector listening
Collection<NetworkConnector> connectors = _server.getBeans(NetworkConnector.class);
if (connectors != null)
{
for (NetworkConnector connector : connectors)
{
if (connector.getPort() == addr.getPort()) {
if (connector.getPort() == addr.getPort()||connector.getLocalPort() == addr.getPort()) {
if (LOG.isDebugEnabled()) LOG.debug("server already bound to port " + addr.getPort() + ", no need to rebind");
return;
}
@ -98,7 +99,7 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
if (_serverShared)
throw new IOException("jetty server is not bound to port " + addr.getPort());
this._addr = addr;
if (LOG.isDebugEnabled()) LOG.debug("binding server to port " + addr.getPort());
ServerConnector connector = new ServerConnector(_server);
@ -153,10 +154,24 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
throw new IllegalArgumentException("missing required 'executor' argument");
ThreadPool threadPool = _server.getThreadPool();
if (threadPool instanceof DelegatingThreadPool)
{
try
{
if (_server.isRunning())
{
_server.stop();
}
((DelegatingThreadPool) _server.getThreadPool()).setExecutor(executor);
else
_server.start();
}
catch ( Exception e )
{
throw new RuntimeException(e.getMessage(), e);
}
} else {
throw new UnsupportedOperationException( "!DelegatingThreadPool" );
}
}
@Override
public Executor getExecutor()

View File

@ -0,0 +1,85 @@
//
// ========================================================================
// 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.http.spi;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
public class TestEndpointMultiplePublishProblem
{
private static String default_impl = System.getProperty("com.sun.net.httpserver.HttpServerProvider");
@BeforeClass
public static void change_Impl()
{
System.setProperty("com.sun.net.httpserver.HttpServerProvider", JettyHttpServerProvider.class.getName());
}
@AfterClass
public static void restore_Impl()
{
if(default_impl != null)
{
System.setProperty( "com.sun.net.httpserver.HttpServerProvider", default_impl );
}
}
@Test
public void mainJetty() throws Exception {
Server jettyWebServer = new Server(new DelegatingThreadPool(new QueuedThreadPool()));
ServerConnector connector = new ServerConnector(jettyWebServer);
connector.setHost("localhost");
connector.setPort(0);
connector.setReuseAddress(true);
jettyWebServer.addConnector(connector);
jettyWebServer.setHandler(new ContextHandlerCollection());
JettyHttpServerProvider.setServer(jettyWebServer);
jettyWebServer.start();
Endpoint.publish(String.format("http://%s:%d/hello", "localhost", 0), new Ws());
// Comment out the below line for success in later java such as java8_u172, works before u151 or so
Endpoint.publish(String.format("http://%s:%d/hello2", "localhost", 0), new Ws());
int port = connector.getLocalPort();
System.out.printf("Started, check: http://localhost:%d/hello?wsdl%n", port);
}
@WebService
public static class Ws {
@WebMethod
public String hello() {
return "Hello";
}
}
}

View File

@ -71,12 +71,7 @@ public class TestSPIServer
server = new JettyHttpServerProvider().createHttpServer(null, 10);
final HttpContext httpContext = server.createContext("/",
new HttpHandler()
{
@Override
public void handle(HttpExchange exchange) throws IOException
{
exchange -> {
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Content-Type","text/plain");
exchange.sendResponseHeaders(200,0);
@ -93,8 +88,6 @@ public class TestSPIServer
responseBody.write(s.getBytes());
}
responseBody.close();
}
});
httpContext.setAuthenticator(new BasicAuthenticator("Test")
@ -161,12 +154,7 @@ public class TestSPIServer
InetSocketAddress("localhost", 0), 10);
final HttpContext httpContext = server.createContext("/",
new HttpHandler()
{
@Override
public void handle(HttpExchange exchange) throws IOException
{
exchange -> {
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Content-Type","text/plain");
exchange.sendResponseHeaders(200,0);
@ -183,8 +171,6 @@ public class TestSPIServer
responseBody.write(s.getBytes());
}
responseBody.close();
}
});
httpContext.setAuthenticator(new BasicAuthenticator("Test")

View File

@ -237,14 +237,6 @@ public class HttpGenerator
if (header==null)
return Result.NEED_HEADER;
// If we have not been told our persistence, set the default
if (_persistent==null)
{
_persistent=info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
if (!_persistent && HttpMethod.CONNECT.is(info.getMethod()))
_persistent=true;
}
// prepare the header
int pos=BufferUtil.flipToFill(header);
try
@ -413,19 +405,9 @@ public class HttpGenerator
HttpVersion version=info.getHttpVersion();
if (version==null)
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"No version");
switch(version)
if (version==HttpVersion.HTTP_0_9)
{
case HTTP_1_0:
if (_persistent==null)
_persistent=Boolean.FALSE;
break;
case HTTP_1_1:
if (_persistent==null)
_persistent=Boolean.TRUE;
break;
default:
_persistent = false;
_endOfContent=EndOfContent.EOF_CONTENT;
if (BufferUtil.hasContent(content))
@ -702,7 +684,7 @@ public class HttpGenerator
_persistent=false;
}
if (!http11 && field.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
if (info.getHttpVersion() == HttpVersion.HTTP_1_0 && _persistent==null && field.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
{
_persistent=true;
}
@ -734,6 +716,9 @@ public class HttpGenerator
boolean assumed_content = assumed_content_request || content_type || chunked_hint;
boolean nocontent_request = request!=null && content_length<=0 && !assumed_content;
if (_persistent==null)
_persistent = http11 || (request!=null && HttpMethod.CONNECT.is(request.getMethod()));
// If the message is known not to have content
if (_noContentResponse || nocontent_request)
{

View File

@ -27,6 +27,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@ -698,7 +699,10 @@ public class StreamResetTest extends AbstractTest
// Give time to the server to process the reset and drain the flusher queue.
Thread.sleep(500);
HTTP2Session session = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).getBean(HTTP2Session.class);
AbstractHTTP2ServerConnectionFactory http2 = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class);
Set<Session> sessions = http2.getBean(AbstractHTTP2ServerConnectionFactory.HTTP2SessionContainer.class).getSessions();
Assert.assertEquals(1, sessions.size());
HTTP2Session session = (HTTP2Session)sessions.iterator().next();
HTTP2Flusher flusher = session.getBean(HTTP2Flusher.class);
Assert.assertEquals(0, flusher.getFrameQueueSize());
}

View File

@ -18,13 +18,18 @@
package org.eclipse.jetty.http2.server;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
@ -38,12 +43,14 @@ import org.eclipse.jetty.server.HttpConfiguration;
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.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle;
@ManagedObject
public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConnectionFactory
{
private final Connection.Listener connectionListener = new ConnectionListener();
private final HTTP2SessionContainer sessionContainer = new HTTP2SessionContainer();
private final HttpConfiguration httpConfiguration;
private int maxDynamicTableSize = 4096;
private int initialSessionRecvWindow = 1024 * 1024;
@ -66,6 +73,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
for (String p:protocols)
if (!HTTP2ServerConnection.isSupportedProtocol(p))
throw new IllegalArgumentException("Unsupported HTTP2 Protocol variant: "+p);
addBean(sessionContainer);
this.httpConfiguration = Objects.requireNonNull(httpConfiguration);
addBean(httpConfiguration);
setInputBufferSize(Frame.DEFAULT_MAX_LENGTH + Frame.HEADER_LENGTH);
@ -234,7 +242,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
connection.addListener(connectionListener);
connection.addListener(sessionContainer);
return configure(connection, connector, endPoint);
}
@ -245,18 +253,55 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize());
}
private class ConnectionListener implements Connection.Listener
@ManagedObject("The container of HTTP/2 sessions")
public static class HTTP2SessionContainer implements Connection.Listener, Dumpable
{
private final Set<Session> sessions = ConcurrentHashMap.newKeySet();
@Override
public void onOpened(Connection connection)
{
addManaged((LifeCycle)((HTTP2Connection)connection).getSession());
Session session = ((HTTP2Connection)connection).getSession();
sessions.add(session);
LifeCycle.start(session);
}
@Override
public void onClosed(Connection connection)
{
removeBean(((HTTP2Connection)connection).getSession());
Session session = ((HTTP2Connection)connection).getSession();
if (sessions.remove(session))
LifeCycle.stop(session);
}
public Set<Session> getSessions()
{
return new HashSet<>(sessions);
}
@ManagedAttribute(value = "The number of HTTP/2 sessions", readonly = true)
public int getSize()
{
return sessions.size();
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dumpObject(out, this);
ContainerLifeCycle.dump(out, indent, sessions);
}
@Override
public String toString()
{
return String.format("%s@%x[size=%d]", getClass().getSimpleName(), hashCode(), getSize());
}
}
}

View File

@ -199,8 +199,20 @@ public abstract class AbstractConnection implements Connection
LOG.debug("onOpen {}", this);
for (Listener listener : _listeners)
onOpened(listener);
}
private void onOpened(Listener listener)
{
try
{
listener.onOpened(this);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
@Override
public void onClose()
@ -209,8 +221,20 @@ public abstract class AbstractConnection implements Connection
LOG.debug("onClose {}",this);
for (Listener listener : _listeners)
onClosed(listener);
}
private void onClosed(Listener listener)
{
try
{
listener.onClosed(this);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
@Override
public EndPoint getEndPoint()

View File

@ -93,15 +93,19 @@ public abstract class FillInterest
/**
* Call to signal that a read is now possible.
*/
public void fillable()
public boolean fillable()
{
if (LOG.isDebugEnabled())
LOG.debug("fillable {}",this);
Callback callback = _interested.get();
if (callback != null && _interested.compareAndSet(callback, null))
{
callback.succeeded();
else if (LOG.isDebugEnabled())
return true;
}
if (LOG.isDebugEnabled())
LOG.debug("{} lost race {}",this,callback);
return false;
}
/**

View File

@ -399,27 +399,27 @@ public class SslConnection extends AbstractConnection
try
{
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
boolean waiting_for_fill = false;
boolean waiting_for_fill;
synchronized(_decryptedEndPoint)
{
if (LOG.isDebugEnabled())
LOG.debug("onFillable {}", SslConnection.this);
_fillState = FillState.IDLE;
switch(_flushState)
{
case WAIT_FOR_FILL:
waiting_for_fill = true;
break;
default:
break;
}
waiting_for_fill = _flushState==FlushState.WAIT_FOR_FILL;
}
// Ensure a fill is always done if needed then wake up any fill interest
getFillInterest().fillable();
if (waiting_for_fill)
{
synchronized(_decryptedEndPoint)
{
waiting_for_fill = _flushState==FlushState.WAIT_FOR_FILL;
}
if (waiting_for_fill)
fill(BufferUtil.EMPTY_BUFFER);
getFillInterest().fillable();
}
}
catch (Throwable e)
{
@ -1247,10 +1247,10 @@ public class SslConnection extends AbstractConnection
_fillState = FillState.IDLE;
}
_decryptedEndPoint.getWriteFlusher().completeWrite();
if (fillable)
_decryptedEndPoint.getFillInterest().fillable();
_decryptedEndPoint.getWriteFlusher().completeWrite();
}
@Override

View File

@ -54,7 +54,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
public static final int DEFAULT_MAX_IDLE_TIME = 30000;
private Server server;
private ServerConnector delegate;
private volatile ServerConnector delegate;
private String host;
private String name;
private int port;
@ -133,33 +133,28 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
this.delegate = null;
}
/**
* @see org.eclipse.jetty.util.component.Graceful#shutdown()
*/
@Override
public Future<Void> shutdown()
{
checkDelegate();
return this.delegate.shutdown();
return checkDelegate().shutdown();
}
@Override
public boolean isShutdown()
{
return checkDelegate().isShutdown();
}
/**
* @see org.eclipse.jetty.server.Connector#getServer()
*/
@Override
public Server getServer()
{
return this.server;
}
/**
* @see org.eclipse.jetty.server.Connector#getExecutor()
*/
@Override
public Executor getExecutor()
{
checkDelegate();
return this.delegate.getExecutor();
return checkDelegate().getExecutor();
}
/**
@ -168,8 +163,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public Scheduler getScheduler()
{
checkDelegate();
return this.delegate.getScheduler();
return checkDelegate().getScheduler();
}
/**
@ -178,8 +172,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public ByteBufferPool getByteBufferPool()
{
checkDelegate();
return this.delegate.getByteBufferPool();
return checkDelegate().getByteBufferPool();
}
/**
@ -188,8 +181,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public ConnectionFactory getConnectionFactory(String nextProtocol)
{
checkDelegate();
return this.delegate.getConnectionFactory(nextProtocol);
return checkDelegate().getConnectionFactory(nextProtocol);
}
/**
@ -198,8 +190,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public <T> T getConnectionFactory(Class<T> factoryType)
{
checkDelegate();
return this.delegate.getConnectionFactory(factoryType);
return checkDelegate().getConnectionFactory(factoryType);
}
/**
@ -208,8 +199,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public ConnectionFactory getDefaultConnectionFactory()
{
checkDelegate();
return this.delegate.getDefaultConnectionFactory();
return checkDelegate().getDefaultConnectionFactory();
}
/**
@ -218,8 +208,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public Collection<ConnectionFactory> getConnectionFactories()
{
checkDelegate();
return this.delegate.getConnectionFactories();
return checkDelegate().getConnectionFactories();
}
/**
@ -228,8 +217,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public List<String> getProtocols()
{
checkDelegate();
return this.delegate.getProtocols();
return checkDelegate().getProtocols();
}
/**
@ -239,8 +227,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
public long getIdleTimeout()
{
checkDelegate();
return this.delegate.getIdleTimeout();
return checkDelegate().getIdleTimeout();
}
/**
@ -249,8 +236,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public Object getTransport()
{
checkDelegate();
return this.delegate.getTransport();
return checkDelegate().getTransport();
}
/**
@ -259,8 +245,7 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
@Override
public Collection<EndPoint> getConnectedEndPoints()
{
checkDelegate();
return this.delegate.getConnectedEndPoints();
return checkDelegate().getConnectedEndPoints();
}
/**
@ -277,9 +262,11 @@ public class MavenServerConnector extends ContainerLifeCycle implements Connecto
return this.delegate.getLocalPort();
}
private void checkDelegate() throws IllegalStateException
private ServerConnector checkDelegate() throws IllegalStateException
{
if (this.delegate == null)
ServerConnector d = this.delegate;
if (d == null)
throw new IllegalStateException ("MavenServerConnector delegate not ready");
return d;
}
}

View File

@ -41,13 +41,13 @@ import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -153,6 +153,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private final Thread[] _acceptors;
private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
private final Graceful.Shutdown _shutdown = new Graceful.Shutdown();
private CountDownLatch _stopping;
private long _idleTimeout = 30000;
private String _defaultProtocol;
@ -261,6 +262,8 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
protected void doStart() throws Exception
{
_shutdown.cancel();
if(_defaultProtocol==null)
throw new IllegalStateException("No default protocol for "+this);
_defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
@ -305,7 +308,13 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
public Future<Void> shutdown()
{
return new FutureCallback(true);
return _shutdown.shutdown();
}
@Override
public boolean isShutdown()
{
return _shutdown.isShutdown();
}
@Override

View File

@ -135,6 +135,16 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
return _state;
}
public boolean addListener(Listener listener)
{
return _listeners.add(listener);
}
public boolean removeListener(Listener listener)
{
return _listeners.remove(listener);
}
public long getBytesWritten()
{
return _written;

View File

@ -704,6 +704,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_callback = callback;
_header = null;
_shutdownOut = false;
if (getConnector().isShutdown())
_generator.setPersistent(false);
return true;
}
@ -818,6 +822,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
case DONE:
{
// If shutdown after commit, we can still close here.
if (getConnector().isShutdown())
_shutdownOut=true;
return Action.SUCCEEDED;
}
case CONTINUE:

View File

@ -406,7 +406,7 @@ public class Request implements HttpServletRequest
_parameters=_contentParameters;
else if (isNoParams(_contentParameters) || _contentParameters.size()==0)
_parameters=_queryParameters;
else
else if(_parameters == null)
{
_parameters = new MultiMap<>();
_parameters.addAllValues(_queryParameters);

View File

@ -18,6 +18,23 @@
package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpGenerator;
@ -32,7 +49,6 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.URIUtil;
@ -40,7 +56,6 @@ import org.eclipse.jetty.util.Uptime;
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.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -49,24 +64,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/* ------------------------------------------------------------ */
/** Jetty HTTP Servlet Server.
* This class is the main class for the Jetty HTTP Servlet server.
@ -465,45 +462,20 @@ public class Server extends HandlerWrapper implements Attributes
MultiException mex = new MultiException();
// list if graceful futures
List<Future<Void>> futures = new ArrayList<>();
// First close the network connectors to stop accepting new connections
for (Connector connector : _connectors)
futures.add(connector.shutdown());
// Then tell the contexts that we are shutting down
Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
for (Handler graceful : gracefuls)
futures.add(((Graceful)graceful).shutdown());
// Shall we gracefully wait for zero connections?
long stopTimeout = getStopTimeout();
if (stopTimeout>0)
{
long stop_by=System.currentTimeMillis()+stopTimeout;
if (LOG.isDebugEnabled())
LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
// Wait for shutdowns
for (Future<Void> future: futures)
{
try
{
if (!future.isDone())
future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
// list if graceful futures
List<Future<Void>> futures = new ArrayList<>();
// First shutdown the network connectors to stop accepting new connections
for (Connector connector : _connectors)
futures.add(connector.shutdown());
// then shutdown all graceful handlers
doShutdown(futures);
}
catch (Exception e)
catch(Throwable e)
{
mex.add(e);
}
}
}
// Cancel any shutdowns not done
for (Future<Void> future: futures)
if (!future.isDone())
future.cancel(true);
// Now stop the connectors (this will close existing connections)
for (Connector connector : _connectors)

View File

@ -18,14 +18,21 @@
package org.eclipse.jetty.server.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HandlerContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
@ -35,6 +42,8 @@ import org.eclipse.jetty.server.Server;
*/
public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
{
private static final Logger LOG = Log.getLogger(AbstractHandlerContainer.class);
/* ------------------------------------------------------------ */
public AbstractHandlerContainer()
{
@ -135,4 +144,60 @@ public abstract class AbstractHandlerContainer extends AbstractHandler implement
for (Handler h : handlers)
h.setServer(server);
}
/* ------------------------------------------------------------ */
/**
* Shutdown nested Gracefule handlers
*
* @param futures A list of Futures which must also be waited on for the shutdown (or null)
* returns A MultiException to which any failures are added or null
*/
protected void doShutdown(List<Future<Void>> futures) throws MultiException
{
MultiException mex = null;
// tell the graceful handlers that we are shutting down
Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
if (futures==null)
futures = new ArrayList<>(gracefuls.length);
for (Handler graceful : gracefuls)
futures.add(((Graceful)graceful).shutdown());
// Wait for all futures with a reducing time budget
long stopTimeout = getStopTimeout();
if (stopTimeout>0)
{
long stop_by=System.currentTimeMillis()+stopTimeout;
if (LOG.isDebugEnabled())
LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
// Wait for shutdowns
for (Future<Void> future: futures)
{
try
{
if (!future.isDone())
future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
}
catch (Exception e)
{
// If the future is also a callback, fail it here (rather than cancel) so we can capture the exception
if (future instanceof Callback && !future.isDone())
((Callback)future).failed(e);
if (mex==null)
mex = new MultiException();
mex.add(e);
}
}
}
// Cancel any shutdowns not done
for (Future<Void> future: futures)
if (!future.isDone())
future.cancel(true);
if (mex!=null)
mex.ifExceptionThrowMulti();
}
}

View File

@ -76,7 +76,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.StringUtil;
@ -969,6 +968,21 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
protected void doStop() throws Exception
{
// Should we attempt a graceful shutdown?
MultiException mex = null;
if (getStopTimeout()>0)
{
try
{
doShutdown(null);
}
catch (MultiException e)
{
mex = e;
}
}
_availability = Availability.UNAVAILABLE;
ClassLoader old_classloader = null;
@ -1014,6 +1028,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
_programmaticListeners.clear();
}
catch(Throwable x)
{
if (mex==null)
mex = new MultiException();
mex.add(x);
}
finally
{
__context.set(old_context);
@ -1022,11 +1042,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// reset the classloader
if ((old_classloader == null || (old_classloader != old_webapploader)) && current_thread != null)
current_thread.setContextClassLoader(old_classloader);
}
_scontext.clearAttributes();
}
if (mex!=null)
mex.ifExceptionThrow();
}
/* ------------------------------------------------------------ */
public boolean checkVirtualHost(final Request baseRequest)
{

View File

@ -20,10 +20,8 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import javax.servlet.AsyncEvent;
@ -70,7 +68,14 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
private final LongAdder _responses5xx = new LongAdder();
private final LongAdder _responsesTotalBytes = new LongAdder();
private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
private final Graceful.Shutdown _shutdown = new Graceful.Shutdown()
{
@Override
protected FutureCallback newShutdownCallback()
{
return new FutureCallback(_requestStats.getCurrent()==0);
}
};
private final AtomicBoolean _wrapWarning = new AtomicBoolean();
@ -165,16 +170,15 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
try
{
Handler handler = getHandler();
if (handler!=null && _shutdown.get()==null && isStarted())
if (handler!=null && !_shutdown.isShutdown() && isStarted())
handler.handle(path, baseRequest, request, response);
else if (baseRequest.isHandled())
{
if (_wrapWarning.compareAndSet(false,true))
LOG.warn("Bad statistics configuration. Latencies will be incorrect in {}",this);
}
else
{
if (!baseRequest.isHandled())
baseRequest.setHandled(true);
else if (_wrapWarning.compareAndSet(false,true))
LOG.warn("Bad statistics configuration. Latencies will be incorrect in {}",this);
if (!baseRequest.getResponse().isCommitted())
response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
}
}
@ -248,19 +252,16 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
@Override
protected void doStart() throws Exception
{
_shutdown.set(null);
_shutdown.cancel();
super.doStart();
statsReset();
}
@Override
protected void doStop() throws Exception
{
_shutdown.cancel();
super.doStop();
FutureCallback shutdown = _shutdown.get();
if (shutdown!=null && !shutdown.isDone())
shutdown.failed(new TimeoutException());
}
/**
@ -551,7 +552,6 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
sb.append("<h2>Dispatches:</h2>\n");
sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
@ -561,7 +561,6 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
@ -575,17 +574,23 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
return sb.toString();
}
@Override
public Future<Void> shutdown()
{
FutureCallback shutdown=new FutureCallback(false);
_shutdown.compareAndSet(null,shutdown);
shutdown=_shutdown.get();
if (_requestStats.getCurrent()==0)
shutdown.succeeded();
return shutdown;
return _shutdown.shutdown();
}
@Override
public boolean isShutdown()
{
return _shutdown.isShutdown();
}
@Override
public String toString()
{
return String.format("%s@%x{%s,r=%d,d=%d}",getClass().getSimpleName(),hashCode(),getState(),_requestStats.getCurrent(),_dispatchedStats.getCurrent());
}
}

View File

@ -23,10 +23,10 @@ import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -35,6 +35,7 @@ import java.net.Socket;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@ -46,14 +47,15 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
@ -105,15 +107,8 @@ public class GracefulStopTest
assertThat(TimeUnit.NANOSECONDS.toMillis(stop-start),lessThan(900L));
assertThat(client.getInputStream().read(),Matchers.is(-1));
assertThat(handler.handling.get(),Matchers.is(false));
assertThat(handler.thrown.get(),
Matchers.anyOf(
instanceOf(ClosedChannelException.class),
instanceOf(EofException.class),
instanceOf(EOFException.class))
);
assertThat(handler.thrown.get(),Matchers.notNullValue());
client.close();
}
@ -228,7 +223,7 @@ public class GracefulStopTest
// Try creating a new connection
try
{
new Socket("127.0.0.1", port);
try(Socket s = new Socket("127.0.0.1", port)){}
throw new IllegalStateException();
}
catch(ConnectException e)
@ -286,10 +281,10 @@ public class GracefulStopTest
{
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
public Connection newConnection(Connector con, EndPoint endPoint)
{
// Slow closing connection
HttpConnection conn = new HttpConnection(getHttpConfiguration(), connector, endPoint, getHttpCompliance(), isRecordHttpComplianceViolations())
HttpConnection conn = new HttpConnection(getHttpConfiguration(), con, endPoint, getHttpCompliance(), isRecordHttpComplianceViolations())
{
@Override
public void close()
@ -322,7 +317,7 @@ public class GracefulStopTest
}
}
};
return configure(conn, connector, endPoint);
return configure(conn, con, endPoint);
}
});
@ -421,6 +416,251 @@ public class GracefulStopTest
testSlowClose(5000,1000,Matchers.allOf(greaterThan(750L),lessThan(4999L)));
}
@Test
public void testResponsesAreClosed() throws Exception
{
Server server= new Server();
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);
StatisticsHandler stats = new StatisticsHandler();
server.setHandler(stats);
ContextHandler context = new ContextHandler(stats,"/");
Exchanger<Void> exchanger0 = new Exchanger<>();
Exchanger<Void> exchanger1 = new Exchanger<>();
context.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(200);
response.setContentLength(13);
response.flushBuffer();
try
{
exchanger0.exchange(null);
exchanger1.exchange(null);
}
catch(Throwable x)
{
throw new ServletException(x);
}
response.getOutputStream().print("The Response\n");
}
});
server.setStopTimeout(1000);
server.start();
LocalEndPoint endp = connector.executeRequest("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
exchanger0.exchange(null);
exchanger1.exchange(null);
String response = endp.getResponse();
assertThat(response,containsString("200 OK"));
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"));
exchanger0.exchange(null);
server.getConnectors()[0].shutdown().get();
// Check completed 200 does not have close
exchanger1.exchange(null);
response = endp.getResponse();
assertThat(response,containsString("200 OK"));
assertThat(response,Matchers.not(containsString("Connection: close")));
// But endpoint is still closes soon after
long end = System.nanoTime()+TimeUnit.SECONDS.toNanos(1);
while (endp.isOpen() && System.nanoTime()<end)
Thread.sleep(10);
Assert.assertFalse(endp.isOpen());
}
@Test
public void testCommittedResponsesAreClosed() throws Exception
{
Server server= new Server();
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);
StatisticsHandler stats = new StatisticsHandler();
server.setHandler(stats);
ContextHandler context = new ContextHandler(stats,"/");
Exchanger<Void> exchanger0 = new Exchanger<>();
Exchanger<Void> exchanger1 = new Exchanger<>();
context.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
try
{
exchanger0.exchange(null);
exchanger1.exchange(null);
}
catch(Throwable x)
{
throw new ServletException(x);
}
baseRequest.setHandled(true);
response.setStatus(200);
response.getWriter().println("The Response");
response.getWriter().close();
}
});
server.setStopTimeout(1000);
server.start();
LocalEndPoint endp = connector.executeRequest(
"GET / HTTP/1.1\r\n"+
"Host: localhost\r\n" +
"\r\n"
);
exchanger0.exchange(null);
exchanger1.exchange(null);
String response = endp.getResponse();
assertThat(response,containsString("200 OK"));
assertThat(response,Matchers.not(containsString("Connection: close")));
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
exchanger0.exchange(null);
CountDownLatch latch = new CountDownLatch(1);
new Thread(()->
{
try
{
server.stop();
latch.countDown();
}
catch(Exception e)
{
e.printStackTrace();
}
}).start();
while(server.isStarted())
Thread.sleep(10);
// Check new connections rejected!
String unavailable = connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n");
assertThat(unavailable,containsString(" 503 Service Unavailable"));
assertThat(unavailable,Matchers.containsString("Connection: close"));
// Check completed 200 has close
exchanger1.exchange(null);
response = endp.getResponse();
assertThat(response,containsString("200 OK"));
assertThat(response,Matchers.containsString("Connection: close"));
assertTrue(latch.await(10,TimeUnit.SECONDS));
}
@Test
public void testContextStop() throws Exception
{
Server server= new Server();
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);
ContextHandler context = new ContextHandler(server,"/");
StatisticsHandler stats = new StatisticsHandler();
context.setHandler(stats);
Exchanger<Void> exchanger0 = new Exchanger<>();
Exchanger<Void> exchanger1 = new Exchanger<>();
stats.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
try
{
exchanger0.exchange(null);
exchanger1.exchange(null);
}
catch(Throwable x)
{
throw new ServletException(x);
}
baseRequest.setHandled(true);
response.setStatus(200);
response.getWriter().println("The Response");
response.getWriter().close();
}
});
context.setStopTimeout(1000);
server.start();
LocalEndPoint endp = connector.executeRequest(
"GET / HTTP/1.1\r\n"+
"Host: localhost\r\n" +
"\r\n"
);
exchanger0.exchange(null);
exchanger1.exchange(null);
String response = endp.getResponse();
assertThat(response,containsString("200 OK"));
assertThat(response,Matchers.not(containsString("Connection: close")));
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
exchanger0.exchange(null);
CountDownLatch latch = new CountDownLatch(1);
new Thread(()->
{
try
{
context.stop();
latch.countDown();
}
catch(Exception e)
{
e.printStackTrace();
}
}).start();
while(context.isStarted())
Thread.sleep(10);
// Check new connections accepted, but don't find context!
String unavailable = connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n");
assertThat(unavailable,containsString(" 404 Not Found"));
// Check completed 200 does not have close
exchanger1.exchange(null);
response = endp.getResponse();
assertThat(response,containsString("200 OK"));
assertThat(response,Matchers.not(Matchers.containsString("Connection: close")));
assertTrue(latch.await(10,TimeUnit.SECONDS));
}
static class NoopHandler extends AbstractHandler
{

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -1252,7 +1253,7 @@ public class RequestTest
200, TimeUnit.MILLISECONDS
);
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(
@ -1282,7 +1283,7 @@ public class RequestTest
"\n"
);
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponse(

View File

@ -29,7 +29,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class FutureCallback implements Future<Void>,Callback
{
private static Throwable COMPLETED=new ConstantThrowable();
private final static Throwable COMPLETED=new ConstantThrowable();
public final static FutureCallback SUCCEEDED=new FutureCallback(true);
private final AtomicBoolean _done=new AtomicBoolean(false);
private final CountDownLatch _latch=new CountDownLatch(1);
private Throwable _cause;

View File

@ -234,4 +234,17 @@ public abstract class AbstractLifeCycle implements LifeCycle
@Override public void lifeCycleStopped(LifeCycle event) {}
@Override public void lifeCycleStopping(LifeCycle event) {}
}
@Override
public String toString()
{
Class<?> clazz = getClass();
String name = clazz.getSimpleName();
if ((name==null || name.length()==0) && clazz.getSuperclass()!=null)
{
clazz = clazz.getSuperclass();
name = clazz.getSimpleName();
}
return String.format("%s@%x{%s}",name,hashCode(),getState());
}
}

View File

@ -19,6 +19,9 @@
package org.eclipse.jetty.util.component;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.FutureCallback;
/* ------------------------------------------------------------ */
/* A Lifecycle that can be gracefully shutdown.
@ -26,4 +29,41 @@ import java.util.concurrent.Future;
public interface Graceful
{
public Future<Void> shutdown();
public boolean isShutdown();
public static class Shutdown implements Graceful
{
private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
protected FutureCallback newShutdownCallback()
{
return FutureCallback.SUCCEEDED;
}
@Override
public Future<Void> shutdown()
{
return _shutdown.updateAndGet(fcb->{return fcb==null?newShutdownCallback():fcb;});
}
@Override
public boolean isShutdown()
{
return _shutdown.get()!=null;
}
public void cancel()
{
FutureCallback shutdown = _shutdown.getAndSet(null);
if (shutdown!=null && !shutdown.isDone())
shutdown.cancel(true);
}
public FutureCallback get()
{
return _shutdown.get();
}
}
}

View File

@ -352,6 +352,17 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
out.append(String.valueOf(this)).append(" trustAll=").append(Boolean.toString(_trustAll)).append(System.lineSeparator());
try
{
List<SslSelectionDump> selections = selectionDump();
ContainerLifeCycle.dump(out, indent, selections);
}
catch (NoSuchAlgorithmException ignore)
{
LOG.ignore(ignore);
}
}
List<SslSelectionDump> selectionDump() throws NoSuchAlgorithmException
{
/* Use a pristine SSLEngine (not one from this SslContextFactory).
* This will allow for proper detection and identification
@ -359,7 +370,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
*/
SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();
List<Object> selections = new ArrayList<>();
List<SslSelectionDump> selections = new ArrayList<>();
// protocols
selections.add(new SslSelectionDump("Protocol",
@ -375,12 +386,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
getExcludeCipherSuites(),
getIncludeCipherSuites()));
ContainerLifeCycle.dump(out, indent, selections);
}
catch (NoSuchAlgorithmException ignore)
{
LOG.ignore(ignore);
}
return selections;
}
@Override

View File

@ -30,9 +30,9 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
class SslSelectionDump extends ContainerLifeCycle implements Dumpable
{
private static class CaptionedList extends ArrayList<String> implements Dumpable
static class CaptionedList extends ArrayList<String> implements Dumpable
{
private final String caption;
@ -57,9 +57,9 @@ public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
}
}
private final String type;
private SslSelectionDump.CaptionedList enabled = new SslSelectionDump.CaptionedList("Enabled");
private SslSelectionDump.CaptionedList disabled = new SslSelectionDump.CaptionedList("Disabled");
final String type;
final SslSelectionDump.CaptionedList enabled = new SslSelectionDump.CaptionedList("Enabled");
final SslSelectionDump.CaptionedList disabled = new SslSelectionDump.CaptionedList("Disabled");
public SslSelectionDump(String type,
String[] supportedByJVM,
@ -87,15 +87,6 @@ public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
StringBuilder s = new StringBuilder();
s.append(entry);
if (!jvmEnabled.contains(entry))
{
if (isPresent)
{
s.append(" -");
isPresent = false;
}
s.append(" JreDisabled:java.security");
}
for (Pattern pattern : excludedPatterns)
{
@ -115,9 +106,10 @@ public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
}
}
boolean isIncluded = false;
if (!includedPatterns.isEmpty())
{
boolean isIncluded = false;
for (Pattern pattern : includedPatterns)
{
Matcher m = pattern.matcher(entry);
@ -139,10 +131,22 @@ public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
{
s.append(",");
}
s.append(" ConfigIncluded:NotSpecified");
s.append(" ConfigIncluded:NotSelected");
}
}
if (!isIncluded && !jvmEnabled.contains(entry))
{
if (isPresent)
{
s.append(" -");
isPresent = false;
}
s.append(" JVM:disabled");
}
if (isPresent)
{
enabled.add(s.toString());

View File

@ -205,65 +205,65 @@ public class ContainerLifeCycleTest
{
ContainerLifeCycle a0 = new ContainerLifeCycle();
String dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, "ContainerLifeCycl");
ContainerLifeCycle aa0 = new ContainerLifeCycle();
a0.addBean(aa0);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " +? org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " +? ContainerLife");
ContainerLifeCycle aa1 = new ContainerLifeCycle();
a0.addBean(aa1);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " +? org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +? org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " +? ContainerLife");
dump = check(dump, " +? ContainerLife");
dump = check(dump, "");
ContainerLifeCycle aa2 = new ContainerLifeCycle();
a0.addBean(aa2, false);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " +? org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +? org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +~ org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " +? ContainerLife");
dump = check(dump, " +? ContainerLife");
dump = check(dump, " +~ ContainerLife");
dump = check(dump, "");
aa1.start();
a0.start();
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +~ org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +~ org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " +~ ContainerLife");
dump = check(dump, " +~ ContainerLife");
dump = check(dump, "");
a0.manage(aa1);
a0.removeBean(aa2);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " += ContainerLife");
dump = check(dump, "");
ContainerLifeCycle aaa0 = new ContainerLifeCycle();
aa0.addBean(aaa0);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, "");
ContainerLifeCycle aa10 = new ContainerLifeCycle();
aa1.addBean(aa10, true);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " += org.eclipse.jetty.util.component.Container");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " += Container");
dump = check(dump, "");
final ContainerLifeCycle a1 = new ContainerLifeCycle();
@ -282,57 +282,57 @@ public class ContainerLifeCycleTest
};
a0.addBean(aa, true);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | += org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | += Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, "");
a2.addBean(aa0, true);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | += org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " | += org.eclipse.jetty.util.component.Conta");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.C");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | += Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, " | += Conta");
dump = check(dump, " | +~ C");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, "");
a2.unmanage(aa0);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | += org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Conta");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, " +- org.eclipse.jetty.util.component.Container");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | += Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, " | +~ Conta");
dump = check(dump, " +- Container");
dump = check(dump, " +- Container");
dump = check(dump, "");
a0.unmanage(aa);
dump = trim(a0.dump());
dump = check(dump, "org.eclipse.jetty.util.component.ContainerLifeCycl");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | +~ org.eclipse.jetty.util.component.Container");
dump = check(dump, " += org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, " | += org.eclipse.jetty.util.component.Container");
dump = check(dump, " +~ org.eclipse.jetty.util.component.ContainerLife");
dump = check(dump, "ContainerLifeCycl");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | +~ Container");
dump = check(dump, " += ContainerLife");
dump = check(dump, " | += Container");
dump = check(dump, " +~ ContainerLife");
dump = check(dump, "");
}
@ -580,7 +580,7 @@ public class ContainerLifeCycleTest
s = s.substring(0, nl);
}
Assert.assertEquals(x, s);
Assert.assertThat(s,Matchers.startsWith(x));
return r;
}

View File

@ -18,10 +18,14 @@
package org.eclipse.jetty.util.ssl;
import static org.eclipse.jetty.toolchain.test.matchers.RegexMatcher.matchesPattern;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
@ -30,7 +34,12 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@ -70,7 +79,49 @@ public class SslContextFactoryTest
cf.start();
cf.dump(System.out, "");
// cf.dump(System.out, "");
List<SslSelectionDump> dumps = cf.selectionDump();
SslSelectionDump cipherDump = dumps.stream()
.filter((dump)-> dump.type.contains("Cipher Suite"))
.findFirst().get();
for(String enabledCipher : cipherDump.enabled)
{
assertThat("Enabled Cipher Suite", enabledCipher, not(matchesPattern(".*_RSA_.*(SHA1|MD5|SHA)")));
}
}
@Test
public void testDump_IncludeTlsRsa() throws Exception
{
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.setIncludeCipherSuites("TLS_RSA_.*");
cf.setExcludeCipherSuites("BOGUS"); // just to not exclude anything
cf.start();
// cf.dump(System.out, "");
List<SslSelectionDump> dumps = cf.selectionDump();
SSLEngine ssl = SSLContext.getDefault().createSSLEngine();
List<String> tlsRsaSuites = Stream.of(ssl.getSupportedCipherSuites())
.filter((suite)->suite.startsWith("TLS_RSA_"))
.collect(Collectors.toList());
List<String> selectedSuites = Arrays.asList(cf.getSelectedCipherSuites());
SslSelectionDump cipherDump = dumps.stream()
.filter((dump)-> dump.type.contains("Cipher Suite"))
.findFirst().get();
assertThat("Dump Enabled List size is equal to selected list size", cipherDump.enabled.size(), is(selectedSuites.size()));
for(String expectedCipherSuite: tlsRsaSuites)
{
assertThat("Selected Cipher Suites", selectedSuites, hasItem(expectedCipherSuite));
assertThat("Dump Enabled Cipher Suites", cipherDump.enabled, hasItem(expectedCipherSuite));
}
}
@Test

View File

@ -313,6 +313,13 @@ public class WebInfConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()+" file="+(web_app.getFile()));
// Track the original web_app Resource, as this could be a PathResource.
// Later steps force the Resource to be a JarFileResource, which introduces
// URLConnection caches in such a way that it prevents Hot Redeployment
// on MS Windows.
Resource originalWarResource = web_app;
// Is the WAR usable directly?
if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
{
@ -373,8 +380,9 @@ public class WebInfConfiguration extends AbstractConfiguration
}
else
{
//only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
// Only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
// Use the original War Resource to obtain lastModified to avoid filesystem locks on MS Windows.
if (originalWarResource.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
{
extractionLock.createNewFile();
IO.delete(extractedWebAppDir);

View File

@ -45,6 +45,8 @@ class DefaultHttpClientProvider
if (sslContextFactory == null)
{
sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustAll(false);
sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
}
HttpClient client = new HttpClient(sslContextFactory);

36
pom.xml
View File

@ -641,7 +641,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<version>0.8.2</version>
</plugin>
<plugin>
<groupId>com.agilejava.docbkx</groupId>
@ -1050,40 +1050,6 @@
</dependencyManagement>
<profiles>
<!-- use last snapshot of jacoco for jdk11 -->
<profile>
<id>jdk11</id>
<activation>
<jdk>11</jdk>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2-SNAPSHOT</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<pluginRepositories>
<pluginRepository>
<id>oss.snapshots</id>
<name>OSS Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>errorprone</id>
<build>