diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index cfc1e89fc51e..97a3c2ebd89c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -24,6 +24,8 @@ updates:
versions: [ ">=1.1.0" ]
- dependency-name: "org.infinispan:*"
versions: [ ">=12" ]
+ - dependency-name: "org.jboss.weld.servlet:*"
+ versions: [ ">=4.0.0" ]
- package-ecosystem: "maven"
directory: "/"
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000000..32599cefea51
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/Jenkinsfile b/Jenkinsfile
index a8c14a84e1b9..28b4bd5ef3a8 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -12,7 +12,7 @@ pipeline {
steps {
container('jetty-build') {
timeout( time: 120, unit: 'MINUTES' ) {
- mavenBuild( "jdk17", "clean install", "maven3")
+ mavenBuild( "jdk17", "clean install -Perrorprone", "maven3")
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
exclusionPattern: '' +
@@ -31,7 +31,7 @@ pipeline {
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'
- recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle()]
+ recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne(), spotBugs()]
}
}
}
@@ -42,8 +42,8 @@ pipeline {
steps {
container( 'jetty-build' ) {
timeout( time: 120, unit: 'MINUTES' ) {
- mavenBuild( "jdk11", "clean install -Dspotbugs.skip=true -Djacoco.skip=true -Perrorprone", "maven3")
- recordIssues id: "jdk11", name: "Static Analysis jdk11", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne()]
+ mavenBuild( "jdk11", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3")
+ recordIssues id: "jdk11", name: "Static Analysis jdk11", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle()]
}
}
}
diff --git a/javadoc/pom.xml b/javadoc/pom.xml
index 878692728cb2..a42680c9b822 100644
--- a/javadoc/pom.xml
+++ b/javadoc/pom.xml
@@ -384,6 +384,12 @@
org.eclipse.jetty
jetty-jspc-maven-plugin
provided
+
+
+ javax.annotation
+ javax.annotation-api
+
+
org.eclipse.jetty
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
index ccecb188b969..31838e0eb7a5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
@@ -54,6 +54,8 @@
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -75,11 +77,6 @@ public static Stream pools()
return Stream.of(DUPLEX, MULTIPLEX, RANDOM, DUPLEX_MAX_DURATION, ROUND_ROBIN);
}
- public static Stream poolsNoMaxDuration()
- {
- return Stream.of(DUPLEX, MULTIPLEX, RANDOM, ROUND_ROBIN);
- }
-
public static Stream poolsNoRoundRobin()
{
return Stream.of(DUPLEX, MULTIPLEX, RANDOM, DUPLEX_MAX_DURATION);
@@ -316,7 +313,10 @@ public void resolve(String host, int port, Promise> prom
assertEquals(1, destinations.size());
HttpDestination destination = (HttpDestination)destinations.get(0);
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
- assertEquals(2, connectionPool.getConnectionCount());
+ if (DUPLEX_MAX_DURATION == factory)
+ assertThat(connectionPool.getConnectionCount(), lessThanOrEqualTo(2)); // The connections can expire upon release.
+ else
+ assertThat(connectionPool.getConnectionCount(), is(2));
}
@ParameterizedTest
@@ -376,8 +376,7 @@ public void resolve(String host, int port, Promise> prom
}
@ParameterizedTest
- // Connection pool aggressively closes expired connections upon release, which interferes with this test's assertion.
- @MethodSource("poolsNoMaxDuration")
+ @MethodSource("pools")
public void testConcurrentRequestsAllBlockedOnServerWithLargeConnectionPool(ConnectionPoolFactory factory) throws Exception
{
int count = 50;
@@ -385,8 +384,7 @@ public void testConcurrentRequestsAllBlockedOnServerWithLargeConnectionPool(Conn
}
@ParameterizedTest
- // Connection pool aggressively closes expired connections upon release, which interferes with this test's assertion.
- @MethodSource("poolsNoMaxDuration")
+ @MethodSource("pools")
public void testConcurrentRequestsAllBlockedOnServerWithExactConnectionPool(ConnectionPoolFactory factory) throws Exception
{
int count = 50;
@@ -449,9 +447,13 @@ protected void service(String target, org.eclipse.jetty.server.Request jettyRequ
assertTrue(latch.await(5, TimeUnit.SECONDS), "server requests " + barrier.getNumberWaiting() + "<" + count + " - client: " + client.dump());
List destinations = client.getDestinations();
assertEquals(1, destinations.size());
- HttpDestination destination = (HttpDestination)destinations.get(0);
- AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
- assertThat(connectionPool.getConnectionCount(), Matchers.greaterThanOrEqualTo(count));
+ // The max duration connection pool aggressively closes expired connections upon release, which interferes with this assertion.
+ if (DUPLEX_MAX_DURATION != factory)
+ {
+ HttpDestination destination = (HttpDestination)destinations.get(0);
+ AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
+ assertThat(connectionPool.getConnectionCount(), Matchers.greaterThanOrEqualTo(count));
+ }
}
@Test
@@ -588,8 +590,17 @@ public void testConnectionMaxUsage(ConnectionPoolFactory factory) throws Excepti
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
assertEquals(0, connectionPool.getActiveConnectionCount());
- assertEquals(1, connectionPool.getIdleConnectionCount());
- assertEquals(1, connectionPool.getConnectionCount());
+ if (DUPLEX_MAX_DURATION == factory)
+ {
+ // The connections can expire upon release.
+ assertThat(connectionPool.getIdleConnectionCount(), lessThanOrEqualTo(1));
+ assertThat(connectionPool.getConnectionCount(), lessThanOrEqualTo(1));
+ }
+ else
+ {
+ assertThat(connectionPool.getIdleConnectionCount(), is(1));
+ assertThat(connectionPool.getConnectionCount(), is(1));
+ }
// Send second request, max usage count will be reached,
// the only connection must be closed.
@@ -625,7 +636,10 @@ public void testIdleTimeoutNoRequests(ConnectionPoolFactory factory) throws Exce
// Trigger the creation of a destination, that will create the connection pool.
HttpDestination destination = client.resolveDestination(new Origin("http", "localhost", connector.getLocalPort()));
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
- assertEquals(1, connectionPool.getConnectionCount());
+ if (DUPLEX_MAX_DURATION == factory)
+ assertThat(connectionPool.getConnectionCount(), lessThanOrEqualTo(1)); // The connections can expire upon release.
+ else
+ assertThat(connectionPool.getConnectionCount(), is(1));
// Wait for the pre-created connections to idle timeout.
Thread.sleep(idleTimeout + idleTimeout / 2);
diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml
index dc7479d15a7d..0979e3091de7 100644
--- a/jetty-gcloud/pom.xml
+++ b/jetty-gcloud/pom.xml
@@ -13,7 +13,7 @@
Jetty :: GCloud
- 2.2.4
+ 2.2.9
diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java
index be9911a8e80c..9dc44c73d253 100644
--- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java
+++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java
@@ -92,6 +92,11 @@ public QpackDecoder getQpackDecoder()
return decoder;
}
+ public QpackEncoder getQpackEncoder()
+ {
+ return encoder;
+ }
+
public HTTP3SessionClient getSessionClient()
{
return session;
diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/HTTP3SessionClient.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/HTTP3SessionClient.java
index e817564a02d3..92c1f3325d40 100644
--- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/HTTP3SessionClient.java
+++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/HTTP3SessionClient.java
@@ -146,4 +146,24 @@ protected GoAwayFrame newGoAwayFrame(boolean graceful)
return GoAwayFrame.CLIENT_GRACEFUL;
return super.newGoAwayFrame(graceful);
}
+
+ @Override
+ protected void onSettingMaxTableCapacity(long value)
+ {
+ getProtocolSession().getQpackEncoder().setCapacity((int)value);
+ }
+
+ @Override
+ protected void onSettingMaxFieldSectionSize(long value)
+ {
+ getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
+ }
+
+ @Override
+ protected void onSettingMaxBlockedStreams(long value)
+ {
+ ClientHTTP3Session session = getProtocolSession();
+ session.getQpackDecoder().setMaxBlockedStreams((int)value);
+ session.getQpackEncoder().setMaxBlockedStreams((int)value);
+ }
}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/frames/SettingsFrame.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/frames/SettingsFrame.java
index 8e44fbb25313..be8af79c72a4 100644
--- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/frames/SettingsFrame.java
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/frames/SettingsFrame.java
@@ -17,11 +17,18 @@
public class SettingsFrame extends Frame
{
+ public static final long MAX_TABLE_CAPACITY = 0x01;
public static final long MAX_FIELD_SECTION_SIZE = 0x06;
+ public static final long MAX_BLOCKED_STREAMS = 0x07;
public static boolean isReserved(long key)
{
- return key >= 0 && key <= 5;
+ if (key == MAX_TABLE_CAPACITY ||
+ key == MAX_FIELD_SECTION_SIZE ||
+ key == MAX_BLOCKED_STREAMS)
+ return false;
+ // Other HTTP/2 settings are reserved and must not be sent/received.
+ return key >= 0x00 && key <= 0x05;
}
private final Map settings;
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
index ea5fab8dc145..2ffd225548c0 100644
--- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
@@ -371,9 +371,32 @@ public void onSettings(SettingsFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("received {} on {}", frame, this);
+
+ frame.getSettings().forEach((key, value) ->
+ {
+ if (key == SettingsFrame.MAX_TABLE_CAPACITY)
+ onSettingMaxTableCapacity(value);
+ else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE)
+ onSettingMaxFieldSectionSize(value);
+ else if (key == SettingsFrame.MAX_BLOCKED_STREAMS)
+ onSettingMaxBlockedStreams(value);
+ });
+
notifySettings(frame);
}
+ protected void onSettingMaxTableCapacity(long value)
+ {
+ }
+
+ protected void onSettingMaxFieldSectionSize(long value)
+ {
+ }
+
+ protected void onSettingMaxBlockedStreams(long value)
+ {
+ }
+
private void notifySettings(SettingsFrame frame)
{
try
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
index 31279e3ef48b..4129979ca68b 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
@@ -50,7 +50,8 @@ public class QpackDecoder implements Dumpable
private final List _encodedFieldSections = new ArrayList<>();
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
private final InstructionHandler _instructionHandler = new InstructionHandler();
- private final int _maxHeaderSize;
+ private int _maxHeaderSize;
+ private int _maxBlockedStreams;
private static class MetaDataNotification
{
@@ -87,6 +88,27 @@ QpackContext getQpackContext()
return _context;
}
+ public int getMaxHeaderSize()
+ {
+ return _maxHeaderSize;
+ }
+
+ public void setMaxHeaderSize(int maxHeaderSize)
+ {
+ _maxHeaderSize = maxHeaderSize;
+ }
+
+ public int getMaxBlockedStreams()
+ {
+ // TODO: implement logic about blocked streams by calling this method.
+ return _maxBlockedStreams;
+ }
+
+ public void setMaxBlockedStreams(int maxBlockedStreams)
+ {
+ _maxBlockedStreams = maxBlockedStreams;
+ }
+
public interface Handler
{
void onMetaData(long streamId, MetaData metadata);
@@ -110,7 +132,8 @@ public boolean decode(long streamId, ByteBuffer buffer, Handler handler) throws
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
// If the buffer is big, don't even think about decoding it
- if (buffer.remaining() > _maxHeaderSize)
+ int maxHeaderSize = getMaxHeaderSize();
+ if (buffer.remaining() > maxHeaderSize)
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
_integerDecoder.setPrefix(8);
@@ -139,7 +162,7 @@ public boolean decode(long streamId, ByteBuffer buffer, Handler handler) throws
// Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections.
if (requiredInsertCount <= insertCount)
{
- MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
+ MetaData metaData = encodedFieldSection.decode(_context, maxHeaderSize);
if (LOG.isDebugEnabled())
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
index ae7ccd24d285..7105b5257bf9 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
@@ -91,7 +91,7 @@ public class QpackEncoder implements Dumpable
private final List _instructions = new ArrayList<>();
private final Instruction.Handler _handler;
private final QpackContext _context;
- private final int _maxBlockedStreams;
+ private int _maxBlockedStreams;
private final Map _streamInfoMap = new HashMap<>();
private final EncoderInstructionParser _parser;
private final InstructionHandler _instructionHandler = new InstructionHandler();
@@ -106,6 +106,21 @@ public QpackEncoder(Instruction.Handler handler, int maxBlockedStreams)
_parser = new EncoderInstructionParser(_instructionHandler);
}
+ public int getMaxBlockedStreams()
+ {
+ return _maxBlockedStreams;
+ }
+
+ public void setMaxBlockedStreams(int maxBlockedStreams)
+ {
+ _maxBlockedStreams = maxBlockedStreams;
+ }
+
+ public int getCapacity()
+ {
+ return _context.getDynamicTable().getCapacity();
+ }
+
/**
* Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder.
*
@@ -411,7 +426,7 @@ private boolean referenceEntry(Entry entry, StreamInfo streamInfo)
return true;
}
- if (_blockedStreams < _maxBlockedStreams)
+ if (_blockedStreams < getMaxBlockedStreams())
{
_blockedStreams++;
sectionInfo.block();
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3SessionServer.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3SessionServer.java
index 504688b3648a..02af205db982 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3SessionServer.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3SessionServer.java
@@ -95,6 +95,26 @@ protected GoAwayFrame newGoAwayFrame(boolean graceful)
return super.newGoAwayFrame(graceful);
}
+ @Override
+ protected void onSettingMaxTableCapacity(long value)
+ {
+ getProtocolSession().getQpackEncoder().setCapacity((int)value);
+ }
+
+ @Override
+ protected void onSettingMaxFieldSectionSize(long value)
+ {
+ getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
+ }
+
+ @Override
+ protected void onSettingMaxBlockedStreams(long value)
+ {
+ ServerHTTP3Session session = getProtocolSession();
+ session.getQpackDecoder().setMaxBlockedStreams((int)value);
+ session.getQpackEncoder().setMaxBlockedStreams((int)value);
+ }
+
private void notifyAccept()
{
Server.Listener listener = getListener();
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
index ac52b5a42802..c62103d28d59 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
@@ -91,6 +91,11 @@ public QpackDecoder getQpackDecoder()
return decoder;
}
+ public QpackEncoder getQpackEncoder()
+ {
+ return encoder;
+ }
+
public HTTP3SessionServer getSessionServer()
{
return session;
diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java
index b5b7c3c42647..e6a9b7bf687c 100644
--- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java
+++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http3.tests;
import java.nio.ByteBuffer;
+import java.util.AbstractMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -34,6 +35,7 @@
import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
import org.eclipse.jetty.http3.internal.HTTP3Session;
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
+import org.eclipse.jetty.http3.server.internal.HTTP3SessionServer;
import org.eclipse.jetty.quic.client.ClientQuicSession;
import org.eclipse.jetty.quic.common.QuicSession;
import org.junit.jupiter.api.Test;
@@ -41,6 +43,7 @@
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -90,6 +93,59 @@ public void onSettings(Session session, SettingsFrame frame)
assertTrue(clientSettingsLatch.await(5, TimeUnit.SECONDS));
}
+ @Test
+ public void testSettings() throws Exception
+ {
+ Map.Entry maxTableCapacity = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_TABLE_CAPACITY, 1024L);
+ Map.Entry maxHeaderSize = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_FIELD_SECTION_SIZE, 2048L);
+ Map.Entry maxBlockedStreams = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_BLOCKED_STREAMS, 16L);
+ CountDownLatch settingsLatch = new CountDownLatch(2);
+ AtomicReference serverSessionRef = new AtomicReference<>();
+ start(new Session.Server.Listener()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ serverSessionRef.set((HTTP3SessionServer)session);
+ return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
+ }
+
+ @Override
+ public void onSettings(Session session, SettingsFrame frame)
+ {
+ settingsLatch.countDown();
+ }
+ });
+
+ HTTP3SessionClient clientSession = (HTTP3SessionClient)newSession(new Session.Client.Listener()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
+ }
+
+ @Override
+ public void onSettings(Session session, SettingsFrame frame)
+ {
+ settingsLatch.countDown();
+ }
+ });
+
+ assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
+
+ HTTP3SessionServer serverSession = serverSessionRef.get();
+ assertEquals(maxTableCapacity.getValue(), serverSession.getProtocolSession().getQpackEncoder().getCapacity());
+ assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
+ assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
+ assertEquals(maxHeaderSize.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
+
+ assertEquals(maxTableCapacity.getValue(), clientSession.getProtocolSession().getQpackEncoder().getCapacity());
+ assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
+ assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
+ assertEquals(maxHeaderSize.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
+ }
+
@Test
public void testGETThenResponseWithoutContent() throws Exception
{
diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
index d5fcf90d9244..42737e2bf3f1 100644
--- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
+++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
@@ -44,8 +44,9 @@ public void testExternalServer() throws Exception
client.start();
try
{
+ HostPort hostPort = new HostPort("google.com:443");
// HostPort hostPort = new HostPort("nghttp2.org:4433");
- HostPort hostPort = new HostPort("quic.tech:8443");
+// HostPort hostPort = new HostPort("quic.tech:8443");
// HostPort hostPort = new HostPort("h2o.examp1e.net:443");
// HostPort hostPort = new HostPort("test.privateoctopus.com:4433");
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {})
diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml
index d5865a03889d..b436db8b597a 100644
--- a/jetty-jspc-maven-plugin/pom.xml
+++ b/jetty-jspc-maven-plugin/pom.xml
@@ -15,13 +15,6 @@
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- true
-
-
org.apache.maven.plugins
maven-plugin-plugin
@@ -69,6 +62,7 @@
org.apache.maven
maven-plugin-api
+ provided
javax.annotation
@@ -81,9 +75,25 @@
maven-plugin-annotations
provided
+
+ org.apache.maven
+ maven-core
+ provided
+
+
+ org.apache.maven
+ maven-model
+ provided
+
+
+ org.apache.maven
+ maven-settings
+ provided
+
org.apache.maven
maven-artifact
+ provided
org.apache.maven.plugin-tools
@@ -110,53 +120,4 @@
ant
-
-
-
- org.apache.maven.plugins
- maven-project-info-reports-plugin
-
- false
-
-
-
-
- team
- mailing-lists
- ci-management
- issue-management
- licenses
- scm
-
-
-
-
-
- org.apache.maven.plugins
- maven-plugin-plugin
-
-
-
-
-
- eclipse-release
-
-
-
- org.apache.maven.plugins
- maven-site-plugin
-
-
- site-jar
-
- jar
-
- package
-
-
-
-
-
-
-
diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml
index 5f78ceb39b17..ea7da834a71c 100644
--- a/jetty-maven-plugin/pom.xml
+++ b/jetty-maven-plugin/pom.xml
@@ -122,6 +122,7 @@
org.apache.maven
maven-plugin-api
+ provided
javax.annotation
@@ -129,13 +130,25 @@
+
+ org.apache.maven
+ maven-model
+ provided
+
+
+ org.apache.maven
+ maven-settings
+ provided
+
org.apache.maven
maven-artifact
+ provided
org.apache.maven
maven-core
+ provided
org.apache.maven.plugin-tools
@@ -354,50 +367,4 @@
test
-
-
-
- org.apache.maven.plugins
- maven-project-info-reports-plugin
-
-
-
- team
- mailing-lists
- ci-management
- issue-management
- licenses
- scm
-
-
-
-
-
- org.apache.maven.plugins
- maven-plugin-plugin
-
-
-
-
-
- eclipse-release
-
-
-
- org.apache.maven.plugins
- maven-site-plugin
-
-
- site-jar
-
- jar
-
- package
-
-
-
-
-
-
-
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
index e471f03ccd0d..4d329e0c8952 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
@@ -19,13 +19,16 @@
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
-
/**
* This goal stops a running instance of jetty.
*
@@ -72,38 +75,157 @@ public void execute() throws MojoExecutionException, MojoFailureException
String command = "forcestop";
- try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"), stopPort);)
+ if (stopWait > 0)
{
- OutputStream out = s.getOutputStream();
- out.write((stopKey + "\r\n" + command + "\r\n").getBytes());
- out.flush();
-
- if (stopWait > 0)
+ //try to get the pid of the forked jetty process
+ Long pid = null;
+ try
+ {
+ String response = send(stopKey + "\r\n" + "pid" + "\r\n", stopWait);
+ pid = Long.valueOf(response);
+ }
+ catch (NumberFormatException e)
+ {
+ getLog().info("Server returned bad pid");
+ }
+ catch (ConnectException e)
{
- s.setSoTimeout(stopWait * 1000);
- s.getInputStream();
+ //jetty not running, no point continuing
+ getLog().info("Jetty not running!");
+ return;
+ }
+ catch (Exception e)
+ {
+ //jetty running, try to stop it regardless of error
+ getLog().error(e);
+ }
- getLog().info("Waiting " + stopWait + " seconds for jetty to stop");
- LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
- String response;
- boolean stopped = false;
- while (!stopped && ((response = lin.readLine()) != null))
+ //now send the stop command and wait for confirmation - either an ack from jetty, or
+ //that the process has stopped
+ if (pid == null)
+ {
+ //no pid, so just wait until jetty reports itself stopped
+ try
{
+ getLog().info("Waiting " + stopWait + " seconds for jetty to stop");
+ String response = send(stopKey + "\r\n" + command + "\r\n", stopWait);
+
if ("Stopped".equals(response))
- {
- stopped = true;
getLog().info("Server reports itself as stopped");
- }
+ else
+ getLog().info("Couldn't verify server as stopped, received " + response);
+ }
+ catch (ConnectException e)
+ {
+ getLog().info("Jetty not running!");
}
+ catch (Exception e)
+ {
+ getLog().error(e);
+ }
+ }
+ else
+ {
+ //wait for pid to stop
+ getLog().info("Waiting " + stopWait + " seconds for jetty " + pid + " to stop");
+ Optional optional = ProcessHandle.of(pid);
+ final long remotePid = pid.longValue();
+ optional.ifPresentOrElse(p ->
+ {
+ try
+ {
+ //if running in the same process, just send the stop
+ //command and wait for the response
+ if (ProcessHandle.current().pid() == remotePid)
+ {
+ send(stopKey + "\r\n" + command + "\r\n", stopWait);
+ }
+ else
+ {
+ //running forked, so wait for the process
+ send(stopKey + "\r\n" + command + "\r\n", 0);
+ CompletableFuture future = p.onExit();
+ if (p.isAlive())
+ {
+ p = future.get(stopWait, TimeUnit.SECONDS);
+ }
+
+ if (p.isAlive())
+ getLog().info("Couldn't verify server process stop");
+ else
+ getLog().info("Server process stopped");
+ }
+ }
+ catch (ConnectException e)
+ {
+ //jetty not listening on the given port, don't wait for the process
+ getLog().info("Jetty not running!");
+ }
+ catch (TimeoutException e)
+ {
+ getLog().error("Timeout expired while waiting for server process to stop");
+ }
+ catch (Throwable e)
+ {
+ getLog().error(e);
+ }
+ }, () -> getLog().info("Process not running"));
}
}
- catch (ConnectException e)
+ else
{
- getLog().info("Jetty not running!");
+ //send the stop command but don't wait to verify the stop
+ getLog().info("Stopping jetty");
+ try
+ {
+ send(stopKey + "\r\n" + command + "\r\n", 0);
+ }
+ catch (ConnectException e)
+ {
+ getLog().info("Jetty not running!");
+ }
+ catch (Exception e)
+ {
+ getLog().error(e);
+ }
}
- catch (Exception e)
+ }
+
+ /**
+ * Send a command to a jetty process, optionally waiting for a response.
+ *
+ * @param command the command to send
+ * @param wait length of time in sec to wait for a response
+ * @return the response, if any, to the command
+ * @throws Exception
+ */
+ private String send(String command, int wait)
+ throws Exception
+ {
+ String response = null;
+ try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"), stopPort); OutputStream out = s.getOutputStream();)
{
- getLog().error(e);
+ out.write(command.getBytes());
+ out.flush();
+
+ if (wait > 0)
+ {
+ //Wait for a response
+ s.setSoTimeout(wait * 1000);
+
+ try (LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));)
+ {
+ response = lin.readLine();
+ }
+ }
+ else
+ {
+ //Wait only a small amount of time to ensure TCP has sent the message
+ s.setSoTimeout(1000);
+ s.getInputStream().read();
+ }
+
+ return response;
}
}
}
diff --git a/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitor.java b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitor.java
new file mode 100644
index 000000000000..e7f451934844
--- /dev/null
+++ b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitor.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.maven.plugin;
+
+import java.net.ServerSocket;
+
+import org.eclipse.jetty.toolchain.test.IO;
+
+/**
+ * MockShutdownMonitor
+ * A helper class that grabs a ServerSocket, spawns a thread and then
+ * passes the ServerSocket to the Runnable. This class has a main so
+ * that it can be used for forking, to mimic the actions of the
+ * org.eclipse.jetty.server.ShutdownMonitor.
+ */
+public class MockShutdownMonitor
+{
+ String key;
+ MockShutdownMonitorRunnable testerRunnable;
+ ServerSocket serverSocket;
+
+ public MockShutdownMonitor(String key, MockShutdownMonitorRunnable testerRunnable)
+ throws Exception
+ {
+ this.key = key;
+ this.testerRunnable = testerRunnable;
+ listen();
+ }
+
+ private ServerSocket listen()
+ throws Exception
+ {
+ serverSocket = new ServerSocket(0);
+ try
+ {
+ serverSocket.setReuseAddress(true);
+ return serverSocket;
+ }
+ catch (Throwable e)
+ {
+ IO.close(serverSocket);
+ throw e;
+ }
+ }
+
+ public int getPort()
+ {
+ if (serverSocket == null)
+ return 0;
+ return serverSocket.getLocalPort();
+ }
+
+ public void start()
+ throws Exception
+ {
+ testerRunnable.setServerSocket(serverSocket);
+ testerRunnable.setKey(key);
+ Thread thread = new Thread(testerRunnable);
+ thread.setDaemon(true);
+ thread.setName("Tester Thread");
+ thread.start();
+ }
+}
diff --git a/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitorRunnable.java b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitorRunnable.java
new file mode 100644
index 000000000000..bfdb18baf167
--- /dev/null
+++ b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/MockShutdownMonitorRunnable.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.maven.plugin;
+
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.toolchain.test.IO;
+
+/**
+ * MockShutdownMonitorRunnable
+ *
+ * Mimics the actions of the org.eclipse.jetty.server.ShutdownMonitor.ShutdownMonitorRunnable
+ * to aid testing.
+ */
+public class MockShutdownMonitorRunnable implements Runnable
+{
+ ServerSocket serverSocket;
+ String key;
+ String statusResponse = "OK";
+ String pidResponse;
+ String defaultResponse = "Stopped";
+ boolean exit;
+
+ public void setExit(boolean exit)
+ {
+ this.exit = exit;
+ }
+
+ public void setKey(String key)
+ {
+ this.key = key;
+ }
+
+ public void setServerSocket(ServerSocket serverSocket)
+ {
+ this.serverSocket = serverSocket;
+ }
+
+ public void setPidResponse(String pidResponse)
+ {
+ this.pidResponse = pidResponse;
+ }
+
+ public void run()
+ {
+ try
+ {
+ while (true)
+ {
+ try (Socket socket = serverSocket.accept())
+ {
+ LineNumberReader reader = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+ String receivedKey = reader.readLine();
+ if (!key.equals(receivedKey))
+ {
+ continue;
+ }
+
+ String cmd = reader.readLine();
+ OutputStream out = socket.getOutputStream();
+
+ if ("status".equalsIgnoreCase(cmd))
+ {
+ out.write((statusResponse + "\r\n").getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ }
+ else if ("pid".equalsIgnoreCase(cmd))
+ {
+ out.write((pidResponse + "\r\n").getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ }
+ else
+ {
+ out.write((defaultResponse + "\r\n").getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ if (exit)
+ System.exit(0);
+ }
+ }
+ catch (Throwable x)
+ {
+ x.printStackTrace();
+ }
+ }
+ }
+ catch (Throwable x)
+ {
+ x.printStackTrace();
+ }
+ finally
+ {
+ IO.close(serverSocket);
+ }
+ }
+}
diff --git a/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestJettyStopMojo.java b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestJettyStopMojo.java
new file mode 100644
index 000000000000..39fd9882c34f
--- /dev/null
+++ b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestJettyStopMojo.java
@@ -0,0 +1,304 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.maven.plugin;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.LineNumberReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.maven.plugin.AbstractWebAppMojo.DeploymentMode;
+import org.eclipse.jetty.server.ShutdownMonitor;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class TestJettyStopMojo
+{
+ /**
+ * ShutdownMonitorMain
+ * Kick off the ShutdownMonitor and wait for it to exit.
+ */
+ public static final class ShutdownMonitorMain
+ {
+ public static void main(String[] args)
+ {
+ try
+ {
+ ShutdownMonitor monitor = ShutdownMonitor.getInstance();
+ monitor.setPort(0);
+ monitor.start();
+ monitor.await();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static class TestLog implements org.apache.maven.plugin.logging.Log
+ {
+ List sink = new ArrayList<>();
+
+ @Override
+ public boolean isDebugEnabled()
+ {
+ return true;
+ }
+
+ @Override
+ public void debug(CharSequence content)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void debug(CharSequence content, Throwable error)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void debug(Throwable error)
+ {
+ }
+
+ @Override
+ public boolean isInfoEnabled()
+ {
+ return true;
+ }
+
+ @Override
+ public void info(CharSequence content)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void info(CharSequence content, Throwable error)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void info(Throwable error)
+ {
+ }
+
+ @Override
+ public boolean isWarnEnabled()
+ {
+ return true;
+ }
+
+ @Override
+ public void warn(CharSequence content)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void warn(CharSequence content, Throwable error)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void warn(Throwable error)
+ {
+ }
+
+ @Override
+ public boolean isErrorEnabled()
+ {
+ return true;
+ }
+
+ @Override
+ public void error(CharSequence content)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void error(CharSequence content, Throwable error)
+ {
+ sink.add(content.toString());
+ }
+
+ @Override
+ public void error(Throwable error)
+ {
+ }
+
+ public void assertContains(String str)
+ {
+ assertThat(sink, Matchers.hasItem(str));
+ }
+
+ public void dumpStdErr()
+ {
+ for (String s : sink)
+ {
+ System.err.println(s);
+ }
+ }
+ }
+
+ @Test
+ public void testStopNoWait() throws Exception
+ {
+ //send a stop message and don't wait for the reply or the process to shutdown
+ String stopKey = "foo";
+ MockShutdownMonitorRunnable runnable = new MockShutdownMonitorRunnable();
+ runnable.setPidResponse("abcd");
+ MockShutdownMonitor monitor = new MockShutdownMonitor(stopKey, runnable);
+ monitor.start();
+
+ TestLog log = new TestLog();
+ JettyStopMojo mojo = new JettyStopMojo();
+ mojo.stopKey = stopKey;
+ mojo.stopPort = monitor.getPort();
+ mojo.setLog(log);
+
+ mojo.execute();
+
+ log.assertContains("Stopping jetty");
+ }
+
+ @Test
+ public void testStopWaitBadPid() throws Exception
+ {
+ //test that even if we receive a bad pid, we still send the stop command and wait to
+ //receive acknowledgement, but we don't wait for the process to exit
+ String stopKey = "foo";
+ MockShutdownMonitorRunnable runnable = new MockShutdownMonitorRunnable();
+ runnable.setPidResponse("abcd");
+ MockShutdownMonitor monitor = new MockShutdownMonitor(stopKey, runnable);
+ monitor.start();
+
+ TestLog log = new TestLog();
+ JettyStopMojo mojo = new JettyStopMojo();
+ mojo.stopWait = 5;
+ mojo.stopKey = stopKey;
+ mojo.stopPort = monitor.getPort();
+ mojo.setLog(log);
+
+ mojo.execute();
+
+ log.assertContains("Server returned bad pid");
+ log.assertContains("Server reports itself as stopped");
+ }
+
+ @Test
+ public void testStopSameProcess() throws Exception
+ {
+ //test that if we need to stop a jetty in the same process as us
+ //we will wait for it to exit
+ String stopKey = "foo";
+ long thisPid = ProcessHandle.current().pid();
+ MockShutdownMonitorRunnable runnable = new MockShutdownMonitorRunnable();
+ runnable.setPidResponse(Long.toString(thisPid));
+ MockShutdownMonitor monitor = new MockShutdownMonitor(stopKey, runnable);
+ monitor.start();
+
+ TestLog log = new TestLog();
+ JettyStopMojo mojo = new JettyStopMojo();
+ mojo.stopWait = 5;
+ mojo.stopKey = stopKey;
+ mojo.stopPort = monitor.getPort();
+ mojo.setLog(log);
+
+ mojo.execute();
+
+ log.assertContains("Waiting 5 seconds for jetty " + Long.toString(thisPid) + " to stop");
+ }
+
+ @Test
+ public void testStopWait() throws Exception
+ {
+ //test that we will communicate with a remote process and wait for it to exit
+ String stopKey = "foo";
+ List cmd = new ArrayList<>();
+ String java = "java";
+ String[] javaexes = new String[]{"java", "java.exe"};
+ File javaHomeDir = new File(System.getProperty("java.home"));
+ Path javaHomePath = javaHomeDir.toPath();
+ for (String javaexe : javaexes)
+ {
+ Path javaBinPath = javaHomePath.resolve(Paths.get("bin", javaexe));
+ if (Files.exists(javaBinPath) && !Files.isDirectory(javaBinPath))
+ java = javaBinPath.toFile().getAbsolutePath();
+ }
+
+ cmd.add(java);
+ cmd.add("-DSTOP.KEY=" + stopKey);
+ cmd.add("-DDEBUG=true");
+ cmd.add("-cp");
+ cmd.add(System.getProperty("java.class.path"));
+ cmd.add(ShutdownMonitorMain.class.getName());
+
+ ProcessBuilder command = new ProcessBuilder(cmd);
+ File file = MavenTestingUtils.getTargetFile("tester.out");
+ command.redirectOutput(file);
+ command.redirectErrorStream(true);
+ command.directory(MavenTestingUtils.getTargetDir());
+ Process fork = command.start();
+
+ Thread.sleep(500);
+ while (!file.exists() && file.length() == 0)
+ {
+ Thread.sleep(300);
+ }
+
+ String tmp = "";
+ String port = null;
+ try (LineNumberReader reader = new LineNumberReader(new FileReader(file)))
+ {
+ while (port == null && tmp != null)
+ {
+ tmp = reader.readLine();
+ if (tmp != null)
+ {
+ if (tmp.startsWith("STOP.PORT="))
+ port = tmp.substring(10);
+ }
+ }
+ }
+
+ assertNotNull(port);
+
+ TestLog log = new TestLog();
+ JettyStopMojo mojo = new JettyStopMojo();
+ mojo.stopWait = 5;
+ mojo.stopKey = stopKey;
+ mojo.stopPort = Integer.parseInt(port);
+ mojo.setLog(log);
+
+ mojo.execute();
+
+ log.dumpStdErr();
+ log.assertContains("Waiting " + mojo.stopWait + " seconds for jetty " + fork.pid() + " to stop");
+ log.assertContains("Server process stopped");
+ }
+}
diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
index cbce1bb634bd..8ab1255c3fba 100644
--- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
+++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
@@ -24,6 +24,7 @@
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ajax.JSON;
+import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
@@ -86,8 +87,13 @@ public OpenIdConfiguration(String issuer, String authorizationEndpoint, String t
* @param authMethod Authentication method to use with the Token Endpoint.
* @param httpClient The {@link HttpClient} instance to use.
*/
- public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
- String clientId, String clientSecret, String authMethod, HttpClient httpClient)
+ public OpenIdConfiguration(@Name("issuer") String issuer,
+ @Name("authorizationEndpoint") String authorizationEndpoint,
+ @Name("tokenEndpoint") String tokenEndpoint,
+ @Name("clientId") String clientId,
+ @Name("clientSecret") String clientSecret,
+ @Name("authMethod") String authMethod,
+ @Name("httpClient") HttpClient httpClient)
{
this.issuer = issuer;
this.clientId = clientId;
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
index 370daec91d3a..2d00c5fc9812 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
@@ -44,6 +44,25 @@
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ set-jsp-api-version
+ validate
+
+ parse-version
+
+
+ ${jsp.impl.version}
+ jspImpl
+
+
+
+
+
org.apache.felix
maven-bundle-plugin
@@ -73,20 +92,20 @@
javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional,
- org.apache.el;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.el.lang;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.el.stream;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.el.util;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.el.parser;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.compiler;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.compiler.tagplugin;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.runtime;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.security;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.servlet;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.tagplugins.jstl;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.util;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
- org.apache.jasper.xmlparser;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
+ org.apache.el;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.el.lang;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.el.stream;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.el.util;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.el.parser;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.compiler;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.compiler.tagplugin;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.runtime;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.security;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.servlet;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.tagplugins.jstl;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.util;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
+ org.apache.jasper.xmlparser;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
org.apache.taglibs.standard;version="1.2";resolution:=optional,
org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional,
org.apache.taglibs.standard.functions;version="1.2";resolution:=optional,
@@ -110,7 +129,7 @@
org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional,
org.apache.taglibs.standard.tei;version="1.2";resolution:=optional,
org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional,
- org.apache.tomcat;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))";resolution:=optional,
+ org.apache.tomcat;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))";resolution:=optional,
org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional,
org.osgi.*,
org.xml.*;resolution:=optional,
@@ -121,8 +140,8 @@
javax.xml.parser;resolution:=optional
org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))",
- org.apache.jasper.*;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))",
- org.apache.el.*;version="[$(version;===;${jsp.impl.version}),$(version;+;${jsp.impl.version}))"
+ org.apache.jasper.*;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))",
+ org.apache.el.*;version="[$(version;===;${jspImpl.osgiVersion}),$(version;+;${jspImpl.osgiVersion}))"
diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml
index f955fde28feb..c75dade73ceb 100644
--- a/jetty-osgi/pom.xml
+++ b/jetty-osgi/pom.xml
@@ -12,7 +12,7 @@
pom
- 3.17.100
+ 3.17.200
3.10.200
3.6.100
1.0.0-v20070606
diff --git a/jetty-p2/pom.xml b/jetty-p2/pom.xml
index c5d9bb5a4607..28090d0fb5c9 100644
--- a/jetty-p2/pom.xml
+++ b/jetty-p2/pom.xml
@@ -12,7 +12,7 @@
Generates a (maven based) P2 Updatesite
pom
- 2.6.0
+ 2.7.0
diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
index 8a4fa08d62f2..af8584f18811 100644
--- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
+++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
@@ -33,7 +33,7 @@ public class quiche_h
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
- private static final String EXPECTED_QUICHE_VERSION = "0.11.0";
+ private static final String EXPECTED_QUICHE_VERSION = "0.12.0";
public static final byte C_FALSE = 0;
public static final byte C_TRUE = 1;
diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
index dbeed8c899bb..2ccb3295a960 100644
--- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
+++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
@@ -31,7 +31,7 @@ public interface LibQuiche extends Library
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
- String EXPECTED_QUICHE_VERSION = "0.11.0";
+ String EXPECTED_QUICHE_VERSION = "0.12.0";
// The charset used to convert java.lang.String to char * and vice versa.
Charset CHARSET = StandardCharsets.UTF_8;
diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
index a134dcd1b748..a2d3c1ffe5da 100644
--- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
+++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
@@ -89,7 +89,7 @@ public QuicServerConnector(Server server, Executor executor, Scheduler scheduler
// One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
quicConfiguration.setMaxBidirectionalRemoteStreams(1);
quicConfiguration.setMaxUnidirectionalRemoteStreams(0);
- quicConfiguration.setVerifyPeerCertificates(true);
+ quicConfiguration.setVerifyPeerCertificates(false);
}
public QuicConfiguration getQuicConfiguration()
diff --git a/jetty-server/src/main/config/etc/well-known.xml b/jetty-server/src/main/config/etc/well-known.xml
index e35118a0df5b..068b676a4abf 100644
--- a/jetty-server/src/main/config/etc/well-known.xml
+++ b/jetty-server/src/main/config/etc/well-known.xml
@@ -12,7 +12,7 @@
- false
+
diff --git a/jetty-server/src/main/config/modules/well-known.mod b/jetty-server/src/main/config/modules/well-known.mod
index e5ee42fe7f40..4db3029b075c 100644
--- a/jetty-server/src/main/config/modules/well-known.mod
+++ b/jetty-server/src/main/config/modules/well-known.mod
@@ -20,3 +20,6 @@ etc/well-known.xml
## Well Known Directory (relative to $JETTY_BASE if relative path, otherwise it is an absolute path).
# jetty.wellknown.dir=.well-known
# end::documentation[]
+
+## Allow contents of the well-known directory to be listed.
+# jetty.wellknown.listDirectories=false
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index da8f3242ad38..bdfcd2a770c2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -205,7 +205,7 @@ public void setPort(int port)
}
}
- protected void start() throws Exception
+ public void start() throws Exception
{
try (AutoLock l = _lock.lock())
{
@@ -236,7 +236,7 @@ private void stop()
}
// For test purposes only.
- void await() throws InterruptedException
+ public void await() throws InterruptedException
{
try (AutoLock.WithCondition l = _lock.lock())
{
@@ -407,6 +407,10 @@ else if ("status".equalsIgnoreCase(cmd))
// Reply to client
informClient(out, "OK\r\n");
}
+ else if ("pid".equalsIgnoreCase(cmd))
+ {
+ informClient(out, Long.toString(ProcessHandle.current().pid()));
+ }
}
catch (Throwable x)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
index be3ade5225cb..1a739fe97293 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
@@ -694,7 +694,7 @@ else if (COMMA_GZIP.matcher(field.getValue()).matches())
String mimeType = context == null ? MimeTypes.getDefaultMimeByExtension(path) : context.getMimeType(path);
if (mimeType != null)
{
- mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
+ mimeType = HttpField.valueParameters(mimeType, null);
if (!isMimeTypeGzipable(mimeType))
{
LOG.debug("{} excluded by path suffix mime type {}", this, request);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
index 996638f9feae..95cc2bf206d8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
@@ -23,7 +23,6 @@
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
@@ -31,7 +30,6 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingNestedCallback;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.compression.DeflaterPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -160,8 +158,8 @@ protected void commit(ByteBuffer content, boolean complete, Callback callback)
String ct = response.getContentType();
if (ct != null)
{
- ct = MimeTypes.getContentTypeWithoutCharset(ct);
- if (!_factory.isMimeTypeGzipable(StringUtil.asciiToLowerCase(ct)))
+ String baseType = HttpField.valueParameters(ct, null);
+ if (!_factory.isMimeTypeGzipable(baseType))
{
LOG.debug("{} exclude by mimeType {}", this, ct);
noCompression();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
index 0bb5904eaea2..b6704fb94366 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
@@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLHandshakeException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -535,6 +536,10 @@ public void testMaxIdleNothingSent() throws Exception
assertThat(response, is(""));
assertEquals(-1, is.read());
}
+ catch (SSLHandshakeException e)
+ {
+ LOG.debug("Legit possible SSL result", e);
+ }
catch (IOException e)
{
LOG.warn("Unable to read stream", e);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index 21a41ea964a4..73269c2d7384 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -34,6 +34,7 @@
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -253,6 +254,64 @@ public void testParamExtraction() throws Exception
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
+ @Test
+ public void testParameterExtractionKeepOrderingIntact() throws Exception
+ {
+ AtomicReference
-
- org.apache.maven.plugins
- maven-project-info-reports-plugin
- ${maven.project-info-reports.plugin.version}
-
org.apache.maven.plugins
maven-release-plugin
@@ -1187,6 +1181,11 @@
+
+ org.apache.maven
+ maven-model
+ ${maven.version}
+
org.apache.maven
maven-plugin-api
@@ -2059,47 +2058,6 @@
-
- checkstyle
-
-
-
- org.apache.maven.plugins
- maven-project-info-reports-plugin
- ${maven.project-info-reports.plugin.version}
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
-
-
-
- checkstyle
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-site-plugin
- ${maven.site.plugin.version}
-
-
- build-site
-
- site
-
- package
-
-
-
-
-
-
config
@@ -2310,7 +2268,6 @@
org.apache.maven.plugins
maven-surefire-plugin
- ${maven.surefire.plugin.version}
external, large-disk-resource
@@ -2335,9 +2292,9 @@
${spotbugs.skip}
${spotbugs.failOnError}
true
- false
${spotbugs.effort}
${spotbugs.threshold}
+ false
@@ -2345,16 +2302,6 @@
-
- jdk17
-
- [17,)
-
-
-
- true
-
-
update-version
diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml
index 571f11529927..daba9a6e146b 100644
--- a/tests/test-distribution/pom.xml
+++ b/tests/test-distribution/pom.xml
@@ -149,6 +149,11 @@
websocket-jetty-api
test
+
+ org.eclipse.jetty
+ jetty-openid
+ test
+
org.eclipse.jetty.tests
test-felix-webapp
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
index 9879b83005c0..92be92551988 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
@@ -41,6 +41,7 @@
import org.eclipse.jetty.http3.client.http.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.start.FS;
+import org.eclipse.jetty.tests.distribution.openid.OpenIdProvider;
import org.eclipse.jetty.toolchain.test.PathAssert;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
@@ -1143,4 +1144,89 @@ public void testH3() throws Exception
}
}
}
+
+ @Test
+ public void testOpenID() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ String[] args1 = {
+ "--create-startd",
+ "--approve-all-licenses",
+ "--add-to-start=http,webapp,deploy,openid"
+ };
+
+ String clientId = "clientId123";
+ String clientSecret = "clientSecret456";
+ OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret);
+ try (JettyHomeTester.Run run1 = distribution.start(args1))
+ {
+ assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
+ assertEquals(0, run1.getExitValue());
+
+ File webApp = distribution.resolveArtifact("org.eclipse.jetty.tests:test-openid-webapp:war:" + jettyVersion);
+ distribution.installWarFile(webApp, "test");
+
+ int port = distribution.freePort();
+ openIdProvider.addRedirectUri("http://localhost:" + port + "/test/j_security_check");
+ openIdProvider.start();
+ String[] args2 = {
+ "jetty.http.port=" + port,
+ "jetty.ssl.port=" + port,
+ "jetty.openid.provider=" + openIdProvider.getProvider(),
+ "jetty.openid.clientId=" + clientId,
+ "jetty.openid.clientSecret=" + clientSecret,
+ //"jetty.server.dumpAfterStart=true",
+ };
+
+ try (JettyHomeTester.Run run2 = distribution.start(args2))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
+ startHttpClient(false);
+ String uri = "http://localhost:" + port + "/test";
+ openIdProvider.setUser(new OpenIdProvider.User("123456789", "Alice"));
+
+ // Initially not authenticated
+ ContentResponse response = client.GET(uri + "/");
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ String content = response.getContentAsString();
+ assertThat(content, containsString("not authenticated"));
+
+ // Request to login is success
+ response = client.GET(uri + "/login");
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ content = response.getContentAsString();
+ assertThat(content, containsString("success"));
+
+ // Now authenticated we can get info
+ response = client.GET(uri + "/");
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ content = response.getContentAsString();
+ assertThat(content, containsString("userId: 123456789"));
+ assertThat(content, containsString("name: Alice"));
+ assertThat(content, containsString("email: Alice@example.com"));
+
+ // Request to admin page gives 403 as we do not have admin role
+ response = client.GET(uri + "/admin");
+ assertThat(response.getStatus(), is(HttpStatus.FORBIDDEN_403));
+
+ // We are no longer authenticated after logging out
+ response = client.GET(uri + "/logout");
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ content = response.getContentAsString();
+ assertThat(content, containsString("not authenticated"));
+
+ }
+ }
+ finally
+ {
+ openIdProvider.stop();
+ }
+ }
}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/JwtEncoder.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/JwtEncoder.java
new file mode 100644
index 000000000000..91036b379497
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/JwtEncoder.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.tests.distribution.openid;
+
+import java.util.Base64;
+
+/**
+ * A basic JWT encoder for testing purposes.
+ */
+public class JwtEncoder
+{
+ private static final Base64.Encoder ENCODER = Base64.getUrlEncoder();
+ private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}";
+ private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow";
+
+ public static String encode(String idToken)
+ {
+ return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." +
+ stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." +
+ stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes()));
+ }
+
+ private static String stripPadding(String paddedBase64)
+ {
+ return paddedBase64.split("=")[0];
+ }
+
+ /**
+ * Create a basic JWT for testing using argument supplied attributes.
+ */
+ public static String createIdToken(String provider, String clientId, String subject, String name, long expiry)
+ {
+ return "{" +
+ "\"iss\": \"" + provider + "\"," +
+ "\"sub\": \"" + subject + "\"," +
+ "\"aud\": \"" + clientId + "\"," +
+ "\"exp\": " + expiry + "," +
+ "\"name\": \"" + name + "\"," +
+ "\"email\": \"" + name + "@example.com" + "\"" +
+ "}";
+ }
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/OpenIdProvider.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/OpenIdProvider.java
new file mode 100644
index 000000000000..4eaa11a119a5
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/openid/OpenIdProvider.java
@@ -0,0 +1,341 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.tests.distribution.openid;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.security.openid.OpenIdConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OpenIdProvider extends ContainerLifeCycle
+{
+ private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class);
+
+ private static final String CONFIG_PATH = "/.well-known/openid-configuration";
+ private static final String AUTH_PATH = "/auth";
+ private static final String TOKEN_PATH = "/token";
+ private final Map issuedAuthCodes = new HashMap<>();
+
+ protected final String clientId;
+ protected final String clientSecret;
+ protected final List redirectUris = new ArrayList<>();
+ private final ServerConnector connector;
+ private final Server server;
+ private int port = 0;
+ private String provider;
+ private User preAuthedUser;
+
+ public static void main(String[] args) throws Exception
+ {
+ String clientId = "CLIENT_ID123";
+ String clientSecret = "PASSWORD123";
+ int port = 5771;
+ String redirectUri = "http://localhost:8080/openid/auth";
+
+ OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret);
+ openIdProvider.addRedirectUri(redirectUri);
+ openIdProvider.setPort(port);
+ openIdProvider.start();
+ try
+ {
+ openIdProvider.join();
+ }
+ finally
+ {
+ openIdProvider.stop();
+ }
+ }
+
+ public OpenIdProvider(String clientId, String clientSecret)
+ {
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+
+ server = new Server();
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler contextHandler = new ServletContextHandler();
+ contextHandler.setContextPath("/");
+ contextHandler.addServlet(new ServletHolder(new OpenIdConfigServlet()), CONFIG_PATH);
+ contextHandler.addServlet(new ServletHolder(new OpenIdAuthEndpoint()), AUTH_PATH);
+ contextHandler.addServlet(new ServletHolder(new OpenIdTokenEndpoint()), TOKEN_PATH);
+ server.setHandler(contextHandler);
+
+ addBean(server);
+ }
+
+ public void join() throws InterruptedException
+ {
+ server.join();
+ }
+
+ public OpenIdConfiguration getOpenIdConfiguration()
+ {
+ String provider = getProvider();
+ String authEndpoint = provider + AUTH_PATH;
+ String tokenEndpoint = provider + TOKEN_PATH;
+ return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ connector.setPort(port);
+ super.doStart();
+ provider = "http://localhost:" + connector.getLocalPort();
+ }
+
+ public void setPort(int port)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+ this.port = port;
+ }
+
+ public void setUser(User user)
+ {
+ this.preAuthedUser = user;
+ }
+
+ public String getProvider()
+ {
+ if (!isStarted() && port == 0)
+ throw new IllegalStateException("Port of OpenIdProvider not configured");
+ return provider;
+ }
+
+ public void addRedirectUri(String uri)
+ {
+ redirectUris.add(uri);
+ }
+
+ public class OpenIdAuthEndpoint extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ if (!clientId.equals(req.getParameter("client_id")))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id");
+ return;
+ }
+
+ String redirectUri = req.getParameter("redirect_uri");
+ if (!redirectUris.contains(redirectUri))
+ {
+ LOG.warn("invalid redirectUri {}", redirectUri);
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri");
+ return;
+ }
+
+ String scopeString = req.getParameter("scope");
+ List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(StringUtil.csvSplit(scopeString));
+ if (!scopes.contains("openid"))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope");
+ return;
+ }
+
+ if (!"code".equals(req.getParameter("response_type")))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code");
+ return;
+ }
+
+ String state = req.getParameter("state");
+ if (state == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param");
+ return;
+ }
+
+ if (preAuthedUser == null)
+ {
+ PrintWriter writer = resp.getWriter();
+ resp.setContentType("text/html");
+ writer.println("Login to OpenID Connect Provider
");
+ writer.println("");
+ }
+ else
+ {
+ redirectUser(req, preAuthedUser, redirectUri, state);
+ }
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ String redirectUri = req.getParameter("redirectUri");
+ if (!redirectUris.contains(redirectUri))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri");
+ return;
+ }
+
+ String state = req.getParameter("state");
+ if (state == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param");
+ return;
+ }
+
+ String username = req.getParameter("username");
+ if (username == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username");
+ return;
+ }
+
+ User user = new User(username);
+ redirectUser(req, user, redirectUri, state);
+ }
+
+ public void redirectUser(HttpServletRequest request, User user, String redirectUri, String state) throws IOException
+ {
+ String authCode = UUID.randomUUID().toString().replace("-", "");
+ issuedAuthCodes.put(authCode, user);
+
+ try
+ {
+ final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
+ final Response baseResponse = baseRequest.getResponse();
+ redirectUri += "?code=" + authCode + "&state=" + state;
+ int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()
+ ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ baseResponse.sendRedirect(redirectCode, baseResponse.encodeRedirectURL(redirectUri));
+ }
+ catch (Throwable t)
+ {
+ issuedAuthCodes.remove(authCode);
+ throw t;
+ }
+ }
+ }
+
+ public class OpenIdTokenEndpoint extends HttpServlet
+ {
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ String code = req.getParameter("code");
+
+ if (!clientId.equals(req.getParameter("client_id")) ||
+ !clientSecret.equals(req.getParameter("client_secret")) ||
+ !redirectUris.contains(req.getParameter("redirect_uri")) ||
+ !"authorization_code".equals(req.getParameter("grant_type")) ||
+ code == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request");
+ return;
+ }
+
+ User user = issuedAuthCodes.remove(code);
+ if (user == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code");
+ return;
+ }
+
+ String accessToken = "ABCDEFG";
+ long expiry = System.currentTimeMillis() + Duration.ofMinutes(10).toMillis();
+ String response = "{" +
+ "\"access_token\": \"" + accessToken + "\"," +
+ "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId)) + "\"," +
+ "\"expires_in\": " + expiry + "," +
+ "\"token_type\": \"Bearer\"" +
+ "}";
+
+ resp.setContentType("text/plain");
+ resp.getWriter().print(response);
+ }
+ }
+
+ public class OpenIdConfigServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ String discoveryDocument = "{" +
+ "\"issuer\": \"" + provider + "\"," +
+ "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," +
+ "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," +
+ "}";
+
+ resp.getWriter().write(discoveryDocument);
+ }
+ }
+
+ public static class User
+ {
+ private final String subject;
+ private final String name;
+
+ public User(String name)
+ {
+ this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name);
+ }
+
+ public User(String subject, String name)
+ {
+ this.subject = subject;
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getSubject()
+ {
+ return subject;
+ }
+
+ public String getIdToken(String provider, String clientId)
+ {
+ long expiry = System.currentTimeMillis() + Duration.ofMinutes(1).toMillis();
+ return JwtEncoder.createIdToken(provider, clientId, subject, name, expiry);
+ }
+ }
+}
diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml
index 1a56568e890b..d7a97ab965f9 100644
--- a/tests/test-webapps/pom.xml
+++ b/tests/test-webapps/pom.xml
@@ -10,17 +10,10 @@
test-webapps-parent
Jetty Tests :: WebApps :: Parent
pom
-
-
-
- org.jacoco
- jacoco-maven-plugin
-
- true
-
-
-
-
+
+ true
+ true
+
test-webapp-rfc2616
test-http2-webapp
@@ -33,5 +26,6 @@
test-bad-websocket-webapp
test-websocket-client-webapp
test-websocket-client-provided-webapp
+ test-openid-webapp
diff --git a/tests/test-webapps/test-openid-webapp/pom.xml b/tests/test-webapps/test-openid-webapp/pom.xml
new file mode 100644
index 000000000000..49fd13757e65
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+ org.eclipse.jetty.tests
+ test-webapps-parent
+ 10.0.9-SNAPSHOT
+
+
+ 4.0.0
+ test-openid-webapp
+ war
+
+ Test :: Jetty OpenId Webapp
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ compile
+
+
+ org.eclipse.jetty.toolchain
+ jetty-servlet-api
+ provided
+
+
+
diff --git a/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/AdminPage.java b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/AdminPage.java
new file mode 100644
index 000000000000..6957b1bb5e75
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/AdminPage.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.test.openid;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class AdminPage extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ @SuppressWarnings("unchecked")
+ Map userInfo = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.claims");
+ response.getWriter().println(userInfo.get("sub") + ": success");
+ }
+}
diff --git a/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/ErrorPage.java b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/ErrorPage.java
new file mode 100644
index 000000000000..6869e21ce33e
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/ErrorPage.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.test.openid;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class ErrorPage extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.setContentType("text/html");
+ response.getWriter().println("not authorized");
+ response.getWriter().println("
Home");
+ }
+}
diff --git a/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/HomePage.java b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/HomePage.java
new file mode 100644
index 000000000000..5ecdb0aedff8
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/HomePage.java
@@ -0,0 +1,45 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.test.openid;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Map;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class HomePage extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.setContentType("text/html");
+ Principal userPrincipal = request.getUserPrincipal();
+ if (userPrincipal != null)
+ {
+ @SuppressWarnings("unchecked")
+ Map userInfo = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.claims");
+ response.getWriter().println("userId: " + userInfo.get("sub") + "
");
+ response.getWriter().println("name: " + userInfo.get("name") + "
");
+ response.getWriter().println("email: " + userInfo.get("email") + "
");
+ response.getWriter().println("
Logout");
+ }
+ else
+ {
+ response.getWriter().println("not authenticated");
+ response.getWriter().println("
Login");
+ }
+ }
+}
diff --git a/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LoginPage.java b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LoginPage.java
new file mode 100644
index 000000000000..0abfbb4eab8c
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LoginPage.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.test.openid;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class LoginPage extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.setContentType("text/html");
+ response.getWriter().println("success");
+ response.getWriter().println("
Home");
+ }
+}
diff --git a/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LogoutPage.java b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LogoutPage.java
new file mode 100644
index 000000000000..13a6c044a6d7
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/java/org/eclipse/jetty/test/openid/LogoutPage.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2022 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.test.openid;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class LogoutPage extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ request.getSession().invalidate();
+ response.sendRedirect(request.getContextPath());
+ }
+}
diff --git a/tests/test-webapps/test-openid-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-openid-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000000..1464c57accaa
--- /dev/null
+++ b/tests/test-webapps/test-openid-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,86 @@
+
+
+
+
+ OPENID
+
+
+
+ AdminPage
+ org.eclipse.jetty.test.openid.AdminPage
+
+
+ AdminPage
+ /admin
+
+
+
+ ErrorPage
+ org.eclipse.jetty.test.openid.ErrorPage
+
+
+ ErrorPage
+ /error
+
+
+
+ HomePage
+ org.eclipse.jetty.test.openid.HomePage
+
+
+ HomePage
+
+
+
+
+ LoginPage
+ org.eclipse.jetty.test.openid.LoginPage
+
+
+ LoginPage
+ /login
+
+
+
+ LogoutPage
+ org.eclipse.jetty.test.openid.LogoutPage
+
+
+ LogoutPage
+ /logout
+
+
+
+ admin
+
+
+ **
+
+
+
+
+ User Pages
+ /profile
+ /login
+
+
+ **
+
+
+
+
+
+ Admin Page
+ /admin
+
+
+ admin
+
+
+
+