org.apache.kerby
kerb-simplekdc
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 76caef9bb3d1..8eb987c34946 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -923,8 +923,10 @@ public void setMaxRedirects(int maxRedirects)
/**
* @return whether TCP_NODELAY is enabled
+ * @deprecated use {@link ClientConnector#isTCPNoDelay()} instead
*/
@ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
+ @Deprecated
public boolean isTCPNoDelay()
{
return tcpNoDelay;
@@ -933,7 +935,9 @@ public boolean isTCPNoDelay()
/**
* @param tcpNoDelay whether TCP_NODELAY is enabled
* @see java.net.Socket#setTcpNoDelay(boolean)
+ * @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead
*/
+ @Deprecated
public void setTCPNoDelay(boolean tcpNoDelay)
{
this.tcpNoDelay = tcpNoDelay;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
index ca01c19287fb..1efb74bb83e2 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
@@ -60,7 +60,6 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
-import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
@@ -1029,6 +1028,7 @@ public void testForcedNonDomainSNI() throws Exception
.send();
assertEquals(HttpStatus.OK_200, response2.getStatus());
+ /* TODO Fix. See #6624
if (Net.isIpv6InterfaceAvailable())
{
// Send a request with SNI "[::1]", we should get the certificate at alias=ip.
@@ -1038,6 +1038,7 @@ public void testForcedNonDomainSNI() throws Exception
assertEquals(HttpStatus.OK_200, response3.getStatus());
}
+ */
}
@Test
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 2a33f7148839..ab46fcdb2505 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -40,7 +40,6 @@
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -49,7 +48,6 @@
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.ReadListener;
-import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -75,6 +73,7 @@
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
@@ -91,7 +90,6 @@
import org.eclipse.jetty.util.SocketAddressResolver;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -688,48 +686,6 @@ public void onComplete(Result result)
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
- @ParameterizedTest
- @ArgumentsSource(ScenarioProvider.class)
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
- public void testRequestIdleTimeout(Scenario scenario) throws Exception
- {
- long idleTimeout = 1000;
- start(scenario, new AbstractHandler()
- {
- @Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
- {
- try
- {
- baseRequest.setHandled(true);
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
- }
- catch (InterruptedException x)
- {
- throw new ServletException(x);
- }
- }
- });
-
- String host = "localhost";
- int port = connector.getLocalPort();
- assertThrows(TimeoutException.class, () ->
- client.newRequest(host, port)
- .scheme(scenario.getScheme())
- .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send());
-
- // Make another request without specifying the idle timeout, should not fail
- ContentResponse response = client.newRequest(host, port)
- .scheme(scenario.getScheme())
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send();
-
- assertNotNull(response);
- assertEquals(200, response.getStatus());
- }
-
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testSendToIPv6Address(Scenario scenario) throws Exception
@@ -1954,6 +1910,45 @@ public long getLength()
assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish");
}
+ @ParameterizedTest
+ @ArgumentsSource(ScenarioProvider.class)
+ public void testBindAddress(Scenario scenario) throws Exception
+ {
+ String bindAddress = "127.0.0.2";
+ start(scenario, new EmptyServerHandler()
+ {
+ @Override
+ protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ assertEquals(bindAddress, request.getRemoteAddr());
+ }
+ });
+
+ client.setBindAddress(new InetSocketAddress(bindAddress, 0));
+
+ CountDownLatch latch = new CountDownLatch(1);
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scenario.getScheme())
+ .path("/1")
+ .onRequestBegin(r ->
+ {
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scenario.getScheme())
+ .path("/2")
+ .send(result ->
+ {
+ assertTrue(result.isSucceeded());
+ assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
+ latch.countDown();
+ });
+ })
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
private void assertCopyRequest(Request original)
{
Request copy = client.copyRequest((HttpRequest)original, original.getURI());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index a28631cfe3fc..b1ec697f01ef 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -32,13 +32,12 @@
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -213,8 +212,6 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testBadRequestWithSlowRequestRemovesConnection(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
@@ -423,8 +420,6 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
@@ -448,10 +443,7 @@ public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Ex
connector.stop();
// Give the connection some time to process the remote close
- TimeUnit.SECONDS.sleep(1);
-
- assertEquals(0, idleConnections.size());
- assertEquals(0, activeConnections.size());
+ await().atMost(5, TimeUnit.SECONDS).until(() -> idleConnections.size() == 0 && activeConnections.size() == 0);
}
@ParameterizedTest
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
index 1712e73a5065..8ec4f83dd194 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -35,8 +35,8 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -91,7 +91,6 @@ public void onSuccess(Request request)
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testSendNoRequestContentIncompleteFlush() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
@@ -105,7 +104,7 @@ public void testSendNoRequestContentIncompleteFlush() throws Exception
StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
// Wait for the write to complete
- TimeUnit.SECONDS.sleep(1);
+ await().atMost(5, TimeUnit.SECONDS).until(() -> endPoint.toEndPointString().contains(",flush=P,"));
String chunk = endPoint.takeOutputString();
while (chunk.length() > 0)
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
index b45ac747af6b..fee6a3d2617c 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -24,7 +24,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
@@ -48,7 +47,6 @@
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
@@ -410,47 +408,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
assertArrayEquals(data, response.getContent());
}
- @Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
- public void testRequestIdleTimeout() throws Exception
- {
- final long idleTimeout = 1000;
- start(new AbstractHandler()
- {
- @Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
- {
- try
- {
- baseRequest.setHandled(true);
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
- }
- catch (InterruptedException x)
- {
- throw new ServletException(x);
- }
- }
- });
-
- final String host = "localhost";
- final int port = connector.getLocalPort();
- assertThrows(TimeoutException.class, () ->
- client.newRequest(host, port)
- .scheme(scheme)
- .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send());
-
- // Make another request without specifying the idle timeout, should not fail
- ContentResponse response = client.newRequest(host, port)
- .scheme(scheme)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send();
-
- assertNotNull(response);
- assertEquals(200, response.getStatus());
- }
-
@Test
public void testConnectionIdleTimeout() throws Exception
{
diff --git a/jetty-home/src/main/resources/bin/jetty.sh b/jetty-home/src/main/resources/bin/jetty.sh
index c128bbad2ce7..3a429107d13a 100755
--- a/jetty-home/src/main/resources/bin/jetty.sh
+++ b/jetty-home/src/main/resources/bin/jetty.sh
@@ -66,7 +66,8 @@ NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//')
# /webapps/jetty.war
#
# JETTY_BASE
-# Where your Jetty base directory is. If not set, the value from
+# Where your Jetty base directory is. If not set, then the currently
+# directory is checked, otherwise the value from
# $JETTY_HOME will be used.
#
# JETTY_RUN
@@ -238,7 +239,6 @@ then
fi
fi
-
##################################################
# No JETTY_HOME yet? We're out of luck!
##################################################
@@ -247,20 +247,23 @@ if [ -z "$JETTY_HOME" ]; then
exit 1
fi
+RUN_DIR=$(pwd)
cd "$JETTY_HOME"
-JETTY_HOME=$PWD
-
+JETTY_HOME=$(pwd)
##################################################
# Set JETTY_BASE
##################################################
+export JETTY_BASE
if [ -z "$JETTY_BASE" ]; then
- JETTY_BASE=$JETTY_HOME
+ if [ -d "$RUN_DIR/start.d" -o -f "$RUN_DIR/start.ini" ]; then
+ JETTY_BASE=$RUN_DIR
+ else
+ JETTY_BASE=$JETTY_HOME
+ fi
fi
-
cd "$JETTY_BASE"
-JETTY_BASE=$PWD
-
+JETTY_BASE=$(pwd)
#####################################################
# Check that jetty is where we think it is
@@ -430,7 +433,7 @@ case "`uname`" in
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
esac
-RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]})
+RUN_ARGS=$(echo $JAVA_OPTIONS ; "$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args ${JETTY_ARGS[*]})
RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
#####################################################
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
index f316cbedb0eb..001584b3a1f1 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
@@ -93,6 +93,8 @@ protected void doStart() throws Exception
client.setInputBufferSize(httpClient.getResponseBufferSize());
client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
+ client.setConnectBlocking(httpClient.isConnectBlocking());
+ client.setBindAddress(httpClient.getBindAddress());
}
addBean(client);
super.doStart();
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index f0a4c987e17f..414db0623366 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -18,6 +18,7 @@
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.SocketException;
+import java.net.SocketOption;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectableChannel;
@@ -33,6 +34,8 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Promise;
+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.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -41,6 +44,32 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * The client-side component that connects to server sockets.
+ * ClientConnector delegates the handling of {@link SocketChannel}s
+ * to a {@link SelectorManager}, and centralizes the configuration of
+ * necessary components such as the executor, the scheduler, etc.
+ * ClientConnector offers a low-level API that can be used to
+ * connect {@link SocketChannel}s to listening servers via the
+ * {@link #connect(SocketAddress, Map)} method.
+ * However, a ClientConnector instance is typically just configured
+ * and then passed to an HttpClient transport, so that applications
+ * can use high-level APIs to make HTTP requests to servers:
+ *
+ * // Create a ClientConnector instance.
+ * ClientConnector connector = new ClientConnector();
+ *
+ * // Configure the ClientConnector.
+ * connector.setSelectors(1);
+ * connector.setSslContextFactory(new SslContextFactory.Client());
+ *
+ * // Pass it to the HttpClient transport.
+ * HttpClientTransport transport = new HttpClientTransportDynamic(connector);
+ * HttpClient httpClient = new HttpClient(transport);
+ * httpClient.start();
+ *
+ */
+@ManagedObject
public class ClientConnector extends ContainerLifeCycle
{
public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
@@ -49,6 +78,12 @@ public class ClientConnector extends ContainerLifeCycle
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
+ /**
+ * Creates a ClientConnector configured to connect via Unix-Domain sockets to the given Unix-Domain path
+ *
+ * @param path the Unix-Domain path to connect to
+ * @return a ClientConnector that connects to the given Unix-Domain path
+ */
public static ClientConnector forUnixDomain(Path path)
{
return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
@@ -65,7 +100,11 @@ public static ClientConnector forUnixDomain(Path path)
private Duration connectTimeout = Duration.ofSeconds(5);
private Duration idleTimeout = Duration.ofSeconds(30);
private SocketAddress bindAddress;
+ private boolean tcpNoDelay = true;
private boolean reuseAddress = true;
+ private boolean reusePort;
+ private int receiveBufferSize = -1;
+ private int sendBufferSize = -1;
public ClientConnector()
{
@@ -129,6 +168,10 @@ public void setSslContextFactory(SslContextFactory.Client sslContextFactory)
this.sslContextFactory = sslContextFactory;
}
+ /**
+ * @return the number of NIO selectors
+ */
+ @ManagedAttribute("The number of NIO selectors")
public int getSelectors()
{
return selectors;
@@ -141,6 +184,10 @@ public void setSelectors(int selectors)
this.selectors = selectors;
}
+ /**
+ * @return whether {@link #connect(SocketAddress, Map)} operations are performed in blocking mode
+ */
+ @ManagedAttribute("Whether connect operations are performed in blocking mode")
public boolean isConnectBlocking()
{
return connectBlocking;
@@ -151,6 +198,10 @@ public void setConnectBlocking(boolean connectBlocking)
this.connectBlocking = connectBlocking;
}
+ /**
+ * @return the timeout of {@link #connect(SocketAddress, Map)} operations
+ */
+ @ManagedAttribute("The timeout of connect operations")
public Duration getConnectTimeout()
{
return connectTimeout;
@@ -163,6 +214,10 @@ public void setConnectTimeout(Duration connectTimeout)
selectorManager.setConnectTimeout(connectTimeout.toMillis());
}
+ /**
+ * @return the max duration for which a connection can be idle (that is, without traffic of bytes in either direction)
+ */
+ @ManagedAttribute("The duration for which a connection can be idle")
public Duration getIdleTimeout()
{
return idleTimeout;
@@ -173,26 +228,120 @@ public void setIdleTimeout(Duration idleTimeout)
this.idleTimeout = idleTimeout;
}
+ /**
+ * @return the address to bind a socket to before the connect operation
+ */
+ @ManagedAttribute("The socket address to bind sockets to before the connect operation")
public SocketAddress getBindAddress()
{
return bindAddress;
}
+ /**
+ * Sets the bind address of sockets before the connect operation.
+ * In multi-homed hosts, you may want to connect from a specific address:
+ *
+ * clientConnector.setBindAddress(new InetSocketAddress("127.0.0.2", 0));
+ *
+ * Note the use of the port {@code 0} to indicate that a different ephemeral port
+ * should be used for each different connection.
+ * In the rare cases where you want to use the same port for all connections,
+ * you must also call {@link #setReusePort(boolean) setReusePort(true)}.
+ *
+ * @param bindAddress the socket address to bind to before the connect operation
+ */
public void setBindAddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
}
+ /**
+ * @return whether small TCP packets are sent without delay
+ */
+ @ManagedAttribute("Whether small TCP packets are sent without delay")
+ public boolean isTCPNoDelay()
+ {
+ return tcpNoDelay;
+ }
+
+ public void setTCPNoDelay(boolean tcpNoDelay)
+ {
+ this.tcpNoDelay = tcpNoDelay;
+ }
+
+ /**
+ * @return whether rebinding is allowed with sockets in tear-down states
+ */
+ @ManagedAttribute("Whether rebinding is allowed with sockets in tear-down states")
public boolean getReuseAddress()
{
return reuseAddress;
}
+ /**
+ * Sets whether it is allowed to bind a socket to a socket address
+ * that may be in use by another socket in tear-down state, for example
+ * in TIME_WAIT state.
+ * This is useful when ClientConnector is restarted: an existing connection
+ * may still be using a network address (same host and same port) that is also
+ * chosen for a new connection.
+ *
+ * @param reuseAddress whether rebinding is allowed with sockets in tear-down states
+ * @see #setReusePort(boolean)
+ */
public void setReuseAddress(boolean reuseAddress)
{
this.reuseAddress = reuseAddress;
}
+ /**
+ * @return whether binding to same host and port is allowed
+ */
+ @ManagedAttribute("Whether binding to same host and port is allowed")
+ public boolean isReusePort()
+ {
+ return reusePort;
+ }
+
+ /**
+ * Sets whether it is allowed to bind multiple sockets to the same
+ * socket address (same host and same port).
+ *
+ * @param reusePort whether binding to same host and port is allowed
+ */
+ public void setReusePort(boolean reusePort)
+ {
+ this.reusePort = reusePort;
+ }
+
+ /**
+ * @return the receive buffer size in bytes, or -1 for the default value
+ */
+ @ManagedAttribute("The receive buffer size in bytes")
+ public int getReceiveBufferSize()
+ {
+ return receiveBufferSize;
+ }
+
+ public void setReceiveBufferSize(int receiveBufferSize)
+ {
+ this.receiveBufferSize = receiveBufferSize;
+ }
+
+ /**
+ * @return the send buffer size in bytes, or -1 for the default value
+ */
+ @ManagedAttribute("The send buffer size in bytes")
+ public int getSendBufferSize()
+ {
+ return sendBufferSize;
+ }
+
+ public void setSendBufferSize(int sendBufferSize)
+ {
+ this.sendBufferSize = sendBufferSize;
+ }
+
@Override
protected void doStart() throws Exception
{
@@ -246,10 +395,12 @@ public void connect(SocketAddress address, Map context)
SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context);
channel = channelWithAddress.getSocketChannel();
address = channelWithAddress.getSocketAddress();
+
+ configure(channel);
+
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
bind(channel, bindAddress);
- configure(channel);
boolean connected = true;
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
@@ -306,33 +457,36 @@ public void accept(SocketChannel channel, Map context)
}
}
- private void bind(SocketChannel channel, SocketAddress bindAddress)
+ private void bind(SocketChannel channel, SocketAddress bindAddress) throws IOException
{
- try
- {
- boolean reuseAddress = getReuseAddress();
- if (LOG.isDebugEnabled())
- LOG.debug("Binding to {} reusing address {}", bindAddress, reuseAddress);
- channel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress);
- channel.bind(bindAddress);
- }
- catch (Throwable x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Could not bind {}", channel);
- }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Binding {} to {}", channel, bindAddress);
+ channel.bind(bindAddress);
}
protected void configure(SocketChannel channel) throws IOException
+ {
+ setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, isTCPNoDelay());
+ setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
+ setSocketOption(channel, StandardSocketOptions.SO_REUSEPORT, isReusePort());
+ int receiveBufferSize = getReceiveBufferSize();
+ if (receiveBufferSize >= 0)
+ setSocketOption(channel, StandardSocketOptions.SO_RCVBUF, receiveBufferSize);
+ int sendBufferSize = getSendBufferSize();
+ if (sendBufferSize >= 0)
+ setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize);
+ }
+
+ private void setSocketOption(SocketChannel channel, SocketOption option, T value)
{
try
{
- channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
+ channel.setOption(option, value);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
- LOG.debug("Could not configure {}", channel);
+ LOG.debug("Could not configure {} to {} on {}", option, value, channel);
}
}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
index 68eb13bb47eb..d93d9dfb6531 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
@@ -22,7 +22,6 @@
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -30,7 +29,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -55,23 +53,21 @@ public void dispose() throws Exception
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception
{
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 0));
SocketAddress address = server.getLocalAddress();
- final AtomicLong timeoutConnection = new AtomicLong();
- final long connectTimeout = 1000;
+ CountDownLatch connectionFinishedLatch = new CountDownLatch(1);
+ CountDownLatch failedConnectionLatch = new CountDownLatch(1);
+ long connectTimeout = 1000;
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
{
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
- endPoint.setIdleTimeout(connectTimeout / 2);
- return endPoint;
+ return new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
}
@Override
@@ -79,15 +75,17 @@ protected boolean doFinishConnect(SelectableChannel channel) throws IOException
{
try
{
- long timeout = timeoutConnection.get();
- if (timeout > 0)
- TimeUnit.MILLISECONDS.sleep(timeout);
+ assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
return super.doFinishConnect(channel);
}
catch (InterruptedException e)
{
return false;
}
+ finally
+ {
+ connectionFinishedLatch.countDown();
+ }
}
@Override
@@ -116,40 +114,36 @@ protected void connectionFailed(SelectableChannel channel, Throwable ex, Object
{
SocketChannel client1 = SocketChannel.open();
client1.configureBlocking(false);
- client1.connect(address);
- long timeout = connectTimeout * 2;
- timeoutConnection.set(timeout);
- final CountDownLatch latch1 = new CountDownLatch(1);
+ assertFalse(client1.connect(address));
selectorManager.connect(client1, new Callback()
{
@Override
public void failed(Throwable x)
{
- latch1.countDown();
+ failedConnectionLatch.countDown();
}
});
- assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
+ assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
assertFalse(client1.isOpen());
- // Wait for the first connect to finish, as the selector thread is waiting in finishConnect().
- Thread.sleep(timeout);
+ // Wait for the first connect to finish, as the selector thread is waiting in doFinishConnect().
+ assertTrue(connectionFinishedLatch.await(5, TimeUnit.SECONDS));
// Verify that after the failure we can connect successfully.
try (SocketChannel client2 = SocketChannel.open())
{
client2.configureBlocking(false);
- client2.connect(address);
- timeoutConnection.set(0);
- final CountDownLatch latch2 = new CountDownLatch(1);
+ assertFalse(client2.connect(address));
+ CountDownLatch successfulConnectionLatch = new CountDownLatch(1);
selectorManager.connect(client2, new Callback()
{
@Override
public void succeeded()
{
- latch2.countDown();
+ successfulConnectionLatch.countDown();
}
});
- assertTrue(latch2.await(connectTimeout * 5, TimeUnit.MILLISECONDS));
+ assertTrue(successfulConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
assertTrue(client2.isOpen());
}
}
diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml
index 7caf00bf284e..7bbd856a06b3 100644
--- a/jetty-jndi/pom.xml
+++ b/jetty-jndi/pom.xml
@@ -60,14 +60,19 @@
org.eclipse.jetty
jetty-slf4j-impl
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java
index c4ea95f41a89..a635445bae4a 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java
@@ -13,8 +13,6 @@
package org.eclipse.jetty.webapp;
-import java.util.ServiceLoader;
-
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,9 +22,7 @@
* This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jaas package.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jaas package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jaas package, so that this configuration only be
- * loaded if the jetty-jaas jars are on the classpath.
+ * which is unknown to the jaas package.
*
*/
public class JaasConfiguration extends AbstractConfiguration
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
index aba74957f58b..7dccaaea820a 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
@@ -24,7 +24,7 @@
/**
* JettyWebConfiguration.
*
- * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml
+ * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty8-web.xml, jetty-web.xml or web-jetty.xml
*/
public class JettyWebXmlConfiguration extends AbstractConfiguration
{
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
index 2eebbbebad68..ed9cef8fd595 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
@@ -13,8 +13,6 @@
package org.eclipse.jetty.webapp;
-import java.util.ServiceLoader;
-
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,9 +22,7 @@
* This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jmx package. This class is defined
* in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jmx package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jmx package, so that this configuration only be
- * loaded if the jetty-jmx jars are on the classpath.
+ * which is unknown to the jmx package.
*
*/
public class JmxConfiguration extends AbstractConfiguration
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java
index 5c446b1b2097..2de34e0c6fff 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java
@@ -13,8 +13,6 @@
package org.eclipse.jetty.webapp;
-import java.util.ServiceLoader;
-
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,9 +22,7 @@
* This configuration configures the WebAppContext system/server classes to
* be able to see the org.eclipse.jetty.jaas package.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jndi package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jndi package, so that this configuration only be
- * loaded if the jetty-jndi jars are on the classpath.
+ * which is unknown to the jndi package.
*
*/
public class JndiConfiguration extends AbstractConfiguration
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java
index 12333f5bbd1e..dd6d1e3cb6cc 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java
@@ -13,8 +13,6 @@
package org.eclipse.jetty.webapp;
-import java.util.ServiceLoader;
-
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,9 +22,7 @@
* This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jsp and org.eclipse.jetty.apache packages.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jsp package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jsp package, so that this configuration only be
- * loaded if the jetty-jsp jars are on the classpath.
+ * which is unknown to the jsp package.
*
*/
public class JspConfiguration extends AbstractConfiguration
diff --git a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
index 7c6bd9bb4ec0..bbaf05c4e5a7 100644
--- a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
+++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
@@ -256,7 +256,7 @@ public SocketAddress getRemoteAddress()
@Override
public boolean isOutputOpen()
{
- return false;
+ return true;
}
@Override
diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
index 99cfd8bb2e6f..030312178243 100644
--- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
+++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
@@ -133,6 +133,9 @@ public void onOpen(CoreSession coreSession, Callback callback)
// Rewire EndpointConfig to call CoreSession setters if Jetty specific properties are set.
endpointConfig = getWrappedEndpointConfig();
session = new JavaxWebSocketSession(container, coreSession, this, endpointConfig);
+ if (!session.isOpen())
+ throw new IllegalStateException("Session is not open");
+
openHandle = InvokerUtils.bindTo(openHandle, session, endpointConfig);
closeHandle = InvokerUtils.bindTo(closeHandle, session);
errorHandle = InvokerUtils.bindTo(errorHandle, session);
@@ -171,7 +174,9 @@ public void onOpen(CoreSession coreSession, Callback callback)
if (openHandle != null)
openHandle.invoke();
- container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session));
+ if (session.isOpen())
+ container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session));
+
callback.succeeded();
}
catch (Throwable cause)
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java
new file mode 100644
index 000000000000..859a8568e5e6
--- /dev/null
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.javax.tests;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CloseInOnOpenTest
+{
+ private Server server;
+ private ServerConnector connector;
+ private JavaxWebSocketServerContainer serverContainer;
+ private JavaxWebSocketClientContainer client;
+
+ @BeforeEach
+ public void beforeEach() throws Exception
+ {
+ server = new Server();
+
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ JavaxWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) ->
+ wsContainer.addEndpoint(ClosingListener.class));
+ server.start();
+
+ serverContainer = JavaxWebSocketServerContainer.getContainer(context.getServletContext());
+ assertNotNull(serverContainer);
+
+ client = new JavaxWebSocketClientContainer();
+ client.start();
+ }
+
+ @AfterEach
+ public void afterEach() throws Exception
+ {
+ client.stop();
+ server.stop();
+ }
+
+ @Test
+ public void testCloseInOnWebSocketConnect() throws Exception
+ {
+ URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws");
+ EventSocket clientEndpoint = new EventSocket();
+
+ client.connectToServer(clientEndpoint, uri);
+ assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.VIOLATED_POLICY));
+
+ assertThat(serverContainer.getOpenSessions().size(), is(0));
+ }
+
+ @ServerEndpoint("/ws")
+ public static class ClosingListener
+ {
+ @OnOpen
+ public void onWebSocketConnect(Session session) throws Exception
+ {
+ session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "I am a WS that closes immediately"));
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
index d3235ad0ed73..992c9042dd2a 100644
--- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
+++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
@@ -151,6 +151,8 @@ public void onOpen(CoreSession coreSession, Callback callback)
{
customizer.customize(coreSession);
session = new WebSocketSession(container, coreSession, this);
+ if (!session.isOpen())
+ throw new IllegalStateException("Session is not open");
frameHandle = InvokerUtils.bindTo(frameHandle, session);
openHandle = InvokerUtils.bindTo(openHandle, session);
@@ -172,7 +174,8 @@ public void onOpen(CoreSession coreSession, Callback callback)
if (openHandle != null)
openHandle.invoke();
- container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session));
+ if (session.isOpen())
+ container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session));
callback.succeeded();
demand();
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java
new file mode 100644
index 000000000000..903c51c5ed6d
--- /dev/null
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java
@@ -0,0 +1,95 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.tests;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketConnectionListener;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
+import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CloseInOnOpenTest
+{
+ private Server server;
+ private ServerConnector connector;
+ private JettyWebSocketServerContainer serverContainer;
+ private WebSocketClient client;
+
+ @BeforeEach
+ public void beforeEach() throws Exception
+ {
+ server = new Server();
+
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) ->
+ wsContainer.addMapping("/ws", (req, resp) -> new ClosingListener()));
+ server.start();
+
+ serverContainer = JettyWebSocketServerContainer.getContainer(context.getServletContext());
+ assertNotNull(serverContainer);
+
+ client = new WebSocketClient();
+ client.start();
+ }
+
+ @AfterEach
+ public void afterEach() throws Exception
+ {
+ client.stop();
+ server.stop();
+ }
+
+ @Test
+ public void testCloseInOnWebSocketConnect() throws Exception
+ {
+ URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws");
+ EventSocket clientEndpoint = new EventSocket();
+
+ client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
+ assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientEndpoint.closeCode, is(StatusCode.POLICY_VIOLATION));
+
+ assertThat(serverContainer.getOpenSessions().size(), is(0));
+ }
+
+ public static class ClosingListener implements WebSocketConnectionListener
+ {
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ session.close(StatusCode.POLICY_VIOLATION, "I am a WS that closes immediately");
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 3048dc8de693..2cfbc481c778 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1131,6 +1131,11 @@
hamcrest
${hamcrest.version}
+