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