Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-9.4.x-3278-empty-resourcecollection

This commit is contained in:
Joakim Erdfelt 2019-02-13 17:09:38 -05:00
commit 7ee7554b8f
39 changed files with 1436 additions and 118 deletions

1
Jenkinsfile vendored
View File

@ -45,7 +45,6 @@ pipeline {
options { timeout(time: 120, unit: 'MINUTES') }
steps {
mavenBuild("jdk11", "-Pmongodb install", "maven3", false)
junit '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/TEST-*.xml'
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
maven_invoker reportsFilenamePattern: "**/target/invoker-reports/BUILD*.xml", invokerBuildDir: "**/target/it"
}

View File

@ -1,18 +1,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<version>9.4.15-SNAPSHOT</version>
</parent>
<version>9.4.15-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
<artifactId>example-async-rest-jar</artifactId>
<packaging>jar</packaging>
<name>Example Async Rest :: Jar</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.examples.async.rest</bundle-symbolic-name>
<bundle-symbolic-name>${project.parent.groupId}.examples.async.rest</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -1,17 +1,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<version>9.4.15-SNAPSHOT</version>
</parent>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
<artifactId>example-async-rest-webapp</artifactId>
<packaging>war</packaging>
<name>Example Async Rest :: Webapp</name>
<build>
<finalName>async-rest</finalName>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
@ -24,10 +28,10 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,17 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
<version>9.4.15-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<packaging>pom</packaging>
<name>Example Async Rest</name>
<modules>
<module>async-rest-jar</module>
<module>async-rest-webapp</module>
</modules>
</project>

View File

@ -1,19 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.4.15-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
<name>Jetty Examples :: Parent</name>
<packaging>pom</packaging>
<properties>
<sonar.skip>true</sonar.skip>
</properties>
<build>
<plugins>
<plugin>
@ -26,6 +29,7 @@
</plugin>
</plugins>
</build>
<modules>
<!--
- The async-rest and embedded are examples that have historical locations,

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.alpn.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -34,11 +33,10 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
import org.eclipse.jetty.io.ssl.ALPNProcessor.Client;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory implements SslHandshakeListener
public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory
{
private static final Logger LOG = Log.getLogger(ALPNClientConnectionFactory.class);
@ -96,7 +94,7 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
}
@Override
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
{
SSLEngine engine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY);
for (Client processor : processors)

View File

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

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.util.BufferUtil;
* specified to the constructors.</p>
* <p>The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)}
* via {@link #getContent()} or {@link #getContentAsString()}.</p>
* <p>Instances of this class are not reusable, so one must be allocated for each request.</p>
*/
public abstract class BufferingResponseListener extends Listener.Adapter
{

View File

@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.client.api.ContentResponse;
@ -325,7 +326,6 @@ public class HttpClientTLSTest
@Test
public void testHandshakeSucceededWithSessionResumption() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
@ -405,7 +405,6 @@ public class HttpClientTLSTest
@Test
public void testClientRawCloseDoesNotInvalidateSession() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
@ -527,4 +526,30 @@ public class HttpClientTLSTest
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@Test
public void testHostNameVerificationFailure() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
SslContextFactory clientTLSFactory = createSslContextFactory();
// Make sure the host name is not verified at the TLS level.
clientTLSFactory.setEndpointIdentificationAlgorithm(null);
// Add host name verification after the TLS handshake.
clientTLSFactory.setHostnameVerifier((host, session) -> false);
startClient(clientTLSFactory);
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send(result ->
{
Throwable failure = result.getFailure();
if (failure instanceof SSLPeerUnverifiedException)
latch.countDown();
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.hazelcast.session;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.session.AbstractSessionDataStore;
@ -74,7 +75,12 @@ public class HazelcastSessionDataStore
public boolean delete( String id )
throws Exception
{
return sessionDataMap == null ? false : sessionDataMap.remove( getCacheKey( id ) ) != null;
if (sessionDataMap == null)
return false;
//use delete which does not deserialize the SessionData object being removed
sessionDataMap.delete( getCacheKey(id));
return true;
}
public IMap<String, SessionData> getSessionDataMap()

View File

@ -23,7 +23,10 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
@ -100,6 +103,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
sslConnection.addHandshakeListener(new HTTPSHandshakeListener(context));
customize(sslConnection, context);
return sslConnection;
@ -124,4 +128,37 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
}
return ClientConnectionFactory.super.customize(connection, context);
}
private class HTTPSHandshakeListener implements SslHandshakeListener
{
private final Map<String, Object> context;
private HTTPSHandshakeListener(Map<String, Object> context)
{
this.context = context;
}
@Override
public void handshakeSucceeded(Event event) throws SSLException
{
HostnameVerifier verifier = sslContextFactory.getHostnameVerifier();
if (verifier != null)
{
String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
try
{
if (!verifier.verify(host, event.getSSLEngine().getSession()))
throw new SSLPeerUnverifiedException("Host name verification failed for host: " + host);
}
catch (SSLException x)
{
throw x;
}
catch (Throwable x)
{
throw (SSLException)new SSLPeerUnverifiedException("Host name verification failed for host: " + host).initCause(x);
}
}
}
}
}

View File

@ -759,7 +759,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
}
}
private void handshakeSucceeded()
private void handshakeSucceeded() throws SSLException
{
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.SUCCEEDED))
{
@ -1182,7 +1182,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
}
}
private void notifyHandshakeSucceeded(SSLEngine sslEngine)
private void notifyHandshakeSucceeded(SSLEngine sslEngine) throws SSLException
{
SslHandshakeListener.Event event = null;
for (SslHandshakeListener listener : handshakeListeners)
@ -1193,6 +1193,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
{
listener.handshakeSucceeded(event);
}
catch (SSLException x)
{
throw x;
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);

View File

@ -22,6 +22,7 @@ import java.util.EventListener;
import java.util.EventObject;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
/**
* <p>Implementations of this interface are notified of TLS handshake events.</p>
@ -36,7 +37,7 @@ public interface SslHandshakeListener extends EventListener
*
* @param event the event object carrying information about the TLS handshake event
*/
default void handshakeSucceeded(Event event)
default void handshakeSucceeded(Event event) throws SSLException
{
}

View File

@ -1,14 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.4.15-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jmh</artifactId>
<name>Jetty :: Jmh</name>
<description>Jmh classes for Jetty</description>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.jmh</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>
@ -39,8 +45,7 @@
<finalName>${jmhjar.name}</finalName>
<shadeTestJar>true</shadeTestJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
@ -72,6 +77,7 @@
</plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -9,7 +9,7 @@
<name>Jetty :: OSGi ALPN Fragment</name>
<packaging>jar</packaging>
<properties>
<bundle-symbolic-name>org.eclipse.jetty.osgi.alpn.fragment</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.alpn.fragment</bundle-symbolic-name>
</properties>
<build>
<plugins>

View File

@ -400,8 +400,7 @@ public class CrossOriginFilter implements Filter
{
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
//W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation
if (!anyOriginAllowed)
response.addHeader("Vary", ORIGIN_HEADER);
response.addHeader("Vary", ORIGIN_HEADER);
if (allowCredentials)
response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
if (!exposedHeaders.isEmpty())

View File

@ -135,7 +135,7 @@ public class CrossOriginFilterTest
Set<String> fieldNames = response.getFieldNamesCollection();
assertThat(response.toString(), CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, isIn(fieldNames));
assertThat(response.toString(), CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, isIn(fieldNames));
assertThat(response.toString(), "Vary", not(isIn(fieldNames)));
assertThat(response.toString(), "Vary", isIn(fieldNames));
assertTrue(latch.await(1, TimeUnit.SECONDS));
}

View File

@ -51,6 +51,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIHostName;
@ -194,6 +195,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
private int _renegotiationLimit = 5;
private Factory _factory;
private PKIXCertPathChecker _pkixCertPathChecker;
private HostnameVerifier _hostnameVerifier;
/**
* Construct an instance of SslContextFactory
@ -1596,6 +1598,31 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
_sslSessionTimeout = sslSessionTimeout;
}
/**
* @return the HostnameVerifier used by a client to verify host names in the server certificate
*/
public HostnameVerifier getHostnameVerifier()
{
return _hostnameVerifier;
}
/**
* <p>Sets a {@code HostnameVerifier} used by a client to verify host names in the server certificate.</p>
* <p>The {@code HostnameVerifier} works in conjunction with {@link #setEndpointIdentificationAlgorithm(String)}.</p>
* <p>When {@code endpointIdentificationAlgorithm=="HTTPS"} (the default) the JDK TLS implementation
* checks that the host name indication set by the client matches the host names in the server certificate.
* If this check passes successfully, the {@code HostnameVerifier} is invoked and the application
* can perform additional checks and allow/deny the connection to the server.</p>
* <p>When {@code endpointIdentificationAlgorithm==null} the JDK TLS implementation will not check
* the host names, and any check is therefore performed only by the {@code HostnameVerifier.}</p>
*
* @param hostnameVerifier the HostnameVerifier used by a client to verify host names in the server certificate
*/
public void setHostnameVerifier(HostnameVerifier hostnameVerifier)
{
_hostnameVerifier = hostnameVerifier;
}
/**
* Returns the password object for the given realm.
*

View File

@ -278,12 +278,20 @@ public class WebAppClassLoader extends URLClassLoader
StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
while (tokenizer.hasMoreTokens())
{
Resource resource= _context.newResource(tokenizer.nextToken().trim());
String token = tokenizer.nextToken().trim();
Resource resource= _context.newResource(token);
if (LOG.isDebugEnabled())
LOG.debug("Path resource=" + resource);
// Add the resource
if (resource.isDirectory() && resource instanceof ResourceCollection)
if(token.endsWith("*"))
{
if(token.length() > 1)
{
token = token.substring(0, token.length() - 1);
resource= _context.newResource(token);
addJars(resource);
}
} else if (resource.isDirectory() && resource instanceof ResourceCollection)
addClassPath(resource);
else
{
@ -525,7 +533,8 @@ public class WebAppClassLoader extends URLClassLoader
// If it is a server class, doesn't matter as we have loaded it from the
// webapp
webapp_class = this.findClass(name);
resolveClass(webapp_class);
if (resolve)
resolveClass(webapp_class);
if (LOG.isDebugEnabled())
LOG.debug("PLP webapp loaded {}",webapp_class);
return webapp_class;

View File

@ -64,6 +64,7 @@ public class WebAppClassLoaderTest
_context = new WebAppContext();
_context.setBaseResource(webapp);
_context.setContextPath("/test");
_context.setExtraClasspath("src/test/resources/ext/*");
_loader = new WebAppClassLoader(_context);
_loader.addJars(webapp.addPath("WEB-INF/lib"));
@ -105,6 +106,10 @@ public class WebAppClassLoaderTest
assertCanLoadClass("org.acme.webapp.ClassInJarB");
assertCanLoadClass("org.acme.other.ClassInClassesC");
assertCanLoadClass("org.acme.extone.Main");
assertCanLoadClass("org.acme.exttwo.Main");
assertCantLoadClass("org.acme.extthree.Main");
assertCantLoadClass("org.eclipse.jetty.webapp.Configuration");
Class<?> clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA");
@ -119,6 +124,10 @@ public class WebAppClassLoaderTest
assertCanLoadClass("org.acme.webapp.ClassInJarB");
assertCanLoadClass("org.acme.other.ClassInClassesC");
assertCanLoadClass("org.acme.extone.Main");
assertCanLoadClass("org.acme.exttwo.Main");
assertCantLoadClass("org.acme.extthree.Main");
assertCantLoadClass("org.eclipse.jetty.webapp.Configuration");
Class<?> clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA");

View File

@ -25,7 +25,6 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.AbstractConnection;
@ -125,12 +124,11 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
private final Generator generator;
private final Parser parser;
private final WebSocketPolicy policy;
private final AtomicBoolean suspendToken;
private final ReadState readState;
private final FrameFlusher flusher;
private final String id;
private WebSocketSession session;
private List<ExtensionConfig> extensions;
private boolean isFilling;
private ByteBuffer prefillBuffer;
private ReadMode readMode = ReadMode.PARSE;
private IOState ioState;
@ -147,7 +145,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
this.parser = new Parser(policy,bufferPool);
this.scheduler = scheduler;
this.extensions = new ArrayList<>();
this.suspendToken = new AtomicBoolean(false);
this.readState = new ReadState();
this.ioState = new IOState();
this.ioState.addListener(this);
this.flusher = new Flusher(bufferPool,generator,endp);
@ -302,7 +300,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public boolean isReading()
{
return isFilling;
return readState.isReading();
}
/**
@ -384,8 +382,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
try
{
isFilling = true;
if(readMode == ReadMode.PARSE)
{
readMode = readParse(buffer);
@ -400,14 +396,10 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
bufferPool.release(buffer);
}
if ((readMode != ReadMode.EOF) && (suspendToken.get() == false))
{
if (readMode == ReadMode.EOF)
readState.eof();
else if (!readState.suspend())
fillInterested();
}
else
{
isFilling = false;
}
}
@Override
@ -586,13 +578,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public void resume()
{
if (suspendToken.getAndSet(false))
{
if (!isReading())
{
fillInterested();
}
}
if (readState.resume())
fillInterested();
}
/**
@ -627,7 +614,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public SuspendToken suspend()
{
suspendToken.set(true);
readState.suspending();
return this;
}
@ -652,43 +639,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
hashCode(),
ioState,flusher,generator,parser);
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
EndPoint endp = getEndPoint();
if(endp != null)
{
result = prime * result + endp.getLocalAddress().hashCode();
result = prime * result + endp.getRemoteAddress().hashCode();
}
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractWebSocketConnection other = (AbstractWebSocketConnection)obj;
EndPoint endp = getEndPoint();
EndPoint otherEndp = other.getEndPoint();
if (endp == null)
{
if (otherEndp != null)
return false;
}
else if (!endp.equals(otherEndp))
return false;
return true;
}
/**
* Extra bytes from the initial HTTP upgrade that need to

View File

@ -0,0 +1,152 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.websocket.common.io;
import java.util.concurrent.atomic.AtomicReference;
class ReadState
{
private final AtomicReference<State> state = new AtomicReference<>(State.READING);
boolean isReading()
{
return state.get() == State.READING;
}
boolean isSuspended()
{
State current = state.get();
return current == State.SUSPENDED || current == State.EOF;
}
/**
* Requests that reads from the connection be suspended when {@link #suspend()} is called.
*
* @return whether the suspending was successful
*/
boolean suspending()
{
while (true)
{
State current = state.get();
switch (current)
{
case READING:
if (state.compareAndSet(current, State.SUSPENDING))
return true;
break;
case EOF:
return false;
default:
throw new IllegalStateException(toString(current));
}
}
}
/**
* Suspends reads from the connection if {@link #suspending()} was called.
*
* @return whether reads from the connection should be suspended
*/
boolean suspend()
{
while (true)
{
State current = state.get();
switch (current)
{
case READING:
return false;
case SUSPENDING:
if (state.compareAndSet(current, State.SUSPENDED))
return true;
break;
case EOF:
return true;
default:
throw new IllegalStateException(toString(current));
}
}
}
/**
* @return true if reads from the connection were suspended and should now resume.
*/
boolean resume()
{
while (true)
{
State current = state.get();
switch (current)
{
case SUSPENDING:
if (state.compareAndSet(current, State.READING))
return false;
break;
case SUSPENDED:
if (state.compareAndSet(current, State.READING))
return true;
break;
case EOF:
return false;
default:
throw new IllegalStateException(toString(current));
}
}
}
void eof()
{
state.set(State.EOF);
}
private String toString(State state)
{
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), state);
}
@Override
public String toString()
{
return toString(state.get());
}
private enum State
{
/**
* Reading from the connection.
*/
READING,
/**
* Suspend has been requested but not yet taken effect.
*/
SUSPENDING,
/**
* Suspended, won't read from the connection until resumed.
*/
SUSPENDED,
/**
* Won't read from the connection (terminal state).
*/
EOF,
}
}

View File

@ -0,0 +1,94 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.websocket.common.io;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ReadStateTest
{
@Test
public void testReading()
{
ReadState readState = new ReadState();
assertThat("Initially reading", readState.isReading(), is(true));
assertThat("No prior suspending", readState.suspend(), is(false));
assertThat("No prior suspending", readState.isSuspended(), is(false));
assertThrows(IllegalStateException.class, readState::resume, "No suspending to resume");
assertThat("No suspending to resume", readState.isSuspended(), is(false));
}
@Test
public void testSuspendingThenResume()
{
ReadState readState = new ReadState();
assertThat("Initially reading", readState.isReading(), is(true));
assertTrue(readState.suspending());
assertThat("Suspending doesn't take effect immediately", readState.isSuspended(), is(false));
assertThat("Resume from suspending requires no followup", readState.resume(), is(false));
assertThat("Resume from suspending requires no followup", readState.isSuspended(), is(false));
assertThat("Suspending was discarded", readState.suspend(), is(false));
assertThat("Suspending was discarded", readState.isSuspended(), is(false));
}
@Test
public void testSuspendingThenSuspendThenResume()
{
ReadState readState = new ReadState();
assertThat("Initially reading", readState.isReading(), is(true));
assertThat(readState.suspending(), is(true));
assertThat("Suspending doesn't take effect immediately", readState.isSuspended(), is(false));
assertThat("Suspended", readState.suspend(), is(true));
assertThat("Suspended", readState.isSuspended(), is(true));
assertThat("Resumed", readState.resume(), is(true));
assertThat("Resumed", readState.isSuspended(), is(false));
}
@Test
public void testEof()
{
ReadState readState = new ReadState();
readState.eof();
assertThat(readState.isReading(), is(false));
assertThat(readState.isSuspended(), is(true));
assertThat(readState.suspend(), is(true));
assertThat(readState.suspending(), is(false));
assertThat(readState.isSuspended(), is(true));
assertThat(readState.suspend(), is(true));
assertThat(readState.isSuspended(), is(true));
assertThat(readState.resume(), is(false));
assertThat(readState.isSuspended(), is(true));
}
}

View File

@ -34,13 +34,15 @@
<tycho-version>1.2.0</tycho-version>
<cbi-plugins.version>1.1.5</cbi-plugins.version>
<junit.version>5.3.1</junit.version>
<maven.version>3.5.0</maven.version>
<maven.version>3.6.0</maven.version>
<maven.resolver.version>1.3.1</maven.resolver.version>
<javax.servlet.api.version>3.1.0</javax.servlet.api.version>
<weld.version>2.4.5.Final</weld.version>
<jetty.perf-helper.version>1.0.5</jetty.perf-helper.version>
<unix.socket.tmp></unix.socket.tmp>
<!-- enable or not TestTracker junit5 extension i.e log message when test method is starting -->
<jetty.testtracker.log>false</jetty.testtracker.log>
<jpms-module-name>${bundle-symbolic-name}</jpms-module-name>
<!-- some maven plugins versions -->
<maven.surefire.version>3.0.0-M2</maven.surefire.version>
@ -514,6 +516,7 @@
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
<manifestEntries>
<Automatic-Module-Name>${jpms-module-name}</Automatic-Module-Name>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-Vendor>Eclipse Jetty Project</Implementation-Vendor>
<url>${jetty.url}</url>

View File

@ -67,5 +67,6 @@
<module>test-quickstart</module>
<module>test-jmx</module>
<module>test-http-client-transport</module>
<module>test-distribution</module>
</modules>
</project>

View File

@ -0,0 +1,100 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>tests-parent</artifactId>
<groupId>org.eclipse.jetty.tests</groupId>
<version>9.4.15-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-distribution</artifactId>
<packaging>jar</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.tests.distribution</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>${maven.resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-file</artifactId>
<version>${maven.resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-http</artifactId>
<version>${maven.resolver.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-distribution</artifactId>
<version>${project.version}</version>
<type>zip</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-http-client-transport</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-simple-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<mavenRepoPath>${settings.localRepository}</mavenRepoPath>
<jettyVersion>${project.version}</jettyVersion>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,604 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.distribution;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.aether.AbstractRepositoryListener;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* <p>Helper class to test the Jetty Distribution</p>.
* <p>API can change without any further notice.</p>
* <p>Usage:</p>
* <pre>
* // Create the distribution.
* String jettyVersion = "9.4.14.v20181114";
* DistributionTester distribution = DistributionTester.Builder.newInstance()
* .jettyVersion(jettyVersion)
* .jettyBase(Paths.get("demo-base"))
* .build();
*
* // The first run initializes the Jetty Base.
* try (DistributionTester.Run run1 = distribution.start("--create-startd", "--add-to-start=http2c,jsp,deploy"))
* {
* assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
* assertEquals(0, run1.getExitValue());
*
* // Install a web application.
* File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion);
* distribution.installWarFile(war, "test");
*
* // The second run starts the distribution.
* int port = 9090;
* try (DistributionTester.Run run = distribution.start("jetty.http.port=" + port))
* {
* // Wait for Jetty to be fully started.
* assertTrue(run1.awaitConsoleLogsFor("Started @", 20, TimeUnit.SECONDS));
*
* // Make a HTTP request to the web application.
* HttpClient client = new HttpClient();
* client.start();
* ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
* assertEquals(HttpStatus.OK_200, response.getStatus());
* }
* }
* </pre>
*/
public class DistributionTester
{
private static final Logger LOGGER = Log.getLogger(DistributionTester.class);
private Config config;
private DistributionTester(Config config)
{
this.config = config;
}
/**
* Starts the distribution with the given arguments
*
* @param args arguments to use to start the distribution
*/
public DistributionTester.Run start(String... args) throws Exception
{
return start(Arrays.asList(args));
}
/**
* Start the distribution with the arguments
*
* @param args arguments to use to start the distribution
*/
public DistributionTester.Run start(List<String> args) throws Exception
{
File jettyBaseDir = config.jettyBase.toFile();
Path workDir = Files.createDirectories(jettyBaseDir.toPath().resolve("work"));
List<String> commands = new ArrayList<>();
commands.add(getJavaExecutable());
commands.add("-Djava.io.tmpdir=" + workDir.toAbsolutePath().toString());
commands.add("-jar");
commands.add(config.jettyHome.toAbsolutePath() + "/start.jar");
commands.addAll(args);
LOGGER.info("Executing: {}", commands);
LOGGER.info("Working Dir: {}", jettyBaseDir.getAbsolutePath());
ProcessBuilder pbCmd = new ProcessBuilder(commands);
pbCmd.directory(jettyBaseDir);
Process process = pbCmd.start();
return new Run(process);
}
/**
* @return a free port chosen by the OS that can be used to listen to
* @throws IOException if a free port is not available
*/
public int freePort() throws IOException
{
try (ServerSocket server = new ServerSocket())
{
server.setReuseAddress(true);
server.bind(new InetSocketAddress("localhost", 0));
return server.getLocalPort();
}
}
/**
* Installs in {@code ${jetty.base}/webapps} the given war file under the given context path.
*
* @param warFile the war file to install
* @param context the context path
* @throws IOException if the installation fails
*/
public void installWarFile(File warFile, String context) throws IOException
{
//webapps
Path webapps = Paths.get(config.jettyBase.toString(), "webapps", context);
if (!Files.exists(webapps))
Files.createDirectories(webapps);
unzip(warFile, webapps.toFile());
}
/**
* Resolves an artifact given its Maven coordinates.
*
* @param coordinates <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>
* @return the artifact
* @see #installWarFile(File, String)
*/
public File resolveArtifact(String coordinates) throws ArtifactResolutionException
{
RepositorySystem repositorySystem = newRepositorySystem();
Artifact artifact = new DefaultArtifact(coordinates);
RepositorySystemSession session = newRepositorySystemSession(repositorySystem);
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.setArtifact(artifact);
artifactRequest.setRepositories(newRepositories());
ArtifactResult artifactResult = repositorySystem.resolveArtifact(session, artifactRequest);
artifact = artifactResult.getArtifact();
return artifact.getFile();
}
private void init() throws Exception
{
if (config.jettyHome == null)
config.jettyHome = resolveDistribution(config.jettyVersion);
if (config.jettyBase == null)
{
config.jettyBase = Files.createTempDirectory("jetty_base_");
}
else
{
if (!config.jettyBase.isAbsolute())
config.jettyBase = config.jettyHome.resolve(config.jettyBase);
}
}
private String getJavaExecutable()
{
String[] javaExecutables = new String[]{"java", "java.exe"};
File javaHomeDir = new File(System.getProperty("java.home"));
for (String javaExecutable : javaExecutables)
{
File javaFile = new File(javaHomeDir, "bin" + File.separator + javaExecutable);
if (javaFile.exists() && javaFile.isFile())
return javaFile.getAbsolutePath();
}
return "java";
}
private void unzip(File zipFile, File output) throws IOException
{
try (InputStream fileInputStream = Files.newInputStream(zipFile.toPath());
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream))
{
ZipEntry entry = zipInputStream.getNextEntry();
while (entry != null)
{
if (entry.isDirectory())
{
File dir = new File(output, entry.getName());
if (!Files.exists(dir.toPath()))
{
Files.createDirectories(dir.toPath());
}
}
else
{
// Read zipEntry and write to a file.
File file = new File(output, entry.getName());
if (!Files.exists(file.getParentFile().toPath()))
{
Files.createDirectories(file.getParentFile().toPath());
}
try (OutputStream outputStream = Files.newOutputStream(file.toPath()))
{
IOUtil.copy(zipInputStream, outputStream);
}
}
// Get next entry
entry = zipInputStream.getNextEntry();
}
}
}
private Path resolveDistribution(String version) throws Exception
{
File artifactFile = resolveArtifact("org.eclipse.jetty:jetty-distribution:zip:" + version);
// create tmp directory to unzip distribution
Path tmp = Files.createTempDirectory("jetty_home_");
File tmpFile = tmp.toFile();
unzip(artifactFile, tmpFile);
return tmp.resolve("jetty-distribution-" + version);
}
private RepositorySystem newRepositorySystem()
{
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler()
{
@Override
public void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable exception)
{
LOGGER.warn("Service creation failed for {} implementation {}: {}",
type, impl, exception.getMessage(), exception);
}
});
return locator.getService(RepositorySystem.class);
}
private List<RemoteRepository> newRepositories()
{
List<RemoteRepository> remoteRepositories = new ArrayList<>(config.mavenRemoteRepositories.size() + 1);
config.mavenRemoteRepositories.forEach((key, value) -> remoteRepositories.add(new RemoteRepository.Builder(key, "default", value).build()));
remoteRepositories.add(newCentralRepository());
return remoteRepositories;
}
private static RemoteRepository newCentralRepository()
{
return new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build();
}
private DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system)
{
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepo = new LocalRepository(config.mavenLocalRepository);
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
session.setTransferListener(new LogTransferListener());
session.setRepositoryListener(new LogRepositoryListener());
return session;
}
private static class Config
{
private Path jettyBase;
private Path jettyHome;
private String jettyVersion;
private String mavenLocalRepository = System.getProperty("user.home") + "/.m2/repository";
private Map<String, String> mavenRemoteRepositories = new HashMap<>();
@Override
public String toString()
{
return String.format("%s@%x{jettyBase=%s, jettyHome=%s, jettyVersion=%s, mavenLocalRepository=%s, mavenRemoteRepositories=%s}",
getClass().getSimpleName(),
hashCode(),
jettyBase,
jettyHome,
jettyVersion,
mavenLocalRepository,
mavenRemoteRepositories);
}
}
private static class LogTransferListener extends AbstractTransferListener
{
// no op
}
private static class LogRepositoryListener extends AbstractRepositoryListener
{
@Override
public void artifactDownloaded(RepositoryEvent event)
{
LOGGER.debug("distribution downloaded to {}", event.getFile());
}
@Override
public void artifactResolved(RepositoryEvent event)
{
LOGGER.debug("distribution resolved to {}", event.getFile());
}
}
/**
* A distribution run wraps the process that started the Jetty distribution.
*/
public static class Run implements Closeable
{
private final Process process;
private final List<ConsoleStreamer> consoleStreamers = new ArrayList<>();
private final List<String> logs = new ArrayList<>();
private Run(Process process)
{
this.process = process;
consoleStreamers.add(startPump("STDOUT", process.getInputStream()));
consoleStreamers.add(startPump("STDERR", process.getErrorStream()));
}
private ConsoleStreamer startPump(String mode, InputStream stream)
{
ConsoleStreamer pump = new ConsoleStreamer(stream);
Thread thread = new Thread(pump, "ConsoleStreamer/" + mode);
thread.start();
return pump;
}
/**
* Waits for the given time for the distribution process to stop.
*
* @param time the time to wait
* @param unit the unit of time
* @return true if the distribution process is terminated, false if the timeout elapsed
* @throws InterruptedException if the wait is interrupted
*/
public boolean awaitFor(long time, TimeUnit unit) throws InterruptedException
{
boolean result = process.waitFor(time, unit);
if (result)
stopConsoleStreamers();
return result;
}
/**
* @return the distribution process exit value
* @throws IllegalThreadStateException if the distribution process is not terminated yet
*/
public int getExitValue() throws IllegalThreadStateException
{
return process.exitValue();
}
/**
* Stops the distribution process.
*
* @see #awaitFor(long, TimeUnit)
*/
public void stop()
{
process.destroy();
stopConsoleStreamers();
}
/**
* Forcibly destroys the distribution process.
*/
public void destroy()
{
process.destroyForcibly();
stopConsoleStreamers();
}
private void stopConsoleStreamers()
{
consoleStreamers.forEach(ConsoleStreamer::stop);
}
/**
* @see #destroy()
*/
@Override
public void close()
{
destroy();
}
/**
* Awaits the console logs to contain the given text, for the given amount of time.
*
* @param txt the text that must be present in the console logs
* @param time the time to wait
* @param unit the unit of time
* @return true if the text was found, false if the timeout elapsed
* @throws InterruptedException if the wait is interrupted
*/
public boolean awaitConsoleLogsFor(String txt, long time, TimeUnit unit) throws InterruptedException
{
long end = System.nanoTime() + unit.toNanos(time);
while (System.nanoTime() < end)
{
boolean result = logs.stream().anyMatch(s -> s.contains(txt));
if (result)
return true;
Thread.sleep(250);
}
return false;
}
/**
* Simple streamer for the console output from a Process
*/
private class ConsoleStreamer implements Runnable
{
private final BufferedReader reader;
private volatile boolean stop;
public ConsoleStreamer(InputStream stream)
{
this.reader = new BufferedReader(new InputStreamReader(stream));
}
@Override
public void run()
{
try
{
String line;
while ((line = reader.readLine()) != null && !stop)
{
LOGGER.info("{}", line);
logs.add(line);
}
}
catch (IOException ignore)
{
// ignore
}
finally
{
IO.close(reader);
}
}
public void stop()
{
stop = true;
IO.close(reader);
}
}
}
public static class Builder
{
private Config config = new Config();
private Builder()
{
}
/**
* @param jettyVersion the version to use (format: 9.4.14.v20181114 9.4.15-SNAPSHOT).
* The distribution will be downloaded from local repository or remote
* @return the {@link Builder}
*/
public Builder jettyVersion(String jettyVersion)
{
config.jettyVersion = jettyVersion;
return this;
}
/**
* @param jettyHome Path to the local exploded jetty distribution
* if configured the jettyVersion parameter will not be used
* @return the {@link Builder}
*/
public Builder jettyHome(Path jettyHome)
{
config.jettyHome = jettyHome;
return this;
}
/**
* <p>Sets the path for the Jetty Base directory.</p>
* <p>If the path is relative, it will be resolved against the Jetty Home directory.</p>
*
* @param jettyBase Path to the local Jetty Base directory
* @return the {@link Builder}
*/
public Builder jettyBase(Path jettyBase)
{
config.jettyBase = jettyBase;
return this;
}
/**
* @param mavenLocalRepository Path to the local maven repository
* @return the {@link Builder}
*/
public Builder mavenLocalRepository(String mavenLocalRepository)
{
config.mavenLocalRepository = mavenLocalRepository;
return this;
}
/**
* If needed to resolve the Jetty distribution from another Maven remote repositories
*
* @param id the id
* @param url the Maven remote repository url
* @return the {@link Builder}
*/
public Builder addRemoteRepository(String id, String url)
{
config.mavenRemoteRepositories.put(id, url);
return this;
}
/**
* @return an empty instance of {@link Builder}
*/
public static Builder newInstance()
{
return new Builder();
}
/**
* @return a new configured instance of {@link DistributionTester}
*/
public DistributionTester build() throws Exception
{
DistributionTester tester = new DistributionTester(config);
tester.init();
return tester;
}
}
}

View File

@ -0,0 +1,47 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.distribution;
import java.util.function.Supplier;
import org.eclipse.jetty.client.HttpClient;
import org.junit.jupiter.api.AfterEach;
public class AbstractDistributionTest
{
protected HttpClient client;
protected void startHttpClient() throws Exception
{
startHttpClient(HttpClient::new);
}
protected void startHttpClient(Supplier<HttpClient> supplier) throws Exception
{
client = supplier.get();
client.start();
}
@AfterEach
public void dispose() throws Exception
{
if (client != null)
client.stop();
}
}

View File

@ -0,0 +1,207 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.distribution;
import java.io.File;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DistributionTests extends AbstractDistributionTest
{
@Test
public void testStartStop() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
try (DistributionTester.Run run1 = distribution.start("--add-to-start=http"))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port);
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
run2.stop();
assertTrue(run2.awaitFor(5, TimeUnit.SECONDS));
}
}
}
@Test
public void testSimpleWebAppWithJSP() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {
"--create-startd",
"--approve-all-licenses",
"--add-to-start=resources,server,http,webapp,deploy,jsp,jmx,servlet,servlets"
};
try (DistributionTester.Run run1 = distribution.start(args1))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Hello"));
assertThat(response.getContentAsString(), not(containsString("<%")));
}
}
}
@Test
@DisabledOnJre(JRE.JAVA_8)
public void testSimpleWebAppWithJSPOnModulePath() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {
"--create-startd",
"--approve-all-licenses",
"--add-to-start=resources,server,http,webapp,deploy,jsp,jmx,servlet,servlets"
};
try (DistributionTester.Run run1 = distribution.start(args1))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
int port = distribution.freePort();
String[] args2 = {
"--jpms",
"jetty.http.port=" + port
};
try (DistributionTester.Run run2 = distribution.start(args2))
{
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Hello"));
assertThat(response.getContentAsString(), not(containsString("<%")));
}
}
}
@Test
@DisabledOnJre(JRE.JAVA_8)
public void testSimpleWebAppWithJSPOverH2C() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {
"--create-startd",
"--add-to-start=http2c,jsp,deploy"
};
try (DistributionTester.Run run1 = distribution.start(args1))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
HTTP2Client h2Client = new HTTP2Client();
startHttpClient(() -> new HttpClient(new HttpClientTransportOverHTTP2(h2Client), null));
ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Hello"));
assertThat(response.getContentAsString(), not(containsString("<%")));
}
}
}
@Test
public void testDemoBase() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.jettyBase(Paths.get("demo-base"))
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
int port = distribution.freePort();
try (DistributionTester.Run run1 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run1.awaitConsoleLogsFor("Started @", 20, TimeUnit.SECONDS));
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port + "/test/jsp/dump.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("PathInfo"));
assertThat(response.getContentAsString(), not(containsString("<%")));
}
}
}

View File

@ -0,0 +1,2 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG

View File

@ -56,6 +56,7 @@ import org.eclipse.jetty.toolchain.test.JAR;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.PathAssert;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.junit.jupiter.api.Assertions;
/**
@ -315,19 +316,15 @@ public class JettyDistro
// The actual jetty-distribution-${version} directory is under this directory.
// Lets find it.
File subdirs[] = distroUnpackDir.listFiles(new FileFilter()
{
@Override
public boolean accept(File path)
File subdirs[] = distroUnpackDir.listFiles( path ->
{
if (!path.isDirectory())
{
return false;
}
return path.getName().startsWith(artifactName + "-");
}
});
);
if (subdirs.length == 0)
{
@ -550,7 +547,7 @@ public class JettyDistro
if (cmdLine == null || !cmdLine.contains("XmlConfiguration"))
{
fail("Unable to get Jetty command line");
Assertions.fail( "Unable to get Jetty command line");
}
// Need to breakdown commandline into parts, as spaces in command line will cause failures.
@ -595,7 +592,7 @@ public class JettyDistro
catch (InterruptedException e)
{
pid.destroy();
fail("Unable to get required information within time limit");
Assertions.fail( "Unable to get required information within time limit");
}
}
@ -804,7 +801,7 @@ public class JettyDistro
}
}
fail("Unable to find java bin");
Assertions.fail( "Unable to find java bin");
return "java";
}

View File

@ -49,7 +49,7 @@ import org.junit.jupiter.api.Disabled;
public class TestableJettyServer
{
private List<URL> _xmlConfigurations;
private final Map<String,String> _properties = new HashMap<String, String>();
private final Map<String,String> _properties = new HashMap<>();
private Server _server;
private int _serverPort;
private String _scheme = HttpScheme.HTTP.asString();
@ -60,7 +60,7 @@ public class TestableJettyServer
public TestableJettyServer() throws IOException
{
_xmlConfigurations = new ArrayList<URL>();
_xmlConfigurations = new ArrayList<>();
Properties properties = new Properties();
/* Establish Popular Directories */

View File

@ -39,5 +39,6 @@
<module>test-jaas-webapp</module>
<module>test-jndi-webapp</module>
<module>test-http2-webapp</module>
<module>test-simple-webapp</module>
</modules>
</project>
</project>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
<version>9.4.15-SNAPSHOT</version>
</parent>
<artifactId>test-simple-webapp</artifactId>
<packaging>war</packaging>
<name>Test :: Jetty Test Simple Webapp</name>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
version="3.1">
<display-name>Very Simple Web Application</display-name>
</web-app>

View File

@ -0,0 +1,5 @@
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>