Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-8216-openid-logout

This commit is contained in:
Lachlan Roberts 2022-07-04 10:03:57 +10:00
commit bf6a394fef
27 changed files with 585 additions and 158 deletions

87
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,87 @@
name: "CodeQL"
on:
push:
branches: [ 'jetty-10.[1-9]?[0-9].x', 'jetty-11.[1-9]?[0-9].x', 'jetty-12.[1-9]?[0-9].x' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'jetty-10.[1-9]?[0-9].x', 'jetty-11.[1-9]?[0-9].x', 'jetty-12.[1-9]?[0-9].x' ]
schedule:
- cron: '22 1 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Install and setup JDK 11
- name: Setup JDK 11
uses: actions/setup-java@v3
if: ${{
startsWith(github.ref, 'refs/heads/jetty-10.') ||
startsWith(github.ref, 'refs/heads/jetty-11.') ||
startsWith(github.base_ref, 'jetty-10.') ||
startsWith(github.base_ref, 'jetty-11.')
}}
with:
distribution: temurin
java-version: 11
cache: maven
# Install and setup JDK 17
- name: Setup JDK 17
uses: actions/setup-java@v3
if: ${{
startsWith(github.ref, 'refs/heads/jetty-12.') ||
startsWith(github.base_ref, 'jetty-12.')
}}
with:
distribution: temurin
java-version: 17
cache: maven
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

7
Jenkinsfile vendored
View File

@ -3,7 +3,10 @@
pipeline {
agent any
// save some io during the build
options { durabilityHint('PERFORMANCE_OPTIMIZED') }
options {
skipDefaultCheckout()
durabilityHint('PERFORMANCE_OPTIMIZED')
}
stages {
stage("Parallel Stage") {
parallel {
@ -12,6 +15,7 @@ pipeline {
steps {
container('jetty-build') {
timeout( time: 180, unit: 'MINUTES' ) {
checkout scm
mavenBuild( "jdk17", "clean install -Perrorprone", "maven3")
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
@ -42,6 +46,7 @@ pipeline {
steps {
container( 'jetty-build' ) {
timeout( time: 180, unit: 'MINUTES' ) {
checkout scm
mavenBuild( "jdk11", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3")
recordIssues id: "jdk11", name: "Static Analysis jdk11", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle()]
}

View File

@ -13,7 +13,7 @@
<name>Jetty :: GCloud</name>
<properties>
<gcloud.version>2.7.0</gcloud.version>
<gcloud.version>2.9.1</gcloud.version>
</properties>
<modules>

View File

@ -53,12 +53,15 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
{
if (LOG.isDebugEnabled())
LOG.debug("idle timeout on {}: {}", this, failure);
offerFailure(failure);
boolean result = true;
Connection connection = getConnection();
if (connection != null)
result = connection.onIdleExpired();
consumer.accept(() -> close(failure));
if (result)
{
offerFailure(failure);
consumer.accept(() -> close(failure));
}
return result;
}

View File

@ -49,6 +49,7 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dstop.port=@{test.stopPort} -Djetty.port=@{test.jettyPort}</argLine>
@ -114,11 +115,6 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-artifact-transfer</artifactId>
<version>${maven-artifact-transfer.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
@ -150,6 +146,16 @@
<artifactId>maven-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
<version>${maven.resolver.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-tools-api</artifactId>

View File

@ -45,8 +45,8 @@ import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.jetty.maven.plugin.utils.MavenProjectHelper;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.RequestLog;
@ -357,7 +357,7 @@ public abstract class AbstractWebAppMojo extends AbstractMojo
*
*/
@Component
private ArtifactResolver artifactResolver;
private RepositorySystem repositorySystem;
/**
* The current maven session
@ -410,7 +410,7 @@ public abstract class AbstractWebAppMojo extends AbstractMojo
}
getLog().info("Configuring Jetty for project: " + getProjectName());
mavenProjectHelper = new MavenProjectHelper(project, artifactResolver, remoteRepositories, session);
mavenProjectHelper = new MavenProjectHelper(project, repositorySystem, remoteRepositories, session);
mergedSystemProperties = mergeSystemProperties();
configureSystemProperties();
augmentPluginClasspath();

View File

@ -25,15 +25,16 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.jetty.maven.plugin.OverlayManager;
import org.eclipse.jetty.maven.plugin.WarPluginInfo;
@ -46,7 +47,7 @@ import org.eclipse.jetty.maven.plugin.WarPluginInfo;
public class MavenProjectHelper
{
private MavenProject project;
private ArtifactResolver artifactResolver;
private RepositorySystem repositorySystem;
private List<ArtifactRepository> remoteRepositories;
private MavenSession session;
private final Map<String, MavenProject> artifactToReactorProjectMap;
@ -62,14 +63,14 @@ public class MavenProjectHelper
/**
* @param project the project being built
* @param artifactResolver a resolve for artifacts
* @param repositorySystem a resolve for artifacts
* @param remoteRepositories repositories from which to resolve artifacts
* @param session the current maven build session
*/
public MavenProjectHelper(MavenProject project, ArtifactResolver artifactResolver, List<ArtifactRepository> remoteRepositories, MavenSession session)
public MavenProjectHelper(MavenProject project, RepositorySystem repositorySystem, List<ArtifactRepository> remoteRepositories, MavenSession session)
{
this.project = project;
this.artifactResolver = artifactResolver;
this.repositorySystem = repositorySystem;
this.remoteRepositories = remoteRepositories;
this.session = session;
//work out which dependent projects are in the reactor
@ -145,26 +146,18 @@ public class MavenProjectHelper
* @param version the version of the artifact to resolve
* @param type the type of the artifact to resolve
* @return a File representing the location of the artifact or null if not resolved
* @throws ArtifactResolverException
* @throws ArtifactResolutionException
*/
public File resolveArtifact(String groupId, String artifactId, String version, String type)
throws ArtifactResolverException
throws ArtifactResolutionException
{
DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
coordinate.setGroupId(groupId);
coordinate.setArtifactId(artifactId);
coordinate.setVersion(version);
coordinate.setExtension(type);
ArtifactRequest request = new ArtifactRequest();
request.setRepositories(RepositoryUtils.toRepos(remoteRepositories));
request.setArtifact(new DefaultArtifact(groupId, artifactId, "", type, version));
ArtifactResult result = repositorySystem.resolveArtifact(session.getRepositorySession(), request);
ProjectBuildingRequest buildingRequest =
new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
buildingRequest.setRemoteRepositories(remoteRepositories);
Artifact a = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact();
if (a != null)
return a.getFile();
if (result.isResolved())
return result.getArtifact().getFile();
return null;
}

View File

@ -12,7 +12,7 @@
<packaging>pom</packaging>
<properties>
<osgi-version>3.17.200</osgi-version>
<osgi-version>3.18.0</osgi-version>
<osgi-services-version>3.10.200</osgi-services-version>
<osgi-util-version>3.6.100</osgi-util-version>
<equinox-http-servlet-version>1.0.0-v20070606</equinox-http-servlet-version>

View File

@ -153,7 +153,6 @@
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
@ -169,7 +168,6 @@
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-jsp</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
@ -185,7 +183,6 @@
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
@ -346,7 +343,6 @@
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-alpn</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -47,5 +47,25 @@
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-alpn</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-jsp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-warurl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,29 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<!-- =============================================================== -->
<!-- Configure extended support for webapps -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- Add plus Configuring classes to all webapps for this Server -->
<!-- =========================================================== -->
<Call class="org.eclipse.jetty.webapp.Configurations" name="setServerDefault">
<Arg><Ref refid="Server" /></Arg>
<Call name="add">
<Arg>
<Array type="String">
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
</Array>
</Arg>
</Call>
</Call>
<Call name="addBean">
<Arg><New class="org.eclipse.jetty.plus.jndi.NamingDump"/></Arg>
</Call>
</Configure>

View File

@ -29,6 +29,7 @@ import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@ -187,7 +188,7 @@ public class Main
public void invokeMain(ClassLoader classloader, StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException
{
if (args.getEnabledModules().isEmpty())
if (args.getSelectedModules().isEmpty())
{
if (Files.exists(getBaseHome().getBasePath("start.jar")))
StartLog.error("Do not start with ${jetty.base} == ${jetty.home}!");
@ -325,12 +326,22 @@ public class Main
modules.registerAll();
// 4) Active Module Resolution
for (String enabledModule : modules.getSortedNames(args.getEnabledModules()))
List<String> selectedModules = args.getSelectedModules();
List<String> sortedSelectedModules = modules.getSortedNames(selectedModules);
List<String> unknownModules = new ArrayList<>(selectedModules);
unknownModules.removeAll(sortedSelectedModules);
if (unknownModules.size() >= 1)
{
for (String source : args.getSources(enabledModule))
throw new UsageException(UsageException.ERR_UNKNOWN, "Unknown module%s=[%s] List available with --list-modules",
unknownModules.size() > 1 ? 's' : "",
String.join(", ", unknownModules));
}
for (String selectedModule : sortedSelectedModules)
{
for (String source : args.getSources(selectedModule))
{
String shortForm = baseHome.toShortForm(source);
modules.enable(enabledModule, shortForm);
modules.enable(selectedModule, shortForm);
}
}
@ -470,8 +481,7 @@ public class Main
CommandLineBuilder cmd = args.getMainArgs(StartArgs.ALL_PARTS);
cmd.debug();
List<String> execModules = args.getEnabledModules().stream()
.map(name -> args.getAllModules().get(name))
List<String> execModules = args.getAllModules().getEnabled().stream()
// Keep only the forking modules.
.filter(module -> !module.getJvmArgs().isEmpty())
.map(Module::getName)

View File

@ -395,7 +395,6 @@ public class Modules implements Iterable<Module>
order.add(name);
}
}
return order;
}

View File

@ -646,7 +646,29 @@ public class StartArgs
return classpath;
}
/**
* @deprecated use {@link #getSelectedModules()} instead
*/
@Deprecated
public List<String> getEnabledModules()
{
return getSelectedModules();
}
/**
* <p>
* The list of selected Modules to enable based on configuration
* obtained from {@code start.d/*.ini}, {@code start.ini}, and command line.
* </p>
*
* <p>
* For full list of enabled modules, use {@link Modules#getEnabled()}
* </p>
*
* @return the list of selected modules (by name) that the configuration has.
* @see Modules#getEnabled()
*/
public List<String> getSelectedModules()
{
return this.modules;
}
@ -1315,11 +1337,11 @@ public class StartArgs
return;
}
// Enable a module
// Select a module to eventually be enabled
if (arg.startsWith("--module="))
{
List<String> moduleNames = Props.getValues(arg);
enableModules(source, moduleNames);
selectModules(source, moduleNames);
return;
}
@ -1464,7 +1486,7 @@ public class StartArgs
setProperty(key, value, source);
}
private void enableModules(String source, List<String> moduleNames)
private void selectModules(String source, List<String> moduleNames)
{
for (String moduleName : moduleNames)
{

View File

@ -71,7 +71,7 @@ public class MavenMetadataTest
@Test
public void testIsExpiredTimestampNow()
{
LocalDateTime now = LocalDateTime.now();
LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
String timestamp = getTimestampFormatter().format(now);
assertFalse(MavenMetadata.isExpiredTimestamp(timestamp), "Timestamp should NOT be stale: " + timestamp);
}

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Set;
import org.eclipse.jetty.start.Props;
import org.eclipse.jetty.start.UsageException;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.PathAssert;
@ -34,10 +35,12 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class BasicTest extends AbstractUseCase
{
@ -110,6 +113,59 @@ public class BasicTest extends AbstractUseCase
assertThat("System.getProperty(jetty.base)", System.getProperty("jetty.base"), is(not(startsWith("file:"))));
}
@Test
public void testAddModuleDoesNotExist() throws Exception
{
setupDistHome();
Files.write(baseDir.resolve("start.ini"),
List.of(
"--module=main",
"--module=does-not-exist"
),
StandardCharsets.UTF_8);
// === Execute Main
List<String> runArgs = new ArrayList<>();
runArgs.add("--create-files");
UsageException usage = assertThrows(UsageException.class, () ->
{
ExecResults results = exec(runArgs, true);
if (results.exception != null)
{
throw results.exception;
}
});
assertThat(usage.getMessage(), containsString("Unknown module=[does-not-exist]"));
}
@Test
public void testAddModuleDoesNotExistMultiple() throws Exception
{
setupDistHome();
Files.write(baseDir.resolve("start.ini"),
List.of(
"--module=main",
"--module=does-not-exist",
"--module=also-not-present"
),
StandardCharsets.UTF_8);
// === Execute Main
List<String> runArgs = new ArrayList<>();
runArgs.add("--create-files");
UsageException usage = assertThrows(UsageException.class, () ->
{
ExecResults results = exec(runArgs, true);
if (results.exception != null)
{
throw results.exception;
}
});
assertThat(usage.getMessage(), containsString("Unknown modules=[does-not-exist, also-not-present]"));
}
@Test
public void testProvidersUsingDefault() throws Exception
{

View File

@ -243,9 +243,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
{
// Fill the job queue with noop jobs to wakeup idle threads.
for (int i = 0; i < threads; ++i)
{
jobs.offer(NOOP);
}
if (!jobs.offer(NOOP))
break;
// try to let jobs complete naturally for half our stop time
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
@ -255,6 +254,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
// interrupt remaining threads
for (Thread thread : _threads)
{
if (thread == Thread.currentThread())
continue;
if (LOG.isDebugEnabled())
LOG.debug("Interrupting {}", thread);
thread.interrupt();
@ -264,24 +265,21 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
Thread.yield();
if (LOG.isDebugEnabled())
for (Thread unstopped : _threads)
{
for (Thread unstopped : _threads)
if (unstopped == Thread.currentThread())
continue;
String stack = "";
if (LOG.isDebugEnabled())
{
StringBuilder dmp = new StringBuilder();
for (StackTraceElement element : unstopped.getStackTrace())
{
dmp.append(System.lineSeparator()).append("\tat ").append(element);
}
LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
}
}
else
{
for (Thread unstopped : _threads)
{
LOG.warn("{} Couldn't stop {}", this, unstopped);
stack = dmp.toString();
}
LOG.warn("Couldn't stop {}{}", unstopped, stack);
}
}
@ -315,13 +313,32 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
private void joinThreads(long stopByNanos) throws InterruptedException
{
for (Thread thread : _threads)
loop : while (true)
{
long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canWait);
if (canWait > 0)
thread.join(canWait);
for (Thread thread : _threads)
{
// Don't join ourselves
if (thread == Thread.currentThread())
continue;
long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canWait);
if (canWait <= 0)
return;
try
{
thread.join(canWait);
}
catch (InterruptedException e)
{
// Don't stop waiting for a join if interrupted
continue loop;
}
}
return;
}
}

View File

@ -101,6 +101,43 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
}
}
private static class StoppingTask implements Runnable
{
private final CountDownLatch _running;
private final CountDownLatch _blocked;
private final QueuedThreadPool _tp;
Thread _thread;
CountDownLatch _completed = new CountDownLatch(1);
public StoppingTask(CountDownLatch running, CountDownLatch blocked, QueuedThreadPool tp)
{
_running = running;
_blocked = blocked;
_tp = tp;
}
@Override
public void run()
{
try
{
_thread = Thread.currentThread();
_running.countDown();
_blocked.await();
_tp.doStop();
_completed.countDown();
}
catch (InterruptedException x)
{
x.printStackTrace();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
private class RunningJob implements Runnable
{
final CountDownLatch _run = new CountDownLatch(1);
@ -947,6 +984,49 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
}
}
@Test
public void testInterruptedStop() throws Exception
{
QueuedThreadPool tp = new QueuedThreadPool();
tp.setStopTimeout(1000);
tp.start();
CountDownLatch running = new CountDownLatch(3);
CountDownLatch blocked = new CountDownLatch(1);
CountDownLatch forever = new CountDownLatch(2);
CountDownLatch interrupted = new CountDownLatch(1);
Runnable runForever = () ->
{
try
{
running.countDown();
forever.await();
}
catch (InterruptedException x)
{
interrupted.countDown();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
};
StoppingTask stopping = new StoppingTask(running, blocked, tp);
tp.execute(runForever);
tp.execute(stopping);
tp.execute(runForever);
assertTrue(running.await(5, TimeUnit.SECONDS));
blocked.countDown();
Thread.sleep(100); // wait until in doStop, then....
stopping._thread.interrupt(); // spurious interrupt
assertTrue(interrupted.await(5, TimeUnit.SECONDS));
assertTrue(stopping._completed.await(5, TimeUnit.SECONDS));
}
private int count(String s, String p)
{
int c = 0;

View File

@ -18,6 +18,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileStore;
@ -188,6 +189,7 @@ public class HugeResourceTest
context.setBaseResource(new PathResource(staticBase));
context.addServlet(PostServlet.class, "/post");
context.addServlet(ChunkedServlet.class, "/chunked/*");
String location = multipartTempDir.toString();
long maxFileSize = Long.MAX_VALUE;
@ -223,7 +225,7 @@ public class HugeResourceTest
@ParameterizedTest
@MethodSource("staticFiles")
public void testDownload(String filename, long expectedSize) throws Exception
public void testDownloadStatic(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
@ -250,7 +252,33 @@ public class HugeResourceTest
@ParameterizedTest
@MethodSource("staticFiles")
public void testHead(String filename, long expectedSize) throws Exception
public void testDownloadChunked(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/chunked/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
Request request = client.newRequest(destUri)
.method(HttpMethod.GET);
request.send(responseListener);
Response response = responseListener.get(5, TimeUnit.SECONDS);
assertThat("HTTP Response Code", response.getStatus(), is(200));
// dumpResponse(response);
String transferEncoding = response.getHeaders().get(HttpHeader.TRANSFER_ENCODING);
assertThat("Http Response Header: \"Transfer-Encoding\"", transferEncoding, is("chunked"));
try (ByteCountingOutputStream out = new ByteCountingOutputStream();
InputStream in = responseListener.getInputStream())
{
IO.copy(in, out);
assertThat("Downloaded Files Size: " + filename, out.getCount(), is(expectedSize));
}
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testHeadStatic(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
@ -273,6 +301,30 @@ public class HugeResourceTest
assertThat("Http Response Header: \"Content-Length: " + contentLength + "\"", contentLengthLong, is(expectedSize));
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testHeadChunked(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/chunked/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
Request request = client.newRequest(destUri)
.method(HttpMethod.HEAD);
request.send(responseListener);
Response response = responseListener.get(5, TimeUnit.SECONDS);
try (InputStream in = responseListener.getInputStream())
{
assertThat(in.read(), is(-1));
}
assertThat("HTTP Response Code", response.getStatus(), is(200));
// dumpResponse(response);
String transferEncoding = response.getHeaders().get(HttpHeader.TRANSFER_ENCODING);
assertThat("Http Response Header: \"Transfer-Encoding\"", transferEncoding, is("chunked"));
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testUpload(String filename, long expectedSize) throws Exception
@ -359,6 +411,22 @@ public class HugeResourceTest
}
}
public static class ChunkedServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
URL resource = req.getServletContext().getResource(req.getPathInfo());
OutputStream output = resp.getOutputStream();
try (InputStream input = resource.openStream())
{
resp.setContentType("application/octet-stream");
resp.flushBuffer();
IO.copy(input, output);
}
}
}
public static class MultipartServlet extends HttpServlet
{
@Override

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.javax.common;
import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import java.time.Duration;
@ -24,7 +23,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
@ -35,7 +33,7 @@ import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -190,13 +188,11 @@ public class JavaxWebSocketSession implements javax.websocket.Session
{
try
{
FutureCallback b = new FutureCallback();
coreSession.close(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase(), b);
b.block(getBlockingTimeout(), TimeUnit.MILLISECONDS);
coreSession.close(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase(), Callback.NOOP);
}
catch (IOException e)
catch (Throwable t)
{
LOG.trace("IGNORED", e);
LOG.trace("IGNORED", t);
}
}

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.websocket.javax.tests;
import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@ -72,7 +73,8 @@ public class JavaxOnCloseTest
public void onClose(CloseReason reason)
{
super.onClose(reason);
onClose.accept(session);
if (onClose != null)
onClose.accept(session);
}
}
@ -226,4 +228,36 @@ public class JavaxOnCloseTest
assertThat(clientEndpoint.error, instanceOf(RuntimeException.class));
assertThat(clientEndpoint.error.getMessage(), containsString("trigger onError from client onClose"));
}
@Test
public void testCloseFromCallback() throws Exception
{
EventSocket clientEndpoint = new EventSocket();
URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/");
client.connectToServer(clientEndpoint, uri);
OnCloseEndpoint serverEndpoint = Objects.requireNonNull(serverEndpoints.poll(5, TimeUnit.SECONDS));
assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS));
CountDownLatch closeSent = new CountDownLatch(1);
clientEndpoint.session.getAsyncRemote().sendText("GOODBYE", sendResult ->
{
try
{
clientEndpoint.session.close();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
finally
{
closeSent.countDown();
}
});
assertTrue(closeSent.await(5, TimeUnit.SECONDS));
assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseCodes.NORMAL_CLOSURE));
}
}

View File

@ -63,6 +63,32 @@ public interface Session extends WebSocketPolicy, Closeable
*/
void close(int statusCode, String reason);
/**
* Send a websocket Close frame, with status code.
* <p>
* This will enqueue a graceful close to the remote endpoint.
*
* @param statusCode the status code
* @param reason the (optional) reason. (can be null for no reason)
* @param callback the callback to track close frame sent (or failed)
* @see StatusCode
* @see #close()
* @see #close(CloseStatus)
* @see #disconnect()
*/
default void close(int statusCode, String reason, WriteCallback callback)
{
try
{
close(statusCode, reason);
callback.writeSuccess();
}
catch (Throwable t)
{
callback.writeFailed(t);
}
}
/**
* Issue a harsh disconnect of the underlying connection.
* <p>

View File

@ -20,7 +20,9 @@ package org.eclipse.jetty.websocket.api;
*/
public interface WriteCallback
{
WriteCallback NOOP = new Adaptor();
WriteCallback NOOP = new WriteCallback()
{
};
/**
* <p>
@ -44,6 +46,7 @@ public interface WriteCallback
{
}
@Deprecated
class Adaptor implements WriteCallback
{
@Override

View File

@ -23,7 +23,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
@ -48,37 +47,6 @@ public class JettyWebSocketRemoteEndpoint implements org.eclipse.jetty.websocket
this.batchMode = batchMode;
}
/**
* Initiate close of the Remote with no status code (no payload)
*
* @since 10.0
*/
public void close()
{
close(StatusCode.NO_CODE, null);
}
/**
* Initiate close of the Remote with specified status code and optional reason phrase
*
* @param statusCode the status code (must be valid and can be sent)
* @param reason optional reason code
* @since 10.0
*/
public void close(int statusCode, String reason)
{
try
{
FutureCallback b = new FutureCallback();
coreSession.close(statusCode, reason, b);
b.block(getBlockingTimeout(), TimeUnit.MILLISECONDS);
}
catch (IOException e)
{
LOG.trace("IGNORED", e);
}
}
@Override
public void sendString(String text) throws IOException
{

View File

@ -18,6 +18,7 @@ import java.net.SocketAddress;
import java.time.Duration;
import java.util.Objects;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.Session;
@ -27,6 +28,7 @@ import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,19 +55,25 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable
@Override
public void close()
{
remoteEndpoint.close(StatusCode.NORMAL, null);
coreSession.close(StatusCode.NORMAL, null, Callback.NOOP);
}
@Override
public void close(CloseStatus closeStatus)
{
remoteEndpoint.close(closeStatus.getCode(), closeStatus.getPhrase());
coreSession.close(closeStatus.getCode(), closeStatus.getPhrase(), Callback.NOOP);
}
@Override
public void close(int statusCode, String reason)
{
remoteEndpoint.close(statusCode, reason);
coreSession.close(statusCode, reason, Callback.NOOP);
}
@Override
public void close(int statusCode, String reason, WriteCallback callback)
{
coreSession.close(statusCode, reason, Callback.from(callback::writeSuccess, callback::writeFailed));
}
@Override

View File

@ -18,6 +18,7 @@ import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -57,6 +58,7 @@ import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.exceptions.UpgradeException;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.core.server.internal.UpgradeHttpServletRequest;
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
@ -68,6 +70,7 @@ import org.junit.jupiter.api.condition.OS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -80,6 +83,7 @@ public class WebSocketOverHTTP2Test
private ServerConnector connector;
private ServerConnector tlsConnector;
private WebSocketClient wsClient;
private ServletContextHandler context;
private void startServer() throws Exception
{
@ -112,7 +116,7 @@ public class WebSocketOverHTTP2Test
tlsConnector = new ServerConnector(server, 1, 1, ssl, alpn, h1s, h2s);
server.addConnector(tlsConnector);
ServletContextHandler context = new ServletContextHandler(server, "/");
context = new ServletContextHandler(server, "/");
context.addServlet(new ServletHolder(servlet), "/ws/*");
JettyWebSocketServletContainerInitializer.configure(context, null);
@ -337,6 +341,41 @@ public class WebSocketOverHTTP2Test
assertThat(cause, instanceOf(ClosedChannelException.class));
}
@Test
public void testServerTimeout() throws Exception
{
startServer();
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(context.getServletContext());
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EchoSocket serverEndpoint = new EchoSocket();
container.addMapping("/specialEcho", (req, resp) -> serverEndpoint);
// Set up idle timeouts.
long timeout = 1000;
container.setIdleTimeout(Duration.ofMillis(timeout));
wsClient.setIdleTimeout(Duration.ZERO);
// Setup a websocket connection.
EventSocket clientEndpoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/specialEcho");
Session session = wsClient.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
session.getRemote().sendString("hello world");
String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
assertThat(received, equalTo("hello world"));
// Wait for timeout on server.
assertTrue(serverEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
assertThat(serverEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
assertThat(serverEndpoint.closeReason, containsStringIgnoringCase("timeout"));
assertNotNull(serverEndpoint.error);
// Wait for timeout on client.
assertTrue(clientEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
assertThat(clientEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
assertThat(clientEndpoint.closeReason, containsStringIgnoringCase("timeout"));
assertNull(clientEndpoint.error);
}
private static class TestJettyWebSocketServlet extends JettyWebSocketServlet
{
@Override

34
pom.xml
View File

@ -34,7 +34,7 @@
<awaitility.version>4.2.0</awaitility.version>
<bndlib.version>6.3.1</bndlib.version>
<build-support.version>1.5</build-support.version>
<checkstyle.version>10.3</checkstyle.version>
<checkstyle.version>10.3.1</checkstyle.version>
<commons-codec.version>1.15</commons-codec.version>
<commons.compress.version>1.21</commons.compress.version>
<commons.io.version>2.11.0</commons.io.version>
@ -90,7 +90,7 @@
<jetty-test-policy.version>1.2</jetty-test-policy.version>
<jetty.test.version>5.9</jetty.test.version>
<jmh.version>1.35</jmh.version>
<jna.version>5.11.0</jna.version>
<jna.version>5.12.1</jna.version>
<jnr-constants.version>0.10.3</jnr-constants.version>
<jnr-enxio.version>0.32.13</jnr-enxio.version>
<jnr-ffi.version>2.2.12</jnr-ffi.version>
@ -104,11 +104,10 @@
<kerb-simplekdc.version>2.0.2</kerb-simplekdc.version>
<log4j2.version>2.17.2</log4j2.version>
<logback.version>1.3.0-alpha16</logback.version>
<mariadb.version>3.0.5</mariadb.version>
<mariadb.version>3.0.6</mariadb.version>
<mariadb.docker.version>10.3.6</mariadb.docker.version>
<maven-artifact-transfer.version>0.13.1</maven-artifact-transfer.version>
<maven.resolver.version>1.8.1</maven.resolver.version>
<maven.version>3.8.4</maven.version>
<maven.version>3.8.6</maven.version>
<mongodb.version>3.12.11</mongodb.version>
<openpojo.version>0.9.1</openpojo.version>
<org.osgi.annotation.version>8.1.0</org.osgi.annotation.version>
@ -121,7 +120,7 @@
<springboot.version>2.1.1.RELEASE</springboot.version>
<taglibs-standard-impl.version>1.2.5</taglibs-standard-impl.version>
<taglibs-standard-spec.version>1.2.5</taglibs-standard-spec.version>
<testcontainers.version>1.17.2</testcontainers.version>
<testcontainers.version>1.17.3</testcontainers.version>
<weld.version>3.1.9.Final</weld.version>
<wildfly.common.version>1.6.0.Final</wildfly.common.version>
<wildfly.elytron.version>1.19.0.Final</wildfly.elytron.version>
@ -148,7 +147,7 @@
<maven.dependency.plugin.version>3.3.0</maven.dependency.plugin.version>
<maven.deploy.plugin.version>3.0.0-M2</maven.deploy.plugin.version>
<maven.eclipse.plugin.version>2.10</maven.eclipse.plugin.version>
<maven.enforcer.plugin.version>3.0.0</maven.enforcer.plugin.version>
<maven.enforcer.plugin.version>3.1.0</maven.enforcer.plugin.version>
<maven.exec.plugin.version>3.0.0</maven.exec.plugin.version>
<maven.gpg.plugin.version>3.0.1</maven.gpg.plugin.version>
<maven.install.plugin.version>3.0.0-M1</maven.install.plugin.version>
@ -1681,11 +1680,32 @@
<artifactId>jetty-memcached-sessions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-alpn</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-jsp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-warurl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-client</artifactId>