This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new ede36cdc8a2 NIFI-16152 Switched from Jersey to Jetty for gzip
compression (#11489)
ede36cdc8a2 is described below
commit ede36cdc8a2c81ae30ff125f2e7ae3c6b747670c
Author: David Handermann <[email protected]>
AuthorDate: Thu Jul 30 16:03:37 2026 -0500
NIFI-16152 Switched from Jersey to Jetty for gzip compression (#11489)
- Added drain and close for replicated request response body to handle gzip
encoded responses
---
.../replication/StandardAsyncClusterResponse.java | 19 +++
.../replication/ThreadPoolRequestReplicator.java | 12 +-
.../client/StandardHttpReplicationClient.java | 18 +++
.../http/replication/io/ReplicatedResponse.java | 72 ++++++-----
.../apache/nifi/cluster/manager/NodeResponse.java | 10 +-
.../nifi-framework/nifi-web/nifi-jetty/pom.xml | 8 ++
.../org/apache/nifi/web/server/JettyServer.java | 9 +-
.../nifi/web/server/StandardServerProvider.java | 27 ++++
.../handler/UnsupportedContentEncodingHandler.java | 47 +++++++
.../web/server/StandardServerProviderTest.java | 58 +++++++++
.../apache/nifi/web/NiFiWebApiResourceConfig.java | 7 +-
...equestContentLengthExceededExceptionMapper.java | 44 +++++++
...stContentLengthExceededExceptionMapperTest.java | 42 +++++++
.../web/security/requests/ContentLengthFilter.java | 17 +--
.../RequestContentLengthExceededException.java | 27 ++++
.../security/requests/ContentLengthFilterTest.java | 138 +++++++++++++++++++++
nifi-framework-bundle/nifi-jetty-nar/pom.xml | 10 ++
nifi-framework-bundle/nifi-server-nar-bom/pom.xml | 12 ++
.../provenance/GetLatestProvenanceEventsIT.java | 2 +-
19 files changed, 521 insertions(+), 58 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/StandardAsyncClusterResponse.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/StandardAsyncClusterResponse.java
index 78532d8e525..a007ad1344d 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/StandardAsyncClusterResponse.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/StandardAsyncClusterResponse.java
@@ -17,6 +17,7 @@
package org.apache.nifi.cluster.coordination.http.replication;
+import jakarta.ws.rs.core.Response;
import org.apache.nifi.cluster.coordination.http.HttpResponseMapper;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
@@ -216,6 +217,7 @@ public class StandardAsyncClusterResponse implements
AsyncClusterResponse {
final long start = System.nanoTime();
mergedResponse = responseMapper.mapResponses(uri, method,
nodeResponses, merge);
+ closeUnusedResponses(nodeResponses);
final long nanos = System.nanoTime() - start;
addTiming("Map/Merge Responses", "All Nodes", nanos);
@@ -329,6 +331,23 @@ public class StandardAsyncClusterResponse implements
AsyncClusterResponse {
+ ", responses=" + getCompletedNodeIdentifiers().size() + "/" +
responseMap.size() + "]";
}
+ private void closeUnusedResponses(final Set<NodeResponse> nodeResponses) {
+ final Response clientMergedResponse =
mergedResponse.getClientResponse();
+ if (mergedResponse.getUpdatedEntity() != null) {
+ // Close merged response since Updated Entity used in place of
stream
+ clientMergedResponse.close();
+ }
+
+ for (final NodeResponse nodeResponse : nodeResponses) {
+ final Response clientNodeResponse =
nodeResponse.getClientResponse();
+ if (clientMergedResponse == clientNodeResponse) {
+ continue;
+ }
+
+ nodeResponse.close();
+ }
+ }
+
private static class ResponseHolder {
private final long nanoStart;
private long requestNanos;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
index df948c7045f..9fe079cc26f 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
@@ -523,8 +523,13 @@ public class ThreadPoolRequestReplicator implements
RequestReplicator, Closeable
// If all nodes responded with 202-Accepted, then we
can replicate the original request
// to all nodes and we are finished.
if (dissentingCount == 0) {
- logger.debug("Received verification from all {}
nodes that mutable request {} {} can be made", numNodes, method, uri.getPath());
- replicate(nodeIds, method, uri, entity, headers,
false, clusterResponse, true, merge, monitor);
+ try {
+ logger.debug("Received verification from all
{} nodes that mutable request {} {} can be made", numNodes, method,
uri.getPath());
+ replicate(nodeIds, method, uri, entity,
headers, false, clusterResponse, true, merge, monitor);
+ } finally {
+ // Close HTTP Responses after replication
completed
+ nodeResponses.forEach(NodeResponse::close);
+ }
return;
}
@@ -582,6 +587,9 @@ public class ThreadPoolRequestReplicator implements
RequestReplicator, Closeable
clusterResponse.setFailure(failure,
response.getNodeId());
}
}
+
+ // Close verification responses for cancelled
transactions
+ nodeResponses.forEach(NodeResponse::close);
} finally {
if (monitor != null) {
synchronized (monitor) {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/client/StandardHttpReplicationClient.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/client/StandardHttpReplicationClient.java
index b34322d54fc..b010176fad6 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/client/StandardHttpReplicationClient.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/client/StandardHttpReplicationClient.java
@@ -17,6 +17,7 @@
package org.apache.nifi.cluster.coordination.http.replication.client;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import
com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector;
import jakarta.ws.rs.core.MultivaluedHashMap;
@@ -41,6 +42,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.LinkedHashMap;
@@ -113,6 +115,8 @@ public class StandardHttpReplicationClient implements
HttpReplicationClient {
objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL,
JsonInclude.Include.ALWAYS));
objectMapper.setAnnotationIntrospector(new
JakartaXmlBindAnnotationIntrospector(objectMapper.getTypeFactory()));
+ // Disable closing source streams to allow draining and subsequent
closing
+ objectMapper.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
jsonSerializer = new JsonEntitySerializer(objectMapper);
xmlSerializer = new XmlEntitySerializer();
@@ -208,11 +212,25 @@ public class StandardHttpReplicationClient implements
HttpReplicationClient {
final InputStream responseBody =
getResponseBody(responseEntity.body(), headers);
final Runnable closeCallback = () -> {
+ try {
+ // Drain raw response stream before closing the Response Entity
+
responseEntity.body().transferTo(OutputStream.nullOutputStream());
+ } catch (final IOException e) {
+ logger.debug("Drain failed for Replicated {} {} HTTP {}",
method, location, statusCode, e);
+ }
+
try {
responseEntity.close();
} catch (final IOException e) {
logger.warn("Close failed for Replicated {} {} HTTP {}",
method, location, statusCode, e);
}
+
+ try {
+ // Release resources for gzip wrapped streams
+ responseBody.close();
+ } catch (final IOException e) {
+ logger.warn("Close failed for Replicated Response Body {} {}
HTTP {}", method, location, statusCode, e);
+ }
};
final long elapsed = System.currentTimeMillis() - started;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/io/ReplicatedResponse.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/io/ReplicatedResponse.java
index 46d16516cf3..8528425a6af 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/io/ReplicatedResponse.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/io/ReplicatedResponse.java
@@ -17,8 +17,6 @@
package org.apache.nifi.cluster.coordination.http.replication.io;
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.GenericType;
@@ -51,7 +49,7 @@ public class ReplicatedResponse extends Response {
private static final int MAXIMUM_BUFFER_SIZE = 1048576;
private static final int CONTENT_LENGTH_UNKNOWN = -1;
- private final ObjectMapper codec;
+ private final ObjectMapper objectMapper;
private final InputStream responseBody;
private final MultivaluedMap<String, String> responseHeaders;
private final URI location;
@@ -59,12 +57,12 @@ public class ReplicatedResponse extends Response {
private final Runnable closeCallback;
private final int contentLength;
- private final JsonFactory jsonFactory = new JsonFactory();
-
private final byte[] bufferedResponseBody;
+ private Object bufferedEntity;
+
public ReplicatedResponse(
- final ObjectMapper codec,
+ final ObjectMapper objectMapper,
final InputStream responseBody,
final MultivaluedMap<String, String> responseHeaders,
final URI location,
@@ -72,7 +70,7 @@ public class ReplicatedResponse extends Response {
final int contentLength,
final Runnable closeCallback
) {
- this.codec = codec;
+ this.objectMapper = objectMapper;
this.responseBody = responseBody;
this.responseHeaders = responseHeaders;
this.location = location;
@@ -101,42 +99,30 @@ public class ReplicatedResponse extends Response {
@Override
public Object getEntity() {
- final InputStream responseBodyStream = getResponseBodyStream();
-
- try {
- final JsonParser parser =
jsonFactory.createParser(responseBodyStream);
- parser.setCodec(codec);
- return parser.readValueAs(Object.class);
- } catch (final Exception e) {
- throw new RuntimeException("Failed to parse response", e);
+ if (bufferedEntity == null) {
+ // Read response entity to buffered entity to support multiple
invocations
+ bufferedEntity = readResponseEntity(Object.class);
}
+
+ return bufferedEntity;
}
@Override
@SuppressWarnings("unchecked")
- public <T> T readEntity(Class<T> entityType) {
- final InputStream responseBodyStream = getResponseBodyStream();
+ public <T> T readEntity(final Class<T> entityType) {
+ final T entity;
+ // Return raw response body stream when requested without buffering
if (InputStream.class.equals(entityType)) {
- return (T) responseBodyStream;
- }
-
- if (String.class.equals(entityType)) {
- try {
- final byte[] responseBytes = responseBodyStream.readAllBytes();
- return (T) new String(responseBytes, StandardCharsets.UTF_8);
- } catch (final IOException e) {
- throw new UncheckedIOException("Read Replicated Response Body
to String failed for %s".formatted(location), e);
- }
+ return (T) getResponseBodyStream();
}
- try {
- final JsonParser parser =
jsonFactory.createParser(responseBodyStream);
- parser.setCodec(codec);
- return parser.readValueAs(entityType);
- } catch (final Exception e) {
- throw new RuntimeException("Failed to parse response as entity of
type " + entityType, e);
+ if (bufferedEntity == null) {
+ // Read response entity to buffered entity to support multiple
invocations
+ bufferedEntity = readResponseEntity(entityType);
}
+ entity = (T) bufferedEntity;
+ return entity;
}
@Override
@@ -287,6 +273,26 @@ public class ReplicatedResponse extends Response {
return responseBodyStream;
}
+ @SuppressWarnings("unchecked")
+ private <T> T readResponseEntity(final Class<T> entityType) {
+ final InputStream responseBodyStream = getResponseBodyStream();
+
+ if (String.class.equals(entityType)) {
+ try {
+ final byte[] responseBytes = responseBodyStream.readAllBytes();
+ return (T) new String(responseBytes, StandardCharsets.UTF_8);
+ } catch (final IOException e) {
+ throw new UncheckedIOException("Read Replicated Response Body
to String failed for %s".formatted(location), e);
+ }
+ }
+
+ try {
+ return objectMapper.readValue(responseBodyStream, entityType);
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to parse response as Entity
[%s] for %s".formatted(entityType, location), e);
+ }
+ }
+
private static byte[] readResponseBody(final InputStream inputStream,
final URI location, final int statusCode) {
try {
return inputStream.readAllBytes();
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java
index ec6cf7870a5..52fc617ec4c 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java
@@ -29,6 +29,7 @@ import org.apache.nifi.web.api.entity.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.Closeable;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
@@ -47,7 +48,7 @@ import java.util.concurrent.TimeUnit;
* This class overrides hashCode and equals and considers two instances to be
equal if they have the equal NodeIdentifiers.
*
*/
-public class NodeResponse {
+public class NodeResponse implements Closeable {
private static final Logger logger =
LoggerFactory.getLogger(NodeResponse.class);
private final String httpMethod;
@@ -269,4 +270,11 @@ public class NodeResponse {
.append(",Duration=").append(TimeUnit.MILLISECONDS.convert(requestDurationNanos,
TimeUnit.NANOSECONDS)).append(" ms]");
return sb.toString();
}
+
+ @Override
+ public void close() {
+ if (response != null) {
+ response.close();
+ }
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
index 3516f3d47e5..e27f886ddcb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
@@ -85,6 +85,14 @@
<artifactId>nifi-web-servlet-shared</artifactId>
<version>2.11.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-gzip</artifactId>
+ </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index 706a349d086..1cce71048a6 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -69,6 +69,7 @@ import
org.apache.nifi.web.server.filter.LogoutCompleteRedirectFilter;
import org.apache.nifi.web.server.filter.RequestFilterProvider;
import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider;
import org.apache.nifi.web.server.filter.StandardRequestFilterProvider;
+import org.eclipse.jetty.compression.server.CompressionHandler;
import org.eclipse.jetty.deploy.StandardDeployer;
import org.eclipse.jetty.ee.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee11.servlet.ErrorPageErrorHandler;
@@ -222,7 +223,13 @@ public class JettyServer implements NiFiServer,
ExtensionUiLoader {
deployer = new StandardDeployer(contextHandlerCollection);
server.addBean(deployer);
- serverHandlerCollection.addHandler(contextHandlerCollection);
+ // Deploy the web applications beneath the CompressionHandler
+ final CompressionHandler compressionHandler =
serverHandlerCollection.getDescendant(CompressionHandler.class);
+ if (compressionHandler == null) {
+ throw new IllegalStateException("Compression Handler not
configured: Server Provider configuration failed");
+ } else {
+ compressionHandler.setHandler(contextHandlerCollection);
+ }
} else {
throw new IllegalStateException("Server Handler not
Handler.Collection: Server Provider configuration failed");
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
index e09e4d1067d..ad5a02be511 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
@@ -21,8 +21,12 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory;
import org.apache.nifi.web.server.handler.ContextPathRedirectPatternRule;
import org.apache.nifi.web.server.handler.HeaderWriterHandler;
+import org.apache.nifi.web.server.handler.UnsupportedContentEncodingHandler;
import org.apache.nifi.web.server.log.RequestLogProvider;
import org.apache.nifi.web.server.log.StandardRequestLogProvider;
+import org.eclipse.jetty.compression.gzip.GzipCompression;
+import org.eclipse.jetty.compression.server.CompressionConfig;
+import org.eclipse.jetty.compression.server.CompressionHandler;
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Handler;
@@ -47,6 +51,8 @@ import javax.net.ssl.SSLContext;
* Standard implementation of Server Provider with default Handlers
*/
class StandardServerProvider implements ServerProvider {
+ private static final String ROOT_PATH = "/";
+
private static final String ALL_PATHS_PATTERN = "/*";
private static final String FRONTEND_CONTEXT_PATH = "/nifi";
@@ -139,6 +145,27 @@ class StandardServerProvider implements ServerProvider {
// Set Handler for standard response headers
standardHandler.addHandler(new HeaderWriterHandler());
+ // Reject requests that declare a Content-Encoding because request
bodies are not decompressed
+ standardHandler.addHandler(new UnsupportedContentEncodingHandler());
+
+ // Set Handler for response compression
+ standardHandler.addHandler(getCompressionHandler());
+
return standardHandler;
}
+
+ private CompressionHandler getCompressionHandler() {
+ final GzipCompression gzipCompression = new GzipCompression();
+ final CompressionHandler compressionHandler = new CompressionHandler();
+ compressionHandler.putCompression(gzipCompression);
+
+ final CompressionConfig compressionConfig = CompressionConfig.builder()
+ .defaults()
+ // Disable decompression of requests
+ .decompressExcludeEncoding(gzipCompression.getEncodingName())
+ .build();
+ compressionHandler.putConfiguration(ROOT_PATH, compressionConfig);
+
+ return compressionHandler;
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
new file mode 100644
index 00000000000..d3668665384
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.server.handler;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * Handler that rejects requests declaring an unsupported Content-Encoding
+ */
+public class UnsupportedContentEncodingHandler extends Handler.Abstract {
+
+ private static final String IDENTITY_ENCODING = "identity";
+
+ private static final String UNSUPPORTED_MESSAGE = "Content-Encoding not
supported";
+
+ @Override
+ public boolean handle(final Request request, final Response response,
final Callback callback) {
+ final String contentEncoding =
request.getHeaders().get(HttpHeader.CONTENT_ENCODING);
+
+ // A request without a Content-Encoding, or one declaring only the
identity encoding, is passed to later Handlers
+ if (contentEncoding == null || contentEncoding.isBlank() ||
IDENTITY_ENCODING.equalsIgnoreCase(contentEncoding.trim())) {
+ return false;
+ }
+
+ Response.writeError(request, response, callback,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, UNSUPPORTED_MESSAGE);
+ return true;
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
index 65c815a08a9..f7a79dcfcae 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
@@ -23,8 +23,12 @@ import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.server.handler.HeaderWriterHandler;
+import org.apache.nifi.web.server.handler.UnsupportedContentEncodingHandler;
import org.apache.nifi.web.servlet.shared.ProxyHeader;
+import org.eclipse.jetty.compression.server.CompressionConfig;
+import org.eclipse.jetty.compression.server.CompressionHandler;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Connector;
@@ -51,6 +55,7 @@ import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.security.auth.x500.X500Principal;
@@ -151,6 +156,59 @@ class StandardServerProviderTest {
assertHttpConnectorFound(server);
}
+ @Test
+ void testGetServerCompressionHandlerCompressesResponses() {
+ final Properties applicationProperties = new Properties();
+ applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT,
RANDOM_PORT);
+ final NiFiProperties properties =
NiFiProperties.createBasicNiFiProperties((String) null, applicationProperties);
+
+ final StandardServerProvider provider = new
StandardServerProvider(null);
+
+ final Server server = provider.getServer(properties);
+
+ final Handler.Collection handlerCollection = (Handler.Collection)
server.getHandler();
+ final CompressionHandler compressionHandler =
handlerCollection.getDescendant(CompressionHandler.class);
+ assertNotNull(compressionHandler);
+
+ final CompressionConfig compressionConfig =
compressionHandler.getConfiguration("/");
+ assertNotNull(compressionConfig);
+
+ final Set<String> compressMethods =
compressionConfig.getCompressIncludeMethods();
+ assertTrue(compressMethods.contains(HttpMethod.GET.asString()));
+ assertTrue(compressMethods.contains(HttpMethod.POST.asString()));
+
+ final UnsupportedContentEncodingHandler
unsupportedContentEncodingHandler =
handlerCollection.getDescendant(UnsupportedContentEncodingHandler.class);
+ assertNotNull(unsupportedContentEncodingHandler);
+ }
+
+ @Timeout(15)
+ @Test
+ void testGetServerRejectsCompressedRequestBody() throws Exception {
+ final Properties applicationProperties = new Properties();
+ applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT,
RANDOM_PORT);
+ final NiFiProperties properties =
NiFiProperties.createBasicNiFiProperties((String) null, applicationProperties);
+
+ final StandardServerProvider provider = new
StandardServerProvider(null);
+
+ final Server server = provider.getServer(properties);
+
+ try {
+ startServer(server);
+ final URI localhostUri =
UriComponentsBuilder.fromUri(server.getURI()).host(LOCALHOST_NAME).build().toUri();
+
+ try (HttpClient httpClient =
HttpClient.newBuilder().connectTimeout(TIMEOUT).build()) {
+ final HttpRequest compressedRequest =
HttpRequest.newBuilder(localhostUri)
+ .version(HttpClient.Version.HTTP_1_1)
+ .header(HttpHeader.CONTENT_ENCODING.asString(), "gzip")
+ .POST(HttpRequest.BodyPublishers.ofByteArray(new
byte[]{1, 2, 3}))
+ .build();
+ assertResponseStatusCode(httpClient, compressedRequest,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415);
+ }
+ } finally {
+ server.stop();
+ }
+ }
+
@Test
void testGetServerHttps() {
final Properties applicationProperties = new Properties();
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
index 8eece65de4e..ff4591c4132 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
@@ -46,6 +46,7 @@ import
org.apache.nifi.web.api.config.NodeDisconnectionExceptionMapper;
import org.apache.nifi.web.api.config.NodeReconnectionExceptionMapper;
import org.apache.nifi.web.api.config.NotFoundExceptionMapper;
import org.apache.nifi.web.api.config.RangeNotSatisfiableExceptionMapper;
+import
org.apache.nifi.web.api.config.RequestContentLengthExceededExceptionMapper;
import org.apache.nifi.web.api.config.ResourceNotFoundExceptionMapper;
import org.apache.nifi.web.api.config.ThrowableMapper;
import org.apache.nifi.web.api.config.UnknownNodeExceptionMapper;
@@ -56,9 +57,7 @@ import org.apache.nifi.web.api.filter.RedirectResourceFilter;
import org.apache.nifi.web.util.ObjectMapperResolver;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
-import org.glassfish.jersey.message.GZipEncoder;
import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.server.filter.EncodingFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
@@ -135,6 +134,7 @@ public class NiFiWebApiResourceConfig extends
ResourceConfig {
register(NodeDisconnectionExceptionMapper.class);
register(NodeReconnectionExceptionMapper.class);
register(RangeNotSatisfiableExceptionMapper.class);
+ register(RequestContentLengthExceededExceptionMapper.class);
register(ResourceNotFoundExceptionMapper.class);
register(NotFoundExceptionMapper.class);
register(UnknownNodeExceptionMapper.class);
@@ -142,9 +142,6 @@ public class NiFiWebApiResourceConfig extends
ResourceConfig {
register(ValidationExceptionMapper.class);
register(WebApplicationExceptionMapper.class);
register(ThrowableMapper.class);
-
- // gzip
- EncodingFilter.enableFor(this, GZipEncoder.class);
}
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapper.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapper.java
new file mode 100644
index 00000000000..edecbd4684f
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.config;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import
org.apache.nifi.web.security.requests.RequestContentLengthExceededException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Map Request Content Length Exceeded Exception to HTTP 413 response status
+ */
+@Provider
+public class RequestContentLengthExceededExceptionMapper implements
ExceptionMapper<RequestContentLengthExceededException> {
+
+ private static final Logger logger =
LoggerFactory.getLogger(RequestContentLengthExceededExceptionMapper.class);
+
+ @Override
+ public Response toResponse(final RequestContentLengthExceededException
exception) {
+ logger.info("HTTP 413 Content Too Large: {}", exception.getMessage());
+
+ return Response.status(Response.Status.REQUEST_ENTITY_TOO_LARGE)
+ .entity(exception.getMessage())
+ .type(MediaType.TEXT_PLAIN_TYPE)
+ .build();
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapperTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapperTest.java
new file mode 100644
index 00000000000..1add6145fcd
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/config/RequestContentLengthExceededExceptionMapperTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.config;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import
org.apache.nifi.web.security.requests.RequestContentLengthExceededException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RequestContentLengthExceededExceptionMapperTest {
+
+ private static final String MESSAGE = "Content Too Large";
+
+ private final RequestContentLengthExceededExceptionMapper mapper = new
RequestContentLengthExceededExceptionMapper();
+
+ @Test
+ void testToResponseRequestEntityTooLarge() {
+ final RequestContentLengthExceededException exception = new
RequestContentLengthExceededException(MESSAGE);
+
+ try (Response response = mapper.toResponse(exception)) {
+
assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(),
response.getStatus());
+ assertEquals(MESSAGE, response.getEntity());
+ assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+ }
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/ContentLengthFilter.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/ContentLengthFilter.java
index 1200e7f2c9d..2eb0d6e0f90 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/ContentLengthFilter.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/ContentLengthFilter.java
@@ -96,19 +96,6 @@ public class ContentLengthFilter implements Filter {
}
}
- @Override
- public void destroy() {
- }
-
- /**
- * Returns the currently configured max content length in bytes.
- *
- * @return the max content length
- */
- public int getMaxContentLength() {
- return maxContentLength;
- }
-
/**
* Returns {@code true} if this request is subject to the filter
operation, {@code false} if not.
*
@@ -137,7 +124,7 @@ public class ContentLengthFilter implements Filter {
// This wrapper ensures that the input stream of the wrapped request is
not read past the given maximum.
private static class LimitedContentLengthRequest extends
HttpServletRequestWrapper {
- private int maxRequestLength;
+ private final int maxRequestLength;
public LimitedContentLengthRequest(HttpServletRequest request, int
maxLength) {
super(request);
@@ -174,7 +161,7 @@ public class ContentLengthFilter implements Filter {
inputStreamByteCounter += 1;
if (inputStreamByteCounter > maxRequestLength) {
- throw new IOException(String.format("Request input
stream longer than %d B.", maxRequestLength));
+ throw new
RequestContentLengthExceededException("Content Too Large: exceeded " +
formatSize(maxRequestLength));
}
return read;
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/RequestContentLengthExceededException.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/RequestContentLengthExceededException.java
new file mode 100644
index 00000000000..3be44ebf2da
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/requests/RequestContentLengthExceededException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.requests;
+
+/**
+ * Request Content Length Exceeded Exception indicating HTTP 413 response
status
+ */
+public class RequestContentLengthExceededException extends RuntimeException {
+
+ public RequestContentLengthExceededException(final String message) {
+ super(message);
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/requests/ContentLengthFilterTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/requests/ContentLengthFilterTest.java
new file mode 100644
index 00000000000..64b611f87c6
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/requests/ContentLengthFilterTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.requests;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ContentLengthFilterTest {
+
+ private static final int MAX_LENGTH = 1000;
+
+ private static final int WITHIN_LIMIT_LENGTH = 500;
+
+ private static final int OVERSIZED_LENGTH = 2000;
+
+ private static final String REQUEST_URI = "/nifi-api/process-groups/root";
+
+ private static final int UNKNOWN_CONTENT_LENGTH_VALUE = -1;
+
+ @Test
+ void
testDecompressedBodyExceedingLimitThrowsRequestContentLengthExceededException()
throws Exception {
+ final ContentLengthFilter filter = createFilter();
+
+ // Create post request with unknown Content-Length header and
compressed content exceeding configured limits
+ final HttpServletRequest request =
createPostRequest(UNKNOWN_CONTENT_LENGTH_VALUE, OVERSIZED_LENGTH);
+ final HttpServletResponse response = mock(HttpServletResponse.class);
+
+ final FilterChain readingChain = (req, res) -> {
+ final InputStream inputStream = req.getInputStream();
+ inputStream.readAllBytes();
+ };
+
+ assertThrows(RequestContentLengthExceededException.class, () ->
filter.doFilter(request, response, readingChain));
+ }
+
+ @Test
+ void testDecompressedBodyWithinLimitConsumedSuccessfully() throws
Exception {
+ final ContentLengthFilter filter = createFilter();
+
+ final HttpServletRequest request =
createPostRequest(UNKNOWN_CONTENT_LENGTH_VALUE, WITHIN_LIMIT_LENGTH);
+ final HttpServletResponse response = mock(HttpServletResponse.class);
+
+ final FilterChain readingChain = (req, res) -> {
+ final InputStream inputStream = req.getInputStream();
+ inputStream.readAllBytes();
+ };
+
+ filter.doFilter(request, response, readingChain);
+ }
+
+ @Test
+ void testDeclaredContentLengthExceedingLimitRejectedWithPayloadTooLarge()
throws Exception {
+ final ContentLengthFilter filter = createFilter();
+
+ final HttpServletRequest request = createPostRequest(OVERSIZED_LENGTH,
OVERSIZED_LENGTH);
+ final HttpServletResponse response = mock(HttpServletResponse.class);
+
when(response.getOutputStream()).thenReturn(mock(ServletOutputStream.class));
+
+ final FilterChain chain = mock(FilterChain.class);
+
+ filter.doFilter(request, response, chain);
+
+
verify(response).setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ private ContentLengthFilter createFilter() throws Exception {
+ final ContentLengthFilter filter = new ContentLengthFilter();
+ final FilterConfig filterConfig = mock(FilterConfig.class);
+
when(filterConfig.getInitParameter(ContentLengthFilter.MAX_LENGTH_INIT_PARAM)).thenReturn(Integer.toString(MAX_LENGTH));
+ filter.init(filterConfig);
+ return filter;
+ }
+
+ private HttpServletRequest createPostRequest(final int
declaredContentLength, final int bodyLength) throws IOException {
+ final HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getRequestURI()).thenReturn(REQUEST_URI);
+ when(request.getContentLength()).thenReturn(declaredContentLength);
+ when(request.getInputStream()).thenReturn(new
ByteArrayServletInputStream(new byte[bodyLength]));
+ return request;
+ }
+
+ private static class ByteArrayServletInputStream extends
ServletInputStream {
+ private final ByteArrayInputStream inputStream;
+
+ private ByteArrayServletInputStream(final byte[] bytes) {
+ this.inputStream = new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public boolean isFinished() {
+ return inputStream.available() == 0;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(final ReadListener readListener) {
+ }
+
+ @Override
+ public int read() {
+ return inputStream.read();
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-jetty-nar/pom.xml
b/nifi-framework-bundle/nifi-jetty-nar/pom.xml
index e1b501a9856..45bd22d2bc3 100644
--- a/nifi-framework-bundle/nifi-jetty-nar/pom.xml
+++ b/nifi-framework-bundle/nifi-jetty-nar/pom.xml
@@ -81,6 +81,16 @@
<artifactId>jetty-xml</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-server</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-gzip</artifactId>
+ <scope>compile</scope>
+ </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
diff --git a/nifi-framework-bundle/nifi-server-nar-bom/pom.xml
b/nifi-framework-bundle/nifi-server-nar-bom/pom.xml
index 760e20b2ee4..79f55f1c738 100644
--- a/nifi-framework-bundle/nifi-server-nar-bom/pom.xml
+++ b/nifi-framework-bundle/nifi-server-nar-bom/pom.xml
@@ -98,6 +98,18 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-rewrite</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-gzip</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-server</artifactId>
+ </exclusion>
</exclusions>
</dependency>
</dependencies>
diff --git
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/provenance/GetLatestProvenanceEventsIT.java
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/provenance/GetLatestProvenanceEventsIT.java
index 382fe843472..32e6b2eabaf 100644
---
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/provenance/GetLatestProvenanceEventsIT.java
+++
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/provenance/GetLatestProvenanceEventsIT.java
@@ -68,7 +68,7 @@ public class GetLatestProvenanceEventsIT extends NiFiSystemIT
{
final LatestProvenanceEventsEntity entity =
getNifiClient().getProvenanceClient().getLatestEvents(reverse.getId());
final List<ProvenanceEventDTO> events =
entity.getLatestProvenanceEvents().getProvenanceEvents();
return events.size() == expectedEventCount;
- });
+ }, 250);
final LatestProvenanceEventsEntity entity =
getNifiClient().getProvenanceClient().getLatestEvents(reverse.getId());
final List<ProvenanceEventDTO> events =
entity.getLatestProvenanceEvents().getProvenanceEvents();