Merged branch 'jetty-9.4.x' into 'jetty-9.4.x-4201-httpclient_throw_sslhandshakeexception'.
This commit is contained in:
commit
20e0453da8
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
org.eclipse.jetty.embedded.JettyDistribution.LEVEL=DEBUG
|
org.eclipse.jetty.embedded.JettyDistribution.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.STACKS=true
|
#org.eclipse.jetty.STACKS=true
|
||||||
#org.eclipse.jetty.STACKS=false
|
#org.eclipse.jetty.STACKS=false
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
@ -31,6 +32,7 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLEngineResult;
|
import javax.net.ssl.SSLEngineResult;
|
||||||
|
@ -49,9 +51,14 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
||||||
|
@ -76,8 +83,6 @@ public class HttpClientTLSTest
|
||||||
private ServerConnector connector;
|
private ServerConnector connector;
|
||||||
private HttpClient client;
|
private HttpClient client;
|
||||||
|
|
||||||
private SSLSocket sslSocket;
|
|
||||||
|
|
||||||
private void startServer(SslContextFactory sslContextFactory, Handler handler) throws Exception
|
private void startServer(SslContextFactory sslContextFactory, Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
ExecutorThreadPool serverThreads = new ExecutorThreadPool();
|
ExecutorThreadPool serverThreads = new ExecutorThreadPool();
|
||||||
|
@ -429,16 +434,16 @@ public class HttpClientTLSTest
|
||||||
|
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
Socket socket = new Socket(host, port);
|
Socket socket1 = new Socket(host, port);
|
||||||
sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
|
SSLSocket sslSocket1 = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket1, host, port, true);
|
||||||
CountDownLatch handshakeLatch1 = new CountDownLatch(1);
|
CountDownLatch handshakeLatch1 = new CountDownLatch(1);
|
||||||
AtomicReference<byte[]> session1 = new AtomicReference<>();
|
AtomicReference<byte[]> session1 = new AtomicReference<>();
|
||||||
sslSocket.addHandshakeCompletedListener(event ->
|
sslSocket1.addHandshakeCompletedListener(event ->
|
||||||
{
|
{
|
||||||
session1.set(event.getSession().getId());
|
session1.set(event.getSession().getId());
|
||||||
handshakeLatch1.countDown();
|
handshakeLatch1.countDown();
|
||||||
});
|
});
|
||||||
sslSocket.startHandshake();
|
sslSocket1.startHandshake();
|
||||||
assertTrue(handshakeLatch1.await(5, TimeUnit.SECONDS));
|
assertTrue(handshakeLatch1.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
// In TLS 1.3 the server sends a NewSessionTicket post-handshake message
|
// In TLS 1.3 the server sends a NewSessionTicket post-handshake message
|
||||||
|
@ -446,29 +451,29 @@ public class HttpClientTLSTest
|
||||||
|
|
||||||
assertThrows(SocketTimeoutException.class, () ->
|
assertThrows(SocketTimeoutException.class, () ->
|
||||||
{
|
{
|
||||||
sslSocket.setSoTimeout(1000);
|
sslSocket1.setSoTimeout(1000);
|
||||||
sslSocket.getInputStream().read();
|
sslSocket1.getInputStream().read();
|
||||||
});
|
});
|
||||||
|
|
||||||
// The client closes abruptly.
|
// The client closes abruptly.
|
||||||
socket.close();
|
socket1.close();
|
||||||
|
|
||||||
// Try again and compare the session ids.
|
// Try again and compare the session ids.
|
||||||
socket = new Socket(host, port);
|
Socket socket2 = new Socket(host, port);
|
||||||
sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
|
SSLSocket sslSocket2 = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket2, host, port, true);
|
||||||
CountDownLatch handshakeLatch2 = new CountDownLatch(1);
|
CountDownLatch handshakeLatch2 = new CountDownLatch(1);
|
||||||
AtomicReference<byte[]> session2 = new AtomicReference<>();
|
AtomicReference<byte[]> session2 = new AtomicReference<>();
|
||||||
sslSocket.addHandshakeCompletedListener(event ->
|
sslSocket2.addHandshakeCompletedListener(event ->
|
||||||
{
|
{
|
||||||
session2.set(event.getSession().getId());
|
session2.set(event.getSession().getId());
|
||||||
handshakeLatch2.countDown();
|
handshakeLatch2.countDown();
|
||||||
});
|
});
|
||||||
sslSocket.startHandshake();
|
sslSocket2.startHandshake();
|
||||||
assertTrue(handshakeLatch2.await(5, TimeUnit.SECONDS));
|
assertTrue(handshakeLatch2.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
assertArrayEquals(session1.get(), session2.get());
|
assertArrayEquals(session1.get(), session2.get());
|
||||||
|
|
||||||
sslSocket.close();
|
sslSocket2.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -486,7 +491,7 @@ public class HttpClientTLSTest
|
||||||
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
|
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
|
||||||
ssl.setAllowMissingCloseMessage(false);
|
ssl.setRequireCloseMessage(true);
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -514,19 +519,19 @@ public class HttpClientTLSTest
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the response is Content-Length delimited, allowing the
|
// If the response is Content-Length delimited, the lack of
|
||||||
// missing TLS Close Message is fine because the application
|
// the TLS Close Message is fine because the application
|
||||||
// will see a EOFException anyway.
|
// will see a EOFException anyway: the Content-Length and
|
||||||
// If the response is connection delimited, allowing the
|
// the actual content bytes count won't match.
|
||||||
// missing TLS Close Message is bad because the application
|
// If the response is connection delimited, the lack of the
|
||||||
// will see a successful response with truncated content.
|
// TLS Close Message is bad because the application will
|
||||||
|
// see a successful response, but with truncated content.
|
||||||
|
|
||||||
// Verify that by not allowing the missing
|
// Verify that by requiring the TLS Close Message we get
|
||||||
// TLS Close Message we get a response failure.
|
// a response failure.
|
||||||
|
|
||||||
byte[] half = new byte[8];
|
byte[] half = new byte[8];
|
||||||
String response = "HTTP/1.1 200 OK\r\n" +
|
String response = "HTTP/1.1 200 OK\r\n" +
|
||||||
// "Content-Length: " + (half.length * 2) + "\r\n" +
|
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
OutputStream output = sslSocket.getOutputStream();
|
OutputStream output = sslSocket.getOutputStream();
|
||||||
|
@ -567,6 +572,198 @@ public class HttpClientTLSTest
|
||||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeverUsedConnectionThenServerIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
long idleTimeout = 2000;
|
||||||
|
|
||||||
|
SslContextFactory serverTLSFactory = createServerSslContextFactory();
|
||||||
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
|
serverThreads.setName("server");
|
||||||
|
server = new Server(serverThreads);
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||||
|
AtomicLong serverBytes = new AtomicLong();
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(serverTLSFactory, http.getProtocol())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
serverBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector = new ServerConnector(server, 1, 1, ssl, http);
|
||||||
|
connector.setIdleTimeout(idleTimeout);
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(new EmptyServerHandler());
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
SslContextFactory clientTLSFactory = createClientSslContextFactory();
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
AtomicLong clientBytes = new AtomicLong();
|
||||||
|
client = new HttpClient(clientTLSFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
if (sslContextFactory == null)
|
||||||
|
sslContextFactory = getSslContextFactory();
|
||||||
|
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
clientBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client.setExecutor(clientThreads);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// Create a connection but don't use it.
|
||||||
|
String scheme = HttpScheme.HTTPS.asString();
|
||||||
|
String host = "localhost";
|
||||||
|
int port = connector.getLocalPort();
|
||||||
|
HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
|
||||||
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
// Trigger the creation of a new connection, but don't use it.
|
||||||
|
connectionPool.tryCreate(-1);
|
||||||
|
// Verify that the connection has been created.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.sleep(50);
|
||||||
|
if (connectionPool.getConnectionCount() == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the server to idle timeout the connection.
|
||||||
|
Thread.sleep(idleTimeout + idleTimeout / 2);
|
||||||
|
|
||||||
|
// The connection should be gone from the connection pool.
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount(), connectionPool.dump());
|
||||||
|
assertEquals(0, serverBytes.get());
|
||||||
|
assertEquals(0, clientBytes.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeverUsedConnectionThenClientIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory serverTLSFactory = createServerSslContextFactory();
|
||||||
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
|
serverThreads.setName("server");
|
||||||
|
server = new Server(serverThreads);
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||||
|
AtomicLong serverBytes = new AtomicLong();
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(serverTLSFactory, http.getProtocol())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
serverBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector = new ServerConnector(server, 1, 1, ssl, http);
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(new EmptyServerHandler());
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
long idleTimeout = 2000;
|
||||||
|
|
||||||
|
SslContextFactory clientTLSFactory = createClientSslContextFactory();
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
AtomicLong clientBytes = new AtomicLong();
|
||||||
|
client = new HttpClient(clientTLSFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
if (sslContextFactory == null)
|
||||||
|
sslContextFactory = getSslContextFactory();
|
||||||
|
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
clientBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client.setIdleTimeout(idleTimeout);
|
||||||
|
client.setExecutor(clientThreads);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// Create a connection but don't use it.
|
||||||
|
String scheme = HttpScheme.HTTPS.asString();
|
||||||
|
String host = "localhost";
|
||||||
|
int port = connector.getLocalPort();
|
||||||
|
HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
|
||||||
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
// Trigger the creation of a new connection, but don't use it.
|
||||||
|
connectionPool.tryCreate(-1);
|
||||||
|
// Verify that the connection has been created.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.sleep(50);
|
||||||
|
if (connectionPool.getConnectionCount() == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to idle timeout the connection.
|
||||||
|
Thread.sleep(idleTimeout + idleTimeout / 2);
|
||||||
|
|
||||||
|
// The connection should be gone from the connection pool.
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount(), connectionPool.dump());
|
||||||
|
assertEquals(0, serverBytes.get());
|
||||||
|
assertEquals(0, clientBytes.get());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSSLEngineClosedDuringHandshake() throws Exception
|
public void testSSLEngineClosedDuringHandshake() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,11 @@ public class WindowRateControl implements RateControl
|
||||||
private final int maxEvents;
|
private final int maxEvents;
|
||||||
private final long window;
|
private final long window;
|
||||||
|
|
||||||
|
public static WindowRateControl fromEventsPerSecond(int maxEvents)
|
||||||
|
{
|
||||||
|
return new WindowRateControl(maxEvents, Duration.ofSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
public WindowRateControl(int maxEvents, Duration window)
|
public WindowRateControl(int maxEvents, Duration window)
|
||||||
{
|
{
|
||||||
this.maxEvents = maxEvents;
|
this.maxEvents = maxEvents;
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
<Set name="maxConcurrentStreams"><Property name="jetty.http2.maxConcurrentStreams" deprecated="http2.maxConcurrentStreams" default="128"/></Set>
|
<Set name="maxConcurrentStreams"><Property name="jetty.http2.maxConcurrentStreams" deprecated="http2.maxConcurrentStreams" default="128"/></Set>
|
||||||
<Set name="initialStreamRecvWindow"><Property name="jetty.http2.initialStreamRecvWindow" default="524288"/></Set>
|
<Set name="initialStreamRecvWindow"><Property name="jetty.http2.initialStreamRecvWindow" default="524288"/></Set>
|
||||||
<Set name="initialSessionRecvWindow"><Property name="jetty.http2.initialSessionRecvWindow" default="1048576"/></Set>
|
<Set name="initialSessionRecvWindow"><Property name="jetty.http2.initialSessionRecvWindow" default="1048576"/></Set>
|
||||||
|
<Set name="maxSettingsKeys"><Property name="jetty.http2.maxSettingsKeys" default="64"/></Set>
|
||||||
|
<Set name="rateControl">
|
||||||
|
<Call class="org.eclipse.jetty.http2.parser.WindowRateControl" name="fromEventsPerSecond">
|
||||||
|
<Arg type="int"><Property name="jetty.http2.rateControl.maxEventsPerSecond" default="20"/></Arg>
|
||||||
|
</Call>
|
||||||
|
</Set>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
</Call>
|
</Call>
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
<Arg name="config"><Ref refid="httpConfig"/></Arg>
|
<Arg name="config"><Ref refid="httpConfig"/></Arg>
|
||||||
<Set name="maxConcurrentStreams"><Property name="jetty.http2c.maxConcurrentStreams" deprecated="http2.maxConcurrentStreams" default="1024"/></Set>
|
<Set name="maxConcurrentStreams"><Property name="jetty.http2c.maxConcurrentStreams" deprecated="http2.maxConcurrentStreams" default="1024"/></Set>
|
||||||
<Set name="initialStreamRecvWindow"><Property name="jetty.http2c.initialStreamRecvWindow" default="65535"/></Set>
|
<Set name="initialStreamRecvWindow"><Property name="jetty.http2c.initialStreamRecvWindow" default="65535"/></Set>
|
||||||
|
<Set name="maxSettingsKeys"><Property name="jetty.http2.maxSettingsKeys" default="64"/></Set>
|
||||||
|
<Set name="rateControl">
|
||||||
|
<Call class="org.eclipse.jetty.http2.parser.WindowRateControl" name="fromEventsPerSecond">
|
||||||
|
<Arg type="int"><Property name="jetty.http2.rateControl.maxEventsPerSecond" default="20"/></Arg>
|
||||||
|
</Call>
|
||||||
|
</Set>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
</Call>
|
</Call>
|
||||||
|
|
|
@ -29,3 +29,9 @@ etc/jetty-http2.xml
|
||||||
|
|
||||||
## Initial session receive window (client to server)
|
## Initial session receive window (client to server)
|
||||||
# jetty.http2.initialSessionRecvWindow=1048576
|
# jetty.http2.initialSessionRecvWindow=1048576
|
||||||
|
|
||||||
|
## The max number of keys in all SETTINGS frames
|
||||||
|
# jetty.http2.maxSettingsKeys=64
|
||||||
|
|
||||||
|
## Max number of bad frames and pings per second
|
||||||
|
# jetty.http2.rateControl.maxEventsPerSecond=20
|
||||||
|
|
|
@ -24,3 +24,9 @@ etc/jetty-http2c.xml
|
||||||
|
|
||||||
## Initial stream receive window (client to server)
|
## Initial stream receive window (client to server)
|
||||||
# jetty.http2c.initialStreamRecvWindow=65535
|
# jetty.http2c.initialStreamRecvWindow=65535
|
||||||
|
|
||||||
|
## The max number of keys in all SETTINGS frames
|
||||||
|
# jetty.http2.maxSettingsKeys=64
|
||||||
|
|
||||||
|
## Max number of bad frames and pings per second
|
||||||
|
# jetty.http2.rateControl.maxEventsPerSecond=20
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
private final ClientConnectionFactory connectionFactory;
|
private final ClientConnectionFactory connectionFactory;
|
||||||
private boolean _directBuffersForEncryption = true;
|
private boolean _directBuffersForEncryption = true;
|
||||||
private boolean _directBuffersForDecryption = true;
|
private boolean _directBuffersForDecryption = true;
|
||||||
private boolean allowMissingCloseMessage = true;
|
private boolean _requireCloseMessage;
|
||||||
|
|
||||||
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
|
@ -77,14 +77,42 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
return _directBuffersForEncryption;
|
return _directBuffersForEncryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether is not required that peers send the TLS {@code close_notify} message
|
||||||
|
* @deprecated use {@link #isRequireCloseMessage()} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isAllowMissingCloseMessage()
|
public boolean isAllowMissingCloseMessage()
|
||||||
{
|
{
|
||||||
return allowMissingCloseMessage;
|
return !isRequireCloseMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param allowMissingCloseMessage whether is not required that peers send the TLS {@code close_notify} message
|
||||||
|
* @deprecated use {@link #setRequireCloseMessage(boolean)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
||||||
{
|
{
|
||||||
this.allowMissingCloseMessage = allowMissingCloseMessage;
|
setRequireCloseMessage(!allowMissingCloseMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether peers must send the TLS {@code close_notify} message
|
||||||
|
* @see SslConnection#isRequireCloseMessage()
|
||||||
|
*/
|
||||||
|
public boolean isRequireCloseMessage()
|
||||||
|
{
|
||||||
|
return _requireCloseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param requireCloseMessage whether peers must send the TLS {@code close_notify} message
|
||||||
|
* @see SslConnection#setRequireCloseMessage(boolean)
|
||||||
|
*/
|
||||||
|
public void setRequireCloseMessage(boolean requireCloseMessage)
|
||||||
|
{
|
||||||
|
_requireCloseMessage = requireCloseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,7 +148,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
SslConnection sslConnection = (SslConnection)connection;
|
SslConnection sslConnection = (SslConnection)connection;
|
||||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||||
sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit());
|
sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit());
|
||||||
sslConnection.setAllowMissingCloseMessage(isAllowMissingCloseMessage());
|
sslConnection.setRequireCloseMessage(isRequireCloseMessage());
|
||||||
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
|
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
|
||||||
connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,9 +80,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
private static final Logger LOG = Log.getLogger(SslConnection.class);
|
private static final Logger LOG = Log.getLogger(SslConnection.class);
|
||||||
private static final String TLS_1_3 = "TLSv1.3";
|
private static final String TLS_1_3 = "TLSv1.3";
|
||||||
|
|
||||||
private enum Handshake
|
private enum HandshakeState
|
||||||
{
|
{
|
||||||
INITIAL,
|
INITIAL,
|
||||||
|
HANDSHAKE,
|
||||||
SUCCEEDED,
|
SUCCEEDED,
|
||||||
FAILED
|
FAILED
|
||||||
}
|
}
|
||||||
|
@ -113,10 +114,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
private boolean _renegotiationAllowed;
|
private boolean _renegotiationAllowed;
|
||||||
private int _renegotiationLimit = -1;
|
private int _renegotiationLimit = -1;
|
||||||
private boolean _closedOutbound;
|
private boolean _closedOutbound;
|
||||||
private boolean _allowMissingCloseMessage = true;
|
private boolean _requireCloseMessage;
|
||||||
private FlushState _flushState = FlushState.IDLE;
|
private FlushState _flushState = FlushState.IDLE;
|
||||||
private FillState _fillState = FillState.IDLE;
|
private FillState _fillState = FillState.IDLE;
|
||||||
private AtomicReference<Handshake> _handshake = new AtomicReference<>(Handshake.INITIAL);
|
private AtomicReference<HandshakeState> _handshake = new AtomicReference<>(HandshakeState.INITIAL);
|
||||||
private boolean _underflown;
|
private boolean _underflown;
|
||||||
|
|
||||||
private abstract class RunnableTask implements Runnable, Invocable
|
private abstract class RunnableTask implements Runnable, Invocable
|
||||||
|
@ -231,7 +232,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The number of renegotions allowed for this connection. When the limit
|
* @return The number of renegotiations allowed for this connection. When the limit
|
||||||
* is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
* is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
||||||
*/
|
*/
|
||||||
public int getRenegotiationLimit()
|
public int getRenegotiationLimit()
|
||||||
|
@ -240,7 +241,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param renegotiationLimit The number of renegotions allowed for this connection.
|
* @param renegotiationLimit The number of renegotiations allowed for this connection.
|
||||||
* When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
* When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
||||||
* Default -1.
|
* Default -1.
|
||||||
*/
|
*/
|
||||||
|
@ -249,14 +250,62 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
_renegotiationLimit = renegotiationLimit;
|
_renegotiationLimit = renegotiationLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether is not required that peers send the TLS {@code close_notify} message
|
||||||
|
* @deprecated use inverted {@link #isRequireCloseMessage()} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isAllowMissingCloseMessage()
|
public boolean isAllowMissingCloseMessage()
|
||||||
{
|
{
|
||||||
return _allowMissingCloseMessage;
|
return !isRequireCloseMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param allowMissingCloseMessage whether is not required that peers send the TLS {@code close_notify} message
|
||||||
|
* @deprecated use inverted {@link #setRequireCloseMessage(boolean)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
||||||
{
|
{
|
||||||
this._allowMissingCloseMessage = allowMissingCloseMessage;
|
setRequireCloseMessage(!allowMissingCloseMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether peers must send the TLS {@code close_notify} message
|
||||||
|
*/
|
||||||
|
public boolean isRequireCloseMessage()
|
||||||
|
{
|
||||||
|
return _requireCloseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets whether it is required that a peer send the TLS {@code close_notify} message
|
||||||
|
* to indicate the will to close the connection, otherwise it may be interpreted as a
|
||||||
|
* truncation attack.</p>
|
||||||
|
* <p>This option is only useful on clients, since typically servers cannot accept
|
||||||
|
* connection-delimited content that may be truncated.</p>
|
||||||
|
*
|
||||||
|
* @param requireCloseMessage whether peers must send the TLS {@code close_notify} message
|
||||||
|
*/
|
||||||
|
public void setRequireCloseMessage(boolean requireCloseMessage)
|
||||||
|
{
|
||||||
|
_requireCloseMessage = requireCloseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeInitial()
|
||||||
|
{
|
||||||
|
return _handshake.get() == HandshakeState.INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeSucceeded()
|
||||||
|
{
|
||||||
|
return _handshake.get() == HandshakeState.SUCCEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeComplete()
|
||||||
|
{
|
||||||
|
HandshakeState state = _handshake.get();
|
||||||
|
return state == HandshakeState.SUCCEEDED || state == HandshakeState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acquireEncryptedInput()
|
private void acquireEncryptedInput()
|
||||||
|
@ -371,6 +420,16 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
return getEndPoint().fill(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean networkFlush(ByteBuffer output) throws IOException
|
||||||
|
{
|
||||||
|
return getEndPoint().flush(output);
|
||||||
|
}
|
||||||
|
|
||||||
public class DecryptedEndPoint extends AbstractEndPoint
|
public class DecryptedEndPoint extends AbstractEndPoint
|
||||||
{
|
{
|
||||||
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
||||||
|
@ -568,14 +627,23 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try reading some encrypted data... even if we have some already.
|
// Let's try reading some encrypted data... even if we have some already.
|
||||||
int netFilled = getEndPoint().fill(_encryptedInput);
|
int netFilled = networkFill(_encryptedInput);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("net filled={}", netFilled);
|
LOG.debug("net filled={}", netFilled);
|
||||||
|
|
||||||
if (netFilled > 0 && _handshake.get() == Handshake.INITIAL && isOutboundDone())
|
// Workaround for Java 11 behavior.
|
||||||
|
if (netFilled < 0 && isHandshakeInitial() && BufferUtil.isEmpty(_encryptedInput))
|
||||||
|
closeInbound();
|
||||||
|
|
||||||
|
if (netFilled > 0 && !isHandshakeComplete() && isOutboundDone())
|
||||||
throw new SSLHandshakeException("Closed during handshake");
|
throw new SSLHandshakeException("Closed during handshake");
|
||||||
|
|
||||||
|
if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("fill starting handshake {}", SslConnection.this);
|
||||||
|
}
|
||||||
|
|
||||||
// Let's unwrap even if we have no net data because in that
|
// Let's unwrap even if we have no net data because in that
|
||||||
// case we want to fall through to the handshake handling
|
// case we want to fall through to the handshake handling
|
||||||
int pos = BufferUtil.flipToFill(appIn);
|
int pos = BufferUtil.flipToFill(appIn);
|
||||||
|
@ -781,7 +849,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private void handshakeSucceeded() throws SSLException
|
private void handshakeSucceeded() throws SSLException
|
||||||
{
|
{
|
||||||
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.SUCCEEDED))
|
if (_handshake.compareAndSet(HandshakeState.HANDSHAKE, HandshakeState.SUCCEEDED))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("handshake succeeded {} {} {}/{}", SslConnection.this,
|
LOG.debug("handshake succeeded {} {} {}/{}", SslConnection.this,
|
||||||
|
@ -789,7 +857,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
_sslEngine.getSession().getProtocol(), _sslEngine.getSession().getCipherSuite());
|
_sslEngine.getSession().getProtocol(), _sslEngine.getSession().getCipherSuite());
|
||||||
notifyHandshakeSucceeded(_sslEngine);
|
notifyHandshakeSucceeded(_sslEngine);
|
||||||
}
|
}
|
||||||
else if (_handshake.get() == Handshake.SUCCEEDED)
|
else if (isHandshakeSucceeded())
|
||||||
{
|
{
|
||||||
if (_renegotiationLimit > 0)
|
if (_renegotiationLimit > 0)
|
||||||
_renegotiationLimit--;
|
_renegotiationLimit--;
|
||||||
|
@ -798,7 +866,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private Throwable handshakeFailed(Throwable failure)
|
private Throwable handshakeFailed(Throwable failure)
|
||||||
{
|
{
|
||||||
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.FAILED))
|
if (_handshake.compareAndSet(HandshakeState.HANDSHAKE, HandshakeState.FAILED))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("handshake failed {} {}", SslConnection.this, failure);
|
LOG.debug("handshake failed {} {}", SslConnection.this, failure);
|
||||||
|
@ -831,7 +899,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
catch (SSLException x)
|
catch (SSLException x)
|
||||||
{
|
{
|
||||||
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && !isAllowMissingCloseMessage())
|
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && isRequireCloseMessage())
|
||||||
throw x;
|
throw x;
|
||||||
LOG.ignore(x);
|
LOG.ignore(x);
|
||||||
return x;
|
return x;
|
||||||
|
@ -861,7 +929,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// finish of any previous flushes
|
// finish of any previous flushes
|
||||||
if (BufferUtil.hasContent(_encryptedOutput) && !getEndPoint().flush(_encryptedOutput))
|
if (BufferUtil.hasContent(_encryptedOutput) && !networkFlush(_encryptedOutput))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
boolean isEmpty = BufferUtil.isEmpty(appOuts);
|
boolean isEmpty = BufferUtil.isEmpty(appOuts);
|
||||||
|
@ -889,6 +957,9 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case NEED_UNWRAP:
|
case NEED_UNWRAP:
|
||||||
|
// Workaround for Java 11 behavior.
|
||||||
|
if (isHandshakeInitial() && isOutboundDone())
|
||||||
|
break;
|
||||||
if (_fillState == FillState.IDLE)
|
if (_fillState == FillState.IDLE)
|
||||||
{
|
{
|
||||||
int filled = fill(BufferUtil.EMPTY_BUFFER);
|
int filled = fill(BufferUtil.EMPTY_BUFFER);
|
||||||
|
@ -906,6 +977,12 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
if (_encryptedOutput == null)
|
if (_encryptedOutput == null)
|
||||||
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
|
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
|
||||||
|
|
||||||
|
if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("flush starting handshake {}", SslConnection.this);
|
||||||
|
}
|
||||||
|
|
||||||
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
|
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
|
||||||
BufferUtil.compact(_encryptedOutput);
|
BufferUtil.compact(_encryptedOutput);
|
||||||
int pos = BufferUtil.flipToFill(_encryptedOutput);
|
int pos = BufferUtil.flipToFill(_encryptedOutput);
|
||||||
|
@ -931,7 +1008,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
// if we have net bytes, let's try to flush them
|
// if we have net bytes, let's try to flush them
|
||||||
boolean flushed = true;
|
boolean flushed = true;
|
||||||
if (BufferUtil.hasContent(_encryptedOutput))
|
if (BufferUtil.hasContent(_encryptedOutput))
|
||||||
flushed = getEndPoint().flush(_encryptedOutput);
|
flushed = networkFlush(_encryptedOutput);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
|
LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
|
||||||
|
@ -1106,15 +1183,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
@Override
|
@Override
|
||||||
public void doShutdownOutput()
|
public void doShutdownOutput()
|
||||||
{
|
{
|
||||||
final EndPoint endp = getEndPoint();
|
EndPoint endPoint = getEndPoint();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
boolean close;
|
boolean close;
|
||||||
boolean flush = false;
|
boolean flush = false;
|
||||||
synchronized (_decryptedEndPoint)
|
synchronized (_decryptedEndPoint)
|
||||||
{
|
{
|
||||||
boolean ishut = endp.isInputShutdown();
|
boolean ishut = endPoint.isInputShutdown();
|
||||||
boolean oshut = endp.isOutputShutdown();
|
boolean oshut = endPoint.isOutputShutdown();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("shutdownOutput: {} oshut={}, ishut={}", SslConnection.this, oshut, ishut);
|
LOG.debug("shutdownOutput: {} oshut={}, ishut={}", SslConnection.this, oshut, ishut);
|
||||||
|
|
||||||
|
@ -1138,19 +1215,19 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
// let's just flush the encrypted output in the background.
|
// let's just flush the encrypted output in the background.
|
||||||
ByteBuffer write = _encryptedOutput;
|
ByteBuffer write = _encryptedOutput;
|
||||||
if (BufferUtil.hasContent(write))
|
if (BufferUtil.hasContent(write))
|
||||||
endp.write(Callback.from(Callback.NOOP::succeeded, t -> endp.close()), write);
|
endPoint.write(Callback.from(Callback.NOOP::succeeded, t -> endPoint.close()), write);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (close)
|
if (close)
|
||||||
endp.close();
|
endPoint.close();
|
||||||
else
|
else
|
||||||
ensureFillInterested();
|
ensureFillInterested();
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
LOG.ignore(x);
|
LOG.ignore(x);
|
||||||
endp.close();
|
endPoint.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1162,7 +1239,8 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
LOG.ignore(x);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1268,7 +1346,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private boolean isRenegotiating()
|
private boolean isRenegotiating()
|
||||||
{
|
{
|
||||||
if (_handshake.get() == Handshake.INITIAL)
|
if (!isHandshakeComplete())
|
||||||
return false;
|
return false;
|
||||||
if (isTLS13())
|
if (isTLS13())
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -128,20 +128,8 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
|
||||||
|
|
||||||
private void addTrailer()
|
private void addTrailer()
|
||||||
{
|
{
|
||||||
int i = _buffer.limit();
|
BufferUtil.putIntLittleEndian(_buffer, (int)_crc.getValue());
|
||||||
_buffer.limit(i + 8);
|
BufferUtil.putIntLittleEndian(_buffer, _deflater.getTotalIn());
|
||||||
|
|
||||||
int v = (int)_crc.getValue();
|
|
||||||
_buffer.put(i++, (byte)(v & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 8) & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 16) & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 24) & 0xFF));
|
|
||||||
|
|
||||||
v = _deflater.getTotalIn();
|
|
||||||
_buffer.put(i++, (byte)(v & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 8) & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 16) & 0xFF));
|
|
||||||
_buffer.put(i++, (byte)((v >>> 24) & 0xFF));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gzip(ByteBuffer content, boolean complete, final Callback callback)
|
private void gzip(ByteBuffer content, boolean complete, final Callback callback)
|
||||||
|
@ -231,8 +219,6 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
|
||||||
|
|
||||||
fields.put(GZIP._contentEncoding);
|
fields.put(GZIP._contentEncoding);
|
||||||
_crc.reset();
|
_crc.reset();
|
||||||
_buffer = _channel.getByteBufferPool().acquire(_bufferSize, false);
|
|
||||||
BufferUtil.fill(_buffer, GZIP_HEADER, 0, GZIP_HEADER.length);
|
|
||||||
|
|
||||||
// Adjust headers
|
// Adjust headers
|
||||||
response.setContentLength(-1);
|
response.setContentLength(-1);
|
||||||
|
@ -325,82 +311,115 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
|
||||||
@Override
|
@Override
|
||||||
protected Action process() throws Exception
|
protected Action process() throws Exception
|
||||||
{
|
{
|
||||||
|
// If we have no deflator
|
||||||
if (_deflater == null)
|
if (_deflater == null)
|
||||||
return Action.SUCCEEDED;
|
|
||||||
|
|
||||||
if (_deflater.needsInput())
|
|
||||||
{
|
{
|
||||||
if (BufferUtil.isEmpty(_content))
|
// then the trailer has been generated and written below.
|
||||||
|
// we have finished compressing the entire content, so
|
||||||
|
// cleanup and succeed.
|
||||||
|
if (_buffer != null)
|
||||||
{
|
{
|
||||||
if (_deflater.finished())
|
_channel.getByteBufferPool().release(_buffer);
|
||||||
{
|
_buffer = null;
|
||||||
_factory.recycle(_deflater);
|
|
||||||
_deflater = null;
|
|
||||||
_channel.getByteBufferPool().release(_buffer);
|
|
||||||
_buffer = null;
|
|
||||||
if (_copy != null)
|
|
||||||
{
|
|
||||||
_channel.getByteBufferPool().release(_copy);
|
|
||||||
_copy = null;
|
|
||||||
}
|
|
||||||
return Action.SUCCEEDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_last)
|
|
||||||
{
|
|
||||||
return Action.SUCCEEDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
_deflater.finish();
|
|
||||||
}
|
}
|
||||||
else if (_content.hasArray())
|
if (_copy != null)
|
||||||
{
|
{
|
||||||
byte[] array = _content.array();
|
_channel.getByteBufferPool().release(_copy);
|
||||||
int off = _content.arrayOffset() + _content.position();
|
_copy = null;
|
||||||
int len = _content.remaining();
|
|
||||||
BufferUtil.clear(_content);
|
|
||||||
|
|
||||||
_crc.update(array, off, len);
|
|
||||||
_deflater.setInput(array, off, len);
|
|
||||||
if (_last)
|
|
||||||
_deflater.finish();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_copy == null)
|
|
||||||
_copy = _channel.getByteBufferPool().acquire(_bufferSize, false);
|
|
||||||
BufferUtil.clearToFill(_copy);
|
|
||||||
int took = BufferUtil.put(_content, _copy);
|
|
||||||
BufferUtil.flipToFlush(_copy, 0);
|
|
||||||
if (took == 0)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
byte[] array = _copy.array();
|
|
||||||
int off = _copy.arrayOffset() + _copy.position();
|
|
||||||
int len = _copy.remaining();
|
|
||||||
|
|
||||||
_crc.update(array, off, len);
|
|
||||||
_deflater.setInput(array, off, len);
|
|
||||||
if (_last && BufferUtil.isEmpty(_content))
|
|
||||||
_deflater.finish();
|
|
||||||
}
|
}
|
||||||
|
return Action.SUCCEEDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferUtil.compact(_buffer);
|
// If we have no buffer
|
||||||
int off = _buffer.arrayOffset() + _buffer.limit();
|
if (_buffer == null)
|
||||||
int len = _buffer.capacity() - _buffer.limit() - (_last ? 8 : 0);
|
|
||||||
if (len > 0)
|
|
||||||
{
|
{
|
||||||
|
// allocate a buffer and add the gzip header
|
||||||
|
_buffer = _channel.getByteBufferPool().acquire(_bufferSize, false);
|
||||||
|
BufferUtil.fill(_buffer, GZIP_HEADER, 0, GZIP_HEADER.length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// otherwise clear the buffer as previous writes will always fully consume.
|
||||||
|
BufferUtil.clear(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the deflator is not finished, then compress more data
|
||||||
|
if (!_deflater.finished())
|
||||||
|
{
|
||||||
|
if (_deflater.needsInput())
|
||||||
|
{
|
||||||
|
// if there is no more content available to compress
|
||||||
|
// then we are either finished all content or just the current write.
|
||||||
|
if (BufferUtil.isEmpty(_content))
|
||||||
|
{
|
||||||
|
if (_last)
|
||||||
|
_deflater.finish();
|
||||||
|
else
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there is more content available to compress, we have to make sure
|
||||||
|
// it is available in an array for the current deflator API, maybe slicing
|
||||||
|
// of content.
|
||||||
|
ByteBuffer slice;
|
||||||
|
if (_content.hasArray())
|
||||||
|
slice = _content;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_copy == null)
|
||||||
|
_copy = _channel.getByteBufferPool().acquire(_bufferSize, false);
|
||||||
|
else
|
||||||
|
BufferUtil.clear(_copy);
|
||||||
|
slice = _copy;
|
||||||
|
BufferUtil.append(_copy, _content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer the data from the slice to the the deflator
|
||||||
|
byte[] array = slice.array();
|
||||||
|
int off = slice.arrayOffset() + slice.position();
|
||||||
|
int len = slice.remaining();
|
||||||
|
_crc.update(array, off, len);
|
||||||
|
_deflater.setInput(array, off, len); // TODO use ByteBuffer API in Jetty-10
|
||||||
|
BufferUtil.clear(slice);
|
||||||
|
if (_last && BufferUtil.isEmpty(_content))
|
||||||
|
_deflater.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deflate the content into the available space in the buffer
|
||||||
|
int off = _buffer.arrayOffset() + _buffer.limit();
|
||||||
|
int len = BufferUtil.space(_buffer);
|
||||||
int produced = _deflater.deflate(_buffer.array(), off, len, _syncFlush ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH);
|
int produced = _deflater.deflate(_buffer.array(), off, len, _syncFlush ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH);
|
||||||
_buffer.limit(_buffer.limit() + produced);
|
_buffer.limit(_buffer.limit() + produced);
|
||||||
}
|
}
|
||||||
boolean finished = _deflater.finished();
|
|
||||||
|
|
||||||
if (finished)
|
// If we have finished deflation and there is room for the trailer.
|
||||||
|
if (_deflater.finished() && BufferUtil.space(_buffer) >= 8)
|
||||||
|
{
|
||||||
|
// add the trailer and recycle the deflator to flag that we will have had completeSuccess when
|
||||||
|
// the write below completes.
|
||||||
addTrailer();
|
addTrailer();
|
||||||
|
_factory.recycle(_deflater);
|
||||||
|
_deflater = null;
|
||||||
|
}
|
||||||
|
|
||||||
_interceptor.write(_buffer, finished, this);
|
// write the compressed buffer.
|
||||||
|
_interceptor.write(_buffer, _deflater == null, this);
|
||||||
return Action.SCHEDULED;
|
return Action.SCHEDULED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("%s[content=%s last=%b copy=%s buffer=%s deflate=%s",
|
||||||
|
super.toString(),
|
||||||
|
BufferUtil.toDetailString(_content),
|
||||||
|
_last,
|
||||||
|
BufferUtil.toDetailString(_copy),
|
||||||
|
BufferUtil.toDetailString(_buffer),
|
||||||
|
_deflater,
|
||||||
|
_deflater != null && _deflater.finished() ? "(finished)" : "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ import javax.servlet.http.HttpSessionEvent;
|
||||||
import javax.servlet.http.HttpSessionIdListener;
|
import javax.servlet.http.HttpSessionIdListener;
|
||||||
import javax.servlet.http.HttpSessionListener;
|
import javax.servlet.http.HttpSessionListener;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpCookie;
|
import org.eclipse.jetty.http.HttpCookie;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
@ -1629,13 +1630,34 @@ public class SessionHandler extends ScopedHandler
|
||||||
{
|
{
|
||||||
if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
|
if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
|
||||||
{
|
{
|
||||||
requestedSessionId = cookies[i].getValue();
|
String id = cookies[i].getValue();
|
||||||
requestedSessionIdFromCookie = true;
|
requestedSessionIdFromCookie = true;
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Got Session ID {} from cookie {}", requestedSessionId, sessionCookie);
|
LOG.debug("Got Session ID {} from cookie {}", id, sessionCookie);
|
||||||
if (requestedSessionId != null)
|
|
||||||
|
HttpSession s = getHttpSession(id);
|
||||||
|
|
||||||
|
if (requestedSessionId == null)
|
||||||
{
|
{
|
||||||
break;
|
//no previous id, always accept this one
|
||||||
|
requestedSessionId = id;
|
||||||
|
session = s;
|
||||||
|
}
|
||||||
|
else if (requestedSessionId.equals(id))
|
||||||
|
{
|
||||||
|
//really a bad request, but will forgive the duplication
|
||||||
|
}
|
||||||
|
else if (session == null || !isValid(session))
|
||||||
|
{
|
||||||
|
//no previous session or invalid, accept this one
|
||||||
|
requestedSessionId = id;
|
||||||
|
session = s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//previous session is valid, use it unless both valid
|
||||||
|
if (s != null && isValid(s))
|
||||||
|
throw new BadMessageException("Duplicate valid session cookies: " + requestedSessionId + "," + id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1666,6 +1688,7 @@ public class SessionHandler extends ScopedHandler
|
||||||
requestedSessionIdFromCookie = false;
|
requestedSessionIdFromCookie = false;
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Got Session ID {} from URL", requestedSessionId);
|
LOG.debug("Got Session ID {} from URL", requestedSessionId);
|
||||||
|
session = getHttpSession(requestedSessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1675,11 +1698,10 @@ public class SessionHandler extends ScopedHandler
|
||||||
|
|
||||||
if (requestedSessionId != null)
|
if (requestedSessionId != null)
|
||||||
{
|
{
|
||||||
session = getHttpSession(requestedSessionId);
|
|
||||||
if (session != null && isValid(session))
|
if (session != null && isValid(session))
|
||||||
{
|
{
|
||||||
baseRequest.enterSession(session); //request enters this session for first time
|
baseRequest.enterSession(session); //request enters this session for first time
|
||||||
baseRequest.setSession(session);
|
baseRequest.setSession(session); //associate the session with the request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.ConnectionLimit.LEVEL=DEBUG
|
#org.eclipse.jetty.server.ConnectionLimit.LEVEL=DEBUG
|
||||||
|
|
|
@ -45,9 +45,11 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
|
import org.eclipse.jetty.server.HttpOutput;
|
||||||
import org.eclipse.jetty.server.LocalConnector;
|
import org.eclipse.jetty.server.LocalConnector;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
|
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -116,6 +118,7 @@ public class GzipHandlerTest
|
||||||
servlets.addServletWithMapping(EchoServlet.class, "/echo/*");
|
servlets.addServletWithMapping(EchoServlet.class, "/echo/*");
|
||||||
servlets.addServletWithMapping(DumpServlet.class, "/dump/*");
|
servlets.addServletWithMapping(DumpServlet.class, "/dump/*");
|
||||||
servlets.addServletWithMapping(AsyncServlet.class, "/async/*");
|
servlets.addServletWithMapping(AsyncServlet.class, "/async/*");
|
||||||
|
servlets.addServletWithMapping(BufferServlet.class, "/buffer/*");
|
||||||
servlets.addFilterWithMapping(CheckFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
servlets.addFilterWithMapping(CheckFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
_server.start();
|
_server.start();
|
||||||
|
@ -221,6 +224,19 @@ public class GzipHandlerTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class BufferServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
HttpOutput out = (HttpOutput)response.getOutputStream();
|
||||||
|
ByteBuffer buffer = BufferUtil.toBuffer(__bytes).asReadOnlyBuffer();
|
||||||
|
response.setContentLength(buffer.remaining());
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
out.write(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class EchoServlet extends HttpServlet
|
public static class EchoServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -357,6 +373,32 @@ public class GzipHandlerTest
|
||||||
assertEquals(__content, testOut.toString("UTF8"));
|
assertEquals(__content, testOut.toString("UTF8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBufferResponse() throws Exception
|
||||||
|
{
|
||||||
|
// generated and parsed test
|
||||||
|
HttpTester.Request request = HttpTester.newRequest();
|
||||||
|
HttpTester.Response response;
|
||||||
|
|
||||||
|
request.setMethod("GET");
|
||||||
|
request.setURI("/ctx/buffer/info");
|
||||||
|
request.setVersion("HTTP/1.0");
|
||||||
|
request.setHeader("Host", "tester");
|
||||||
|
request.setHeader("accept-encoding", "gzip");
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(_connector.getResponse(request.generate()));
|
||||||
|
|
||||||
|
assertThat(response.getStatus(), is(200));
|
||||||
|
assertThat(response.get("Content-Encoding"), Matchers.equalToIgnoringCase("gzip"));
|
||||||
|
assertThat(response.getCSV("Vary", false), Matchers.contains("Accept-Encoding"));
|
||||||
|
|
||||||
|
InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
|
||||||
|
ByteArrayOutputStream testOut = new ByteArrayOutputStream();
|
||||||
|
IO.copy(testIn, testOut);
|
||||||
|
|
||||||
|
assertEquals(__content, testOut.toString("UTF8"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncLargeResponse() throws Exception
|
public void testAsyncLargeResponse() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -387,6 +429,31 @@ public class GzipHandlerTest
|
||||||
assertEquals(__content, new String(Arrays.copyOfRange(bytes,i * __bytes.length, (i + 1) * __bytes.length), StandardCharsets.UTF_8), "chunk " + i);
|
assertEquals(__content, new String(Arrays.copyOfRange(bytes,i * __bytes.length, (i + 1) * __bytes.length), StandardCharsets.UTF_8), "chunk " + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncEmptyResponse() throws Exception
|
||||||
|
{
|
||||||
|
int writes = 0;
|
||||||
|
_server.getChildHandlerByClass(GzipHandler.class).setMinGzipSize(0);
|
||||||
|
|
||||||
|
// generated and parsed test
|
||||||
|
HttpTester.Request request = HttpTester.newRequest();
|
||||||
|
HttpTester.Response response;
|
||||||
|
|
||||||
|
request.setMethod("GET");
|
||||||
|
request.setURI("/ctx/async/info?writes=" + writes);
|
||||||
|
request.setVersion("HTTP/1.0");
|
||||||
|
request.setHeader("Host", "tester");
|
||||||
|
request.setHeader("accept-encoding", "gzip");
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(_connector.getResponse(request.generate()));
|
||||||
|
|
||||||
|
assertThat(response.getStatus(), is(200));
|
||||||
|
assertThat(response.get("Content-Encoding"), Matchers.equalToIgnoringCase("gzip"));
|
||||||
|
assertThat(response.getCSV("Vary", false), Matchers.contains("Accept-Encoding"));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGzipHandlerWithMultipleAcceptEncodingHeaders() throws Exception
|
public void testGzipHandlerWithMultipleAcceptEncodingHeaders() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.servlet.LEVEL=DEBUG
|
#org.eclipse.jetty.servlet.LEVEL=DEBUG
|
||||||
|
|
|
@ -219,6 +219,20 @@ public class BufferUtil
|
||||||
buffer.position(position);
|
buffer.position(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Put an integer little endian
|
||||||
|
* @param buffer The buffer to put to
|
||||||
|
* @param value The value to put.
|
||||||
|
*/
|
||||||
|
public static void putIntLittleEndian(ByteBuffer buffer, int value)
|
||||||
|
{
|
||||||
|
int p = flipToFill(buffer);
|
||||||
|
buffer.put((byte)(value & 0xFF));
|
||||||
|
buffer.put((byte)((value >>> 8) & 0xFF));
|
||||||
|
buffer.put((byte)((value >>> 16) & 0xFF));
|
||||||
|
buffer.put((byte)((value >>> 24) & 0xFF));
|
||||||
|
flipToFlush(buffer, p);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a ByteBuffer to a byte array.
|
* Convert a ByteBuffer to a byte array.
|
||||||
*
|
*
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class ClasspathPattern extends AbstractSet<String>
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClasspathPattern.class);
|
private static final Logger LOG = Log.getLogger(ClasspathPattern.class);
|
||||||
|
|
||||||
private static class Entry
|
static class Entry
|
||||||
{
|
{
|
||||||
private final String _pattern;
|
private final String _pattern;
|
||||||
private final String _name;
|
private final String _name;
|
||||||
|
@ -727,21 +727,43 @@ public class ClasspathPattern extends AbstractSet<String>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean combine(IncludeExcludeSet<Entry, String> names, String name, IncludeExcludeSet<Entry, URI> locations, Supplier<URI> location)
|
/**
|
||||||
|
* Match a class against inclusions and exclusions by name and location.
|
||||||
|
* Name based checks are performed before location checks. For a class to match,
|
||||||
|
* it must not be excluded by either name or location, and must either be explicitly
|
||||||
|
* included, or for there to be no inclusions. In the case where the location
|
||||||
|
* of the class is null, it will match if it is included by name, or
|
||||||
|
* if there are no location exclusions.
|
||||||
|
*
|
||||||
|
* @param names configured inclusions and exclusions by name
|
||||||
|
* @param name the name to check
|
||||||
|
* @param locations configured inclusions and exclusions by location
|
||||||
|
* @param location the location of the class (can be null)
|
||||||
|
* @return true if the class is not excluded but is included, or there are
|
||||||
|
* no inclusions. False otherwise.
|
||||||
|
*/
|
||||||
|
static boolean combine(IncludeExcludeSet<Entry, String> names, String name, IncludeExcludeSet<Entry, URI> locations, Supplier<URI> location)
|
||||||
{
|
{
|
||||||
|
// check the name set
|
||||||
Boolean byName = names.isIncludedAndNotExcluded(name);
|
Boolean byName = names.isIncludedAndNotExcluded(name);
|
||||||
|
|
||||||
|
// If we excluded by name, then no match
|
||||||
if (Boolean.FALSE == byName)
|
if (Boolean.FALSE == byName)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// check the location set
|
||||||
URI uri = location.get();
|
URI uri = location.get();
|
||||||
if (uri == null)
|
Boolean byLocation = uri == null ? null : locations.isIncludedAndNotExcluded(uri);
|
||||||
return locations.isEmpty() || locations.hasExcludes() && !locations.hasIncludes();
|
|
||||||
|
|
||||||
Boolean byLocation = locations.isIncludedAndNotExcluded(uri);
|
// If we excluded by location or couldn't check location exclusion, then no match
|
||||||
if (Boolean.FALSE == byLocation)
|
if (Boolean.FALSE == byLocation || (locations.hasExcludes() && uri == null))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return Boolean.TRUE.equals(byName) || Boolean.TRUE.equals(byLocation) || !(names.hasIncludes() || locations.hasIncludes());
|
// If there are includes, then we must be included to match.
|
||||||
|
if (names.hasIncludes() || locations.hasIncludes())
|
||||||
|
return byName == Boolean.TRUE || byLocation == Boolean.TRUE;
|
||||||
|
|
||||||
|
// Otherwise there are no includes and it was not excluded, so match
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,13 @@ package org.eclipse.jetty.webapp;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.IncludeExcludeSet;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
import org.eclipse.jetty.webapp.ClasspathPattern.ByLocationOrModule;
|
||||||
|
import org.eclipse.jetty.webapp.ClasspathPattern.ByPackageOrName;
|
||||||
|
import org.eclipse.jetty.webapp.ClasspathPattern.Entry;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -36,6 +41,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
public class ClasspathPatternTest
|
public class ClasspathPatternTest
|
||||||
{
|
{
|
||||||
private final ClasspathPattern _pattern = new ClasspathPattern();
|
private final ClasspathPattern _pattern = new ClasspathPattern();
|
||||||
|
|
||||||
|
protected static Supplier<URI> NULL_SUPPLIER = new Supplier<URI>()
|
||||||
|
{
|
||||||
|
public URI get()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before()
|
public void before()
|
||||||
|
@ -262,6 +275,44 @@ public class ClasspathPatternTest
|
||||||
assertThat(pattern.match(Test.class), Matchers.is(false));
|
assertThat(pattern.match(Test.class), Matchers.is(false));
|
||||||
assertThat(pattern.match(ClasspathPatternTest.class), Matchers.is(false));
|
assertThat(pattern.match(ClasspathPatternTest.class), Matchers.is(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithNullLocation() throws Exception
|
||||||
|
{
|
||||||
|
ClasspathPattern pattern = new ClasspathPattern();
|
||||||
|
|
||||||
|
IncludeExcludeSet<Entry, String> names = new IncludeExcludeSet<>(ByPackageOrName.class);
|
||||||
|
IncludeExcludeSet<Entry, URI> locations = new IncludeExcludeSet<>(ByLocationOrModule.class);
|
||||||
|
|
||||||
|
//Test no name or location includes or excludes - should match
|
||||||
|
assertThat(ClasspathPattern.combine(names, "a.b.c", locations, NULL_SUPPLIER), Matchers.is(true));
|
||||||
|
|
||||||
|
names.include(pattern.newEntry("a.b.", true));
|
||||||
|
names.exclude(pattern.newEntry("d.e.", false));
|
||||||
|
|
||||||
|
//Test explicit include by name no locations - should match
|
||||||
|
assertThat(ClasspathPattern.combine(names, "a.b.c", locations, NULL_SUPPLIER), Matchers.is(true));
|
||||||
|
|
||||||
|
//Test explicit exclude by name no locations - should not match
|
||||||
|
assertThat(ClasspathPattern.combine(names, "d.e.f", locations, NULL_SUPPLIER), Matchers.is(false));
|
||||||
|
|
||||||
|
//Test include by name with location includes - should match
|
||||||
|
locations.include(pattern.newEntry("file:/foo/bar", true));
|
||||||
|
assertThat(ClasspathPattern.combine(names, "a.b.c", locations, NULL_SUPPLIER), Matchers.is(true));
|
||||||
|
|
||||||
|
//Test include by name but with location exclusions - should not match
|
||||||
|
locations.clear();
|
||||||
|
locations.exclude(pattern.newEntry("file:/high/low", false));
|
||||||
|
assertThat(ClasspathPattern.combine(names, "a.b.c", locations, NULL_SUPPLIER), Matchers.is(false));
|
||||||
|
|
||||||
|
//Test neither included or excluded by name, but with location exclusions - should not match
|
||||||
|
assertThat(ClasspathPattern.combine(names, "g.b.r", locations, NULL_SUPPLIER), Matchers.is(false));
|
||||||
|
|
||||||
|
//Test neither included nor excluded by name, but with location inclusions - should not match
|
||||||
|
locations.clear();
|
||||||
|
locations.include(pattern.newEntry("file:/foo/bar", true));
|
||||||
|
assertThat(ClasspathPattern.combine(names, "g.b.r", locations, NULL_SUPPLIER), Matchers.is(false));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLarge()
|
public void testLarge()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
|
|
||||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.LEVEL=ALL
|
# org.eclipse.jetty.websocket.LEVEL=ALL
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
|
|
||||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.LEVEL=INFO
|
# org.eclipse.jetty.websocket.LEVEL=INFO
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
# org.eclipse.jetty.LEVEL=DEBUG
|
# org.eclipse.jetty.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.io.LEVEL=INFO
|
# org.eclipse.jetty.io.LEVEL=INFO
|
||||||
# org.eclipse.jetty.client.LEVEL=DEBUG
|
# org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
# org.eclipse.jetty.LEVEL=DEBUG
|
# org.eclipse.jetty.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.io.LEVEL=INFO
|
# org.eclipse.jetty.io.LEVEL=INFO
|
||||||
# org.eclipse.jetty.client.LEVEL=DEBUG
|
# org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.common.test.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.test.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
|
|
||||||
# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
|
# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
# org.eclipse.jetty.server.LEVEL=DEBUG
|
# org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.websocket.LEVEL=DEBUG
|
#org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
|
|
|
@ -103,8 +103,6 @@ public abstract class AbstractClusteredInvalidationSessionTest extends AbstractT
|
||||||
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
|
||||||
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
||||||
assertTrue(sessionCookie != null);
|
assertTrue(sessionCookie != null);
|
||||||
// Mangle the cookie, replacing Path with $Path, etc.
|
|
||||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
|
||||||
|
|
||||||
//ensure request is fully finished processing
|
//ensure request is fully finished processing
|
||||||
latch.await(5, TimeUnit.SECONDS);
|
latch.await(5, TimeUnit.SECONDS);
|
||||||
|
@ -113,7 +111,6 @@ public abstract class AbstractClusteredInvalidationSessionTest extends AbstractT
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
scopeListener.setExitSynchronizer(latch);
|
scopeListener.setExitSynchronizer(latch);
|
||||||
Request request2 = client.newRequest(urls[1] + "?action=increment");
|
Request request2 = client.newRequest(urls[1] + "?action=increment");
|
||||||
request2.header("Cookie", sessionCookie);
|
|
||||||
ContentResponse response2 = request2.send();
|
ContentResponse response2 = request2.send();
|
||||||
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
||||||
|
|
||||||
|
@ -153,6 +150,8 @@ public abstract class AbstractClusteredInvalidationSessionTest extends AbstractT
|
||||||
|
|
||||||
public static class TestServlet extends HttpServlet
|
public static class TestServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
{
|
{
|
||||||
|
|
|
@ -117,6 +117,7 @@ public abstract class AbstractClusteredSessionScavengingTest extends AbstractTes
|
||||||
assertTrue(response1.getContentAsString().startsWith("init"));
|
assertTrue(response1.getContentAsString().startsWith("init"));
|
||||||
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
||||||
assertTrue(sessionCookie != null);
|
assertTrue(sessionCookie != null);
|
||||||
|
String id = TestServer.extractSessionId(sessionCookie);
|
||||||
|
|
||||||
//ensure request has finished being handled
|
//ensure request has finished being handled
|
||||||
synchronizer.await(5, TimeUnit.SECONDS);
|
synchronizer.await(5, TimeUnit.SECONDS);
|
||||||
|
@ -124,9 +125,7 @@ public abstract class AbstractClusteredSessionScavengingTest extends AbstractTes
|
||||||
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsCurrent());
|
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsCurrent());
|
||||||
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsMax());
|
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsMax());
|
||||||
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsTotal());
|
assertEquals(1, ((DefaultSessionCache)m1.getSessionCache()).getSessionsTotal());
|
||||||
// Mangle the cookie, replacing Path with $Path, etc.
|
|
||||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
|
||||||
String id = TestServer.extractSessionId(sessionCookie);
|
|
||||||
|
|
||||||
//Peek at the contents of the cache without doing all the reference counting etc
|
//Peek at the contents of the cache without doing all the reference counting etc
|
||||||
Session s1 = ((AbstractSessionCache)m1.getSessionCache()).doGet(id);
|
Session s1 = ((AbstractSessionCache)m1.getSessionCache()).doGet(id);
|
||||||
|
@ -144,7 +143,6 @@ public abstract class AbstractClusteredSessionScavengingTest extends AbstractTes
|
||||||
synchronizer = new CountDownLatch(1);
|
synchronizer = new CountDownLatch(1);
|
||||||
scopeListener2.setExitSynchronizer(synchronizer);
|
scopeListener2.setExitSynchronizer(synchronizer);
|
||||||
Request request = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping.substring(1));
|
Request request = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping.substring(1));
|
||||||
request.header("Cookie", sessionCookie); //use existing session
|
|
||||||
ContentResponse response2 = request.send();
|
ContentResponse response2 = request.send();
|
||||||
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
||||||
assertTrue(response2.getContentAsString().startsWith("test"));
|
assertTrue(response2.getContentAsString().startsWith("test"));
|
||||||
|
|
|
@ -82,12 +82,11 @@ public class ClientCrossContextSessionTest
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||||
assertTrue(sessionCookie != null);
|
assertTrue(sessionCookie != null);
|
||||||
// Mangle the cookie, replacing Path with $Path, etc.
|
String sessionId = TestServer.extractSessionId(sessionCookie);
|
||||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
|
||||||
|
|
||||||
// Perform a request to contextB with the same session cookie
|
// Perform a request to contextB with the same session cookie
|
||||||
Request request = client.newRequest("http://localhost:" + port + contextB + servletMapping);
|
Request request = client.newRequest("http://localhost:" + port + contextB + servletMapping);
|
||||||
request.header("Cookie", sessionCookie);
|
request.header("Cookie", "JSESSIONID=" + sessionId);
|
||||||
ContentResponse responseB = request.send();
|
ContentResponse responseB = request.send();
|
||||||
assertEquals(HttpServletResponse.SC_OK, responseB.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, responseB.getStatus());
|
||||||
assertEquals(servletA.sessionId, servletB.sessionId);
|
assertEquals(servletA.sessionId, servletB.sessionId);
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.server.session;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test having multiple session cookies in a request.
|
||||||
|
*/
|
||||||
|
public class DuplicateCookieTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testMultipleSessionCookiesOnlyOneExists() throws Exception
|
||||||
|
{
|
||||||
|
String contextPath = "";
|
||||||
|
String servletMapping = "/server";
|
||||||
|
HttpClient client = null;
|
||||||
|
|
||||||
|
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||||
|
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
|
||||||
|
|
||||||
|
TestServer server1 = new TestServer(0, -1, -1, cacheFactory, storeFactory);
|
||||||
|
TestServlet servlet = new TestServlet();
|
||||||
|
ServletHolder holder = new ServletHolder(servlet);
|
||||||
|
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||||
|
contextHandler.addServlet(holder, servletMapping);
|
||||||
|
server1.start();
|
||||||
|
int port1 = server1.getPort();
|
||||||
|
|
||||||
|
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
|
||||||
|
{
|
||||||
|
//create a valid session
|
||||||
|
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"4422");
|
||||||
|
|
||||||
|
client = new HttpClient();
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
//make a request with another session cookie in there that does not exist
|
||||||
|
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
|
||||||
|
request.header("Cookie", "JSESSIONID=123"); //doesn't exist
|
||||||
|
request.header("Cookie", "JSESSIONID=4422"); //does exist
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
assertEquals("4422", response.getContentAsString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
server1.stop();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleSessionCookiesOnlyOneValid() throws Exception
|
||||||
|
{
|
||||||
|
String contextPath = "";
|
||||||
|
String servletMapping = "/server";
|
||||||
|
HttpClient client = null;
|
||||||
|
|
||||||
|
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||||
|
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
|
||||||
|
|
||||||
|
TestServer server1 = new TestServer(0, -1, -1, cacheFactory, storeFactory);
|
||||||
|
TestServlet servlet = new TestServlet();
|
||||||
|
ServletHolder holder = new ServletHolder(servlet);
|
||||||
|
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||||
|
contextHandler.addServlet(holder, servletMapping);
|
||||||
|
server1.start();
|
||||||
|
int port1 = server1.getPort();
|
||||||
|
|
||||||
|
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
|
||||||
|
{
|
||||||
|
//create a valid session
|
||||||
|
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"1122");
|
||||||
|
//create an invalid session
|
||||||
|
createInvalidSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"2233");
|
||||||
|
|
||||||
|
client = new HttpClient();
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
//make a request with another session cookie in there that is not valid
|
||||||
|
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
|
||||||
|
request.header("Cookie", "JSESSIONID=1122"); //is valid
|
||||||
|
request.header("Cookie", "JSESSIONID=2233"); //is invalid
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
assertEquals("1122", response.getContentAsString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
server1.stop();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleSessionCookiesMultipleExists() throws Exception
|
||||||
|
{
|
||||||
|
String contextPath = "";
|
||||||
|
String servletMapping = "/server";
|
||||||
|
HttpClient client = null;
|
||||||
|
|
||||||
|
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||||
|
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
|
||||||
|
|
||||||
|
TestServer server1 = new TestServer(0, -1, -1, cacheFactory, storeFactory);
|
||||||
|
TestServlet servlet = new TestServlet();
|
||||||
|
ServletHolder holder = new ServletHolder(servlet);
|
||||||
|
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||||
|
contextHandler.addServlet(holder, servletMapping);
|
||||||
|
server1.start();
|
||||||
|
int port1 = server1.getPort();
|
||||||
|
|
||||||
|
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
|
||||||
|
{
|
||||||
|
//create some of unexpired sessions
|
||||||
|
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"1234");
|
||||||
|
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"5678");
|
||||||
|
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||||
|
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||||
|
"9111");
|
||||||
|
|
||||||
|
client = new HttpClient();
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
//make a request with multiple valid session ids
|
||||||
|
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
|
||||||
|
request.header("Cookie", "JSESSIONID=1234");
|
||||||
|
request.header("Cookie", "JSESSIONID=5678");
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
server1.stop();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Session createUnExpiredSession(SessionCache cache, SessionDataStore store, String id) throws Exception
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
SessionData data = store.newSessionData(id, now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10));
|
||||||
|
data.setExpiry(now + TimeUnit.DAYS.toMillis(1));
|
||||||
|
Session s = cache.newSession(data);
|
||||||
|
cache.add(id, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Session createInvalidSession(SessionCache cache, SessionDataStore store, String id) throws Exception
|
||||||
|
{
|
||||||
|
Session session = createUnExpiredSession(cache, store, id);
|
||||||
|
session._state = Session.State.INVALID;
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String action = request.getParameter("action");
|
||||||
|
if (StringUtil.isBlank(action))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (action.equalsIgnoreCase("check"))
|
||||||
|
{
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
assertNotNull(session);
|
||||||
|
httpServletResponse.getWriter().print(session.getId());
|
||||||
|
}
|
||||||
|
else if (action.equalsIgnoreCase("create"))
|
||||||
|
{
|
||||||
|
request.getSession(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,7 +85,6 @@ public class ReentrantRequestSessionTest
|
||||||
|
|
||||||
//make a request that will make a simultaneous request for the same session
|
//make a request that will make a simultaneous request for the same session
|
||||||
Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=reenter&port=" + port + "&path=" + contextPath + servletMapping);
|
Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=reenter&port=" + port + "&path=" + contextPath + servletMapping);
|
||||||
request.header("Cookie", sessionCookie);
|
|
||||||
response = request.send();
|
response = request.send();
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue