Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dynamic-compression-handler

This commit is contained in:
Joakim Erdfelt 2024-09-26 14:40:16 -05:00
commit 1575b0a904
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
48 changed files with 895 additions and 937 deletions

4
Jenkinsfile vendored
View File

@ -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")) {

View File

@ -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[]
}
}

View File

@ -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 <<threadpool,`threadpool`>> 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 <<threadpool-virtual-preview,`threadpool-virtual-preview`>> 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`

View File

@ -269,6 +269,35 @@ Defaulting the number of reserved threads to zero ensures that the <<execution-s
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java[tags=virtualVirtual]
----
Despite the name, `VirtualThreadPool` does not pool virtual threads, but allows you to impose a limit on the maximum number of current virtual threads, in order to limit resource consumption.
Despite the name, `VirtualThreadPool` does not pool virtual threads, but allows you to impose a limit on the maximum number of current virtual threads, using a `Semaphore`.
Furthermore, you can configure it to track virtual threads so that a xref:troubleshooting/component-dump.adoc[Jetty component dump] will show all virtual threads, including those that are unmounted.
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.
Furthermore, you can configure it to track virtual threads so that a xref:troubleshooting/component-dump.adoc[Jetty component dump] will show all virtual threads currently in use, including those that are unmounted.
[[thread-pool-virtual-threads-pinning]]
==== Virtual Threads Pinning
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.
If you configure a server-side `Connector`, or Jetty's `HttpClient` with `N` selectors, then `N` carrier threads will be pinned by the virtual threads calling `Selector.select()`.
If you have less than `N` CPU cores in your system, then by default all carriers will be pinned in the `Selector.select()` call, leaving no carrier to execute virtual threads, and therefore completely locking up your system, which will become completely unresponsive.
If you have more than `N` CPU cores in your system, then by default your system may be less efficient, since the carrier threads may be pinned in the `Selector.select()` call, and therefore not available to run virtual threads.
[WARNING]
====
The number of CPU cores of your system determines, by default, the number of carrier threads.
The number of carrier threads may be explicitly configured by setting the system property `-Djdk.virtualThreadScheduler.parallelism=N`, where `N` is your desired number of carrier threads.
Selector threads used by Jetty pin carrier threads.
Choose the number of selectors wisely when using virtual threads: the number of selectors must always be less than the number of carrier threads, to leave some of the carrier threads free to run virtual threads.
As an extreme example, if your system only has `1` CPU core, then `1` selector is enough to pin the only carrier thread, and your system will eventually lock up.
In this case, you must explicitly configure the number of carrier threads by setting the system property `-Djdk.virtualThreadScheduler.parallelism=2` (or to a larger value).
====

View File

@ -26,7 +26,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
* <p>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.</p>
* <p>Example usage:</p>
* <pre>
* <pre>{@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()
* {
* &#64;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().
* </pre>
* }</pre>
*/
public class OutputStreamRequestContent extends OutputStreamContentSource implements Request.Content
{

View File

@ -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)

View File

@ -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();

View File

@ -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";

View File

@ -23,12 +23,11 @@ import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.IO;
/**
* <p>
* 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.
* </p>
* <p>A {@link Content.Source} that provides content asynchronously through an {@link OutputStream}.</p>
* <p>Bytes written to the {@link OutputStream} returned by {@link #getOutputStream()}
* are converted to a {@link Content.Chunk} and returned from {@link #read()}.</p>
* <p>The {@code OutputStream} must be closed to signal that all the content has been written.</p>
*
* @see AsyncContent
*/
public class OutputStreamContentSource implements Content.Source, Closeable

View File

@ -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

View File

@ -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)

View File

@ -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<ContextHandler> 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<Handler> handlerList = handlers.getHandlers();
assertEquals(testHandlerA, handlerList.get(0));
Handler second = handlerList.get(1);
assertInstanceOf(ContextHandlerCollection.class, second);
ContextHandlerCollection contextHandlers = (ContextHandlerCollection)second;
Set<String> contextPaths = contextHandlers.getContextPaths();
assertNotNull(contextPaths);
assertTrue(contextPaths.contains("/A"));
assertTrue(contextPaths.contains("/B"));
}
}

View File

@ -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)
{

View File

@ -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 ->
{

View File

@ -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<ContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
{
try
{
_contextListeners.get(i).exitScope(_context, request);
i.previous().exitScope(_context, request);
}
catch (Throwable e)
{

View File

@ -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;
}
}

View File

@ -79,36 +79,6 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
this(strategyType, maxSize, pooled -> 1);
}
/**
* <p>Creates an instance with the specified strategy.</p>
*
* @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);
}
/**
* <p>Creates an instance with the specified strategy.
* and a function that returns the max multiplex count for a given pooled object.</p>
*
* @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<P> maxMultiplex)
{
this(strategyType, maxSize, maxMultiplex);
}
/**
* <p>Creates an instance with the specified strategy.
* and a function that returns the max multiplex count for a given pooled object.</p>
@ -148,7 +118,7 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
{
leaked.increment();
if (LOG.isDebugEnabled())
LOG.debug("Leaked " + holder);
LOG.debug("Leaked {}", holder);
leaked();
}
@ -195,15 +165,14 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
void sweep()
{
for (int i = 0; i < entries.size(); i++)
// Remove entries atomically with respect to remove(Entry).
entries.removeIf(holder ->
{
Holder<P> 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<P> implements Pool<P>, 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<P> holder = ((ConcurrentEntry<P>)entry).getHolder();
boolean evicted = entries.remove(holder);
if (LOG.isDebugEnabled())
@ -313,10 +281,7 @@ public class ConcurrentPool<P> implements Pool<P>, 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();
}

View File

@ -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
*/

View File

@ -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);
}
/**
* <p>Returns a {@link ListIterator} positioned at the last item in a list.</p>
* @param list the list
* @param <T> the element type
* @return A {@link ListIterator} positioned at the last item of the list.
*/
public static <T> ListIterator<T> listIteratorAtEnd(List<T> 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<T> i = list.listIterator();
while (i.hasNext())
i.next();
return i;
}
}
/**
* Class from a canonical name for a type.
*

View File

@ -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
{

View File

@ -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

View File

@ -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<ServletContextListener> 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<ServletRequestListener> 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<ServletRequestAttributeListener> 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<ServletContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
{
try
{
_contextListeners.get(i).exitScope(getContext(), scopedRequest);
i.previous().exitScope(getContext(), scopedRequest);
}
catch (Throwable e)
{

View File

@ -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<HttpSessionListener> i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();)
{
_sessionListeners.get(i).sessionDestroyed(event);
i.previous().sessionDestroyed(event);
}
});
}

View File

@ -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<String> 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<String> 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"));
}
}

View File

@ -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<ServletContextListener> 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<ServletRequestListener> 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<ServletRequestAttributeListener> 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<ServletContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
{
try
{
_contextListeners.get(i).exitScope(getContext(), scopedRequest);
i.previous().exitScope(getContext(), scopedRequest);
}
catch (Throwable e)
{

View File

@ -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<HttpSessionListener> i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();)
{
_sessionListeners.get(i).sessionDestroyed(event);
i.previous().sessionDestroyed(event);
}
});
}

View File

@ -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<String> 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<String> 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"));
}
}

View File

@ -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<ServletRequestListener> 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<ServletRequestAttributeListener> 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<ContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
{
try
{
_contextListeners.get(i).exitScope(_apiContext, request);
i.previous().exitScope(_apiContext, request);
}
catch (Throwable e)
{

View File

@ -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<HttpFields>
protected static class HttpFieldsSupplier implements Supplier<HttpFields>
{
private final Supplier<Map<String, String>> _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;

View File

@ -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<HttpSessionListener> i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();)
{
_sessionListeners.get(i).sessionDestroyed(event);
i.previous().sessionDestroyed(event);
}
};
_contextHandler.getCoreContextHandler().getContext().run(r);

View File

@ -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());

View File

@ -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

View File

@ -13,9 +13,9 @@
<modules>
<module>jetty-testers</module>
<module>jetty-jmh</module>
<module>jetty-test-common</module>
<module>jetty-test-multipart</module>
<module>jetty-test-session-common</module>
<module>jetty-test-common</module>
<module>test-cross-context-dispatch</module>
<module>test-distribution</module>
<module>test-integration</module>

View File

@ -13,9 +13,6 @@
<modules>
<module>test-distribution-common</module>
<module>test-ee11-distribution</module>
<module>test-ee10-distribution</module>
<module>test-ee9-distribution</module>
</modules>
<properties>

View File

@ -15,9 +15,21 @@
<bundle-symbolic-name>${project.groupId}.tests.distribution.common</bundle-symbolic-name>
<!-- <junit.jupiter.execution.parallel.enabled>false</junit.jupiter.execution.parallel.enabled>-->
<junit.jupiter.execution.parallel.config.fixed.parallelism>2</junit.jupiter.execution.parallel.config.fixed.parallelism>
<testcontainers-keycloak.version>3.4.0</testcontainers-keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<version>${testcontainers-keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit4-mock</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
@ -54,6 +66,10 @@
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-util</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-xml</artifactId>
@ -64,6 +80,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ethereum</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
@ -138,6 +158,12 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan-common</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.smallrye</groupId>
<artifactId>jandex</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -155,6 +181,41 @@
<artifactId>jetty-util-ajax</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-test-log4j2-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-test-log4j2-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>jetty-http2-client</artifactId>
@ -244,17 +305,6 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>

View File

@ -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<Arguments> 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 = """
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.ee11.webapp.WebAppContext">
<Set name="contextPath">/test</Set>
<Set name="war"><Property name="jetty.webapps"/>/test.war</Set>
<Set name="tempDirectory"><Property name="jetty.base"/>/work/test</Set>
<Set name="tempDirectoryPersistent">false</Set>
</Configure>
""";
String warXml =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
"<!DOCTYPE Configure PUBLIC \"-//Jetty//Configure//EN\" \"https://jetty.org/configure_10_0.dtd\">" +
"<Configure class=\"org.eclipse.jetty." + env + ".webapp.WebAppContext\">" +
" <Set name=\"contextPath\">/test</Set>" +
" <Set name=\"war\"><Property name=\"jetty.webapps\"/>/test.war</Set>" +
" <Set name=\"tempDirectory\"><Property name=\"jetty.base\"/>/work/test</Set>" +
" <Set name=\"tempDirectoryPersistent\">false</Set>" +
"</Configure>";
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));

View File

@ -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<Arguments> 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 <form ***** action="***"
String postUrl = html.substring(html.indexOf("action=\"")).substring(0, html.substring(html.indexOf("action=\"")).indexOf("\"", 9)).substring(8);
Fields fields = new Fields();
fields.put("username", userName);
fields.add("password", password);
contentResponse = client.FORM(postUrl, fields);
assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200));
assertThat(contentResponse.getContentAsString(), containsString("success"));
// Now authenticated we can get info
String content = client.GET(uri + "/").getContentAsString();
assertThat(content, containsString("userId: " + userId));
assertThat(content, containsString("name: " + firstName + " " + lastName));
assertThat(content, containsString("email: " + email));
// Request to admin page gives 403 as we do not have admin role
contentResponse = client.GET(uri + "/admin");
assertThat(contentResponse.getStatus(), is(HttpStatus.FORBIDDEN_403));
// We are no longer authenticated after logging out
contentResponse = client.GET(uri + "/logout");
assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200));
content = contentResponse.getContentAsString();
assertThat(content, containsString("not authenticated"));
}
}
}
}

View File

@ -6,3 +6,6 @@ org.testcontainers.LEVEL=INFO
#org.testcontainers.LEVEL=DEBUG
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.tests.distribution.LEVEL=DEBUG
# uncomment to get output of forked jetty process in the logs
#org.eclipse.jetty.tests.testers.ProcessWrapper.LEVEL=DEBUG
com.gargoylesoftware.htmlunit.LEVEL=ERROR

View File

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution</artifactId>
<version>12.1.0-SNAPSHOT</version>
</parent>
<artifactId>test-ee10-distribution</artifactId>
<packaging>jar</packaging>
<name>Tests :: Distribution :: EE10</name>
<properties>
<bundle-symbolic-name>${project.groupId}.ee10.distribution</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<type>zip</type>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ethereum</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-openid</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-testers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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 = """
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Set name="contextPath">/test</Set>
<Set name="war"><Property name="jetty.webapps"/>/test.war</Set>
<Set name="tempDirectory"><Property name="jetty.base"/>/work/test</Set>
<Set name="tempDirectoryPersistent">false</Set>
</Configure>
""";
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);
}
}

View File

@ -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();
}
}
}

View File

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution</artifactId>
<version>12.1.0-SNAPSHOT</version>
</parent>
<artifactId>test-ee11-distribution</artifactId>
<packaging>jar</packaging>
<name>Tests :: Distribution :: EE11</name>
<properties>
<bundle-symbolic-name>${project.groupId}.ee11.distribution</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<type>zip</type>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-openid</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-testers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution-common</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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();
}
}
}

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution</artifactId>
<version>12.1.0-SNAPSHOT</version>
</parent>
<artifactId>test-ee9-distribution</artifactId>
<packaging>jar</packaging>
<name>Tests :: Distribution :: EE9</name>
<properties>
<bundle-symbolic-name>${project.groupId}.ee9.distribution</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<type>zip</type>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-openid</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-test-openid-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-testers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-distribution-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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();
}
}
}