Repository: nifi
Updated Branches:
  refs/heads/master 92b4a3208 -> 7a4990e7f


NIFI-5146 Only support HTTP or HTTPS operation for NiFi API/UI

- Added logic to check for simultaneous configuration of HTTP and HTTPS 
connectors in JettyServer.
- Added test logging resources. Added unit tests.
- Refactored shared functionality to generic method which accepts lambdas.
  Fixed unit test with logging side effects.
- Added note about exclusive HTTP/HTTPS behavior to Admin Guide. Fixed typos.

This closes #2683.

Signed-off-by: Kevin Doran <kdo...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/7a4990e7
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/7a4990e7
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/7a4990e7

Branch: refs/heads/master
Commit: 7a4990e7fe7c38c95b4ee1436a822428ff1f5f98
Parents: 92b4a32
Author: Andy LoPresto <alopre...@apache.org>
Authored: Thu May 3 16:02:19 2018 -0700
Committer: Kevin Doran <kdo...@apache.org>
Committed: Wed May 9 12:14:11 2018 -0400

----------------------------------------------------------------------
 .../src/main/asciidoc/administration-guide.adoc |  10 +-
 .../nifi-framework/nifi-web/nifi-jetty/pom.xml  |  26 ++
 .../org/apache/nifi/web/server/JettyServer.java | 220 ++++++++++------
 .../web/server/JettyServerGroovyTest.groovy     | 258 +++++++++++++++++++
 .../src/test/resources/log4j.properties         |  27 ++
 5 files changed, 451 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-docs/src/main/asciidoc/administration-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 64facc7..b9857d6 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -148,7 +148,7 @@ should run on. If it is desired that the HTTPS interface be 
accessible from all
 admins to configure the application to run only on specific network 
interfaces, `nifi.web.http.network.interface*` or 
`nifi.web.https.network.interface*`
 properties can be specified.
 
-NOTE: It is important when enabling HTTPS that the `nifi.web.http.port` 
property be unset.
+NOTE: It is important when enabling HTTPS that the `nifi.web.http.port` 
property be unset. NiFi only supports running on HTTP *or* HTTPS, not both 
simultaneously.
 
 Similar to `nifi.security.needClientAuth`, the web server can be configured to 
require certificate based client authentication for users accessing
 the User Interface. In order to do this it must be configured to not support 
username/password authentication using  <<ldap_login_identity_provider>> or 
<<kerberos_login_identity_provider>>. Either of these options
@@ -1967,8 +1967,8 @@ they must be set the same on every instance in the 
cluster.
 
 For each Node, the minimum properties to configure are as follows:
 
-* Under the _Web Properties_ section, set either the http or https port that 
you want the Node to run on.
-  Also, consider whether you need to set the http or https host property.
+* Under the _Web Properties_ section, set either the HTTP or HTTPS port that 
you want the Node to run on.
+  Also, consider whether you need to set the HTTP or HTTPS host property. All 
nodes in the cluster should use the same protocol setting.
 * Under the _State Management section_, set the 
`nifi.state.management.provider.cluster` property
   to the identifier of the Cluster State Provider. Ensure that the Cluster 
State Provider has been
   configured in the _state-management.xml_ file. See <<state_providers>> for 
more information.
@@ -2164,7 +2164,7 @@ In order to secure the communications, we need to ensure 
that both the client an
 NiFi ZooKeeper client and embedded ZooKeeper server to use Kerberos are 
provided below.
 
 If Kerberos is not already setup in your environment, you can find information 
on installing and setting up a Kerberos Server at
-link:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html[https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html^].
 This guide assumes that Kerberos already has been installed in the environment 
in which NiFi is running.
+https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html[Red
 Hat Customer Portal: Configuring a Kerberos 5 Server]. This guide assumes that 
Kerberos already has been installed in the environment in which NiFi is running.
 
 Note, the following procedures for kerberizing an Embedded ZooKeeper server in 
your NiFi Node and kerberizing a ZooKeeper NiFi client will require that
 Kerberos client libraries be installed. This is accomplished in Fedora-based 
Linux distributions via:
@@ -2652,7 +2652,7 @@ documentation of the proxy for guidance for your 
deployment environment and use
 
 ** By default, if NiFi is running securely it will only accept HTTP requests 
with a Host header matching the host[:port] that it is bound to. If NiFi is to 
accept requests directed to a different
 host[:port] the expected values need to be configured. This may be required 
when running behind a proxy or in a containerized environment. This is 
configured in a comma
-separated list in _nifi.properties_ using the `nifi.web.proxy.host` property 
(e.g. localhost:18443, proxyhost:443). IPv6 addressed are accepted. Please 
refer to
+separated list in _nifi.properties_ using the `nifi.web.proxy.host` property 
(e.g. localhost:18443, proxyhost:443). IPv6 addresses are accepted. Please 
refer to
 RFC 5952 Sections link:https://tools.ietf.org/html/rfc5952#section-4[4] and 
link:https://tools.ietf.org/html/rfc5952#section-6[6] for additional details.
 
 ** NiFi will only accept HTTP requests with a X-ProxyContextPath or 
X-Forwarded-Context header if the value is whitelisted in the 
`nifi.web.proxy.context.path` property in

http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
index 80a2a9c..3d1c6ba 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
@@ -22,8 +22,28 @@
     </parent>
     <artifactId>nifi-jetty</artifactId>
     <packaging>jar</packaging>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.codehaus.groovy</groupId>
+                <artifactId>groovy-all</artifactId>
+                <version>2.4.13</version>
+                <scope>compile</scope>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.slf4j</groupId>
+                        <artifactId>slf4j-log4j12</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
     <dependencies>
         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-api</artifactId>
             <scope>compile</scope>
@@ -168,6 +188,12 @@
             <artifactId>nifi-framework-cluster</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.github.stefanbirkner</groupId>
+            <artifactId>system-rules</artifactId>
+            <version>1.16.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index de53a24..83b489f 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -173,6 +173,14 @@ public class JettyServer implements NiFiServer {
         server.setHandler(allHandlers);
     }
 
+    /**
+     * Instantiates this object but does not perform any configuration. Used 
for unit testing.
+     */
+     JettyServer(Server server, NiFiProperties properties) {
+        this.server = server;
+        this.props = properties;
+    }
+
     private Handler loadWars(final Set<Bundle> bundles) {
 
         // load WARs
@@ -601,106 +609,143 @@ public class JettyServer implements NiFiServer {
         httpConfiguration.setRequestHeaderSize(headerSize);
         httpConfiguration.setResponseHeaderSize(headerSize);
 
-        if (props.getPort() != null) {
-            final Integer port = props.getPort();
-            if (port < 0 || (int) Math.pow(2, 16) <= port) {
-                throw new ServerConfigurationException("Invalid HTTP port: " + 
port);
-            }
+        // Check if both HTTP and HTTPS connectors are configured and fail if 
both are configured
+        if (bothHttpAndHttpsConnectorsConfigured(props)) {
+            logger.error("NiFi only supports one mode of HTTP or HTTPS 
operation, not both simultaneously. " +
+                    "Check the nifi.properties file and ensure that either the 
HTTP hostname and port or the HTTPS hostname and port are empty");
+            startUpFailure(new IllegalStateException("Only one of the HTTP and 
HTTPS connectors can be configured at one time"));
+        }
 
-            logger.info("Configuring Jetty for HTTP on port: " + port);
+        if (props.getSslPort() != null) {
+            configureHttpsConnector(server, httpConfiguration);
+        } else if (props.getPort() != null) {
+            configureHttpConnector(server, httpConfiguration);
+        } else {
+            logger.error("Neither the HTTP nor HTTPS connector was configured 
in nifi.properties");
+            startUpFailure(new IllegalStateException("Must configure HTTP or 
HTTPS connector"));
+        }
+    }
 
-            final List<Connector> serverConnectors = Lists.newArrayList();
+    /**
+     * Configures an HTTPS connector and adds it to the server.
+     *
+     * @param server            the Jetty server instance
+     * @param httpConfiguration the configuration object for the HTTPS 
protocol settings
+     */
+    private void configureHttpsConnector(Server server, HttpConfiguration 
httpConfiguration) {
+        String hostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
+        final Integer port = props.getSslPort();
+        String connectorLabel = "HTTPS";
+        final Map<String, String> httpsNetworkInterfaces = 
props.getHttpsNetworkInterfaces();
+        ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc 
= (s, c) -> createUnconfiguredSslServerConnector(s, c, port);
+
+        configureGenericConnector(server, httpConfiguration, hostname, port, 
connectorLabel, httpsNetworkInterfaces, scc);
+    }
 
-            final Map<String, String> httpNetworkInterfaces = 
props.getHttpNetworkInterfaces();
-            if (httpNetworkInterfaces.isEmpty() || 
httpNetworkInterfaces.values().stream().filter(value -> 
!Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) {
-                // create the connector
-                final ServerConnector http = new ServerConnector(server, new 
HttpConnectionFactory(httpConfiguration));
-                // set host and port
-                if 
(StringUtils.isNotBlank(props.getProperty(NiFiProperties.WEB_HTTP_HOST))) {
-                    
http.setHost(props.getProperty(NiFiProperties.WEB_HTTP_HOST));
-                }
-                http.setPort(port);
-                serverConnectors.add(http);
-            } else {
-                // add connectors for all IPs from http network interfaces
-                
serverConnectors.addAll(Lists.newArrayList(httpNetworkInterfaces.values().stream().map(ifaceName
 -> {
-                    NetworkInterface iface = null;
-                    try {
-                        iface = NetworkInterface.getByName(ifaceName);
-                    } catch (SocketException e) {
-                        logger.error("Unable to get network interface by name 
{}", ifaceName, e);
-                    }
-                    if (iface == null) {
-                        logger.warn("Unable to find network interface named 
{}", ifaceName);
-                    }
-                    return iface;
-                }).filter(Objects::nonNull).flatMap(iface -> 
Collections.list(iface.getInetAddresses()).stream())
-                        .map(inetAddress -> {
-                            // create the connector
-                            final ServerConnector http = new 
ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
-                            // set host and port
-                            http.setHost(inetAddress.getHostAddress());
-                            http.setPort(port);
-                            return http;
-                        }).collect(Collectors.toList())));
-            }
-            // add all connectors
-            serverConnectors.forEach(server::addConnector);
-        }
+    /**
+     * Configures an HTTP connector and adds it to the server.
+     *
+     * @param server            the Jetty server instance
+     * @param httpConfiguration the configuration object for the HTTP protocol 
settings
+     */
+    private void configureHttpConnector(Server server, HttpConfiguration 
httpConfiguration) {
+        String hostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+        final Integer port = props.getPort();
+        String connectorLabel = "HTTP";
+        final Map<String, String> httpNetworkInterfaces = 
props.getHttpNetworkInterfaces();
+        ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc 
= (s, c) -> new ServerConnector(s, new HttpConnectionFactory(c));
+
+        configureGenericConnector(server, httpConfiguration, hostname, port, 
connectorLabel, httpNetworkInterfaces, scc);
+    }
 
-        if (props.getSslPort() != null) {
-            final Integer port = props.getSslPort();
-            if (port < 0 || (int) Math.pow(2, 16) <= port) {
-                throw new ServerConfigurationException("Invalid HTTPs port: " 
+ port);
-            }
+    /**
+     * Configures an HTTP(S) connector for the server given the provided 
parameters. The functionality between HTTP and HTTPS connectors is largely 
similar.
+     * Here the common behavior has been extracted into a shared method and 
the respective calling methods obtain the right values and a lambda function 
for the differing behavior.
+     *
+     * @param server                 the Jetty server instance
+     * @param configuration          the HTTP/HTTPS configuration instance
+     * @param hostname               the hostname from the nifi.properties file
+     * @param port                   the port to expose
+     * @param connectorLabel         used for log output (e.g. "HTTP" or 
"HTTPS")
+     * @param networkInterfaces      the map of network interfaces from 
nifi.properties
+     * @param serverConnectorCreator a function which accepts a {@code Server} 
and {@code HttpConnection} instance and returns a {@code ServerConnector}
+     */
+    private void configureGenericConnector(Server server, HttpConfiguration 
configuration, String hostname, Integer port, String connectorLabel, 
Map<String, String> networkInterfaces,
+                                           ServerConnectorCreator<Server, 
HttpConfiguration, ServerConnector> serverConnectorCreator) {
+        if (port < 0 || (int) Math.pow(2, 16) <= port) {
+            throw new ServerConfigurationException("Invalid " + connectorLabel 
+ " port: " + port);
+        }
 
-            logger.info("Configuring Jetty for HTTPs on port: " + port);
+        logger.info("Configuring Jetty for " + connectorLabel + " on port: " + 
port);
 
-            final List<Connector> serverConnectors = Lists.newArrayList();
+        final List<Connector> serverConnectors = Lists.newArrayList();
 
-            final Map<String, String> httpsNetworkInterfaces = 
props.getHttpsNetworkInterfaces();
-            if (httpsNetworkInterfaces.isEmpty() || 
httpsNetworkInterfaces.values().stream().filter(value -> 
!Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) {
-                final ServerConnector https = 
createUnconfiguredSslServerConnector(server, httpConfiguration);
+        // If the interfaces collection is empty or each element is empty
+        if (networkInterfaces.isEmpty() || 
networkInterfaces.values().stream().filter(value -> 
!Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) {
+            final ServerConnector serverConnector = 
serverConnectorCreator.create(server, configuration);
 
-                // set host and port
-                if 
(StringUtils.isNotBlank(props.getProperty(NiFiProperties.WEB_HTTPS_HOST))) {
-                    
https.setHost(props.getProperty(NiFiProperties.WEB_HTTPS_HOST));
-                }
-                https.setPort(port);
-                serverConnectors.add(https);
-            } else {
-                // add connectors for all IPs from https network interfaces
-                
serverConnectors.addAll(Lists.newArrayList(httpsNetworkInterfaces.values().stream().map(ifaceName
 -> {
-                    NetworkInterface iface = null;
-                    try {
-                        iface = NetworkInterface.getByName(ifaceName);
-                    } catch (SocketException e) {
-                        logger.error("Unable to get network interface by name 
{}", ifaceName, e);
-                    }
-                    if (iface == null) {
-                        logger.warn("Unable to find network interface named 
{}", ifaceName);
-                    }
-                    return iface;
-                }).filter(Objects::nonNull).flatMap(iface -> 
Collections.list(iface.getInetAddresses()).stream())
-                        .map(inetAddress -> {
-                            final ServerConnector https = 
createUnconfiguredSslServerConnector(server, httpConfiguration);
-
-                            // set host and port
-                            https.setHost(inetAddress.getHostAddress());
-                            https.setPort(port);
-                            return https;
-                        }).collect(Collectors.toList())));
+            // Set host and port
+            if (StringUtils.isNotBlank(hostname)) {
+                serverConnector.setHost(hostname);
             }
-            // add all connectors
-            serverConnectors.forEach(server::addConnector);
+            serverConnector.setPort(port);
+            serverConnectors.add(serverConnector);
+        } else {
+            // Add connectors for all IPs from network interfaces
+            
serverConnectors.addAll(Lists.newArrayList(networkInterfaces.values().stream().map(ifaceName
 -> {
+                NetworkInterface iface = null;
+                try {
+                    iface = NetworkInterface.getByName(ifaceName);
+                } catch (SocketException e) {
+                    logger.error("Unable to get network interface by name {}", 
ifaceName, e);
+                }
+                if (iface == null) {
+                    logger.warn("Unable to find network interface named {}", 
ifaceName);
+                }
+                return iface;
+            }).filter(Objects::nonNull).flatMap(iface -> 
Collections.list(iface.getInetAddresses()).stream())
+                    .map(inetAddress -> {
+                        final ServerConnector serverConnector = 
serverConnectorCreator.create(server, configuration);
+
+                        // Set host and port
+                        serverConnector.setHost(inetAddress.getHostAddress());
+                        serverConnector.setPort(port);
+                        return serverConnector;
+                    }).collect(Collectors.toList())));
         }
+        // Add all connectors
+        serverConnectors.forEach(server::addConnector);
     }
 
-    private ServerConnector createUnconfiguredSslServerConnector(Server 
server, HttpConfiguration httpConfiguration) {
+    /**
+     * Returns true if there are configured properties for both HTTP and HTTPS 
connectors (specifically port because the hostname can be left blank in the 
HTTP connector).
+     * Prints a warning log message with the relevant properties.
+     *
+     * @param props the NiFiProperties
+     * @return true if both ports are present
+     */
+    static boolean bothHttpAndHttpsConnectorsConfigured(NiFiProperties props) {
+        Integer httpPort = props.getPort();
+        String httpHostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+
+        Integer httpsPort = props.getSslPort();
+        String httpsHostname = 
props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
+
+        if (httpPort != null && httpsPort != null) {
+            logger.warn("Both the HTTP and HTTPS connectors are configured in 
nifi.properties. Only one of these connectors should be configured. See the 
NiFi Admin Guide for more details");
+            logger.warn("HTTP connector:   http://"; + httpHostname + ":" + 
httpPort);
+            logger.warn("HTTPS connector: https://"; + httpsHostname + ":" + 
httpsPort);
+            return true;
+        }
+
+        return false;
+    }
+
+    private ServerConnector createUnconfiguredSslServerConnector(Server 
server, HttpConfiguration httpConfiguration, int port) {
         // add some secure config
         final HttpConfiguration httpsConfiguration = new 
HttpConfiguration(httpConfiguration);
         httpsConfiguration.setSecureScheme("https");
-        httpsConfiguration.setSecurePort(props.getSslPort());
+        httpsConfiguration.setSecurePort(port);
         httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
 
         // build the connector
@@ -990,3 +1035,8 @@ public class JettyServer implements NiFiServer {
         }
     };
 }
+
+@FunctionalInterface
+interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> {
+    ServerConnector create(Server server, HttpConfiguration httpConfiguration);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
new file mode 100644
index 0000000..174efc0
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
@@ -0,0 +1,258 @@
+/*
+ * 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
+
+import org.apache.log4j.AppenderSkeleton
+import org.apache.log4j.spi.LoggingEvent
+import org.apache.nifi.bundle.Bundle
+import org.apache.nifi.properties.StandardNiFiProperties
+import org.apache.nifi.util.NiFiProperties
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.eclipse.jetty.server.Connector
+import org.eclipse.jetty.server.HttpConfiguration
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.server.ServerConnector
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.Assertion
+import org.junit.contrib.java.lang.system.ExpectedSystemExit
+import org.junit.contrib.java.lang.system.SystemErrRule
+import org.junit.contrib.java.lang.system.SystemOutRule
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.security.Security
+
+@RunWith(JUnit4.class)
+class JettyServerGroovyTest extends GroovyTestCase {
+    private static final Logger logger = 
LoggerFactory.getLogger(JettyServerGroovyTest.class)
+
+    @Rule
+    public final ExpectedSystemExit exit = ExpectedSystemExit.none()
+
+    @Rule
+    public final SystemOutRule systemOutRule = new SystemOutRule().enableLog()
+
+    @Rule
+    public final SystemErrRule systemErrRule = new SystemErrRule().enableLog()
+
+    @BeforeClass
+    static void setUpOnce() throws Exception {
+        Security.addProvider(new BouncyCastleProvider())
+
+        logger.metaClass.methodMissing = { String name, args ->
+            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+        }
+
+        TestAppender.reset()
+    }
+
+    @AfterClass
+    static void tearDownOnce() throws Exception {
+
+    }
+
+    @Before
+    void setUp() throws Exception {
+
+    }
+
+    @After
+    void tearDown() throws Exception {
+        TestAppender.reset()
+    }
+
+    @Test
+    void testShouldDetectHttpAndHttpsConfigurationsBothPresent() {
+        // Arrange
+        Map badProps = [
+                (NiFiProperties.WEB_HTTP_HOST) : "localhost",
+                (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
+                (NiFiProperties.WEB_THREADS)   : 
NiFiProperties.DEFAULT_WEB_THREADS
+        ]
+        NiFiProperties mockProps = [
+                getPort    : { -> 8080 },
+                getSslPort : { -> 8443 },
+                getProperty: { String prop ->
+                    String value = badProps[prop] ?: "no_value"
+                    logger.mock("getProperty(${prop}) -> ${value}")
+                    value
+                },
+        ] as StandardNiFiProperties
+
+        // Act
+        boolean bothConfigsPresent = 
JettyServer.bothHttpAndHttpsConnectorsConfigured(mockProps)
+        logger.info("Both configs present: ${bothConfigsPresent}")
+        def log = TestAppender.getLogLines()
+
+        // Assert
+        assert bothConfigsPresent
+        assert !log.isEmpty()
+        assert log.first() =~ "Both the HTTP and HTTPS connectors are 
configured in nifi.properties. Only one of these connectors should be 
configured. See the NiFi Admin Guide for more details"
+    }
+
+    @Test
+    void testDetectHttpAndHttpsConfigurationsShouldAllowEither() {
+        // Arrange
+        Map httpMap = [
+                (NiFiProperties.WEB_HTTP_HOST) : "localhost",
+                (NiFiProperties.WEB_HTTPS_HOST): null,
+        ]
+        NiFiProperties httpProps = [
+                getPort    : { -> 8080 },
+                getSslPort : { -> null },
+                getProperty: { String prop ->
+                    String value = httpMap[prop] ?: "no_value"
+                    logger.mock("getProperty(${prop}) -> ${value}")
+                    value
+                },
+        ] as StandardNiFiProperties
+
+        Map httpsMap = [
+                (NiFiProperties.WEB_HTTP_HOST) : null,
+                (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
+        ]
+        NiFiProperties httpsProps = [
+                getPort    : { -> null },
+                getSslPort : { -> 8443 },
+                getProperty: { String prop ->
+                    String value = httpsMap[prop] ?: "no_value"
+                    logger.mock("getProperty(${prop}) -> ${value}")
+                    value
+                },
+        ] as StandardNiFiProperties
+
+        // Act
+        boolean bothConfigsPresentForHttp = 
JettyServer.bothHttpAndHttpsConnectorsConfigured(httpProps)
+        logger.info("Both configs present for HTTP properties: 
${bothConfigsPresentForHttp}")
+
+        boolean bothConfigsPresentForHttps = 
JettyServer.bothHttpAndHttpsConnectorsConfigured(httpsProps)
+        logger.info("Both configs present for HTTPS properties: 
${bothConfigsPresentForHttps}")
+        def log = TestAppender.getLogLines()
+
+        // Assert
+        assert !bothConfigsPresentForHttp
+        assert !bothConfigsPresentForHttps
+
+        // Verifies that the warning was not logged
+        assert log.size() == 2
+        assert log.first() == "Both configs present for HTTP properties: false"
+        assert log.last() == "Both configs present for HTTPS properties: false"
+    }
+
+    @Test
+    void testShouldFailToStartWithHttpAndHttpsConfigurationsBothPresent() {
+        // Arrange
+        Map badProps = [
+                (NiFiProperties.WEB_HTTP_HOST) : "localhost",
+                (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
+        ]
+        NiFiProperties mockProps = [
+                getPort    : { -> 8080 },
+                getSslPort : { -> 8443 },
+                getProperty: { String prop ->
+                    String value = badProps[prop] ?: "no_value"
+                    logger.mock("getProperty(${prop}) -> ${value}")
+                    value
+                },
+                getWebThreads: { -> NiFiProperties.DEFAULT_WEB_THREADS },
+                getWebMaxHeaderSize: { -> 
NiFiProperties.DEFAULT_WEB_MAX_HEADER_SIZE },
+                isHTTPSConfigured: { -> true }
+        ] as StandardNiFiProperties
+
+        // The web server should fail to start and exit Java
+        exit.expectSystemExitWithStatus(1)
+        exit.checkAssertionAfterwards(new Assertion() {
+            void checkAssertion() {
+                final String standardErr = systemErrRule.getLog()
+                List<String> errLines = standardErr.split("\n")
+
+                assert errLines.any { it =~ "Failed to start web server: "}
+                assert errLines.any { it =~ "Shutting down..."}
+            }
+        })
+        
+        // Act
+        JettyServer jettyServer = new JettyServer(mockProps, [] as Set<Bundle>)
+
+        // Assert
+
+        // Assertions defined above
+    }
+
+    @Test
+    void testShouldConfigureHTTPSConnector() {
+        // Arrange
+       NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: 
new Properties([
+//               (NiFiProperties.WEB_HTTP_PORT): null,
+//               (NiFiProperties.WEB_HTTP_HOST): null,
+               (NiFiProperties.WEB_HTTPS_PORT): "8443",
+               (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
+       ]))
+        
+        Server internalServer = new Server()
+        JettyServer jetty = new JettyServer(internalServer, httpsProps)
+
+        // Act
+       jetty.configureHttpsConnector(internalServer, new HttpConfiguration())
+       List<Connector> connectors = Arrays.asList(internalServer.connectors)
+
+        // Assert
+        assert connectors.size() == 1
+        ServerConnector connector = connectors.first() as ServerConnector
+        assert connector.host == "secure.host.com"
+        assert connector.port == 8443
+    }
+}
+
+class TestAppender extends AppenderSkeleton {
+    static final List<LoggingEvent> events = new ArrayList<>()
+
+    @Override
+    protected void append(LoggingEvent e) {
+        synchronized (events) {
+            events.add(e)
+        }
+    }
+
+    static void reset() {
+        synchronized (events) {
+            events.clear()
+        }
+    }
+
+    @Override
+    void close() {
+    }
+
+    @Override
+    boolean requiresLayout() {
+        return false
+    }
+
+    static List<String> getLogLines() {
+        synchronized (events) {
+            events.collect { LoggingEvent le -> le.getRenderedMessage() }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties
new file mode 100644
index 0000000..4608f9e
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties
@@ -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.
+#
+
+log4j.rootLogger=DEBUG,console,test
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.err
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p 
%c{2}: %m%n
+
+log4j.appender.test=org.apache.nifi.web.server.TestAppender
+log4j.appender.test.layout=org.apache.log4j.PatternLayout
+log4j.appender.test.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: 
%m%n
\ No newline at end of file

Reply via email to