You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by el...@apache.org on 2018/05/16 18:43:43 UTC

calcite-avatica git commit: [CALCITE-2284] Allow Jetty Server to be customized before startup

Repository: calcite-avatica
Updated Branches:
  refs/heads/master b9f1193b7 -> 612b80cb2


[CALCITE-2284] Allow Jetty Server to be customized before startup

This allows downstream Avatica users to have fine grained control over
SSL configuration without exposing uncommon settings through the
HttpServer Builder.

Closes #46

Signed-off-by: Josh Elser <el...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite-avatica/commit/612b80cb
Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica/tree/612b80cb
Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica/diff/612b80cb

Branch: refs/heads/master
Commit: 612b80cb2e2dff7913a4d29a252a4c0db28d2095
Parents: b9f1193
Author: Alex Araujo <al...@gmail.com>
Authored: Mon Apr 30 19:32:10 2018 -0500
Committer: Josh Elser <el...@apache.org>
Committed: Wed May 16 13:57:06 2018 -0400

----------------------------------------------------------------------
 .../calcite/avatica/server/HttpServer.java      | 105 +++++++++++++++----
 .../avatica/server/ServerCustomizer.java        |  31 ++++++
 .../server/HttpServerCustomizerTest.java        |  89 ++++++++++++++++
 3 files changed, 205 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/612b80cb/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
index 7ffb181..08f4274 100644
--- a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
+++ b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
@@ -48,7 +48,10 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.security.Principal;
 import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
@@ -75,6 +78,7 @@ public class HttpServer {
   private final AvaticaServerConfiguration config;
   private final Subject subject;
   private final SslContextFactory sslFactory;
+  private final List<ServerCustomizer<Server>> serverCustomizers;
   private final int maxAllowedHeaderSize;
 
   @Deprecated
@@ -134,9 +138,12 @@ public class HttpServer {
    * @param subject The javax.security Subject for the server, or null
    * @param sslFactory A configured SslContextFactory, or null
    */
-  public HttpServer(int port, AvaticaHandler handler, AvaticaServerConfiguration config,
-      Subject subject, SslContextFactory sslFactory) {
-    this(port, handler, config, subject, sslFactory, MAX_ALLOWED_HEADER_SIZE);
+  public HttpServer(int port, AvaticaHandler handler,
+      AvaticaServerConfiguration config, Subject subject,
+      SslContextFactory sslFactory) {
+    this(port, handler, config, subject, sslFactory,
+        Collections.<ServerCustomizer<Server>>emptyList(),
+        MAX_ALLOWED_HEADER_SIZE);
   }
 
   /**
@@ -150,11 +157,29 @@ public class HttpServer {
    */
   public HttpServer(int port, AvaticaHandler handler, AvaticaServerConfiguration config,
       Subject subject, SslContextFactory sslFactory, int maxAllowedHeaderSize) {
+    this(port, handler, config, subject, sslFactory,
+        Collections.<ServerCustomizer<Server>>emptyList(),
+        maxAllowedHeaderSize);
+  }
+
+  /**
+   * Constructs an {@link HttpServer}.
+   * @param port The listen port
+   * @param handler The Handler to run
+   * @param config Optional configuration for the server
+   * @param subject The javax.security Subject for the server, or null
+   * @param sslFactory A configured SslContextFactory, or null
+   * @param maxAllowedHeaderSize A maximum size in bytes that are allowed in an HTTP header
+   */
+  private HttpServer(int port, AvaticaHandler handler, AvaticaServerConfiguration config,
+      Subject subject, SslContextFactory sslFactory,
+      List<ServerCustomizer<Server>> serverCustomizers, int maxAllowedHeaderSize) {
     this.port = port;
     this.handler = handler;
     this.config = config;
     this.subject = subject;
     this.sslFactory = sslFactory;
+    this.serverCustomizers = serverCustomizers;
     this.maxAllowedHeaderSize = maxAllowedHeaderSize;
   }
 
@@ -226,6 +251,11 @@ public class HttpServer {
     handlerList.setHandlers(new Handler[] {avaticaHandler, new DefaultHandler()});
 
     server.setHandler(handlerList);
+    // Apply server customizers
+    for (ServerCustomizer<Server> customizer : this.serverCustomizers) {
+      customizer.customize(server);
+    }
+
     try {
       server.start();
     } catch (Exception e) {
@@ -405,7 +435,7 @@ public class HttpServer {
   /**
    * Builder class for creating instances of {@link HttpServer}.
    */
-  public static class Builder {
+  public static class Builder<T> {
     private int port;
 
     private Service service;
@@ -433,12 +463,23 @@ public class HttpServer {
     private File truststore;
     private String truststorePassword;
 
+    private List<ServerCustomizer<T>> serverCustomizers = Collections.emptyList();
+
     // The maximum size in bytes of an http header the server will read (64KB)
     private int maxAllowedHeaderSize = MAX_ALLOWED_HEADER_SIZE;
 
     public Builder() {}
 
-    public Builder withPort(int port) {
+    /**
+     * Creates a typed Builder for Server customization.
+     * @param <T> The type of HttpServer
+     * @return A typed Builder
+     */
+    public static <T> Builder<T> newBuilder() {
+      return new Builder<>();
+    }
+
+    public Builder<T> withPort(int port) {
       this.port = port;
       return this;
     }
@@ -451,7 +492,7 @@ public class HttpServer {
      * @param serialization The serialization method
      * @return <code>this</code>
      */
-    public Builder withHandler(Service service, Serialization serialization) {
+    public Builder<T> withHandler(Service service, Serialization serialization) {
       this.service = Objects.requireNonNull(service);
       this.serialization = Objects.requireNonNull(serialization);
       return this;
@@ -464,7 +505,7 @@ public class HttpServer {
      * @param handler The handler
      * @return <code>this</code>
      */
-    public Builder withHandler(AvaticaHandler handler) {
+    public Builder<T> withHandler(AvaticaHandler handler) {
       this.handler = Objects.requireNonNull(handler);
       return this;
     }
@@ -475,7 +516,7 @@ public class HttpServer {
      * @param metricsConfig Configuration object for metrics.
      * @return <code>this</code>
      */
-    public Builder withMetricsConfiguration(MetricsSystemConfiguration<?> metricsConfig) {
+    public Builder<T> withMetricsConfiguration(MetricsSystemConfiguration<?> metricsConfig) {
       this.metricsConfig = Objects.requireNonNull(metricsConfig);
       return this;
     }
@@ -488,7 +529,7 @@ public class HttpServer {
      * @param principal A kerberos principal with the realm required.
      * @return <code>this</code>
      */
-    public Builder withSpnego(String principal) {
+    public Builder<T> withSpnego(String principal) {
       return withSpnego(principal, (String[]) null);
     }
 
@@ -504,7 +545,7 @@ public class HttpServer {
      *    should be allowed to authenticate against the server. Can be null.
      * @return <code>this</code>
      */
-    public Builder withSpnego(String principal, String[] additionalAllowedRealms) {
+    public Builder<T> withSpnego(String principal, String[] additionalAllowedRealms) {
       int index = Objects.requireNonNull(principal).lastIndexOf('@');
       if (-1 == index) {
         throw new IllegalArgumentException("Could not find '@' symbol in '" + principal
@@ -525,7 +566,7 @@ public class HttpServer {
      * @param realm The kerberos realm
      * @return <code>this</code>
      */
-    public Builder withSpnego(String principal, String realm) {
+    public Builder<T> withSpnego(String principal, String realm) {
       return this.withSpnego(principal, realm, null);
     }
 
@@ -543,7 +584,7 @@ public class HttpServer {
      *    should be allowed to authenticate against the server. Can be null.
      * @return <code>this</code>
      */
-    public Builder withSpnego(String principal, String realm, String[] additionalAllowedRealms) {
+    public Builder<T> withSpnego(String principal, String realm, String[] additionalAllowedRealms) {
       this.authenticationType = AuthenticationType.SPNEGO;
       this.kerberosPrincipal = Objects.requireNonNull(principal);
       this.kerberosRealm = Objects.requireNonNull(realm);
@@ -557,7 +598,7 @@ public class HttpServer {
      * @param keytab A KeyTab file for the server's login.
      * @return <code>this</code>
      */
-    public Builder withAutomaticLogin(File keytab) {
+    public Builder<T> withAutomaticLogin(File keytab) {
       this.keytab = Objects.requireNonNull(keytab);
       return this;
     }
@@ -569,7 +610,7 @@ public class HttpServer {
      * @param remoteUserCallback User-provided implementation of the callback
      * @return <code>this</code>
      */
-    public Builder withImpersonation(DoAsRemoteUserCallback remoteUserCallback) {
+    public Builder<T> withImpersonation(DoAsRemoteUserCallback remoteUserCallback) {
       this.remoteUserCallback = Objects.requireNonNull(remoteUserCallback);
       return this;
     }
@@ -599,7 +640,7 @@ public class HttpServer {
      * @param allowedRoles An array of allowed roles in the properties file
      * @return <code>this</code>
      */
-    public Builder withBasicAuthentication(String properties, String[] allowedRoles) {
+    public Builder<T> withBasicAuthentication(String properties, String[] allowedRoles) {
       return withAuthentication(AuthenticationType.BASIC, properties, allowedRoles);
     }
 
@@ -614,11 +655,11 @@ public class HttpServer {
      * @param allowedRoles An array of allowed roles in the properties file
      * @return <code>this</code>
      */
-    public Builder withDigestAuthentication(String properties, String[] allowedRoles) {
+    public Builder<T> withDigestAuthentication(String properties, String[] allowedRoles) {
       return withAuthentication(AuthenticationType.DIGEST, properties, allowedRoles);
     }
 
-    private Builder withAuthentication(AuthenticationType authType, String properties,
+    private Builder<T> withAuthentication(AuthenticationType authType, String properties,
         String[] allowedRoles) {
       this.loginServiceRealm = "Avatica";
       this.authenticationType = authType;
@@ -636,7 +677,7 @@ public class HttpServer {
      * @param truststorePassword The truststore's password
      * @return <code>this</code>
      */
-    public Builder withTLS(File keystore, String keystorePassword, File truststore,
+    public Builder<T> withTLS(File keystore, String keystorePassword, File truststore,
         String truststorePassword) {
       this.usingTLS = true;
       this.keystore = Objects.requireNonNull(keystore);
@@ -647,12 +688,29 @@ public class HttpServer {
     }
 
     /**
+     * Adds customizers to configure a Server before startup.
+     *
+     * @param serverCustomizers The customizers to use
+     * @param clazz The type of server to customize
+     * @return <code>this</code>
+     */
+    public Builder<T> withServerCustomizers(List<ServerCustomizer<T>> serverCustomizers,
+        Class<T> clazz) {
+      Objects.requireNonNull(clazz);
+      if (!clazz.isAssignableFrom(Server.class)) {
+        throw new IllegalArgumentException("Only Jetty Server customizers are supported");
+      }
+      this.serverCustomizers = Objects.requireNonNull(serverCustomizers);
+      return this;
+    }
+
+    /**
      * Configures the maximum size, in bytes, of an HTTP header that the server will read.
      *
      * @param maxHeaderSize Maximums HTTP header size in bytes
      * @return <code>this</code>
      */
-    public Builder withMaxHeaderSize(int maxHeaderSize) {
+    public Builder<T> withMaxHeaderSize(int maxHeaderSize) {
       this.maxAllowedHeaderSize = maxHeaderSize;
       return this;
     }
@@ -661,6 +719,7 @@ public class HttpServer {
      * Builds the HttpServer instance from <code>this</code>.
      * @return An HttpServer.
      */
+    @SuppressWarnings("unchecked")
     public HttpServer build() {
       final AvaticaServerConfiguration serverConfig;
       final Subject subject;
@@ -702,7 +761,13 @@ public class HttpServer {
         sslFactory.setTrustStorePassword(truststorePassword);
       }
 
-      return new HttpServer(port, handler, serverConfig, subject, sslFactory,
+      List<ServerCustomizer<Server>> jettyCustomizers = new ArrayList<>();
+      for (ServerCustomizer<?> customizer : this.serverCustomizers) {
+        // Type checked in withServerCustomizers
+        jettyCustomizers.add((ServerCustomizer<Server>) customizer);
+      }
+
+      return new HttpServer(port, handler, serverConfig, subject, sslFactory, jettyCustomizers,
           maxAllowedHeaderSize);
     }
 

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/612b80cb/server/src/main/java/org/apache/calcite/avatica/server/ServerCustomizer.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/avatica/server/ServerCustomizer.java b/server/src/main/java/org/apache/calcite/avatica/server/ServerCustomizer.java
new file mode 100644
index 0000000..1070ea6
--- /dev/null
+++ b/server/src/main/java/org/apache/calcite/avatica/server/ServerCustomizer.java
@@ -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.calcite.avatica.server;
+
+/**
+ * Callback for customizing a Server.
+ * @param <T> Type of server
+ */
+public interface ServerCustomizer<T> {
+  /**
+   * Customize the server during initialization.
+   * @param server The server to customize
+   */
+  void customize(T server);
+}
+
+// End ServerCustomizer.java

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/612b80cb/server/src/test/java/org/apache/calcite/avatica/server/HttpServerCustomizerTest.java
----------------------------------------------------------------------
diff --git a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerCustomizerTest.java b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerCustomizerTest.java
new file mode 100644
index 0000000..a525682
--- /dev/null
+++ b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerCustomizerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.calcite.avatica.server;
+
+import org.apache.calcite.avatica.Meta;
+import org.apache.calcite.avatica.remote.Driver;
+import org.apache.calcite.avatica.remote.LocalService;
+import org.apache.calcite.avatica.remote.Service;
+
+import org.eclipse.jetty.server.Server;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * HTTP server customizer tests
+ */
+public class HttpServerCustomizerTest {
+
+  private static Meta mockMeta = mock(Meta.class);
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @SuppressWarnings("unchecked") // needed for the mocked customizers, not the builder
+  @Test public void serverCustomizersInvoked() {
+    ServerCustomizer<Server> mockCustomizer1 =
+        (ServerCustomizer<Server>) mock(ServerCustomizer.class);
+    ServerCustomizer<Server> mockCustomizer2 =
+        (ServerCustomizer<Server>) mock(ServerCustomizer.class);
+    Service service = new LocalService(mockMeta);
+    HttpServer server =
+        HttpServer.Builder.<Server>newBuilder().withHandler(service, Driver.Serialization.PROTOBUF)
+            .withServerCustomizers(Arrays.asList(mockCustomizer1, mockCustomizer2), Server.class)
+            .withPort(0).build();
+    try {
+      server.start();
+      verify(mockCustomizer2).customize(any(Server.class));
+      verify(mockCustomizer1).customize(any(Server.class));
+    } finally {
+      server.stop();
+    }
+  }
+
+  @Test public void onlyJettyCustomizersAllowed() {
+    Service service = new LocalService(mockMeta);
+    List<ServerCustomizer<UnsupportedServer>> unsupportedCustomizers = new ArrayList<>();
+    unsupportedCustomizers.add(new ServerCustomizer<UnsupportedServer>() {
+      @Override public void customize(UnsupportedServer server) {
+      }
+    });
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Only Jetty Server customizers are supported");
+    HttpServer.Builder.<UnsupportedServer>newBuilder()
+        .withHandler(service, Driver.Serialization.PROTOBUF)
+        .withServerCustomizers(unsupportedCustomizers, UnsupportedServer.class).withPort(0).build();
+  }
+
+  /**
+   * A server type that cannot be customized
+   */
+  private static class UnsupportedServer {
+  }
+}
+
+// End HttpServerCustomizerTest.java