mirror of https://github.com/apache/nifi.git
NIFI-3869 Added HTTP/2 support to ListenHTTP and HandleHttpRequest
Signed-off-by: Nathan Gough <thenatog@gmail.com> This closes #6048.
This commit is contained in:
parent
38b51b0dde
commit
e0976f42d3
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>1.17.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-jetty-configuration</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-server</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector;
|
||||
|
||||
/**
|
||||
* Application Layer Protocols supported for Server Connectors
|
||||
*/
|
||||
public enum ApplicationLayerProtocol {
|
||||
HTTP_1_1("http/1.1"),
|
||||
|
||||
H2("h2");
|
||||
|
||||
private String protocol;
|
||||
|
||||
ApplicationLayerProtocol(final String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector;
|
||||
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
|
||||
/**
|
||||
* Jetty Server Connector Factory
|
||||
*/
|
||||
public interface ServerConnectorFactory {
|
||||
/**
|
||||
* Get Server Connector
|
||||
*
|
||||
* @return Configured Server Connector
|
||||
*/
|
||||
ServerConnector getServerConnector();
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector;
|
||||
|
||||
import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http2.HTTP2Cipher;
|
||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Standard implementation of Server Connector Factory supporting HTTP/2 and HTTP/1.1 with TLS or simple HTTP/1.1
|
||||
*/
|
||||
public class StandardServerConnectorFactory implements ServerConnectorFactory {
|
||||
private static final boolean SEND_SERVER_VERSION = false;
|
||||
|
||||
private static final String[] INCLUDE_ALL_SECURITY_PROTOCOLS = new String[0];
|
||||
|
||||
private static final Set<ApplicationLayerProtocol> DEFAULT_APPLICATION_LAYER_PROTOCOLS = Collections.singleton(ApplicationLayerProtocol.HTTP_1_1);
|
||||
|
||||
private final Server server;
|
||||
|
||||
private final int port;
|
||||
|
||||
private Set<ApplicationLayerProtocol> applicationLayerProtocols = DEFAULT_APPLICATION_LAYER_PROTOCOLS;
|
||||
|
||||
private SSLContext sslContext;
|
||||
|
||||
private boolean needClientAuth;
|
||||
|
||||
private boolean wantClientAuth;
|
||||
|
||||
private String[] includeSecurityProtocols = INCLUDE_ALL_SECURITY_PROTOCOLS;
|
||||
|
||||
/**
|
||||
* Standard Server Connector Factory Constructor with required properties
|
||||
*
|
||||
* @param server Jetty Server
|
||||
* @param port Secure Port Number
|
||||
*/
|
||||
public StandardServerConnectorFactory(
|
||||
final Server server,
|
||||
final int port
|
||||
) {
|
||||
this.server = Objects.requireNonNull(server, "Server required");
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Server Connector configured with HTTP/2 and ALPN as well as fallback to HTTP/1.1 with TLS
|
||||
*
|
||||
* @return Secure Server Connector
|
||||
*/
|
||||
@Override
|
||||
public ServerConnector getServerConnector() {
|
||||
final HttpConfiguration httpConfiguration = getHttpConfiguration();
|
||||
final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfiguration);
|
||||
|
||||
final ServerConnector serverConnector;
|
||||
if (sslContext == null) {
|
||||
serverConnector = new ServerConnector(server, httpConnectionFactory);
|
||||
} else {
|
||||
final List<ConnectionFactory> connectionFactories = new ArrayList<>();
|
||||
if (applicationLayerProtocols.contains(ApplicationLayerProtocol.H2)) {
|
||||
final ALPNServerConnectionFactory alpnServerConnectionFactory = new ALPNServerConnectionFactory();
|
||||
final HTTP2ServerConnectionFactory http2ServerConnectionFactory = new HTTP2ServerConnectionFactory(httpConfiguration);
|
||||
|
||||
connectionFactories.add(alpnServerConnectionFactory);
|
||||
connectionFactories.add(http2ServerConnectionFactory);
|
||||
}
|
||||
// Add HTTP/1.1 Connection Factory after HTTP/2
|
||||
if (applicationLayerProtocols.contains(ApplicationLayerProtocol.HTTP_1_1)) {
|
||||
connectionFactories.add(httpConnectionFactory);
|
||||
}
|
||||
|
||||
// SslConnectionFactory must be first and must indicate the next protocol
|
||||
final String nextProtocol = connectionFactories.get(0).getProtocol();
|
||||
final SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(getSslContextFactory(), nextProtocol);
|
||||
connectionFactories.add(0, sslConnectionFactory);
|
||||
|
||||
final ConnectionFactory[] factories = connectionFactories.toArray(new ConnectionFactory[0]);
|
||||
serverConnector = new ServerConnector(server, factories);
|
||||
}
|
||||
|
||||
serverConnector.setPort(port);
|
||||
return serverConnector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SSL Context enables TLS communication
|
||||
*
|
||||
* @param sslContext SSL Context
|
||||
*/
|
||||
public void setSslContext(final SSLContext sslContext) {
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Need Client Authentication requires clients to provide certificates for mutual TLS
|
||||
*
|
||||
* @param needClientAuth Need Client Authentication status
|
||||
*/
|
||||
public void setNeedClientAuth(final boolean needClientAuth) {
|
||||
this.needClientAuth = needClientAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Want Client Authentication requests clients to provide certificates for mutual TLS but does not require certificates
|
||||
*
|
||||
* @param wantClientAuth Want Client Authentication status
|
||||
*/
|
||||
public void setWantClientAuth(final boolean wantClientAuth) {
|
||||
this.wantClientAuth = wantClientAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Include Security Protocols limits enabled TLS Protocols to the values provided
|
||||
*
|
||||
* @param includeSecurityProtocols Security Protocols with null or empty enabling all standard TLS protocol versions
|
||||
*/
|
||||
public void setIncludeSecurityProtocols(final String[] includeSecurityProtocols) {
|
||||
this.includeSecurityProtocols = includeSecurityProtocols == null ? INCLUDE_ALL_SECURITY_PROTOCOLS : includeSecurityProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Application Layer Protocols applicable when TLS is enabled
|
||||
*
|
||||
* @param applicationLayerProtocols Protocols requires at one Application Layer Protocol
|
||||
*/
|
||||
public void setApplicationLayerProtocols(final Set<ApplicationLayerProtocol> applicationLayerProtocols) {
|
||||
if (Objects.requireNonNull(applicationLayerProtocols, "Application Layer Protocols required").isEmpty()) {
|
||||
throw new IllegalArgumentException("Application Layer Protocols not specified");
|
||||
}
|
||||
this.applicationLayerProtocols = applicationLayerProtocols;
|
||||
}
|
||||
|
||||
private HttpConfiguration getHttpConfiguration() {
|
||||
final HttpConfiguration httpConfiguration = new HttpConfiguration();
|
||||
|
||||
if (sslContext != null) {
|
||||
httpConfiguration.setSecurePort(port);
|
||||
httpConfiguration.setSecureScheme(HttpScheme.HTTPS.asString());
|
||||
httpConfiguration.setSendServerVersion(SEND_SERVER_VERSION);
|
||||
|
||||
final SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
|
||||
httpConfiguration.addCustomizer(secureRequestCustomizer);
|
||||
}
|
||||
|
||||
return httpConfiguration;
|
||||
}
|
||||
|
||||
private SslContextFactory.Server getSslContextFactory() {
|
||||
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
sslContextFactory.setSslContext(sslContext);
|
||||
sslContextFactory.setNeedClientAuth(needClientAuth);
|
||||
sslContextFactory.setWantClientAuth(wantClientAuth);
|
||||
sslContextFactory.setIncludeProtocols(includeSecurityProtocols);
|
||||
|
||||
if (applicationLayerProtocols.contains(ApplicationLayerProtocol.H2)) {
|
||||
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
||||
}
|
||||
|
||||
return sslContextFactory;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector.alpn;
|
||||
|
||||
import org.eclipse.jetty.alpn.server.ALPNServerConnection;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.ALPNProcessor;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ALPN Server Connection Factory with standard ALPN Processor implementation
|
||||
*/
|
||||
public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory {
|
||||
private static final String ALPN_PROTOCOL = "alpn";
|
||||
|
||||
private final ALPNProcessor.Server processor;
|
||||
|
||||
public ALPNServerConnectionFactory() {
|
||||
super(ALPN_PROTOCOL);
|
||||
processor = new StandardALPNProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Server Connection and configure the connection using ALPN Processor
|
||||
*
|
||||
* @param connector Connector for the Connection
|
||||
* @param endPoint End Point for the Connection
|
||||
* @param sslEngine SSL Engine for the Connection
|
||||
* @param protocols Application Protocols
|
||||
* @param defaultProtocol Default Application Protocol
|
||||
* @return ALPN Server Connection
|
||||
*/
|
||||
@Override
|
||||
protected AbstractConnection newServerConnection(
|
||||
final Connector connector,
|
||||
final EndPoint endPoint,
|
||||
final SSLEngine sslEngine,
|
||||
final List<String> protocols,
|
||||
final String defaultProtocol
|
||||
) {
|
||||
final ALPNServerConnection connection = new ALPNServerConnection(connector, endPoint, sslEngine, protocols, defaultProtocol);
|
||||
processor.configure(sslEngine, connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector.alpn;
|
||||
|
||||
import org.eclipse.jetty.alpn.server.ALPNServerConnection;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.ssl.ALPNProcessor;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Standard ALPN Processor supporting JDK 1.8.0-251 and higher based on Jetty JDK9ServerALPNProcessor
|
||||
*/
|
||||
public class StandardALPNProcessor implements ALPNProcessor.Server, SslHandshakeListener {
|
||||
private static final Logger logger = LoggerFactory.getLogger(StandardALPNProcessor.class);
|
||||
|
||||
/**
|
||||
* Applies to SSL Engine instances regardless of implementation
|
||||
*
|
||||
* @param sslEngine SSL Engine to be evaluated
|
||||
* @return Applicable Status
|
||||
*/
|
||||
@Override
|
||||
public boolean appliesTo(final SSLEngine sslEngine) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure ALPN negotiation for Connection
|
||||
*
|
||||
* @param sslEngine SSL Engine to be configured
|
||||
* @param connection Connection to be configured
|
||||
*/
|
||||
@Override
|
||||
public void configure(final SSLEngine sslEngine, final Connection connection) {
|
||||
logger.debug("Configuring Connection Remote Address [{}]", connection.getEndPoint().getRemoteAddress());
|
||||
final ALPNServerConnection serverConnection = (ALPNServerConnection) connection;
|
||||
final ProtocolSelector protocolSelector = new ProtocolSelector(serverConnection);
|
||||
sslEngine.setHandshakeApplicationProtocolSelector(protocolSelector);
|
||||
|
||||
final SslConnection.DecryptedEndPoint endPoint = (SslConnection.DecryptedEndPoint) serverConnection.getEndPoint();
|
||||
endPoint.getSslConnection().addHandshakeListener(protocolSelector);
|
||||
}
|
||||
|
||||
private static final class ProtocolSelector implements BiFunction<SSLEngine, List<String>, String>, SslHandshakeListener {
|
||||
private final ALPNServerConnection serverConnection;
|
||||
|
||||
private ProtocolSelector(final ALPNServerConnection connection) {
|
||||
serverConnection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select supported Application Layer Protocol based on requested protocols
|
||||
*
|
||||
* @param sslEngine SSL Engine
|
||||
* @param protocols Protocols requested
|
||||
* @return Protocol selected or null when no supported protocol found
|
||||
*/
|
||||
@Override
|
||||
public String apply(final SSLEngine sslEngine, final List<String> protocols) {
|
||||
String protocol = null;
|
||||
try {
|
||||
serverConnection.select(protocols);
|
||||
protocol = serverConnection.getProtocol();
|
||||
logger.debug("Connection Remote Address [{}] Application Layer Protocol [{}] selected", serverConnection.getEndPoint().getRemoteAddress(), protocol);
|
||||
} catch (final Throwable e) {
|
||||
logger.debug("Connection Remote Address [{}] Application Layer Protocols {} not supported", serverConnection.getEndPoint().getRemoteAddress(), protocols);
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for successful handshake checks for selected Application Layer Protocol
|
||||
*
|
||||
* @param event Event is not used
|
||||
*/
|
||||
@Override
|
||||
public void handshakeSucceeded(final Event event) {
|
||||
final InetSocketAddress remoteAddress = serverConnection.getEndPoint().getRemoteAddress();
|
||||
final SSLSession session = event.getSSLEngine().getSession();
|
||||
logger.debug("Connection Remote Address [{}] Handshake Succeeded [{}] Cipher Suite [{}]", remoteAddress, session.getProtocol(), session.getCipherSuite());
|
||||
|
||||
final String protocol = serverConnection.getProtocol();
|
||||
if (protocol == null) {
|
||||
logger.debug("Connection Remote Address [{}] Application Layer Protocol not supported", remoteAddress);
|
||||
serverConnection.unsupported();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle for failed handshake logs status
|
||||
*
|
||||
* @param event Event is not used
|
||||
* @param failure Failure cause to be logged
|
||||
*/
|
||||
@Override
|
||||
public void handshakeFailed(final Event event, final Throwable failure) {
|
||||
logger.warn("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.jetty.configuration.connector;
|
||||
|
||||
import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class StandardServerConnectorFactoryTest {
|
||||
private static final int HTTP_PORT = 8080;
|
||||
|
||||
private static final int HTTPS_PORT = 8443;
|
||||
|
||||
private static final String[] INCLUDE_PROTOCOLS = new String[]{ "TLSv1.2" };
|
||||
|
||||
@Test
|
||||
void testGetServerConnector() {
|
||||
final Server server = new Server();
|
||||
final StandardServerConnectorFactory factory = new StandardServerConnectorFactory(server, HTTP_PORT);
|
||||
|
||||
final ServerConnector serverConnector = factory.getServerConnector();
|
||||
|
||||
assertHttpConnectionFactoryFound(serverConnector);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetServerConnectorSecured() throws NoSuchAlgorithmException {
|
||||
final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
|
||||
|
||||
final ServerConnector serverConnector = factory.getServerConnector();
|
||||
|
||||
assertHttpConnectionFactoryFound(serverConnector);
|
||||
final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
|
||||
|
||||
final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
|
||||
assertHttpConnectionFactorySecured(httpConnectionFactory);
|
||||
|
||||
final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
|
||||
assertFalse(sslContextFactory.getNeedClientAuth());
|
||||
assertFalse(sslContextFactory.getWantClientAuth());
|
||||
assertNotNull(sslContextFactory.getIncludeProtocols());
|
||||
|
||||
final HTTP2ServerConnectionFactory http2ConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
|
||||
assertNull(http2ConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetServerConnectorSecuredNeedClientAuthentication() throws NoSuchAlgorithmException {
|
||||
final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
|
||||
factory.setNeedClientAuth(true);
|
||||
factory.setIncludeSecurityProtocols(INCLUDE_PROTOCOLS);
|
||||
|
||||
final ServerConnector serverConnector = factory.getServerConnector();
|
||||
|
||||
assertHttpConnectionFactoryFound(serverConnector);
|
||||
final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
|
||||
|
||||
final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
|
||||
assertHttpConnectionFactorySecured(httpConnectionFactory);
|
||||
|
||||
final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
|
||||
assertTrue(sslContextFactory.getNeedClientAuth());
|
||||
assertArrayEquals(INCLUDE_PROTOCOLS, sslContextFactory.getIncludeProtocols());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetServerConnectorSecuredHttp2AndHttp1() throws NoSuchAlgorithmException {
|
||||
final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
|
||||
factory.setApplicationLayerProtocols(new LinkedHashSet<>(Arrays.asList(ApplicationLayerProtocol.H2, ApplicationLayerProtocol.HTTP_1_1)));
|
||||
|
||||
final ServerConnector serverConnector = factory.getServerConnector();
|
||||
|
||||
final HttpConnectionFactory httpConnectionFactory = assertHttpConnectionFactoryFound(serverConnector);
|
||||
assertHttpConnectionFactorySecured(httpConnectionFactory);
|
||||
|
||||
final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
|
||||
final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
|
||||
assertFalse(sslContextFactory.getNeedClientAuth());
|
||||
|
||||
assertHttp2ConnectionFactoriesFound(serverConnector);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetServerConnectorSecuredHttp2() throws NoSuchAlgorithmException {
|
||||
final StandardServerConnectorFactory factory = createSecuredStandardServerConnectorFactory();
|
||||
factory.setApplicationLayerProtocols(Collections.singleton(ApplicationLayerProtocol.H2));
|
||||
|
||||
final ServerConnector serverConnector = factory.getServerConnector();
|
||||
|
||||
final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
|
||||
assertNull(connectionFactory);
|
||||
|
||||
final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
|
||||
final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
|
||||
assertFalse(sslContextFactory.getNeedClientAuth());
|
||||
|
||||
assertHttp2ConnectionFactoriesFound(serverConnector);
|
||||
}
|
||||
|
||||
private StandardServerConnectorFactory createSecuredStandardServerConnectorFactory() throws NoSuchAlgorithmException {
|
||||
final Server server = new Server();
|
||||
final StandardServerConnectorFactory factory = new StandardServerConnectorFactory(server, HTTPS_PORT);
|
||||
final SSLContext sslContext = SSLContext.getDefault();
|
||||
factory.setSslContext(sslContext);
|
||||
return factory;
|
||||
}
|
||||
|
||||
private HttpConnectionFactory assertHttpConnectionFactoryFound(final ServerConnector serverConnector) {
|
||||
assertNotNull(serverConnector);
|
||||
final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
|
||||
assertNotNull(connectionFactory);
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
private void assertHttp2ConnectionFactoriesFound(final ServerConnector serverConnector) {
|
||||
final HTTP2ServerConnectionFactory http2ConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
|
||||
assertNotNull(http2ConnectionFactory);
|
||||
|
||||
final ALPNServerConnectionFactory alpnServerConnectionFactory = serverConnector.getConnectionFactory(ALPNServerConnectionFactory.class);
|
||||
assertNotNull(alpnServerConnectionFactory);
|
||||
}
|
||||
|
||||
private SslConnectionFactory assertSslConnectionFactoryFound(final ServerConnector serverConnector) {
|
||||
final SslConnectionFactory sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class);
|
||||
assertNotNull(sslConnectionFactory);
|
||||
return sslConnectionFactory;
|
||||
}
|
||||
|
||||
private void assertHttpConnectionFactorySecured(final HttpConnectionFactory httpConnectionFactory) {
|
||||
final HttpConfiguration configuration = httpConnectionFactory.getHttpConfiguration();
|
||||
assertEquals(HTTPS_PORT, configuration.getSecurePort());
|
||||
assertEquals(HttpScheme.HTTPS.asString(), configuration.getSecureScheme());
|
||||
final SecureRequestCustomizer secureRequestCustomizer = configuration.getCustomizer(SecureRequestCustomizer.class);
|
||||
assertNotNull(secureRequestCustomizer);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@
|
|||
<module>nifi-flow-encryptor</module>
|
||||
<module>nifi-hl7-query-language</module>
|
||||
<module>nifi-json-utils</module>
|
||||
<module>nifi-jetty-configuration</module>
|
||||
<module>nifi-logging-utils</module>
|
||||
<module>nifi-metrics</module>
|
||||
<module>nifi-parameter</module>
|
||||
|
|
|
@ -78,5 +78,15 @@
|
|||
<artifactId>apache-jstl</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-server</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -92,6 +92,11 @@
|
|||
<artifactId>nifi-flowfile-packager</artifactId>
|
||||
<version>1.17.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-jetty-configuration</artifactId>
|
||||
<version>1.17.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-distributed-cache-client-service-api</artifactId>
|
||||
|
@ -180,6 +185,14 @@
|
|||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.apache.nifi.components.PropertyDescriptor;
|
|||
import org.apache.nifi.expression.ExpressionLanguageScope;
|
||||
import org.apache.nifi.flowfile.FlowFile;
|
||||
import org.apache.nifi.http.HttpContextMap;
|
||||
import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
|
||||
import org.apache.nifi.processor.AbstractProcessor;
|
||||
import org.apache.nifi.processor.DataUnit;
|
||||
import org.apache.nifi.processor.ProcessContext;
|
||||
|
@ -40,21 +41,17 @@ import org.apache.nifi.processor.ProcessSession;
|
|||
import org.apache.nifi.processor.Relationship;
|
||||
import org.apache.nifi.processor.exception.ProcessException;
|
||||
import org.apache.nifi.processor.util.StandardValidators;
|
||||
import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
|
||||
import org.apache.nifi.processors.standard.util.HTTPUtils;
|
||||
import org.apache.nifi.scheduling.ExecutionNode;
|
||||
import org.apache.nifi.ssl.RestrictedSSLContextService;
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
import org.apache.nifi.stream.io.StreamUtils;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.servlet.AsyncContext;
|
||||
|
@ -187,6 +184,14 @@ public class HandleHttpRequest extends AbstractProcessor {
|
|||
.required(false)
|
||||
.identifiesControllerService(RestrictedSSLContextService.class)
|
||||
.build();
|
||||
public static final PropertyDescriptor HTTP_PROTOCOL_STRATEGY = new PropertyDescriptor.Builder()
|
||||
.name("HTTP Protocols")
|
||||
.description("HTTP Protocols supported for Application Layer Protocol Negotiation with TLS")
|
||||
.required(true)
|
||||
.allowableValues(HttpProtocolStrategy.class)
|
||||
.defaultValue(HttpProtocolStrategy.HTTP_1_1.getValue())
|
||||
.dependsOn(SSL_CONTEXT)
|
||||
.build();
|
||||
public static final PropertyDescriptor URL_CHARACTER_SET = new PropertyDescriptor.Builder()
|
||||
.name("Default URL Character Set")
|
||||
.description("The character set to use for decoding URL parameters if the HTTP Request does not supply one")
|
||||
|
@ -303,6 +308,7 @@ public class HandleHttpRequest extends AbstractProcessor {
|
|||
descriptors.add(PORT);
|
||||
descriptors.add(HOSTNAME);
|
||||
descriptors.add(SSL_CONTEXT);
|
||||
descriptors.add(HTTP_PROTOCOL_STRATEGY);
|
||||
descriptors.add(HTTP_CONTEXT_MAP);
|
||||
descriptors.add(PATH_REGEX);
|
||||
descriptors.add(URL_CHARACTER_SET);
|
||||
|
@ -356,61 +362,24 @@ public class HandleHttpRequest extends AbstractProcessor {
|
|||
final long requestTimeout = httpContextMap.getRequestTimeout(TimeUnit.MILLISECONDS);
|
||||
|
||||
final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
|
||||
final boolean need;
|
||||
final boolean want;
|
||||
if (CLIENT_NEED.getValue().equals(clientAuthValue)) {
|
||||
need = true;
|
||||
want = false;
|
||||
} else if (CLIENT_WANT.getValue().equals(clientAuthValue)) {
|
||||
need = false;
|
||||
want = true;
|
||||
} else {
|
||||
need = false;
|
||||
want = false;
|
||||
}
|
||||
|
||||
final SslContextFactory sslFactory = (sslService == null) ? null : createSslFactory(sslService, need, want);
|
||||
final Server server = new Server(port);
|
||||
|
||||
// create the http configuration
|
||||
final HttpConfiguration httpConfiguration = new HttpConfiguration();
|
||||
if (sslFactory == null) {
|
||||
// create the connector
|
||||
final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
|
||||
|
||||
// set host and port
|
||||
if (StringUtils.isNotBlank(host)) {
|
||||
http.setHost(host);
|
||||
}
|
||||
http.setPort(port);
|
||||
|
||||
// If request timeout is longer than default Idle Timeout, then increase Idle Timeout as well.
|
||||
http.setIdleTimeout(Math.max(http.getIdleTimeout(), requestTimeout));
|
||||
|
||||
// add this connector
|
||||
server.setConnectors(new Connector[]{http});
|
||||
} else {
|
||||
// add some secure config
|
||||
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
|
||||
httpsConfiguration.setSecureScheme("https");
|
||||
httpsConfiguration.setSecurePort(port);
|
||||
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
// build the connector
|
||||
final ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslFactory, "http/1.1"), new HttpConnectionFactory(httpsConfiguration));
|
||||
|
||||
// set host and port
|
||||
if (StringUtils.isNotBlank(host)) {
|
||||
https.setHost(host);
|
||||
}
|
||||
https.setPort(port);
|
||||
|
||||
// If request timeout is longer than default Idle Timeout, then increase Idle Timeout as well.
|
||||
https.setIdleTimeout(Math.max(https.getIdleTimeout(), requestTimeout));
|
||||
|
||||
// add this connector
|
||||
server.setConnectors(new Connector[]{https});
|
||||
final Server server = new Server();
|
||||
|
||||
final StandardServerConnectorFactory serverConnectorFactory = new StandardServerConnectorFactory(server, port);
|
||||
final boolean needClientAuth = CLIENT_NEED.getValue().equals(clientAuthValue);
|
||||
serverConnectorFactory.setNeedClientAuth(needClientAuth);
|
||||
final boolean wantClientAuth = CLIENT_WANT.getValue().equals(clientAuthValue);
|
||||
serverConnectorFactory.setNeedClientAuth(wantClientAuth);
|
||||
final SSLContext sslContext = sslService == null ? null : sslService.createContext();
|
||||
serverConnectorFactory.setSslContext(sslContext);
|
||||
final HttpProtocolStrategy httpProtocolStrategy = HttpProtocolStrategy.valueOf(context.getProperty(HTTP_PROTOCOL_STRATEGY).getValue());
|
||||
serverConnectorFactory.setApplicationLayerProtocols(httpProtocolStrategy.getApplicationLayerProtocols());
|
||||
|
||||
final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
|
||||
serverConnector.setIdleTimeout(Math.max(serverConnector.getIdleTimeout(), requestTimeout));
|
||||
if (StringUtils.isNotBlank(host)) {
|
||||
serverConnector.setHost(host);
|
||||
}
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
final Set<String> allowedMethods = new HashSet<>();
|
||||
if (context.getProperty(ALLOW_GET).asBoolean()) {
|
||||
|
@ -522,18 +491,6 @@ public class HandleHttpRequest extends AbstractProcessor {
|
|||
return containerQueue.size();
|
||||
}
|
||||
|
||||
private SslContextFactory createSslFactory(final SSLContextService sslContextService, final boolean needClientAuth, final boolean wantClientAuth) {
|
||||
final SslContextFactory.Server sslFactory = new SslContextFactory.Server();
|
||||
|
||||
sslFactory.setNeedClientAuth(needClientAuth);
|
||||
sslFactory.setWantClientAuth(wantClientAuth);
|
||||
|
||||
final SSLContext sslContext = sslContextService.createContext();
|
||||
sslFactory.setSslContext(sslContext);
|
||||
|
||||
return sslFactory;
|
||||
}
|
||||
|
||||
@OnUnscheduled
|
||||
public void shutdown() throws Exception {
|
||||
ready = false;
|
||||
|
|
|
@ -188,7 +188,6 @@ public class HandleHttpResponse extends AbstractProcessor {
|
|||
|
||||
try {
|
||||
session.exportTo(flowFile, response.getOutputStream());
|
||||
response.flushBuffer();
|
||||
} catch (final ProcessException e) {
|
||||
getLogger().error("Failed to respond to HTTP request for {} due to {}", new Object[]{flowFile, e});
|
||||
try {
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.nifi.components.ValidationContext;
|
|||
import org.apache.nifi.components.ValidationResult;
|
||||
import org.apache.nifi.expression.ExpressionLanguageScope;
|
||||
import org.apache.nifi.flowfile.FlowFile;
|
||||
import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
|
||||
import org.apache.nifi.processor.AbstractSessionFactoryProcessor;
|
||||
import org.apache.nifi.processor.DataUnit;
|
||||
import org.apache.nifi.processor.ProcessContext;
|
||||
|
@ -39,26 +40,21 @@ import org.apache.nifi.processor.ProcessSessionFactory;
|
|||
import org.apache.nifi.processor.Relationship;
|
||||
import org.apache.nifi.processor.exception.ProcessException;
|
||||
import org.apache.nifi.processor.util.StandardValidators;
|
||||
import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
|
||||
import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet;
|
||||
import org.apache.nifi.processors.standard.servlets.HealthCheckServlet;
|
||||
import org.apache.nifi.processors.standard.servlets.ListenHTTPServlet;
|
||||
import org.apache.nifi.scheduling.ExecutionNode;
|
||||
import org.apache.nifi.security.util.ClientAuth;
|
||||
import org.apache.nifi.security.util.TlsConfiguration;
|
||||
import org.apache.nifi.serialization.RecordReaderFactory;
|
||||
import org.apache.nifi.serialization.RecordSetWriterFactory;
|
||||
import org.apache.nifi.ssl.RestrictedSSLContextService;
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
import org.apache.nifi.stream.io.LeakyBucketStreamThrottler;
|
||||
import org.apache.nifi.stream.io.StreamThrottler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
@ -191,10 +187,18 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
.build();
|
||||
public static final PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder()
|
||||
.name("SSL Context Service")
|
||||
.description("The Controller Service to use in order to obtain an SSL Context")
|
||||
.description("SSL Context Service enables support for HTTPS")
|
||||
.required(false)
|
||||
.identifiesControllerService(RestrictedSSLContextService.class)
|
||||
.build();
|
||||
public static final PropertyDescriptor HTTP_PROTOCOL_STRATEGY = new PropertyDescriptor.Builder()
|
||||
.name("HTTP Protocols")
|
||||
.description("HTTP Protocols supported for Application Layer Protocol Negotiation with TLS")
|
||||
.required(true)
|
||||
.allowableValues(HttpProtocolStrategy.class)
|
||||
.defaultValue(HttpProtocolStrategy.HTTP_1_1.getValue())
|
||||
.dependsOn(SSL_CONTEXT_SERVICE)
|
||||
.build();
|
||||
public static final PropertyDescriptor HEADERS_AS_ATTRIBUTES_REGEX = new PropertyDescriptor.Builder()
|
||||
.name("HTTP Headers to receive as Attributes (Regex)")
|
||||
.description("Specifies the Regular Expression that determines the names of HTTP Headers that should be passed along as FlowFile attributes")
|
||||
|
@ -276,6 +280,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
HEALTH_CHECK_PORT,
|
||||
MAX_DATA_RATE,
|
||||
SSL_CONTEXT_SERVICE,
|
||||
HTTP_PROTOCOL_STRATEGY,
|
||||
CLIENT_AUTHENTICATION,
|
||||
AUTHORIZED_DN_PATTERN,
|
||||
AUTHORIZED_ISSUER_DN_PATTERN,
|
||||
|
@ -396,7 +401,6 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
int maxThreadPoolSize = context.getProperty(MAX_THREAD_POOL_SIZE).asInteger();
|
||||
throttlerRef.set(streamThrottler);
|
||||
|
||||
final boolean sslRequired = sslContextService != null;
|
||||
final PropertyValue clientAuthenticationProperty = context.getProperty(CLIENT_AUTHENTICATION);
|
||||
final ClientAuthentication clientAuthentication = getClientAuthentication(sslContextService, clientAuthenticationProperty);
|
||||
|
||||
|
@ -409,12 +413,13 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
|
||||
// get the configured port
|
||||
final int port = context.getProperty(PORT).evaluateAttributeExpressions().asInteger();
|
||||
|
||||
final HttpProtocolStrategy httpProtocolStrategy = HttpProtocolStrategy.valueOf(context.getProperty(HTTP_PROTOCOL_STRATEGY).getValue());
|
||||
final ServerConnector connector = createServerConnector(server,
|
||||
port,
|
||||
sslContextService,
|
||||
sslRequired,
|
||||
clientAuthentication);
|
||||
clientAuthentication,
|
||||
httpProtocolStrategy
|
||||
);
|
||||
server.addConnector(connector);
|
||||
|
||||
// Add a separate connector for the health check port (if specified)
|
||||
|
@ -423,12 +428,14 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
final ServerConnector healthCheckConnector = createServerConnector(server,
|
||||
healthCheckPort,
|
||||
sslContextService,
|
||||
sslRequired,
|
||||
ClientAuthentication.NONE);
|
||||
ClientAuthentication.NONE,
|
||||
httpProtocolStrategy
|
||||
);
|
||||
server.addConnector(healthCheckConnector);
|
||||
}
|
||||
|
||||
final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, sslRequired);
|
||||
final boolean securityEnabled = sslContextService != null;
|
||||
final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, securityEnabled);
|
||||
for (final Class<? extends Servlet> cls : getServerClasses()) {
|
||||
final Path path = cls.getAnnotation(Path.class);
|
||||
// Note: servlets must have a path annotation - this will NPE otherwise
|
||||
|
@ -488,41 +495,24 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
private ServerConnector createServerConnector(final Server server,
|
||||
final int port,
|
||||
final SSLContextService sslContextService,
|
||||
final boolean sslRequired,
|
||||
final ClientAuthentication clientAuthentication) {
|
||||
final ServerConnector connector;
|
||||
final HttpConfiguration httpConfiguration = new HttpConfiguration();
|
||||
if (sslRequired) {
|
||||
httpConfiguration.setSecureScheme("https");
|
||||
httpConfiguration.setSecurePort(port);
|
||||
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
|
||||
final ClientAuthentication clientAuthentication,
|
||||
final HttpProtocolStrategy httpProtocolStrategy
|
||||
) {
|
||||
final StandardServerConnectorFactory serverConnectorFactory = new StandardServerConnectorFactory(server, port);
|
||||
final SSLContext sslContext = sslContextService == null ? null : sslContextService.createContext();
|
||||
serverConnectorFactory.setSslContext(sslContext);
|
||||
|
||||
final SslContextFactory contextFactory = createSslContextFactory(sslContextService, clientAuthentication);
|
||||
final String[] enabledProtocols = sslContextService == null ? new String[0] : sslContextService.createTlsConfiguration().getEnabledProtocols();
|
||||
serverConnectorFactory.setIncludeSecurityProtocols(enabledProtocols);
|
||||
|
||||
connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration));
|
||||
} else {
|
||||
connector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
|
||||
if (ClientAuthentication.REQUIRED == clientAuthentication) {
|
||||
serverConnectorFactory.setNeedClientAuth(true);
|
||||
} else if (ClientAuthentication.WANT == clientAuthentication) {
|
||||
serverConnectorFactory.setWantClientAuth(true);
|
||||
}
|
||||
|
||||
connector.setPort(port);
|
||||
return connector;
|
||||
}
|
||||
|
||||
private SslContextFactory createSslContextFactory(final SSLContextService sslContextService, final ClientAuthentication clientAuthentication) {
|
||||
final SslContextFactory.Server contextFactory = new SslContextFactory.Server();
|
||||
final SSLContext sslContext = sslContextService.createContext();
|
||||
contextFactory.setSslContext(sslContext);
|
||||
|
||||
final TlsConfiguration tlsConfiguration = sslContextService.createTlsConfiguration();
|
||||
contextFactory.setIncludeProtocols(tlsConfiguration.getEnabledProtocols());
|
||||
|
||||
if (ClientAuthentication.REQUIRED.equals(clientAuthentication)) {
|
||||
contextFactory.setNeedClientAuth(true);
|
||||
} else if (ClientAuthentication.WANT.equals(clientAuthentication)) {
|
||||
contextFactory.setWantClientAuth(true);
|
||||
}
|
||||
|
||||
return contextFactory;
|
||||
serverConnectorFactory.setApplicationLayerProtocols(httpProtocolStrategy.getApplicationLayerProtocols());
|
||||
return serverConnectorFactory.getServerConnector();
|
||||
}
|
||||
|
||||
@OnScheduled
|
||||
|
@ -572,7 +562,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
|||
for (final String id : findOldFlowFileIds(context)) {
|
||||
final FlowFileEntryTimeWrapper wrapper = flowFileMap.remove(id);
|
||||
if (wrapper != null) {
|
||||
getLogger().warn("failed to received acknowledgment for HOLD with ID {} sent by {}; rolling back session", new Object[] {id, wrapper.getClientIP()});
|
||||
getLogger().warn("failed to received acknowledgment for HOLD with ID {} sent by {}; rolling back session", id, wrapper.getClientIP());
|
||||
wrapper.session.rollback();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.processors.standard.http;
|
||||
|
||||
import org.apache.nifi.components.DescribedValue;
|
||||
import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
/**
|
||||
* HTTP protocol configuration strategy
|
||||
*/
|
||||
public enum HttpProtocolStrategy implements DescribedValue {
|
||||
HTTP_1_1("http/1.1", "HTTP/1.1", singleton(ApplicationLayerProtocol.HTTP_1_1)),
|
||||
|
||||
H2_HTTP_1_1("h2 http/1.1", "HTTP/2 and HTTP/1.1 negotiated based on requested protocols", new LinkedHashSet<>(asList(ApplicationLayerProtocol.HTTP_1_1, ApplicationLayerProtocol.H2))),
|
||||
|
||||
H2("h2", "HTTP/2", singleton(ApplicationLayerProtocol.H2));
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final Set<ApplicationLayerProtocol> applicationLayerProtocols;
|
||||
|
||||
HttpProtocolStrategy(final String displayName, final String description, final Set<ApplicationLayerProtocol> applicationLayerProtocols) {
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
this.applicationLayerProtocols = applicationLayerProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Set<ApplicationLayerProtocol> getApplicationLayerProtocols() {
|
||||
return applicationLayerProtocols;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ import okhttp3.RequestBody;
|
|||
import okhttp3.Response;
|
||||
import org.apache.nifi.processor.ProcessContext;
|
||||
import org.apache.nifi.processor.ProcessSessionFactory;
|
||||
import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
|
||||
import org.apache.nifi.remote.io.socket.NetworkUtils;
|
||||
import org.apache.nifi.reporting.InitializationException;
|
||||
import org.apache.nifi.security.util.SslContextFactory;
|
||||
|
@ -99,7 +100,6 @@ public class TestListenHTTP {
|
|||
private static final Duration CLIENT_CALL_TIMEOUT = Duration.ofSeconds(10);
|
||||
public static final String LOCALHOST_DN = "CN=localhost";
|
||||
|
||||
private static TlsConfiguration tlsConfiguration;
|
||||
private static TlsConfiguration serverConfiguration;
|
||||
private static TlsConfiguration serverTls_1_3_Configuration;
|
||||
private static TlsConfiguration serverNoTruststoreConfiguration;
|
||||
|
@ -117,7 +117,7 @@ public class TestListenHTTP {
|
|||
@BeforeClass
|
||||
public static void setUpSuite() throws GeneralSecurityException {
|
||||
// generate new keystore and truststore
|
||||
tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||
final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||
|
||||
serverConfiguration = new StandardTlsConfiguration(
|
||||
tlsConfiguration.getKeystorePath(),
|
||||
|
@ -223,23 +223,25 @@ public class TestListenHTTP {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSecurePOSTRequestsReceivedWithoutEL() throws Exception {
|
||||
public void testSecurePOSTRequestsReceivedWithoutELHttp2AndHttp1() throws Exception {
|
||||
configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration);
|
||||
|
||||
runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
|
||||
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
|
||||
runner.setProperty(ListenHTTP.HTTP_PROTOCOL_STRATEGY, HttpProtocolStrategy.H2_HTTP_1_1.getValue());
|
||||
runner.assertValid();
|
||||
|
||||
testPOSTRequestsReceived(HttpServletResponse.SC_OK, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() throws Exception {
|
||||
public void testSecurePOSTRequestsReturnCodeReceivedWithoutELHttp2() throws Exception {
|
||||
configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration);
|
||||
|
||||
runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
|
||||
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
|
||||
runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT));
|
||||
runner.setProperty(ListenHTTP.HTTP_PROTOCOL_STRATEGY, HttpProtocolStrategy.H2.getValue());
|
||||
runner.assertValid();
|
||||
|
||||
testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT, true, false);
|
||||
|
|
13
pom.xml
13
pom.xml
|
@ -420,6 +420,19 @@
|
|||
<version>${jetty.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-client</artifactId>
|
||||
|
|
Loading…
Reference in New Issue