Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dynamic-compression-handler
This commit is contained in:
commit
1575b0a904
|
@ -125,11 +125,11 @@ def mavenBuild(jdk, cmdline, mvnName) {
|
||||||
buildCache = useBuildCache()
|
buildCache = useBuildCache()
|
||||||
if (buildCache) {
|
if (buildCache) {
|
||||||
echo "Using build cache"
|
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 {
|
} else {
|
||||||
// when not using cache
|
// when not using cache
|
||||||
echo "Not using build 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 (env.BRANCH_NAME ==~ /PR-\d+/) {
|
||||||
if (pullRequest.labels.contains("build-all-tests")) {
|
if (pullRequest.labels.contains("build-all-tests")) {
|
||||||
|
|
|
@ -15,6 +15,9 @@ package org.eclipse.jetty.docs.programming;
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
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.server.Server;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.eclipse.jetty.util.thread.VirtualThreadPool;
|
import org.eclipse.jetty.util.thread.VirtualThreadPool;
|
||||||
|
@ -26,9 +29,26 @@ public class ArchitectureDocs
|
||||||
{
|
{
|
||||||
// tag::queuedVirtual[]
|
// tag::queuedVirtual[]
|
||||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||||
|
|
||||||
|
// Simple, unlimited, virtual thread Executor.
|
||||||
threadPool.setVirtualThreadsExecutor(Executors.newVirtualThreadPerTaskExecutor());
|
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);
|
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[]
|
// end::queuedVirtual[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +58,21 @@ public class ArchitectureDocs
|
||||||
VirtualThreadPool threadPool = new VirtualThreadPool();
|
VirtualThreadPool threadPool = new VirtualThreadPool();
|
||||||
// Limit the max number of current virtual threads.
|
// Limit the max number of current virtual threads.
|
||||||
threadPool.setMaxThreads(200);
|
threadPool.setMaxThreads(200);
|
||||||
|
// Track, with details, virtual threads usage.
|
||||||
|
threadPool.setTracking(true);
|
||||||
|
threadPool.setDetailedDump(true);
|
||||||
|
|
||||||
|
// For server-side usage.
|
||||||
Server server = new Server(threadPool);
|
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[]
|
// end::virtualVirtual[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
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.
|
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:
|
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.
|
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.
|
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.
|
||||||
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.
|
|
||||||
====
|
|
||||||
|
|
||||||
[[threadpool-virtual]]
|
[[threadpool-virtual]]
|
||||||
== Module `threadpool-virtual`
|
== Module `threadpool-virtual`
|
||||||
|
|
|
@ -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]
|
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).
|
||||||
|
====
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
|
||||||
* <p>Content must be provided by writing to the {@link #getOutputStream() output stream}
|
* <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>
|
* that must be {@link OutputStream#close() closed} when all content has been provided.</p>
|
||||||
* <p>Example usage:</p>
|
* <p>Example usage:</p>
|
||||||
* <pre>
|
* <pre>{@code
|
||||||
* HttpClient httpClient = ...;
|
* HttpClient httpClient = ...;
|
||||||
*
|
*
|
||||||
* // Use try-with-resources to autoclose the output stream.
|
* // Use try-with-resources to autoclose the output stream.
|
||||||
|
@ -37,7 +37,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
|
||||||
* .body(content)
|
* .body(content)
|
||||||
* .send(new Response.CompleteListener()
|
* .send(new Response.CompleteListener()
|
||||||
* {
|
* {
|
||||||
* @Override
|
* @Override
|
||||||
* public void onComplete(Result result)
|
* public void onComplete(Result result)
|
||||||
* {
|
* {
|
||||||
* // Your logic here
|
* // Your logic here
|
||||||
|
@ -50,7 +50,7 @@ import org.eclipse.jetty.io.content.OutputStreamContentSource;
|
||||||
* // Even later...
|
* // Even later...
|
||||||
* output.write("more content".getBytes());
|
* output.write("more content".getBytes());
|
||||||
* } // Implicit call to output.close().
|
* } // Implicit call to output.close().
|
||||||
* </pre>
|
* }</pre>
|
||||||
*/
|
*/
|
||||||
public class OutputStreamRequestContent extends OutputStreamContentSource implements Request.Content
|
public class OutputStreamRequestContent extends OutputStreamContentSource implements Request.Content
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,6 @@ import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.Request;
|
import org.eclipse.jetty.client.Request;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
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.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -42,7 +41,7 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
||||||
|
|
||||||
public HttpClientTransportOverHTTP()
|
public HttpClientTransportOverHTTP()
|
||||||
{
|
{
|
||||||
this(Math.max(1, ProcessorUtils.availableProcessors() / 2));
|
this(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientTransportOverHTTP(int selectors)
|
public HttpClientTransportOverHTTP(int selectors)
|
||||||
|
|
|
@ -422,6 +422,11 @@ public class HttpParser
|
||||||
return _state;
|
return _state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasContent()
|
||||||
|
{
|
||||||
|
return _endOfContent != EndOfContent.NO_CONTENT;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean inContentState()
|
public boolean inContentState()
|
||||||
{
|
{
|
||||||
return _state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal();
|
return _state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal();
|
||||||
|
|
|
@ -401,8 +401,8 @@ public interface EndPoint extends Closeable, Content.Sink
|
||||||
interface SslSessionData
|
interface SslSessionData
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name at which an {@code SslSessionData} instance may be found as a request
|
* The name at which an {@code SslSessionData} instance may be found
|
||||||
* {@link org.eclipse.jetty.util.Attributes Attribute} or from {@link SSLSession#getValue(String)}.
|
* as a request {@link org.eclipse.jetty.util.Attributes attribute}.
|
||||||
*/
|
*/
|
||||||
String ATTRIBUTE = "org.eclipse.jetty.io.Endpoint.SslSessionData";
|
String ATTRIBUTE = "org.eclipse.jetty.io.Endpoint.SslSessionData";
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,11 @@ import org.eclipse.jetty.util.FutureCallback;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>A {@link Content.Source} that provides content asynchronously through an {@link OutputStream}.</p>
|
||||||
* A {@link Content.Source} backed by an {@link OutputStream}.
|
* <p>Bytes written to the {@link OutputStream} returned by {@link #getOutputStream()}
|
||||||
* Any bytes written to the {@link OutputStream} returned by {@link #getOutputStream()}
|
* are converted to a {@link Content.Chunk} and returned from {@link #read()}.</p>
|
||||||
* is converted to a {@link Content.Chunk} and returned from {@link #read()}. If
|
* <p>The {@code OutputStream} must be closed to signal that all the content has been written.</p>
|
||||||
* necessary, any {@link Runnable} passed to {@link #demand(Runnable)} is invoked.
|
*
|
||||||
* </p>
|
|
||||||
* @see AsyncContent
|
* @see AsyncContent
|
||||||
*/
|
*/
|
||||||
public class OutputStreamContentSource implements Content.Source, Closeable
|
public class OutputStreamContentSource implements Content.Source, Closeable
|
||||||
|
|
|
@ -507,6 +507,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
||||||
private Throwable _failure;
|
private Throwable _failure;
|
||||||
|
private SslSessionData _sslSessionData;
|
||||||
|
|
||||||
public SslEndPoint()
|
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
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@ -1644,28 +1667,6 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
return String.format("SSL@%h.DEP.writeCallback", SslConnection.this);
|
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
|
private abstract class RunnableTask implements Invocable.Task
|
||||||
|
|
|
@ -19,8 +19,6 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.security.LoginService;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
@ -65,6 +63,14 @@ public class ServerSupport
|
||||||
if (contexts == null)
|
if (contexts == null)
|
||||||
{
|
{
|
||||||
contexts = new ContextHandlerCollection();
|
contexts = new ContextHandlerCollection();
|
||||||
|
if (server.getHandler() != null)
|
||||||
|
{
|
||||||
|
Handler.Sequence handlers = new Handler.Sequence();
|
||||||
|
handlers.addHandler(server.getHandler());
|
||||||
|
handlers.addHandler(contexts);
|
||||||
|
server.setHandler(handlers);
|
||||||
|
}
|
||||||
|
else
|
||||||
server.setHandler(contexts);
|
server.setHandler(contexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ public class SessionAuthentication extends LoginAuthenticator.UserAuthentication
|
||||||
|
|
||||||
private final String _name;
|
private final String _name;
|
||||||
private final Object _credentials;
|
private final Object _credentials;
|
||||||
private Session _session;
|
private transient Session _session;
|
||||||
|
|
||||||
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
|
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,6 +86,7 @@ public class Server extends Handler.Wrapper implements Attributes
|
||||||
private final AutoLock _dateLock = new AutoLock();
|
private final AutoLock _dateLock = new AutoLock();
|
||||||
private final MimeTypes.Mutable _mimeTypes = new MimeTypes.Mutable();
|
private final MimeTypes.Mutable _mimeTypes = new MimeTypes.Mutable();
|
||||||
private String _serverInfo = __serverInfo;
|
private String _serverInfo = __serverInfo;
|
||||||
|
private boolean _openEarly = true;
|
||||||
private boolean _stopAtShutdown;
|
private boolean _stopAtShutdown;
|
||||||
private boolean _dumpAfterStart;
|
private boolean _dumpAfterStart;
|
||||||
private boolean _dumpBeforeStop;
|
private boolean _dumpBeforeStop;
|
||||||
|
@ -276,6 +277,22 @@ public class Server extends Handler.Wrapper implements Attributes
|
||||||
return type;
|
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()
|
public boolean isDryRun()
|
||||||
{
|
{
|
||||||
return _dryRun;
|
return _dryRun;
|
||||||
|
@ -543,7 +560,7 @@ public class Server extends Handler.Wrapper implements Attributes
|
||||||
final ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException();
|
final ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException();
|
||||||
|
|
||||||
// Open network connector to ensure ports are available
|
// 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 ->
|
_connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector ->
|
||||||
{
|
{
|
||||||
|
|
|
@ -653,11 +653,11 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
||||||
*/
|
*/
|
||||||
protected void notifyExitScope(Request request)
|
protected void notifyExitScope(Request request)
|
||||||
{
|
{
|
||||||
for (int i = _contextListeners.size(); i-- > 0; )
|
for (ListIterator<ContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_contextListeners.get(i).exitScope(_context, request);
|
i.previous().exitScope(_context, request);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -405,6 +405,15 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("HANDLE {} {}", request, this);
|
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.
|
// Handle the request by running the task.
|
||||||
_handling.set(true);
|
_handling.set(true);
|
||||||
Runnable onRequest = _onRequest;
|
Runnable onRequest = _onRequest;
|
||||||
|
@ -431,9 +440,17 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("upgraded {} -> {}", this, getEndPoint().getConnection());
|
LOG.debug("upgraded {} -> {}", this, getEndPoint().getConnection());
|
||||||
|
if (_requestBuffer != null)
|
||||||
releaseRequestBuffer();
|
releaseRequestBuffer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have already released the request buffer, then use fill interest before allocating another
|
||||||
|
if (_requestBuffer == null)
|
||||||
|
{
|
||||||
|
fillInterested();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (filled == 0)
|
else if (filled == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -79,36 +79,6 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
|
||||||
this(strategyType, maxSize, pooled -> 1);
|
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.
|
* <p>Creates an instance with the specified strategy.
|
||||||
* and a function that returns the max multiplex count for a given pooled object.</p>
|
* 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();
|
leaked.increment();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Leaked " + holder);
|
LOG.debug("Leaked {}", holder);
|
||||||
leaked();
|
leaked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,15 +165,14 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
|
||||||
|
|
||||||
void sweep()
|
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);
|
boolean remove = holder.getEntry() == null;
|
||||||
if (holder.getEntry() == null)
|
if (remove)
|
||||||
{
|
|
||||||
entries.remove(i--);
|
|
||||||
leaked(holder);
|
leaked(holder);
|
||||||
}
|
return remove;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -285,8 +254,7 @@ public class ConcurrentPool<P> implements Pool<P>, Dumpable
|
||||||
if (!removed)
|
if (!removed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// No need to lock, no race with reserve()
|
// In a harmless race with reserve()/sweep()/terminate().
|
||||||
// and the race with terminate() is harmless.
|
|
||||||
Holder<P> holder = ((ConcurrentEntry<P>)entry).getHolder();
|
Holder<P> holder = ((ConcurrentEntry<P>)entry).getHolder();
|
||||||
boolean evicted = entries.remove(holder);
|
boolean evicted = entries.remove(holder);
|
||||||
if (LOG.isDebugEnabled())
|
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
|
// Field this.terminated must be modified with the lock held
|
||||||
// because the list of entries is modified, see reserve().
|
// because the list of entries is modified, see reserve().
|
||||||
terminated = true;
|
terminated = true;
|
||||||
copy = entries.stream()
|
copy = stream().toList();
|
||||||
.map(Holder::getEntry)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
entries.clear();
|
entries.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// we won the race against the callback, so the callback has to process and we can break processing
|
||||||
_state = State.PENDING;
|
_state = State.PENDING;
|
||||||
|
_reprocess = false;
|
||||||
if (_aborted)
|
if (_aborted)
|
||||||
{
|
{
|
||||||
onAbortedOnFailureIfNotPendingDoCompleted = _failure;
|
onAbortedOnFailureIfNotPendingDoCompleted = _failure;
|
||||||
|
@ -482,6 +483,7 @@ public abstract class IteratingCallback implements Callback
|
||||||
}
|
}
|
||||||
callOnSuccess = true;
|
callOnSuccess = true;
|
||||||
_state = State.PROCESSING;
|
_state = State.PROCESSING;
|
||||||
|
_reprocess = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -818,6 +820,14 @@ public abstract class IteratingCallback implements Callback
|
||||||
return true;
|
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
|
* @return whether this callback is idle, and {@link #iterate()} needs to be called
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,6 +34,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ServiceConfigurationError;
|
import java.util.ServiceConfigurationError;
|
||||||
|
@ -173,6 +174,32 @@ public class TypeUtil
|
||||||
return Arrays.asList(a);
|
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.
|
* Class from a canonical name for a type.
|
||||||
*
|
*
|
||||||
|
|
|
@ -33,7 +33,9 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
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.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
@ -63,6 +65,45 @@ public class IteratingCallbackTest
|
||||||
scheduler.stop();
|
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
|
@Test
|
||||||
public void testNonWaitingProcess() throws Exception
|
public void testNonWaitingProcess() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpProxy;
|
import org.eclipse.jetty.client.HttpProxy;
|
||||||
import org.eclipse.jetty.client.Request;
|
import org.eclipse.jetty.client.Request;
|
||||||
import org.eclipse.jetty.client.Response;
|
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.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
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.IO;
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||||
import org.eclipse.jetty.util.ajax.JSON;
|
import org.eclipse.jetty.util.ajax.JSON;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -163,11 +165,32 @@ public class AsyncMiddleManServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void dispose() throws Exception
|
public void dispose()
|
||||||
{
|
{
|
||||||
client.stop();
|
LifeCycle.stop(client);
|
||||||
proxy.stop();
|
LifeCycle.stop(proxy);
|
||||||
server.stop();
|
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
|
@Test
|
||||||
|
|
|
@ -31,6 +31,7 @@ import java.util.EventListener;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
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.ExceptionUtil;
|
||||||
import org.eclipse.jetty.util.Loader;
|
import org.eclipse.jetty.util.Loader;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
|
@ -516,12 +518,11 @@ public class ServletContextHandler extends ContextHandler
|
||||||
//Call context listeners
|
//Call context listeners
|
||||||
Throwable multiException = null;
|
Throwable multiException = null;
|
||||||
ServletContextEvent event = new ServletContextEvent(getServletContext());
|
ServletContextEvent event = new ServletContextEvent(getServletContext());
|
||||||
Collections.reverse(_destroyServletContextListeners);
|
for (ListIterator<ServletContextListener> i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();)
|
||||||
for (ServletContextListener listener : _destroyServletContextListeners)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
callContextDestroyed(listener, event);
|
callContextDestroyed(i.previous(), event);
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
|
@ -568,17 +569,17 @@ public class ServletContextHandler extends ContextHandler
|
||||||
if (!_servletRequestListeners.isEmpty())
|
if (!_servletRequestListeners.isEmpty())
|
||||||
{
|
{
|
||||||
final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request);
|
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())
|
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);
|
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
|
||||||
if (!_contextListeners.isEmpty())
|
if (!_contextListeners.isEmpty())
|
||||||
{
|
{
|
||||||
for (int i = _contextListeners.size(); i-- > 0; )
|
for (ListIterator<ServletContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_contextListeners.get(i).exitScope(getContext(), scopedRequest);
|
i.previous().exitScope(getContext(), scopedRequest);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Enumeration;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.ManagedSession;
|
||||||
import org.eclipse.jetty.session.SessionConfig;
|
import org.eclipse.jetty.session.SessionConfig;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
|
||||||
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
|
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
|
||||||
{
|
{
|
||||||
|
@ -253,6 +255,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
||||||
SessionHandler.this.setSecureCookies(secure);
|
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()
|
private void checkState()
|
||||||
{
|
{
|
||||||
//It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started,
|
//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()
|
public SessionHandler()
|
||||||
{
|
{
|
||||||
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
|
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
|
||||||
|
installBean(_cookieConfig);
|
||||||
|
installBean(_sessionListeners);
|
||||||
|
installBean(_sessionIdListeners);
|
||||||
|
installBean(_sessionAttributeListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -559,9 +573,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
||||||
getSessionContext().run(() ->
|
getSessionContext().run(() ->
|
||||||
{
|
{
|
||||||
HttpSessionEvent event = new HttpSessionEvent(session.getApi());
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.http.ComplianceViolation;
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.HttpCompliance;
|
import org.eclipse.jetty.http.HttpCompliance;
|
||||||
|
import org.eclipse.jetty.http.UriCompliance;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.LocalConnector;
|
import org.eclipse.jetty.server.LocalConnector;
|
||||||
|
@ -87,8 +88,8 @@ public class ComplianceViolations2616Test
|
||||||
{
|
{
|
||||||
resp.setContentType("text/plain");
|
resp.setContentType("text/plain");
|
||||||
PrintWriter out = resp.getWriter();
|
PrintWriter out = resp.getWriter();
|
||||||
List<String> headerNames = new ArrayList<>();
|
out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo());
|
||||||
headerNames.addAll(Collections.list(req.getHeaderNames()));
|
List<String> headerNames = new ArrayList<>(Collections.list(req.getHeaderNames()));
|
||||||
Collections.sort(headerNames);
|
Collections.sort(headerNames);
|
||||||
for (String name : 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 headers", response, containsString("X-Http-Violation-0: Line Folding not supported"));
|
||||||
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import java.util.EventListener;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
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.ExceptionUtil;
|
||||||
import org.eclipse.jetty.util.Loader;
|
import org.eclipse.jetty.util.Loader;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
|
@ -515,12 +517,11 @@ public class ServletContextHandler extends ContextHandler
|
||||||
//Call context listeners
|
//Call context listeners
|
||||||
Throwable multiException = null;
|
Throwable multiException = null;
|
||||||
ServletContextEvent event = new ServletContextEvent(getServletContext());
|
ServletContextEvent event = new ServletContextEvent(getServletContext());
|
||||||
Collections.reverse(_destroyServletContextListeners);
|
for (ListIterator<ServletContextListener> i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();)
|
||||||
for (ServletContextListener listener : _destroyServletContextListeners)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
callContextDestroyed(listener, event);
|
callContextDestroyed(i.previous(), event);
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
|
@ -566,18 +567,18 @@ public class ServletContextHandler extends ContextHandler
|
||||||
// Handle more REALLY SILLY request events!
|
// Handle more REALLY SILLY request events!
|
||||||
if (!_servletRequestListeners.isEmpty())
|
if (!_servletRequestListeners.isEmpty())
|
||||||
{
|
{
|
||||||
final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request);
|
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())
|
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);
|
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
|
||||||
if (!_contextListeners.isEmpty())
|
if (!_contextListeners.isEmpty())
|
||||||
{
|
{
|
||||||
for (int i = _contextListeners.size(); i-- > 0; )
|
for (ListIterator<ServletContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_contextListeners.get(i).exitScope(getContext(), scopedRequest);
|
i.previous().exitScope(getContext(), scopedRequest);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Enumeration;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.ManagedSession;
|
||||||
import org.eclipse.jetty.session.SessionConfig;
|
import org.eclipse.jetty.session.SessionConfig;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
|
||||||
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
|
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
|
||||||
{
|
{
|
||||||
|
@ -254,6 +256,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
||||||
SessionHandler.this.setSecureCookies(secure);
|
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()
|
private void checkState()
|
||||||
{
|
{
|
||||||
//It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started,
|
//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()
|
public SessionHandler()
|
||||||
{
|
{
|
||||||
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
|
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
|
||||||
|
installBean(_cookieConfig);
|
||||||
|
installBean(_sessionListeners);
|
||||||
|
installBean(_sessionIdListeners);
|
||||||
|
installBean(_sessionAttributeListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -603,9 +617,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
|
||||||
getSessionContext().run(() ->
|
getSessionContext().run(() ->
|
||||||
{
|
{
|
||||||
HttpSessionEvent event = new HttpSessionEvent(session.getApi());
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.http.ComplianceViolation;
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.HttpCompliance;
|
import org.eclipse.jetty.http.HttpCompliance;
|
||||||
|
import org.eclipse.jetty.http.UriCompliance;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.LocalConnector;
|
import org.eclipse.jetty.server.LocalConnector;
|
||||||
|
@ -87,8 +88,8 @@ public class ComplianceViolations2616Test
|
||||||
{
|
{
|
||||||
resp.setContentType("text/plain");
|
resp.setContentType("text/plain");
|
||||||
PrintWriter out = resp.getWriter();
|
PrintWriter out = resp.getWriter();
|
||||||
List<String> headerNames = new ArrayList<>();
|
out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo());
|
||||||
headerNames.addAll(Collections.list(req.getHeaderNames()));
|
List<String> headerNames = new ArrayList<>(Collections.list(req.getHeaderNames()));
|
||||||
Collections.sort(headerNames);
|
Collections.sort(headerNames);
|
||||||
for (String name : 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 headers", response, containsString("X-Http-Violation-0: Line Folding not supported"));
|
||||||
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.EventListener;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -1000,17 +1001,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
||||||
if (!_servletRequestListeners.isEmpty())
|
if (!_servletRequestListeners.isEmpty())
|
||||||
{
|
{
|
||||||
final ServletRequestEvent sre = new ServletRequestEvent(_apiContext, request);
|
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())
|
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())
|
if (!_contextListeners.isEmpty())
|
||||||
{
|
{
|
||||||
for (int i = _contextListeners.size(); i-- > 0; )
|
for (ListIterator<ContextScopeListener> i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_contextListeners.get(i).exitScope(_apiContext, request);
|
i.previous().exitScope(_apiContext, request);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -76,20 +76,20 @@ public class Response implements HttpServletResponse
|
||||||
* String used in the {@code Comment} attribute of {@link Cookie}
|
* String used in the {@code Comment} attribute of {@link Cookie}
|
||||||
* to support the {@code HttpOnly} attribute.
|
* 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}
|
* String used in the {@code Comment} attribute of {@link Cookie}
|
||||||
* to support the {@code Partitioned} attribute.
|
* 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}
|
* The strings used in the {@code Comment} attribute of {@link Cookie}
|
||||||
* to support the {@code SameSite} attribute.
|
* to support the {@code SameSite} attribute.
|
||||||
**/
|
**/
|
||||||
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
protected static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
||||||
private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
protected static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
||||||
private static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
|
protected 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_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
|
||||||
|
|
||||||
public enum OutputType
|
public enum OutputType
|
||||||
{
|
{
|
||||||
|
@ -1465,7 +1465,7 @@ public class Response implements HttpServletResponse
|
||||||
return (HttpServletResponse)servletResponse;
|
return (HttpServletResponse)servletResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class HttpFieldsSupplier implements Supplier<HttpFields>
|
protected static class HttpFieldsSupplier implements Supplier<HttpFields>
|
||||||
{
|
{
|
||||||
private final Supplier<Map<String, String>> _supplier;
|
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 Cookie _cookie;
|
||||||
private final String _comment;
|
private final String _comment;
|
||||||
|
@ -1622,12 +1622,12 @@ public class Response implements HttpServletResponse
|
||||||
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
|
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);
|
return comment != null && comment.contains(PARTITIONED_COMMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SameSite getSameSiteFromComment(String comment)
|
protected static SameSite getSameSiteFromComment(String comment)
|
||||||
{
|
{
|
||||||
if (comment == null)
|
if (comment == null)
|
||||||
return null;
|
return null;
|
||||||
|
@ -1640,7 +1640,7 @@ public class Response implements HttpServletResponse
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getCommentWithoutAttributes(String comment)
|
protected static String getCommentWithoutAttributes(String comment)
|
||||||
{
|
{
|
||||||
if (comment == null)
|
if (comment == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.util.EnumSet;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.function.Function;
|
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.SessionIdManager;
|
||||||
import org.eclipse.jetty.session.SessionManager;
|
import org.eclipse.jetty.session.SessionManager;
|
||||||
import org.eclipse.jetty.util.Callback;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -613,7 +616,8 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
||||||
* CookieConfig
|
* CookieConfig
|
||||||
*
|
*
|
||||||
* Implementation of the jakarta.servlet.SessionCookieConfig.
|
* 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
|
* @see HttpCookie
|
||||||
*/
|
*/
|
||||||
|
@ -671,7 +675,19 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
||||||
public void setComment(String comment)
|
public void setComment(String comment)
|
||||||
{
|
{
|
||||||
checkAvailable();
|
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
|
@Override
|
||||||
|
@ -715,6 +731,14 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
||||||
checkAvailable();
|
checkAvailable();
|
||||||
_sessionManager.setSecureCookies(secure);
|
_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
|
private class CoreSessionManager extends AbstractSessionManager
|
||||||
|
@ -812,9 +836,9 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
||||||
Runnable r = () ->
|
Runnable r = () ->
|
||||||
{
|
{
|
||||||
HttpSessionEvent event = new HttpSessionEvent(session.getApi());
|
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);
|
_contextHandler.getCoreContextHandler().getContext().run(r);
|
||||||
|
|
|
@ -168,7 +168,7 @@ public class SessionHandlerTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionCookie() throws Exception
|
public void testSessionCookieConfig() throws Exception
|
||||||
{
|
{
|
||||||
Server server = new Server();
|
Server server = new Server();
|
||||||
MockSessionIdManager idMgr = new MockSessionIdManager(server);
|
MockSessionIdManager idMgr = new MockSessionIdManager(server);
|
||||||
|
@ -191,8 +191,47 @@ public class SessionHandlerTest
|
||||||
sessionCookieConfig.setPath("/foo");
|
sessionCookieConfig.setPath("/foo");
|
||||||
sessionCookieConfig.setMaxAge(99);
|
sessionCookieConfig.setMaxAge(99);
|
||||||
|
|
||||||
//for < ee10, SameSite cannot be set on the SessionCookieConfig, only on the SessionManager, or
|
//test setting SameSite and Partitioned the old way in the comment
|
||||||
//a default value on the context attribute org.eclipse.jetty.cookie.sameSiteDefault
|
sessionCookieConfig.setComment(Response.PARTITIONED_COMMENT + " " + Response.SAME_SITE_STRICT_COMMENT);
|
||||||
|
|
||||||
|
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.setSameSite(HttpCookie.SameSite.STRICT);
|
||||||
mgr.setPartitioned(true);
|
mgr.setPartitioned(true);
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpProxy;
|
import org.eclipse.jetty.client.HttpProxy;
|
||||||
import org.eclipse.jetty.client.Request;
|
import org.eclipse.jetty.client.Request;
|
||||||
import org.eclipse.jetty.client.Response;
|
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.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -163,11 +164,32 @@ public class AsyncMiddleManServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void dispose() throws Exception
|
public void dispose()
|
||||||
{
|
{
|
||||||
LifeCycle.stop(client);
|
LifeCycle.stop(client);
|
||||||
LifeCycle.stop(proxy);
|
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
|
@Test
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
<modules>
|
<modules>
|
||||||
<module>jetty-testers</module>
|
<module>jetty-testers</module>
|
||||||
<module>jetty-jmh</module>
|
<module>jetty-jmh</module>
|
||||||
|
<module>jetty-test-common</module>
|
||||||
<module>jetty-test-multipart</module>
|
<module>jetty-test-multipart</module>
|
||||||
<module>jetty-test-session-common</module>
|
<module>jetty-test-session-common</module>
|
||||||
<module>jetty-test-common</module>
|
|
||||||
<module>test-cross-context-dispatch</module>
|
<module>test-cross-context-dispatch</module>
|
||||||
<module>test-distribution</module>
|
<module>test-distribution</module>
|
||||||
<module>test-integration</module>
|
<module>test-integration</module>
|
||||||
|
|
|
@ -13,9 +13,6 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>test-distribution-common</module>
|
<module>test-distribution-common</module>
|
||||||
<module>test-ee11-distribution</module>
|
|
||||||
<module>test-ee10-distribution</module>
|
|
||||||
<module>test-ee9-distribution</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -15,9 +15,21 @@
|
||||||
<bundle-symbolic-name>${project.groupId}.tests.distribution.common</bundle-symbolic-name>
|
<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.enabled>false</junit.jupiter.execution.parallel.enabled>-->
|
||||||
<junit.jupiter.execution.parallel.config.fixed.parallelism>2</junit.jupiter.execution.parallel.config.fixed.parallelism>
|
<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>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<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>
|
<dependency>
|
||||||
<groupId>org.apache.maven</groupId>
|
<groupId>org.apache.maven</groupId>
|
||||||
<artifactId>maven-artifact</artifactId>
|
<artifactId>maven-artifact</artifactId>
|
||||||
|
@ -54,6 +66,10 @@
|
||||||
<groupId>org.apache.maven.resolver</groupId>
|
<groupId>org.apache.maven.resolver</groupId>
|
||||||
<artifactId>maven-resolver-util</artifactId>
|
<artifactId>maven-resolver-util</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15to18</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.plexus</groupId>
|
<groupId>org.codehaus.plexus</groupId>
|
||||||
<artifactId>plexus-xml</artifactId>
|
<artifactId>plexus-xml</artifactId>
|
||||||
|
@ -64,6 +80,10 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-ethereum</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-home</artifactId>
|
<artifactId>jetty-home</artifactId>
|
||||||
|
@ -138,6 +158,12 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-infinispan-common</artifactId>
|
<artifactId>jetty-infinispan-common</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>io.smallrye</groupId>
|
||||||
|
<artifactId>jandex</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
@ -155,6 +181,41 @@
|
||||||
<artifactId>jetty-util-ajax</artifactId>
|
<artifactId>jetty-util-ajax</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.http2</groupId>
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
<artifactId>jetty-http2-client</artifactId>
|
<artifactId>jetty-http2-client</artifactId>
|
||||||
|
@ -244,17 +305,6 @@
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<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>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.ee11.tests.distribution;
|
package org.eclipse.jetty.tests.distribution;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -19,16 +19,21 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.FileTime;
|
import java.nio.file.attribute.FileTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.ContentResponse;
|
import org.eclipse.jetty.client.ContentResponse;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
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.JettyHomeTester;
|
||||||
import org.eclipse.jetty.tests.testers.Tester;
|
import org.eclipse.jetty.tests.testers.Tester;
|
||||||
import org.eclipse.jetty.toolchain.test.FS;
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.parallel.Isolated;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -43,8 +48,17 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DisableUrlCacheTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DisableUrlCacheTest.class);
|
||||||
|
|
||||||
@Test
|
public static Stream<Arguments> tests()
|
||||||
public void testReloadWebAppWithLog4j2() throws Exception
|
{
|
||||||
|
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();
|
Path jettyBase = newTestJettyBaseDirectory();
|
||||||
String jettyVersion = System.getProperty("jettyVersion");
|
String jettyVersion = System.getProperty("jettyVersion");
|
||||||
|
@ -55,7 +69,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
String[] setupArgs = {
|
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))
|
try (JettyHomeTester.Run setupRun = distribution.start(setupArgs))
|
||||||
|
@ -63,7 +77,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
assertTrue(setupRun.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
|
assertTrue(setupRun.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
|
||||||
assertEquals(0, setupRun.getExitValue());
|
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");
|
Path testWebApp = distribution.getJettyBase().resolve("webapps/test.war");
|
||||||
|
|
||||||
Files.copy(webApp, testWebApp);
|
Files.copy(webApp, testWebApp);
|
||||||
|
@ -75,16 +89,15 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
FS.ensureEmpty(resourcesDir);
|
FS.ensureEmpty(resourcesDir);
|
||||||
|
|
||||||
Path webappsDir = distribution.getJettyBase().resolve("webapps");
|
Path webappsDir = distribution.getJettyBase().resolve("webapps");
|
||||||
String warXml = """
|
String warXml =
|
||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
|
||||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
|
"<!DOCTYPE Configure PUBLIC \"-//Jetty//Configure//EN\" \"https://jetty.org/configure_10_0.dtd\">" +
|
||||||
<Configure class="org.eclipse.jetty.ee11.webapp.WebAppContext">
|
"<Configure class=\"org.eclipse.jetty." + env + ".webapp.WebAppContext\">" +
|
||||||
<Set name="contextPath">/test</Set>
|
" <Set name=\"contextPath\">/test</Set>" +
|
||||||
<Set name="war"><Property name="jetty.webapps"/>/test.war</Set>
|
" <Set name=\"war\"><Property name=\"jetty.webapps\"/>/test.war</Set>" +
|
||||||
<Set name="tempDirectory"><Property name="jetty.base"/>/work/test</Set>
|
" <Set name=\"tempDirectory\"><Property name=\"jetty.base\"/>/work/test</Set>" +
|
||||||
<Set name="tempDirectoryPersistent">false</Set>
|
" <Set name=\"tempDirectoryPersistent\">false</Set>" +
|
||||||
</Configure>
|
"</Configure>";
|
||||||
""";
|
|
||||||
Path warXmlPath = webappsDir.resolve("test.xml");
|
Path warXmlPath = webappsDir.resolve("test.xml");
|
||||||
Files.writeString(warXmlPath, warXml, StandardCharsets.UTF_8);
|
Files.writeString(warXmlPath, warXml, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
@ -92,10 +105,11 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
String loggingConfig = """
|
String loggingConfig = """
|
||||||
org.eclipse.jetty.LEVEL=INFO
|
org.eclipse.jetty.LEVEL=INFO
|
||||||
org.eclipse.jetty.deploy.LEVEL=DEBUG
|
org.eclipse.jetty.deploy.LEVEL=DEBUG
|
||||||
org.eclipse.jetty.ee11.webapp.LEVEL=DEBUG
|
org.eclipse.jetty.eexx.webapp.LEVEL=DEBUG
|
||||||
org.eclipse.jetty.ee11.webapp.WebAppClassLoader.LEVEL=INFO
|
org.eclipse.jetty.eexx.webapp.WebAppClassLoader.LEVEL=INFO
|
||||||
org.eclipse.jetty.ee11.servlet.LEVEL=DEBUG
|
org.eclipse.jetty.exx.servlet.LEVEL=DEBUG
|
||||||
""";
|
""";
|
||||||
|
loggingConfig = loggingConfig.replace("eexx", env);
|
||||||
Files.writeString(loggingFile, loggingConfig, StandardCharsets.UTF_8);
|
Files.writeString(loggingFile, loggingConfig, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,7 +135,7 @@ public class DisableUrlCacheTest extends AbstractJettyHomeTest
|
||||||
touch(warXmlPath);
|
touch(warXmlPath);
|
||||||
|
|
||||||
// Wait for reload to start context
|
// 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
|
// wait for deployer node to complete so context is Started not Starting
|
||||||
assertTrue(run2.awaitConsoleLogsFor("Executing Node Node[started]", START_TIMEOUT, TimeUnit.SECONDS));
|
assertTrue(run2.awaitConsoleLogsFor("Executing Node Node[started]", START_TIMEOUT, TimeUnit.SECONDS));
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,3 +6,6 @@ org.testcontainers.LEVEL=INFO
|
||||||
#org.testcontainers.LEVEL=DEBUG
|
#org.testcontainers.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.tests.distribution.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
|
|
@ -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>
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue