diff --git a/Jenkinsfile b/Jenkinsfile index 18886fbe488..c3ac98e1f00 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -125,11 +125,11 @@ def mavenBuild(jdk, cmdline, mvnName) { buildCache = useBuildCache() if (buildCache) { echo "Using build cache" - extraArgs = " -Dmaven.build.cache.restoreGeneratedSources=false -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true " + extraArgs = " -Dmaven.build.cache.restoreGeneratedSources=false -Dmaven.build.cache.remote.url=http://nexus-service.nexus.svc.cluster.local:8081/repository/maven-build-cache -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=nexus-cred " } else { // when not using cache echo "Not using build cache" - extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.skipCache=true -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true " + extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.skipCache=true -Dmaven.build.cache.remote.url=http://nexus-service.nexus.svc.cluster.local:8081/repository/maven-build-cache -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=nexus-cred " } if (env.BRANCH_NAME ==~ /PR-\d+/) { if (pullRequest.labels.contains("build-all-tests")) { diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java index 5edfd89fbf5..d3ad64fe497 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java @@ -15,6 +15,9 @@ package org.eclipse.jetty.docs.programming; import java.util.concurrent.Executors; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.VirtualThreadPool; @@ -26,9 +29,26 @@ public class ArchitectureDocs { // tag::queuedVirtual[] QueuedThreadPool threadPool = new QueuedThreadPool(); + + // Simple, unlimited, virtual thread Executor. threadPool.setVirtualThreadsExecutor(Executors.newVirtualThreadPerTaskExecutor()); + // Configurable, bounded, virtual thread executor (preferred). + VirtualThreadPool virtualExecutor = new VirtualThreadPool(); + virtualExecutor.setMaxThreads(128); + threadPool.setVirtualThreadsExecutor(virtualExecutor); + + // For server-side usage. Server server = new Server(threadPool); + + // Simple client-side usage. + HttpClient client = new HttpClient(); + client.setExecutor(threadPool); + + // Client-side usage with explicit HttpClientTransport. + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setExecutor(threadPool); + HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); // end::queuedVirtual[] } @@ -38,8 +58,21 @@ public class ArchitectureDocs VirtualThreadPool threadPool = new VirtualThreadPool(); // Limit the max number of current virtual threads. threadPool.setMaxThreads(200); + // Track, with details, virtual threads usage. + threadPool.setTracking(true); + threadPool.setDetailedDump(true); + // For server-side usage. Server server = new Server(threadPool); + + // Simple client-side usage. + HttpClient client = new HttpClient(); + client.setExecutor(threadPool); + + // Client-side usage with explicit HttpClientTransport. + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setExecutor(threadPool); + HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); // end::virtualVirtual[] } } diff --git a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc index d3992234c71..95c020f899c 100644 --- a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc +++ b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc @@ -714,7 +714,6 @@ See also the xref:server/index.adoc#threadpool[section about configuring the thr The `threadpool-all-virtual` module allows you to configure the server-wide thread pool, similarly to what you can do with the <> Jetty module, so that all threads are virtual threads, introduced as an official feature since Java 21. CAUTION: Only use this module if you are using Java 21 or later. -If you are using Java 19 or Java 20, use the <> Jetty module instead. The module properties to configure the thread pool are: @@ -724,17 +723,7 @@ include::{jetty-home}/modules/threadpool-all-virtual.mod[tags=documentation] The property `jetty.threadpool.maxThreads` limits, using a `Semaphore`, the number of current virtual threads in use. -Limiting the number of current virtual threads helps to limit resource usage in applications, especially in case of load spikes. -When an unlimited number of virtual threads is allowed, the server might be brought down due to resource (typically memory) exhaustion. - -[CAUTION] -==== -Even when using virtual threads, Jetty uses non-blocking I/O, and dedicates a thread to each `java.nio.channels.Selector` to perform the `Selector.select()` operation. - -Currently (up to Java 22), calling `Selector.select()` from a virtual thread pins the carrier thread. - -When using the `threadpool-all-virtual` Jetty module, if you have `N` selectors, then `N` carrier threads will be pinned by the virtual threads calling `Selector.select()`, possibly making your system less efficient, and at worst locking up the entire system if there are no carrier threads available to run virtual threads. -==== +Please refer to the xref:programming-guide:arch/threads.adoc#thread-pool-virtual-threads[virtual threads section] of the Jetty Threading Architecture for more information about virtual threads and their pitfalls. [[threadpool-virtual]] == Module `threadpool-virtual` diff --git a/documentation/jetty/modules/programming-guide/pages/arch/threads.adoc b/documentation/jetty/modules/programming-guide/pages/arch/threads.adoc index 1fb7dff1d2b..39f4b756503 100644 --- a/documentation/jetty/modules/programming-guide/pages/arch/threads.adoc +++ b/documentation/jetty/modules/programming-guide/pages/arch/threads.adoc @@ -269,6 +269,35 @@ Defaulting the number of reserved threads to zero ensures that the <Content must be provided by writing to the {@link #getOutputStream() output stream} * that must be {@link OutputStream#close() closed} when all content has been provided.

*

Example usage:

- *
+ * 
{@code
  * HttpClient httpClient = ...;
  *
  * // Use try-with-resources to autoclose the output stream.
@@ -37,7 +37,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
  *             .body(content)
  *             .send(new Response.CompleteListener()
  *             {
- *                 @Override
+ *                 @Override
  *                 public void onComplete(Result result)
  *                 {
  *                     // Your logic here
@@ -50,7 +50,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
  *     // Even later...
  *     output.write("more content".getBytes());
  * } // Implicit call to output.close().
- * 
+ * }
*/ public class OutputStreamRequestContent extends OutputStreamContentSource implements Request.Content { diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java index 2ed1ad5fba6..61add8e5791 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java @@ -24,7 +24,6 @@ import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.Request; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.slf4j.Logger; @@ -42,7 +41,7 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran public HttpClientTransportOverHTTP() { - this(Math.max(1, ProcessorUtils.availableProcessors() / 2)); + this(1); } public HttpClientTransportOverHTTP(int selectors) diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index de5f6edc3d3..7ba4c573f34 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -422,6 +422,11 @@ public class HttpParser return _state; } + public boolean hasContent() + { + return _endOfContent != EndOfContent.NO_CONTENT; + } + public boolean inContentState() { return _state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal(); diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index b715a8525ac..758802a05ba 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -401,8 +401,8 @@ public interface EndPoint extends Closeable, Content.Sink interface SslSessionData { /** - * The name at which an {@code SslSessionData} instance may be found as a request - * {@link org.eclipse.jetty.util.Attributes Attribute} or from {@link SSLSession#getValue(String)}. + * The name at which an {@code SslSessionData} instance may be found + * as a request {@link org.eclipse.jetty.util.Attributes attribute}. */ String ATTRIBUTE = "org.eclipse.jetty.io.Endpoint.SslSessionData"; diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/OutputStreamContentSource.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/OutputStreamContentSource.java index f7a9c8429bb..1734af349d1 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/OutputStreamContentSource.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/OutputStreamContentSource.java @@ -23,12 +23,11 @@ import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.IO; /** - *

- * A {@link Content.Source} backed by an {@link OutputStream}. - * Any bytes written to the {@link OutputStream} returned by {@link #getOutputStream()} - * is converted to a {@link Content.Chunk} and returned from {@link #read()}. If - * necessary, any {@link Runnable} passed to {@link #demand(Runnable)} is invoked. - *

+ *

A {@link Content.Source} that provides content asynchronously through an {@link OutputStream}.

+ *

Bytes written to the {@link OutputStream} returned by {@link #getOutputStream()} + * are converted to a {@link Content.Chunk} and returned from {@link #read()}.

+ *

The {@code OutputStream} must be closed to signal that all the content has been written.

+ * * @see AsyncContent */ public class OutputStreamContentSource implements Content.Source, Closeable diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 965752c8c45..7db65a461fa 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -507,6 +507,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr private final Callback _incompleteWriteCallback = new IncompleteWriteCallback(); private Throwable _failure; + private SslSessionData _sslSessionData; public SslEndPoint() { @@ -1572,6 +1573,28 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } } + @Override + public SslSessionData getSslSessionData() + { + SSLSession sslSession = _sslEngine.getSession(); + SslSessionData sslSessionData = _sslSessionData; + if (sslSessionData == null) + { + String cipherSuite = sslSession.getCipherSuite(); + + X509Certificate[] peerCertificates = _sslContextFactory != null + ? _sslContextFactory.getX509CertChain(sslSession) + : SslContextFactory.getCertChain(sslSession); + + byte[] bytes = sslSession.getId(); + String idStr = StringUtil.toHexString(bytes); + + sslSessionData = SslSessionData.from(sslSession, idStr, cipherSuite, peerCertificates); + _sslSessionData = sslSessionData; + } + return sslSessionData; + } + @Override public String toString() { @@ -1644,28 +1667,6 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr return String.format("SSL@%h.DEP.writeCallback", SslConnection.this); } } - - @Override - public SslSessionData getSslSessionData() - { - SSLSession sslSession = _sslEngine.getSession(); - SslSessionData sslSessionData = (SslSessionData)sslSession.getValue(SslSessionData.ATTRIBUTE); - if (sslSessionData == null) - { - String cipherSuite = sslSession.getCipherSuite(); - - X509Certificate[] peerCertificates = _sslContextFactory != null - ? _sslContextFactory.getX509CertChain(sslSession) - : SslContextFactory.getCertChain(sslSession); - - byte[] bytes = sslSession.getId(); - String idStr = StringUtil.toHexString(bytes); - - sslSessionData = SslSessionData.from(sslSession, idStr, cipherSuite, peerCertificates); - sslSession.putValue(SslSessionData.ATTRIBUTE, sslSessionData); - } - return sslSessionData; - } } private abstract class RunnableTask implements Invocable.Task diff --git a/jetty-core/jetty-maven/src/main/java/org/eclipse/jetty/maven/ServerSupport.java b/jetty-core/jetty-maven/src/main/java/org/eclipse/jetty/maven/ServerSupport.java index cc766b67706..99178482737 100644 --- a/jetty-core/jetty-maven/src/main/java/org/eclipse/jetty/maven/ServerSupport.java +++ b/jetty-core/jetty-maven/src/main/java/org/eclipse/jetty/maven/ServerSupport.java @@ -19,8 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.eclipse.jetty.maven.MavenServerConnector; -import org.eclipse.jetty.maven.PluginLog; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; @@ -65,7 +63,15 @@ public class ServerSupport if (contexts == null) { contexts = new ContextHandlerCollection(); - server.setHandler(contexts); + if (server.getHandler() != null) + { + Handler.Sequence handlers = new Handler.Sequence(); + handlers.addHandler(server.getHandler()); + handlers.addHandler(contexts); + server.setHandler(handlers); + } + else + server.setHandler(contexts); } if (contextHandlers != null) diff --git a/jetty-core/jetty-maven/src/test/java/org/eclipse/jetty/maven/ServerSupportTest.java b/jetty-core/jetty-maven/src/test/java/org/eclipse/jetty/maven/ServerSupportTest.java new file mode 100644 index 00000000000..cf7fa181ed9 --- /dev/null +++ b/jetty-core/jetty-maven/src/test/java/org/eclipse/jetty/maven/ServerSupportTest.java @@ -0,0 +1,109 @@ +// +// ======================================================================== +// Copyright (c) 1995 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; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerSupportTest +{ + @Test + public void testNoServerHandlers() throws Exception + { + //Test that a server will always create a ContextHandlerCollection and DefaultHandler + Server server = new Server(); + assertNull(server.getHandler()); + ServerSupport.configureHandlers(server, null, null); + assertNotNull(server.getDefaultHandler()); + assertNotNull(server.getHandler()); + } + + @Test + public void testExistingServerHandler() throws Exception + { + //Test that if a Server already has a handler, we replace it with a + //sequence containing the original handler plus a ContextHandlerCollection + Server server = new Server(); + Handler.Abstract testHandler = new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + return false; + } + }; + + server.setHandler(testHandler); + ServerSupport.configureHandlers(server, null, null); + assertNotNull(server.getDefaultHandler()); + assertInstanceOf(Handler.Sequence.class, server.getHandler()); + Handler.Sequence handlers = (Handler.Sequence)server.getHandler(); + assertTrue(handlers.contains(testHandler)); + assertNotNull(handlers.getDescendant(ContextHandlerCollection.class)); + } + + @Test + public void testExistingServerHandlerWithContextHandlers() throws Exception + { + //Test that if a Server already has a handler, we replace it with + //a sequence containing the original handler plus a ContextHandlerCollection + //into which we add any supplied ContextHandlers + Server server = new Server(); + Handler.Abstract testHandlerA = new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + return false; + } + }; + + ContextHandler contextHandlerA = new ContextHandler(); + contextHandlerA.setContextPath("/A"); + ContextHandler contextHandlerB = new ContextHandler(); + contextHandlerB.setContextPath("/B"); + List contextHandlerList = Arrays.asList(contextHandlerA, contextHandlerB); + + server.setHandler(testHandlerA); + ServerSupport.configureHandlers(server, contextHandlerList, null); + assertNotNull(server.getDefaultHandler()); + assertInstanceOf(Handler.Sequence.class, server.getHandler()); + Handler.Sequence handlers = (Handler.Sequence)server.getHandler(); + List handlerList = handlers.getHandlers(); + assertEquals(testHandlerA, handlerList.get(0)); + Handler second = handlerList.get(1); + assertInstanceOf(ContextHandlerCollection.class, second); + ContextHandlerCollection contextHandlers = (ContextHandlerCollection)second; + Set contextPaths = contextHandlers.getContextPaths(); + assertNotNull(contextPaths); + assertTrue(contextPaths.contains("/A")); + assertTrue(contextPaths.contains("/B")); + } +} diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java index e88187a0eac..504f3835bc1 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java @@ -43,7 +43,7 @@ public class SessionAuthentication extends LoginAuthenticator.UserAuthentication private final String _name; private final Object _credentials; - private Session _session; + private transient Session _session; public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials) { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 166c70b0952..222d7f74f14 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -86,6 +86,7 @@ public class Server extends Handler.Wrapper implements Attributes private final AutoLock _dateLock = new AutoLock(); private final MimeTypes.Mutable _mimeTypes = new MimeTypes.Mutable(); private String _serverInfo = __serverInfo; + private boolean _openEarly = true; private boolean _stopAtShutdown; private boolean _dumpAfterStart; private boolean _dumpBeforeStop; @@ -276,6 +277,22 @@ public class Server extends Handler.Wrapper implements Attributes return type; } + public boolean isOpenEarly() + { + return _openEarly; + } + + /** + * Allows to disable early opening of network sockets. Network sockets are opened early by default. + * @param openEarly If {@code openEarly} is {@code true} (default), network sockets are opened before + * starting other components. If {@code openEarly} is {@code false}, network connectors open sockets + * when they're started. + */ + public void setOpenEarly(boolean openEarly) + { + _openEarly = openEarly; + } + public boolean isDryRun() { return _dryRun; @@ -543,7 +560,7 @@ public class Server extends Handler.Wrapper implements Attributes final ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException(); // Open network connector to ensure ports are available - if (!_dryRun) + if (!_dryRun && _openEarly) { _connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector -> { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 08f0c5047aa..e09a9d9af9d 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -653,11 +653,11 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias */ protected void notifyExitScope(Request request) { - for (int i = _contextListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();) { try { - _contextListeners.get(i).exitScope(_context, request); + i.previous().exitScope(_context, request); } catch (Throwable e) { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java index 6905a3ce258..eade4137899 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java @@ -405,6 +405,15 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab if (LOG.isDebugEnabled()) LOG.debug("HANDLE {} {}", request, this); + // If the buffer is empty and no body is expected, then release the buffer + if (isRequestBufferEmpty() && !_parser.hasContent()) + { + // try parsing now to the end of the message + parseRequestBuffer(); + if (_parser.isComplete()) + releaseRequestBuffer(); + } + // Handle the request by running the task. _handling.set(true); Runnable onRequest = _onRequest; @@ -431,7 +440,15 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab { if (LOG.isDebugEnabled()) LOG.debug("upgraded {} -> {}", this, getEndPoint().getConnection()); - releaseRequestBuffer(); + if (_requestBuffer != null) + releaseRequestBuffer(); + break; + } + + // If we have already released the request buffer, then use fill interest before allocating another + if (_requestBuffer == null) + { + fillInterested(); break; } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java index c96a75f6c23..dba38e2ef4f 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java @@ -79,36 +79,6 @@ public class ConcurrentPool

implements Pool

, Dumpable this(strategyType, maxSize, pooled -> 1); } - /** - *

Creates an instance with the specified strategy.

- * - * @param strategyType the strategy to used to lookup entries - * @param maxSize the maximum number of pooled entries - * @param cache whether a {@link ThreadLocal} cache should be used for the most recently released entry - * @deprecated cache is no longer supported. Use {@link StrategyType#THREAD_ID} - */ - @Deprecated - public ConcurrentPool(StrategyType strategyType, int maxSize, boolean cache) - { - this(strategyType, maxSize, pooled -> 1); - } - - /** - *

Creates an instance with the specified strategy. - * and a function that returns the max multiplex count for a given pooled object.

- * - * @param strategyType the strategy to used to lookup entries - * @param maxSize the maximum number of pooled entries - * @param cache whether a {@link ThreadLocal} cache should be used for the most recently released entry - * @param maxMultiplex a function that given the pooled object returns the max multiplex count - * @deprecated cache is no longer supported. Use {@link StrategyType#THREAD_ID} - */ - @Deprecated - public ConcurrentPool(StrategyType strategyType, int maxSize, boolean cache, ToIntFunction

maxMultiplex) - { - this(strategyType, maxSize, maxMultiplex); - } - /** *

Creates an instance with the specified strategy. * and a function that returns the max multiplex count for a given pooled object.

@@ -148,7 +118,7 @@ public class ConcurrentPool

implements Pool

, Dumpable { leaked.increment(); if (LOG.isDebugEnabled()) - LOG.debug("Leaked " + holder); + LOG.debug("Leaked {}", holder); leaked(); } @@ -195,15 +165,14 @@ public class ConcurrentPool

implements Pool

, Dumpable void sweep() { - for (int i = 0; i < entries.size(); i++) + // Remove entries atomically with respect to remove(Entry). + entries.removeIf(holder -> { - Holder

holder = entries.get(i); - if (holder.getEntry() == null) - { - entries.remove(i--); + boolean remove = holder.getEntry() == null; + if (remove) leaked(holder); - } - } + return remove; + }); } @Override @@ -285,8 +254,7 @@ public class ConcurrentPool

implements Pool

, Dumpable if (!removed) return false; - // No need to lock, no race with reserve() - // and the race with terminate() is harmless. + // In a harmless race with reserve()/sweep()/terminate(). Holder

holder = ((ConcurrentEntry

)entry).getHolder(); boolean evicted = entries.remove(holder); if (LOG.isDebugEnabled()) @@ -313,10 +281,7 @@ public class ConcurrentPool

implements Pool

, Dumpable // Field this.terminated must be modified with the lock held // because the list of entries is modified, see reserve(). terminated = true; - copy = entries.stream() - .map(Holder::getEntry) - .filter(Objects::nonNull) - .toList(); + copy = stream().toList(); entries.clear(); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java index 96b05f42735..10910bc2c7c 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java @@ -292,7 +292,7 @@ public abstract class IteratingCallback implements Callback { ExceptionUtil.callAndThen(cause, this::onAborted, this::onFailure); } - + private void doOnAbortedOnFailureOnCompleted(Throwable cause) { ExceptionUtil.callAndThen(cause, this::doOnAbortedOnFailure, _onCompleted); @@ -423,6 +423,7 @@ public abstract class IteratingCallback implements Callback { // we won the race against the callback, so the callback has to process and we can break processing _state = State.PENDING; + _reprocess = false; if (_aborted) { onAbortedOnFailureIfNotPendingDoCompleted = _failure; @@ -482,6 +483,7 @@ public abstract class IteratingCallback implements Callback } callOnSuccess = true; _state = State.PROCESSING; + _reprocess = false; break; } @@ -818,6 +820,14 @@ public abstract class IteratingCallback implements Callback return true; } + boolean isPending() + { + try (AutoLock ignored = _lock.lock()) + { + return _state == State.PENDING; + } + } + /** * @return whether this callback is idle, and {@link #iterate()} needs to be called */ diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java index 7c21e92731d..7d0bdb94331 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java @@ -34,6 +34,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Objects; import java.util.Optional; import java.util.ServiceConfigurationError; @@ -173,6 +174,32 @@ public class TypeUtil return Arrays.asList(a); } + /** + *

Returns a {@link ListIterator} positioned at the last item in a list.

+ * @param list the list + * @param the element type + * @return A {@link ListIterator} positioned at the last item of the list. + */ + public static ListIterator listIteratorAtEnd(List list) + { + try + { + int size = list.size(); + if (size == 0) + return Collections.emptyListIterator(); + return list.listIterator(size); + } + catch (IndexOutOfBoundsException e) + { + // list was concurrently modified, so do this the hard way + ListIterator i = list.listIterator(); + while (i.hasNext()) + i.next(); + + return i; + } + } + /** * Class from a canonical name for a type. * diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java index 686e1397704..4a4bc1e2140 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java @@ -33,7 +33,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -63,6 +65,45 @@ public class IteratingCallbackTest scheduler.stop(); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testIterateWhileProcessingLoopCount(boolean succeededWinsRace) + { + var icb = new IteratingCallback() + { + int counter = 0; + + @Override + protected Action process() + { + int counter = this.counter++; + if (counter == 0) + { + iterate(); + if (succeededWinsRace) + { + succeeded(); + } + else + { + new Thread(() -> + { + await().atMost(5, TimeUnit.SECONDS).until(this::isPending, is(true)); + succeeded(); + }).start(); + } + return Action.SCHEDULED; + } + return Action.IDLE; + } + }; + + icb.iterate(); + + await().atMost(10, TimeUnit.SECONDS).until(icb::isIdle, is(true)); + assertEquals(2, icb.counter); + } + @Test public void testNonWaitingProcess() throws Exception { diff --git a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java index c1218587ab0..b32b99487ad 100644 --- a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java +++ b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java @@ -61,6 +61,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.http.HttpHeader; @@ -79,6 +80,7 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.ajax.JSON; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -163,11 +165,32 @@ public class AsyncMiddleManServletTest } @AfterEach - public void dispose() throws Exception + public void dispose() { - client.stop(); - proxy.stop(); - server.stop(); + LifeCycle.stop(client); + LifeCycle.stop(proxy); + LifeCycle.stop(server); + } + + @Test + public void testExpect100WithBody() throws Exception + { + startServer(new EchoHttpServlet()); + startProxy(new AsyncMiddleManServlet()); + startClient(); + + for (int i = 0; i < 100; i++) + { + String body = Character.toString('a' + (i % 26)); // only use 'a' to 'z' + ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) + .path("/" + body) + .headers(h -> h.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) + .timeout(5, TimeUnit.SECONDS) + .body(new StringRequestContent(body)) + .send(); + assertEquals(200, response.getStatus()); + assertEquals(body, response.getContentAsString()); + } } @Test diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java index 5031b641d02..48293310927 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java @@ -31,6 +31,7 @@ import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -95,6 +96,7 @@ import org.eclipse.jetty.util.DeprecationWarning; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -516,12 +518,11 @@ public class ServletContextHandler extends ContextHandler //Call context listeners Throwable multiException = null; ServletContextEvent event = new ServletContextEvent(getServletContext()); - Collections.reverse(_destroyServletContextListeners); - for (ServletContextListener listener : _destroyServletContextListeners) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();) { try { - callContextDestroyed(listener, event); + callContextDestroyed(i.previous(), event); } catch (Exception x) { @@ -568,17 +569,17 @@ public class ServletContextHandler extends ContextHandler if (!_servletRequestListeners.isEmpty()) { final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request); - for (int i = _servletRequestListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();) { - _servletRequestListeners.get(i).requestDestroyed(sre); + i.previous().requestDestroyed(sre); } } if (!_servletRequestAttributeListeners.isEmpty()) { - for (int i = _servletRequestAttributeListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();) { - scopedRequest.removeEventListener(_servletRequestAttributeListeners.get(i)); + scopedRequest.removeEventListener(i.previous()); } } } @@ -1217,11 +1218,11 @@ public class ServletContextHandler extends ContextHandler ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class); if (!_contextListeners.isEmpty()) { - for (int i = _contextListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); ) { try { - _contextListeners.get(i).exitScope(getContext(), scopedRequest); + i.previous().exitScope(getContext(), scopedRequest); } catch (Throwable e) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java index 788f24f68c3..0023f99d4bd 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java @@ -19,6 +19,7 @@ import java.util.Enumeration; import java.util.EventListener; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -47,6 +48,7 @@ import org.eclipse.jetty.session.AbstractSessionManager; import org.eclipse.jetty.session.ManagedSession; import org.eclipse.jetty.session.SessionConfig; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.TypeUtil; public class SessionHandler extends AbstractSessionManager implements Handler.Singleton { @@ -253,6 +255,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si SessionHandler.this.setSecureCookies(secure); } + @Override + public String toString() + { + return String.format("%s@%x[name=%s,domain=%s,path=%s,max-age=%d,secure=%b,http-only=%b,comment=%s,attributes=%s]", + this.getClass().getName(), this.hashCode(), getName(), getDomain(), getPath(), + getMaxAge(), isSecure(), isHttpOnly(), getComment(), getSessionCookieAttributes().toString()); + } + private void checkState() { //It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started, @@ -383,6 +393,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si public SessionHandler() { setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES); + installBean(_cookieConfig); + installBean(_sessionListeners); + installBean(_sessionIdListeners); + installBean(_sessionAttributeListeners); } @Override @@ -559,9 +573,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si getSessionContext().run(() -> { HttpSessionEvent event = new HttpSessionEvent(session.getApi()); - for (int i = _sessionListeners.size() - 1; i >= 0; i--) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();) { - _sessionListeners.get(i).sessionDestroyed(event); + i.previous().sessionDestroyed(event); } }); } diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java index f3bea6be187..a617a9f9aab 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java @@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.ComplianceViolation; import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; @@ -87,8 +88,8 @@ public class ComplianceViolations2616Test { resp.setContentType("text/plain"); PrintWriter out = resp.getWriter(); - List headerNames = new ArrayList<>(); - headerNames.addAll(Collections.list(req.getHeaderNames())); + out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo()); + List headerNames = new ArrayList<>(Collections.list(req.getHeaderNames())); Collections.sort(headerNames); for (String name : headerNames) { @@ -183,4 +184,25 @@ public class ComplianceViolations2616Test assertThat("Response headers", response, containsString("X-Http-Violation-0: Line Folding not supported")); assertThat("Response body", response, containsString("[Name] = [Some Value]")); } + + @Test + public void testAmbiguousSlash() throws Exception + { + String request = """ + GET /dump/foo//bar HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """; + + String response = connector.getResponse(request); + assertThat(response, containsString("HTTP/1.1 400 Bad")); + + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986.with("test", UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT)); + server.getContainedBeans(ServletHandler.class).stream().findFirst().get().setDecodeAmbiguousURIs(true); + + response = connector.getResponse(request); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("GET /dump/foo//bar")); + } } diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java index f97021240da..872ababa6e1 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java +++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java @@ -31,6 +31,7 @@ import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -95,6 +96,7 @@ import org.eclipse.jetty.util.DeprecationWarning; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -515,12 +517,11 @@ public class ServletContextHandler extends ContextHandler //Call context listeners Throwable multiException = null; ServletContextEvent event = new ServletContextEvent(getServletContext()); - Collections.reverse(_destroyServletContextListeners); - for (ServletContextListener listener : _destroyServletContextListeners) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();) { try { - callContextDestroyed(listener, event); + callContextDestroyed(i.previous(), event); } catch (Exception x) { @@ -566,18 +567,18 @@ public class ServletContextHandler extends ContextHandler // Handle more REALLY SILLY request events! if (!_servletRequestListeners.isEmpty()) { - final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request); - for (int i = _servletRequestListeners.size(); i-- > 0; ) + ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request); + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();) { - _servletRequestListeners.get(i).requestDestroyed(sre); + i.previous().requestDestroyed(sre); } } if (!_servletRequestAttributeListeners.isEmpty()) { - for (int i = _servletRequestAttributeListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();) { - scopedRequest.removeEventListener(_servletRequestAttributeListeners.get(i)); + scopedRequest.removeEventListener(i.previous()); } } } @@ -1217,11 +1218,11 @@ public class ServletContextHandler extends ContextHandler ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class); if (!_contextListeners.isEmpty()) { - for (int i = _contextListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); ) { try { - _contextListeners.get(i).exitScope(getContext(), scopedRequest); + i.previous().exitScope(getContext(), scopedRequest); } catch (Throwable e) { diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java index 9c887e97bc7..917745a9332 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java +++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java @@ -19,6 +19,7 @@ import java.util.Enumeration; import java.util.EventListener; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -48,6 +49,7 @@ import org.eclipse.jetty.session.AbstractSessionManager; import org.eclipse.jetty.session.ManagedSession; import org.eclipse.jetty.session.SessionConfig; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.TypeUtil; public class SessionHandler extends AbstractSessionManager implements Handler.Singleton { @@ -254,6 +256,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si SessionHandler.this.setSecureCookies(secure); } + @Override + public String toString() + { + return String.format("%s@%x[name=%s,domain=%s,path=%s,max-age=%d,secure=%b,http-only=%b,comment=%s,attributes=%s]", + this.getClass().getName(), this.hashCode(), getName(), getDomain(), getPath(), + getMaxAge(), isSecure(), isHttpOnly(), getComment(), getSessionCookieAttributes().toString()); + } + private void checkState() { //It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started, @@ -427,6 +437,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si public SessionHandler() { setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES); + installBean(_cookieConfig); + installBean(_sessionListeners); + installBean(_sessionIdListeners); + installBean(_sessionAttributeListeners); } @Override @@ -603,9 +617,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si getSessionContext().run(() -> { HttpSessionEvent event = new HttpSessionEvent(session.getApi()); - for (int i = _sessionListeners.size() - 1; i >= 0; i--) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();) { - _sessionListeners.get(i).sessionDestroyed(event); + i.previous().sessionDestroyed(event); } }); } diff --git a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java index b80086245b8..5da1f083d4b 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java +++ b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java @@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.ComplianceViolation; import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; @@ -87,8 +88,8 @@ public class ComplianceViolations2616Test { resp.setContentType("text/plain"); PrintWriter out = resp.getWriter(); - List headerNames = new ArrayList<>(); - headerNames.addAll(Collections.list(req.getHeaderNames())); + out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo()); + List headerNames = new ArrayList<>(Collections.list(req.getHeaderNames())); Collections.sort(headerNames); for (String name : headerNames) { @@ -183,4 +184,25 @@ public class ComplianceViolations2616Test assertThat("Response headers", response, containsString("X-Http-Violation-0: Line Folding not supported")); assertThat("Response body", response, containsString("[Name] = [Some Value]")); } + + @Test + public void testAmbiguousSlash() throws Exception + { + String request = """ + GET /dump/foo//bar HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """; + + String response = connector.getResponse(request); + assertThat(response, containsString("HTTP/1.1 400 Bad")); + + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986.with("test", UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT)); + server.getContainedBeans(ServletHandler.class).stream().findFirst().get().setDecodeAmbiguousURIs(true); + + response = connector.getResponse(request); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("GET /dump/foo//bar")); + } } diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java index dfe73d44112..db3e1ab6afd 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java @@ -29,6 +29,7 @@ import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -1000,17 +1001,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie if (!_servletRequestListeners.isEmpty()) { final ServletRequestEvent sre = new ServletRequestEvent(_apiContext, request); - for (int i = _servletRequestListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();) { - _servletRequestListeners.get(i).requestDestroyed(sre); + i.previous().requestDestroyed(sre); } } if (!_servletRequestAttributeListeners.isEmpty()) { - for (int i = _servletRequestAttributeListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();) { - baseRequest.removeEventListener(_servletRequestAttributeListeners.get(i)); + baseRequest.removeEventListener(i.previous()); } } } @@ -1070,11 +1071,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie { if (!_contextListeners.isEmpty()) { - for (int i = _contextListeners.size(); i-- > 0; ) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();) { try { - _contextListeners.get(i).exitScope(_apiContext, request); + i.previous().exitScope(_apiContext, request); } catch (Throwable e) { diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java index 78df43d272c..b115e3d6cb4 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java @@ -76,20 +76,20 @@ public class Response implements HttpServletResponse * String used in the {@code Comment} attribute of {@link Cookie} * to support the {@code HttpOnly} attribute. **/ - private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; + protected static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; /** * String used in the {@code Comment} attribute of {@link Cookie} * to support the {@code Partitioned} attribute. **/ - private static final String PARTITIONED_COMMENT = "__PARTITIONED__"; + protected static final String PARTITIONED_COMMENT = "__PARTITIONED__"; /** * The strings used in the {@code Comment} attribute of {@link Cookie} * to support the {@code SameSite} attribute. **/ - private static final String SAME_SITE_COMMENT = "__SAME_SITE_"; - private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__"; - private static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__"; - private static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__"; + protected static final String SAME_SITE_COMMENT = "__SAME_SITE_"; + protected static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__"; + protected static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__"; + protected static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__"; public enum OutputType { @@ -1465,7 +1465,7 @@ public class Response implements HttpServletResponse return (HttpServletResponse)servletResponse; } - private static class HttpFieldsSupplier implements Supplier + protected static class HttpFieldsSupplier implements Supplier { private final Supplier> _supplier; @@ -1494,7 +1494,7 @@ public class Response implements HttpServletResponse } } - private static class HttpCookieFacade implements HttpCookie + protected static class HttpCookieFacade implements HttpCookie { private final Cookie _cookie; private final String _comment; @@ -1622,12 +1622,12 @@ public class Response implements HttpServletResponse return comment != null && comment.contains(HTTP_ONLY_COMMENT); } - private static boolean isPartitionedInComment(String comment) + protected static boolean isPartitionedInComment(String comment) { return comment != null && comment.contains(PARTITIONED_COMMENT); } - private static SameSite getSameSiteFromComment(String comment) + protected static SameSite getSameSiteFromComment(String comment) { if (comment == null) return null; @@ -1640,7 +1640,7 @@ public class Response implements HttpServletResponse return null; } - private static String getCommentWithoutAttributes(String comment) + protected static String getCommentWithoutAttributes(String comment) { if (comment == null) return null; diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java index c178d0bc9e0..24e7dc0e37b 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java @@ -20,6 +20,7 @@ import java.util.EnumSet; import java.util.Enumeration; import java.util.EventListener; import java.util.List; +import java.util.ListIterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; @@ -53,6 +54,8 @@ import org.eclipse.jetty.session.SessionConfig; import org.eclipse.jetty.session.SessionIdManager; import org.eclipse.jetty.session.SessionManager; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -613,7 +616,8 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab * CookieConfig * * Implementation of the jakarta.servlet.SessionCookieConfig. - * SameSite configuration can be achieved by using setComment + * SameSite configuration can be achieved by using setComment. + * Partitioned configuration can be achieved by using setComment. * * @see HttpCookie */ @@ -671,7 +675,19 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab public void setComment(String comment) { checkAvailable(); - _sessionManager.setSessionComment(comment); + + if (!StringUtil.isEmpty(comment)) + { + HttpCookie.SameSite sameSite = Response.HttpCookieFacade.getSameSiteFromComment(comment); + if (sameSite != null) + _sessionManager.setSameSite(sameSite); + + boolean partitioned = Response.HttpCookieFacade.isPartitionedInComment(comment); + if (partitioned) + _sessionManager.setPartitioned(partitioned); + + _sessionManager.setSessionComment(Response.HttpCookieFacade.getCommentWithoutAttributes(comment)); + } } @Override @@ -715,6 +731,14 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab checkAvailable(); _sessionManager.setSecureCookies(secure); } + + @Override + public String toString() + { + return String.format("%s@%x[name=%s,domain=%s,path=%s,max-age=%d,secure=%b,http-only=%b,same-site=%s,comment=%s]", + this.getClass().getName(), this.hashCode(), _sessionManager.getSessionCookie(), _sessionManager.getSessionDomain(), _sessionManager.getSessionPath(), + _sessionManager.getMaxCookieAge(), _sessionManager.isSecureCookies(), _sessionManager.isHttpOnly(), _sessionManager.getSameSite(), _sessionManager.getSessionComment()); + } } private class CoreSessionManager extends AbstractSessionManager @@ -812,9 +836,9 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab Runnable r = () -> { HttpSessionEvent event = new HttpSessionEvent(session.getApi()); - for (int i = _sessionListeners.size() - 1; i >= 0; i--) + for (ListIterator i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();) { - _sessionListeners.get(i).sessionDestroyed(event); + i.previous().sessionDestroyed(event); } }; _contextHandler.getCoreContextHandler().getContext().run(r); diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java index 8053c8315ca..cf0ed7df1d1 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java @@ -168,7 +168,7 @@ public class SessionHandlerTest } @Test - public void testSessionCookie() throws Exception + public void testSessionCookieConfig() throws Exception { Server server = new Server(); MockSessionIdManager idMgr = new MockSessionIdManager(server); @@ -190,12 +190,51 @@ public class SessionHandlerTest sessionCookieConfig.setSecure(false); sessionCookieConfig.setPath("/foo"); sessionCookieConfig.setMaxAge(99); + + //test setting SameSite and Partitioned the old way in the comment + sessionCookieConfig.setComment(Response.PARTITIONED_COMMENT + " " + Response.SAME_SITE_STRICT_COMMENT); - //for < ee10, SameSite cannot be set on the SessionCookieConfig, only on the SessionManager, or - //a default value on the context attribute org.eclipse.jetty.cookie.sameSiteDefault + HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false); + assertEquals("SPECIAL", cookie.getName()); + assertEquals("universe", cookie.getDomain()); + assertEquals("/foo", cookie.getPath()); + assertFalse(cookie.isHttpOnly()); + assertFalse(cookie.isSecure()); + assertTrue(cookie.isPartitioned()); + assertEquals(99, cookie.getMaxAge()); + assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite()); + + String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie); + assertThat(cookieStr, containsString("; Partitioned; SameSite=Strict")); + } + + @Test + public void testSessionCookieViaSetters() throws Exception + { + Server server = new Server(); + MockSessionIdManager idMgr = new MockSessionIdManager(server); + idMgr.setWorkerName("node1"); + SessionHandler mgr = new SessionHandler(); + MockSessionCache cache = new MockSessionCache(mgr.getSessionManager()); + cache.setSessionDataStore(new NullSessionDataStore()); + mgr.setSessionCache(cache); + mgr.setSessionIdManager(idMgr); + + long now = System.currentTimeMillis(); + + ManagedSession session = new ManagedSession(mgr.getSessionManager(), new SessionData("123", "_foo", "0.0.0.0", now, now, now, 30)); + session.setExtendedId("123.node1"); + + //test setting up session cookie via setters on SessionHandler + mgr.setSessionCookie("SPECIAL"); + mgr.setSessionDomain("universe"); + mgr.setHttpOnly(false); + mgr.setSecureCookies(false); + mgr.setSessionPath("/foo"); + mgr.setMaxCookieAge(99); mgr.setSameSite(HttpCookie.SameSite.STRICT); mgr.setPartitioned(true); - + HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false); assertEquals("SPECIAL", cookie.getName()); assertEquals("universe", cookie.getDomain()); diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/AsyncMiddleManServletTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/AsyncMiddleManServletTest.java index 064ac668745..433e59a41f2 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/AsyncMiddleManServletTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/AsyncMiddleManServletTest.java @@ -61,6 +61,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.ee9.servlet.ServletContextHandler; import org.eclipse.jetty.ee9.servlet.ServletHolder; import org.eclipse.jetty.http.HttpHeader; @@ -163,11 +164,32 @@ public class AsyncMiddleManServletTest } @AfterEach - public void dispose() throws Exception + public void dispose() { LifeCycle.stop(client); LifeCycle.stop(proxy); - LifeCycle.stop(proxy); + LifeCycle.stop(server); + } + + @Test + public void testExpect100WithBody() throws Exception + { + startServer(new EchoHttpServlet()); + startProxy(new AsyncMiddleManServlet()); + startClient(); + + for (int i = 0; i < 100; i++) + { + String body = Character.toString('a' + (i % 26)); // only use 'a' to 'z' + ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) + .path("/" + body) + .headers(h -> h.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) + .timeout(5, TimeUnit.SECONDS) + .body(new StringRequestContent(body)) + .send(); + assertEquals(200, response.getStatus()); + assertEquals(body, response.getContentAsString()); + } } @Test diff --git a/tests/pom.xml b/tests/pom.xml index 022baf529be..73a94d115ce 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -13,9 +13,9 @@ jetty-testers jetty-jmh + jetty-test-common jetty-test-multipart jetty-test-session-common - jetty-test-common test-cross-context-dispatch test-distribution test-integration diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 31613448a04..1ff1e82c161 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -13,9 +13,6 @@ test-distribution-common - test-ee11-distribution - test-ee10-distribution - test-ee9-distribution diff --git a/tests/test-distribution/test-distribution-common/pom.xml b/tests/test-distribution/test-distribution-common/pom.xml index fef3a356f7a..1899042a9d0 100644 --- a/tests/test-distribution/test-distribution-common/pom.xml +++ b/tests/test-distribution/test-distribution-common/pom.xml @@ -15,9 +15,21 @@ ${project.groupId}.tests.distribution.common 2 + 3.4.0 + + com.github.dasniko + testcontainers-keycloak + ${testcontainers-keycloak.version} + + + io.quarkus + quarkus-junit4-mock + + + org.apache.maven maven-artifact @@ -54,6 +66,10 @@ org.apache.maven.resolver maven-resolver-util + + org.bouncycastle + bcprov-jdk15to18 + org.codehaus.plexus plexus-xml @@ -64,6 +80,10 @@ + + org.eclipse.jetty + jetty-ethereum + org.eclipse.jetty jetty-home @@ -138,6 +158,12 @@ org.eclipse.jetty jetty-infinispan-common test + + + io.smallrye + jandex + + org.eclipse.jetty @@ -155,6 +181,41 @@ jetty-util-ajax test + + org.eclipse.jetty.ee10 + jetty-ee10-test-log4j2-webapp + ${project.version} + war + test + + + org.eclipse.jetty.ee10 + jetty-ee10-test-openid-webapp + ${project.version} + war + test + + + org.eclipse.jetty.ee11 + jetty-ee11-test-log4j2-webapp + ${project.version} + war + test + + + org.eclipse.jetty.ee11 + jetty-ee11-test-openid-webapp + ${project.version} + war + test + + + org.eclipse.jetty.ee9 + jetty-ee9-test-openid-webapp + ${project.version} + war + test + org.eclipse.jetty.http2 jetty-http2-client @@ -244,17 +305,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/DisableUrlCacheTest.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java similarity index 72% rename from tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/DisableUrlCacheTest.java rename to tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java index a4725f879a4..02d13fcdd45 100644 --- a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/DisableUrlCacheTest.java +++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.ee11.tests.distribution; +package org.eclipse.jetty.tests.distribution; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -19,16 +19,21 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.testers.JettyHomeTester; import org.eclipse.jetty.tests.testers.Tester; import org.eclipse.jetty.toolchain.test.FS; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,8 +48,17 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest { private static final Logger LOG = LoggerFactory.getLogger(DisableUrlCacheTest.class); - @Test - public void testReloadWebAppWithLog4j2() throws Exception + public static Stream tests() + { + return Stream.of( + Arguments.of("ee10", "Started oeje10w.WebAppContext@"), + Arguments.of("ee11", "Started oeje11w.WebAppContext@") + ); + } + + @ParameterizedTest + @MethodSource("tests") + public void testReloadWebAppWithLog4j2(String env, String logToSearch) throws Exception { Path jettyBase = newTestJettyBaseDirectory(); String jettyVersion = System.getProperty("jettyVersion"); @@ -55,7 +69,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest .build(); String[] setupArgs = { - "--add-modules=http,ee11-webapp,ee11-deploy,disable-urlcache" + "--add-modules=http," + toEnvironment("webapp", env) + "," + toEnvironment("deploy", env) + ",disable-urlcache" }; try (JettyHomeTester.Run setupRun = distribution.start(setupArgs)) @@ -63,7 +77,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest assertTrue(setupRun.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); assertEquals(0, setupRun.getExitValue()); - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee11:jetty-ee11-test-log4j2-webapp:war:" + jettyVersion); + Path webApp = distribution.resolveArtifact("org.eclipse.jetty." + env + ":jetty-" + env + "-test-log4j2-webapp:war:" + jettyVersion); Path testWebApp = distribution.getJettyBase().resolve("webapps/test.war"); Files.copy(webApp, testWebApp); @@ -75,16 +89,15 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest FS.ensureEmpty(resourcesDir); Path webappsDir = distribution.getJettyBase().resolve("webapps"); - String warXml = """ - - - - /test - /test.war - /work/test - false - - """; + String warXml = + "" + + "" + + "" + + " /test" + + " /test.war" + + " /work/test" + + " false" + + ""; Path warXmlPath = webappsDir.resolve("test.xml"); Files.writeString(warXmlPath, warXml, StandardCharsets.UTF_8); @@ -92,10 +105,11 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest String loggingConfig = """ org.eclipse.jetty.LEVEL=INFO org.eclipse.jetty.deploy.LEVEL=DEBUG - org.eclipse.jetty.ee11.webapp.LEVEL=DEBUG - org.eclipse.jetty.ee11.webapp.WebAppClassLoader.LEVEL=INFO - org.eclipse.jetty.ee11.servlet.LEVEL=DEBUG + org.eclipse.jetty.eexx.webapp.LEVEL=DEBUG + org.eclipse.jetty.eexx.webapp.WebAppClassLoader.LEVEL=INFO + org.eclipse.jetty.exx.servlet.LEVEL=DEBUG """; + loggingConfig = loggingConfig.replace("eexx", env); Files.writeString(loggingFile, loggingConfig, StandardCharsets.UTF_8); @@ -121,7 +135,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest touch(warXmlPath); // Wait for reload to start context - assertTrue(run2.awaitConsoleLogsFor("Started oeje11w.WebAppContext@", START_TIMEOUT, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor(logToSearch, START_TIMEOUT, TimeUnit.SECONDS)); // wait for deployer node to complete so context is Started not Starting assertTrue(run2.awaitConsoleLogsFor("Executing Node Node[started]", START_TIMEOUT, TimeUnit.SECONDS)); diff --git a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java new file mode 100644 index 00000000000..113500ff6ee --- /dev/null +++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java @@ -0,0 +1,204 @@ +// +// ======================================================================== +// Copyright (c) 1995 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; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import dasniko.testcontainers.keycloak.KeycloakContainer; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.tests.testers.JettyHomeTester; +import org.eclipse.jetty.tests.testers.Tester; +import org.eclipse.jetty.util.Fields; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.keycloak.admin.client.CreatedResponseUtil; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OpenIdTests extends AbstractJettyHomeTest +{ + private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdTests.class); + + private static final KeycloakContainer KEYCLOAK_CONTAINER = new KeycloakContainer(); + + private static final String clientId = "jetty-api"; + private static final String clientSecret = "JettyRocks!"; + + private static final String userName = "jetty"; + private static final String password = "JettyRocks!Really"; + + private static final String firstName = "John"; + private static final String lastName = "Doe"; + private static final String email = "jetty@jetty.org"; + + private static String userId; + + @BeforeAll + public static void startKeycloak() + { + KEYCLOAK_CONTAINER.start(); + // init keycloak + try (Keycloak keycloak = KEYCLOAK_CONTAINER.getKeycloakAdminClient()) + { + RealmRepresentation jettyRealm = new RealmRepresentation(); + jettyRealm.setId("jetty"); + jettyRealm.setRealm("jetty"); + jettyRealm.setEnabled(true); + keycloak.realms().create(jettyRealm); + + ClientRepresentation clientRepresentation = new ClientRepresentation(); + clientRepresentation.setClientId(clientId); + clientRepresentation.setSecret(clientSecret); + clientRepresentation.setRedirectUris(List.of("http://localhost:*")); + clientRepresentation.setEnabled(true); + clientRepresentation.setPublicClient(Boolean.TRUE); + keycloak.realm("jetty").clients().create(clientRepresentation); + + UserRepresentation user = new UserRepresentation(); + user.setEnabled(true); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setUsername(userName); + user.setEmail(email); + + userId = CreatedResponseUtil.getCreatedId(keycloak.realm("jetty").users().create(user)); + + CredentialRepresentation passwordCred = new CredentialRepresentation(); + passwordCred.setTemporary(false); + passwordCred.setType(CredentialRepresentation.PASSWORD); + passwordCred.setValue(password); + + // Set password credential + keycloak.realm("jetty").users().get(userId).resetPassword(passwordCred); + } + } + + @AfterAll + public static void stopKeycloak() + { + if (KEYCLOAK_CONTAINER.isRunning()) + { + KEYCLOAK_CONTAINER.stop(); + } + } + + public static Stream tests() + { + return Stream.of( + Arguments.of("ee9", "ee9-openid"), + Arguments.of("ee10", "openid"), + Arguments.of("ee11", "openid") + ); + } + + @ParameterizedTest + @MethodSource("tests") + public void testOpenID(String env, String openIdModule) throws Exception + { + Path jettyBase = newTestJettyBaseDirectory(); + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .build(); + + String[] args1 = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=http," + toEnvironment("webapp", env) + "," + toEnvironment("deploy", env) + "," + openIdModule + }; + + try (JettyHomeTester.Run run1 = distribution.start(args1)) + { + assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + Path webApp = distribution.resolveArtifact("org.eclipse.jetty." + env + ":jetty-" + env + "-test-openid-webapp:war:" + jettyVersion); + distribution.installWar(webApp, "test"); + String openIdProvider = KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/jetty"; + LOGGER.info("openIdProvider: {}", openIdProvider); + + int port = Tester.freePort(); + String[] args2 = { + "jetty.http.port=" + port, + "jetty.ssl.port=" + port, + "jetty.openid.provider=" + openIdProvider, + "jetty.openid.clientId=" + clientId, + "jetty.openid.clientSecret=" + clientSecret, + //"jetty.server.dumpAfterStart=true", + }; + + try (JettyHomeTester.Run run2 = distribution.start(args2)) + { + assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS)); + String uri = "http://localhost:" + port + "/test"; + // Initially not authenticated + startHttpClient(); + ContentResponse contentResponse = client.GET(uri + "/"); + assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200)); + assertThat(contentResponse.getContentAsString(), containsString("not authenticated")); + + // Request to login is success + contentResponse = client.GET(uri + "/login"); + assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200)); + // need to extract form + String html = contentResponse.getContentAsString(); + // need this attribute
- - - 4.0.0 - - org.eclipse.jetty.tests - test-distribution - 12.1.0-SNAPSHOT - - test-ee10-distribution - jar - Tests :: Distribution :: EE10 - - - ${project.groupId}.ee10.distribution - - - - - org.eclipse.jetty - jetty-home - zip - - - * - * - - - - - org.eclipse.jetty - jetty-client - test - - - org.eclipse.jetty - jetty-ethereum - test - - - org.eclipse.jetty - jetty-openid - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.ee10 - jetty-ee10-servlet - ${project.version} - test - - - org.eclipse.jetty.ee10 - jetty-ee10-test-openid-webapp - ${project.version} - war - test - - - org.eclipse.jetty.tests - jetty-test-common - test - - - org.eclipse.jetty.tests - jetty-testers - test - - - org.eclipse.jetty.tests - test-distribution-common - test-jar - test - - - - diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java deleted file mode 100644 index 81299ef8038..00000000000 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java +++ /dev/null @@ -1,143 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee10.tests.distribution; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.eclipse.jetty.toolchain.test.FS; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class DisableUrlCacheTest extends AbstractJettyHomeTest -{ - private static final Logger LOG = LoggerFactory.getLogger(DisableUrlCacheTest.class); - - @Test - public void testReloadWebAppWithLog4j2() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .jvmArgs(List.of("-Dorg.eclipse.jetty.deploy.LEVEL=DEBUG")) - .build(); - - String[] setupArgs = { - "--add-modules=http,ee10-webapp,ee10-deploy,disable-urlcache" - }; - - try (JettyHomeTester.Run setupRun = distribution.start(setupArgs)) - { - assertTrue(setupRun.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, setupRun.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee10:jetty-ee10-test-log4j2-webapp:war:" + jettyVersion); - Path testWebApp = distribution.getJettyBase().resolve("webapps/test.war"); - - Files.copy(webApp, testWebApp); - - Path tempDir = distribution.getJettyBase().resolve("work"); - FS.ensureEmpty(tempDir); - - Path resourcesDir = distribution.getJettyBase().resolve("resources"); - FS.ensureEmpty(resourcesDir); - - Path webappsDir = distribution.getJettyBase().resolve("webapps"); - String warXml = """ - - - - /test - /test.war - /work/test - false - - """; - Path warXmlPath = webappsDir.resolve("test.xml"); - Files.writeString(warXmlPath, warXml, StandardCharsets.UTF_8); - - Path loggingFile = resourcesDir.resolve("jetty-logging.properties"); - String loggingConfig = """ - org.eclipse.jetty.LEVEL=INFO - org.eclipse.jetty.deploy.LEVEL=DEBUG - org.eclipse.jetty.ee10.webapp.LEVEL=DEBUG - org.eclipse.jetty.ee10.webapp.WebAppClassLoader.LEVEL=INFO - org.eclipse.jetty.ee10.servlet.LEVEL=DEBUG - """; - Files.writeString(loggingFile, loggingConfig, StandardCharsets.UTF_8); - - - int port = Tester.freePort(); - String[] runArgs = { - "jetty.http.port=" + port, - "jetty.deploy.scanInterval=1" - //"jetty.server.dumpAfterStart=true", - }; - try (JettyHomeTester.Run run2 = distribution.start(runArgs)) - { - assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS)); - startHttpClient(false); - - // Test webapp is there - ContentResponse response = client.GET("http://localhost:" + port + "/test/log/"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - String content = response.getContentAsString(); - assertThat(content, containsString("GET at LogServlet")); - - // Trigger a hot-reload - run2.getLogs().clear(); - touch(warXmlPath); - - // Wait for reload to start context - assertTrue(run2.awaitConsoleLogsFor("Started oeje10w.WebAppContext@", START_TIMEOUT, TimeUnit.SECONDS)); - // wait for deployer node to complete so context is Started not Starting - assertTrue(run2.awaitConsoleLogsFor("Executing Node Node[started]", START_TIMEOUT, TimeUnit.SECONDS)); - - // Is webapp still there? - response = client.GET("http://localhost:" + port + "/test/log/"); - content = response.getContentAsString(); - assertThat(content, response.getStatus(), is(HttpStatus.OK_200)); - assertThat(content, containsString("GET at LogServlet")); - } - } - } - - private void touch(Path path) throws IOException - { - LOG.info("Touch: {}", path); - FileTime now = FileTime.fromMillis(System.currentTimeMillis() + 2000); - Files.setLastModifiedTime(path, now); - } -} diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java deleted file mode 100644 index d8b3cea3e5d..00000000000 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java +++ /dev/null @@ -1,119 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee10.tests.distribution; - -import java.nio.file.Path; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.OpenIdProvider; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class OpenIdTests extends AbstractJettyHomeTest -{ - @Test - public void testOpenID() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .build(); - - String[] args1 = { - "--create-startd", - "--approve-all-licenses", - "--add-to-start=http,ee10-webapp,ee10-deploy,openid" - }; - - String clientId = "clientId123"; - String clientSecret = "clientSecret456"; - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - try (JettyHomeTester.Run run1 = distribution.start(args1)) - { - assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, run1.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee10:jetty-ee10-test-openid-webapp:war:" + jettyVersion); - distribution.installWar(webApp, "test"); - - int port = Tester.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 oejs.Server@", START_TIMEOUT, 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/test-ee11-distribution/pom.xml b/tests/test-distribution/test-ee11-distribution/pom.xml deleted file mode 100644 index a2135bfff1d..00000000000 --- a/tests/test-distribution/test-ee11-distribution/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - 4.0.0 - - org.eclipse.jetty.tests - test-distribution - 12.1.0-SNAPSHOT - - test-ee11-distribution - jar - Tests :: Distribution :: EE11 - - - ${project.groupId}.ee11.distribution - - - - - org.eclipse.jetty - jetty-home - zip - - - * - * - - - - - org.eclipse.jetty - jetty-client - test - - - org.eclipse.jetty - jetty-openid - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.ee11 - jetty-ee11-servlet - ${project.version} - test - - - org.eclipse.jetty.ee11 - jetty-ee11-test-openid-webapp - ${project.version} - war - test - - - org.eclipse.jetty.tests - jetty-test-common - test - - - org.eclipse.jetty.tests - jetty-testers - test - - - org.eclipse.jetty.tests - test-distribution-common - test-jar - test - - - org.eclipse.jetty.tests - test-distribution-common - test - - - - diff --git a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java deleted file mode 100644 index 216003d492d..00000000000 --- a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java +++ /dev/null @@ -1,119 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee11.tests.distribution; - -import java.nio.file.Path; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.OpenIdProvider; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class OpenIdTests extends AbstractJettyHomeTest -{ - @Test - public void testOpenID() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .build(); - - String[] args1 = { - "--create-startd", - "--approve-all-licenses", - "--add-to-start=http,ee11-webapp,ee11-deploy,openid" - }; - - String clientId = "clientId123"; - String clientSecret = "clientSecret456"; - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - try (JettyHomeTester.Run run1 = distribution.start(args1)) - { - assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, run1.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee11:jetty-ee11-test-openid-webapp:war:" + jettyVersion); - distribution.installWar(webApp, "test"); - - int port = Tester.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 oejs.Server@", START_TIMEOUT, 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/test-ee9-distribution/pom.xml b/tests/test-distribution/test-ee9-distribution/pom.xml deleted file mode 100644 index 15ac6a3ce38..00000000000 --- a/tests/test-distribution/test-ee9-distribution/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - 4.0.0 - - org.eclipse.jetty.tests - test-distribution - 12.1.0-SNAPSHOT - - test-ee9-distribution - jar - Tests :: Distribution :: EE9 - - - ${project.groupId}.ee9.distribution - - - - - org.eclipse.jetty - jetty-home - zip - - - * - * - - - - - org.eclipse.jetty - jetty-client - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.ee9 - jetty-ee9-openid - ${project.version} - test - - - org.eclipse.jetty.ee9 - jetty-ee9-servlet - ${project.version} - test - - - org.eclipse.jetty.ee9 - jetty-ee9-test-openid-webapp - ${project.version} - war - test - - - org.eclipse.jetty.tests - jetty-test-common - test - - - org.eclipse.jetty.tests - jetty-testers - test - - - org.eclipse.jetty.tests - test-distribution-common - test-jar - test - - - - diff --git a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java deleted file mode 100644 index cb7803bf787..00000000000 --- a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java +++ /dev/null @@ -1,120 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee9.tests.distribution; - -import java.nio.file.Path; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.OpenIdProvider; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class OpenIdTests extends AbstractJettyHomeTest -{ - @Test - public void testOpenID() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .build(); - - String[] args1 = { - "--create-startd", - "--approve-all-licenses", - "--add-to-start=http,ee9-webapp,ee9-deploy,ee9-openid" - }; - - String clientId = "clientId123"; - String clientSecret = "clientSecret456"; - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - try (JettyHomeTester.Run run1 = distribution.start(args1)) - { - assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, run1.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee9:jetty-ee9-test-openid-webapp:war:" + jettyVersion); - distribution.installWar(webApp, "test"); - - int port = Tester.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 oejs.Server@", START_TIMEOUT, 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(); - } - } -}