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 2015/12/02 19:06:09 UTC

[3/3] calcite git commit: [CALCITE-989] Add server's address in each response

[CALCITE-989] Add server's address in each response

If the clients knows what server processed a response, the
client do perform their own load-balancing.

Fix some server Handler class hierarchy to make more sense
supporting both json and protobuf.


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

Branch: refs/heads/master
Commit: 3be816f450450e8e43286e6ea208af6e30520146
Parents: 81b4876
Author: Josh Elser <el...@apache.org>
Authored: Tue Nov 24 19:03:44 2015 -0500
Committer: Josh Elser <el...@apache.org>
Committed: Wed Dec 2 13:03:51 2015 -0500

----------------------------------------------------------------------
 .../calcite/avatica/server/AvaticaHandler.java  |   60 +-
 .../avatica/server/AvaticaJsonHandler.java      |   93 +
 .../avatica/server/AvaticaProtobufHandler.java  |   17 +-
 .../server/DelegatingAvaticaHandler.java        |  126 +
 .../calcite/avatica/server/HandlerFactory.java  |    2 +-
 .../calcite/avatica/server/HttpServer.java      |   47 +-
 .../org/apache/calcite/avatica/server/Main.java |    2 +-
 .../remote/AlternatingRemoteMetaTest.java       |    4 +-
 .../calcite/avatica/remote/RemoteMetaTest.java  |   68 +-
 .../avatica/server/HandlerFactoryTest.java      |    2 +-
 .../avatica/AvaticaClientRuntimeException.java  |   10 +-
 .../calcite/avatica/AvaticaSqlException.java    |   12 +-
 .../java/org/apache/calcite/avatica/Helper.java |    6 +-
 .../apache/calcite/avatica/proto/Responses.java | 3110 +++++++++++++++++-
 .../calcite/avatica/remote/AbstractHandler.java |    9 +-
 .../calcite/avatica/remote/AbstractService.java |   14 +-
 .../apache/calcite/avatica/remote/Handler.java  |    9 +
 .../calcite/avatica/remote/LocalService.java    |   44 +-
 .../avatica/remote/MockProtobufService.java     |   12 +-
 .../avatica/remote/ProtobufTranslationImpl.java |    4 +
 .../apache/calcite/avatica/remote/Service.java  |  506 ++-
 avatica/src/main/protobuf/responses.proto       |   21 +-
 .../avatica/remote/ErrorResponseTest.java       |   11 +-
 .../avatica/remote/ProtobufHandlerTest.java     |    4 +-
 .../remote/ProtobufTranslationImplTest.java     |   35 +-
 .../test/AvaticaClientRuntimeExceptionTest.java |    5 +-
 .../avatica/test/AvaticaSqlExceptionTest.java   |    5 +-
 .../calcite/avatica/test/JsonHandlerTest.java   |    7 +-
 28 files changed, 4001 insertions(+), 244 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
index f7c4800..d5501b9 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
@@ -16,65 +16,15 @@
  */
 package org.apache.calcite.avatica.server;
 
-import org.apache.calcite.avatica.AvaticaUtils;
-import org.apache.calcite.avatica.remote.Handler.HandlerResponse;
-import org.apache.calcite.avatica.remote.JsonHandler;
-import org.apache.calcite.avatica.remote.Service;
+import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-
-import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Handler;
 
 /**
- * Jetty handler that executes Avatica JSON request-responses.
+ * A custom interface that extends the Jetty interface to enable extra control within Avatica.
  */
-public class AvaticaHandler extends AbstractHandler {
-  private static final Log LOG = LogFactory.getLog(AvaticaHandler.class);
-
-  final JsonHandler jsonHandler;
+public interface AvaticaHandler extends Handler {
 
-  public AvaticaHandler(Service service) {
-    this.jsonHandler = new JsonHandler(service);
-  }
+  void setServerRpcMetadata(RpcMetadataResponse metadata);
 
-  public void handle(String target, Request baseRequest,
-      HttpServletRequest request, HttpServletResponse response)
-      throws IOException, ServletException {
-    response.setContentType("application/json;charset=utf-8");
-    response.setStatus(HttpServletResponse.SC_OK);
-    if (request.getMethod().equals("POST")) {
-      // First look for a request in the header, then look in the body.
-      // The latter allows very large requests without hitting HTTP 413.
-      String rawRequest = request.getHeader("request");
-      if (rawRequest == null) {
-        try (ServletInputStream inputStream = request.getInputStream()) {
-          rawRequest = AvaticaUtils.readFully(inputStream);
-        }
-      }
-      final String jsonRequest =
-          new String(rawRequest.getBytes("ISO-8859-1"), "UTF-8");
-      if (LOG.isTraceEnabled()) {
-        LOG.trace("request: " + jsonRequest);
-      }
-
-      final HandlerResponse<String> jsonResponse = jsonHandler.apply(jsonRequest);
-      if (LOG.isTraceEnabled()) {
-        LOG.trace("response: " + jsonResponse);
-      }
-      baseRequest.setHandled(true);
-      // Set the status code and write out the response.
-      response.setStatus(jsonResponse.getStatusCode());
-      response.getWriter().println(jsonResponse.getResponse());
-    }
-  }
 }
-
-// End AvaticaHandler.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaJsonHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaJsonHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaJsonHandler.java
new file mode 100644
index 0000000..106d53d
--- /dev/null
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaJsonHandler.java
@@ -0,0 +1,93 @@
+/*
+ * 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.AvaticaUtils;
+import org.apache.calcite.avatica.remote.Handler.HandlerResponse;
+import org.apache.calcite.avatica.remote.JsonHandler;
+import org.apache.calcite.avatica.remote.Service;
+import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Jetty handler that executes Avatica JSON request-responses.
+ */
+public class AvaticaJsonHandler extends AbstractHandler implements AvaticaHandler {
+  private static final Log LOG = LogFactory.getLog(AvaticaJsonHandler.class);
+
+  final Service service;
+  final JsonHandler jsonHandler;
+
+  public AvaticaJsonHandler(Service service) {
+    this.service = Objects.requireNonNull(service);
+    this.jsonHandler = new JsonHandler(service);
+  }
+
+  public void handle(String target, Request baseRequest,
+      HttpServletRequest request, HttpServletResponse response)
+      throws IOException, ServletException {
+    response.setContentType("application/json;charset=utf-8");
+    response.setStatus(HttpServletResponse.SC_OK);
+    if (request.getMethod().equals("POST")) {
+      // First look for a request in the header, then look in the body.
+      // The latter allows very large requests without hitting HTTP 413.
+      String rawRequest = request.getHeader("request");
+      if (rawRequest == null) {
+        try (ServletInputStream inputStream = request.getInputStream()) {
+          rawRequest = AvaticaUtils.readFully(inputStream);
+        }
+      }
+      final String jsonRequest =
+          new String(rawRequest.getBytes("ISO-8859-1"), "UTF-8");
+      if (LOG.isTraceEnabled()) {
+        LOG.trace("request: " + jsonRequest);
+      }
+
+      final HandlerResponse<String> jsonResponse = jsonHandler.apply(jsonRequest);
+      if (LOG.isTraceEnabled()) {
+        LOG.trace("response: " + jsonResponse);
+      }
+      baseRequest.setHandled(true);
+      // Set the status code and write out the response.
+      response.setStatus(jsonResponse.getStatusCode());
+      response.getWriter().println(jsonResponse.getResponse());
+    }
+  }
+
+  @Override
+  public void setServerRpcMetadata(RpcMetadataResponse metadata) {
+    // Set the metadata for the normal service calls
+    service.setRpcMetadata(metadata);
+    // Also add it to the handler to include with exceptions
+    jsonHandler.setRpcMetadata(metadata);
+  }
+}
+
+// End AvaticaHandler.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
index 9dc6df0..bd4a8c9 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
@@ -22,6 +22,7 @@ import org.apache.calcite.avatica.remote.ProtobufHandler;
 import org.apache.calcite.avatica.remote.ProtobufTranslation;
 import org.apache.calcite.avatica.remote.ProtobufTranslationImpl;
 import org.apache.calcite.avatica.remote.Service;
+import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -30,6 +31,8 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 
 import java.io.IOException;
+import java.util.Objects;
+
 import javax.servlet.ServletException;
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.HttpServletRequest;
@@ -38,14 +41,16 @@ import javax.servlet.http.HttpServletResponse;
 /**
  * Jetty handler that executes Avatica JSON request-responses.
  */
-public class AvaticaProtobufHandler extends AbstractHandler {
-  private static final Log LOG = LogFactory.getLog(AvaticaHandler.class);
+public class AvaticaProtobufHandler extends AbstractHandler implements AvaticaHandler {
+  private static final Log LOG = LogFactory.getLog(AvaticaJsonHandler.class);
 
+  private final Service service;
   private final ProtobufHandler pbHandler;
   private final ProtobufTranslation protobufTranslation;
 
   public AvaticaProtobufHandler(Service service) {
     this.protobufTranslation = new ProtobufTranslationImpl();
+    this.service = Objects.requireNonNull(service);
     this.pbHandler = new ProtobufHandler(service, protobufTranslation);
   }
 
@@ -67,6 +72,14 @@ public class AvaticaProtobufHandler extends AbstractHandler {
       response.getOutputStream().write(handlerResponse.getResponse());
     }
   }
+
+  @Override
+  public void setServerRpcMetadata(RpcMetadataResponse metadata) {
+    // Set the metadata for the normal service calls
+    service.setRpcMetadata(metadata);
+    // Also add it to the handler to include with exceptions
+    pbHandler.setRpcMetadata(metadata);
+  }
 }
 
 // End AvaticaProtobufHandler.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/DelegatingAvaticaHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/DelegatingAvaticaHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/DelegatingAvaticaHandler.java
new file mode 100644
index 0000000..4b2457c
--- /dev/null
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/DelegatingAvaticaHandler.java
@@ -0,0 +1,126 @@
+/*
+ * 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.remote.Service.RpcMetadataResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * An AvaticaHandler implementation which delegates to a provided Jetty Handler instance.
+ *
+ * This implementation provides a no-op implementation for
+ * {@link #setServerRpcMetadata(RpcMetadataResponse)}.
+ */
+public class DelegatingAvaticaHandler implements AvaticaHandler {
+  private static final Log LOG = LogFactory.getLog(DelegatingAvaticaHandler.class);
+
+  private final Handler handler;
+
+  public DelegatingAvaticaHandler(Handler handler) {
+    this.handler = Objects.requireNonNull(handler);
+  }
+
+  @Override
+  public void handle(String target, Request baseRequest, HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+    handler.handle(target, baseRequest, request, response);
+  }
+
+  @Override
+  public void setServer(Server server) {
+    handler.setServer(server);
+  }
+
+  @Override
+  public Server getServer() {
+    return handler.getServer();
+  }
+
+  @Override
+  public void destroy() {
+    handler.destroy();
+  }
+
+  @Override
+  public void start() throws Exception {
+    handler.start();
+  }
+
+  @Override
+  public void stop() throws Exception {
+    handler.stop();
+  }
+
+  @Override
+  public boolean isRunning() {
+    return handler.isRunning();
+  }
+
+  @Override
+  public boolean isStarted() {
+    return handler.isStarted();
+  }
+
+  @Override
+  public boolean isStarting() {
+    return handler.isStarting();
+  }
+
+  @Override
+  public boolean isStopping() {
+    return handler.isStopping();
+  }
+
+  @Override
+  public boolean isStopped() {
+    return handler.isStopped();
+  }
+
+  @Override
+  public boolean isFailed() {
+    return handler.isFailed();
+  }
+
+  @Override
+  public void addLifeCycleListener(Listener listener) {
+    handler.addLifeCycleListener(listener);
+  }
+
+  @Override
+  public void removeLifeCycleListener(Listener listener) {
+    handler.removeLifeCycleListener(listener);
+  }
+
+  @Override
+  public void setServerRpcMetadata(RpcMetadataResponse metadata) {
+    LOG.warn("Setting RpcMetadata is not implemented for DelegatingAvaticaHandler");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/HandlerFactory.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/HandlerFactory.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/HandlerFactory.java
index c98f593..724626d 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/HandlerFactory.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/HandlerFactory.java
@@ -35,7 +35,7 @@ public class HandlerFactory {
   public Handler getHandler(Service service, Driver.Serialization serialization) {
     switch (serialization) {
     case JSON:
-      return new AvaticaHandler(service);
+      return new AvaticaJsonHandler(service);
     case PROTOBUF:
       return new AvaticaProtobufHandler(service);
     default:

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
index 161a752..cffbf60 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.avatica.server;
 
+import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -27,6 +29,9 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerList;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
 /**
  * Avatica HTTP server.
  *
@@ -38,17 +43,35 @@ public class HttpServer {
 
   private Server server;
   private int port = -1;
-  private final Handler handler;
+  private final AvaticaHandler handler;
 
+  @Deprecated
   public HttpServer(Handler handler) {
+    this(wrapJettyHandler(handler));
+  }
+
+  public HttpServer(AvaticaHandler handler) {
     this(0, handler);
   }
 
+  @Deprecated
   public HttpServer(int port, Handler handler) {
+    this(port, wrapJettyHandler(handler));
+  }
+
+  public HttpServer(int port, AvaticaHandler handler) {
     this.port = port;
     this.handler = handler;
   }
 
+  private static AvaticaHandler wrapJettyHandler(Handler handler) {
+    if (handler instanceof AvaticaHandler) {
+      return (AvaticaHandler) handler;
+    }
+    // Backwards compatibility, noop's the AvaticaHandler interface
+    return new DelegatingAvaticaHandler(handler);
+  }
+
   public void start() {
     if (server != null) {
       throw new RuntimeException("Server is already started");
@@ -74,6 +97,28 @@ public class HttpServer {
     port = connector.getLocalPort();
 
     LOG.info("Service listening on port " + getPort() + ".");
+
+    // Set the information about the address for this server
+    try {
+      this.handler.setServerRpcMetadata(createRpcServerMetadata(connector));
+    } catch (UnknownHostException e) {
+      // Failed to do the DNS lookup, bail out.
+      throw new RuntimeException(e);
+    }
+  }
+
+  private RpcMetadataResponse createRpcServerMetadata(ServerConnector connector) throws
+      UnknownHostException {
+    String host = connector.getHost();
+    if (null == host) {
+      // "null" means binding to all interfaces, we need to pick one so the client gets a real
+      // address and not "0.0.0.0" or similar.
+      host = InetAddress.getLocalHost().getHostName();
+    }
+
+    final int port = connector.getLocalPort();
+
+    return new RpcMetadataResponse(String.format("%s:%d", host, port));
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/main/java/org/apache/calcite/avatica/server/Main.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/Main.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/Main.java
index 82691f6..8b05931 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/Main.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/Main.java
@@ -46,7 +46,7 @@ public class Main {
 
   private static final HandlerFactory JSON_HANDLER_FACTORY = new HandlerFactory() {
     public AbstractHandler createHandler(Service service) {
-      return new AvaticaHandler(service);
+      return new AvaticaJsonHandler(service);
     }
   };
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/test/java/org/apache/calcite/avatica/remote/AlternatingRemoteMetaTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/AlternatingRemoteMetaTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/AlternatingRemoteMetaTest.java
index df2862d..ffd6f7c 100644
--- a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/AlternatingRemoteMetaTest.java
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/AlternatingRemoteMetaTest.java
@@ -23,7 +23,7 @@ import org.apache.calcite.avatica.ConnectionPropertiesImpl;
 import org.apache.calcite.avatica.ConnectionSpec;
 import org.apache.calcite.avatica.Meta;
 import org.apache.calcite.avatica.jdbc.JdbcMeta;
-import org.apache.calcite.avatica.server.AvaticaHandler;
+import org.apache.calcite.avatica.server.AvaticaJsonHandler;
 import org.apache.calcite.avatica.server.HttpServer;
 import org.apache.calcite.avatica.server.Main;
 import org.apache.calcite.avatica.server.Main.HandlerFactory;
@@ -193,7 +193,7 @@ public class AlternatingRemoteMetaTest {
       }
       HttpServer jsonServer = Main.start(mainArgs, 0, new HandlerFactory() {
         @Override public AbstractHandler createHandler(Service service) {
-          return new AvaticaHandler(service);
+          return new AvaticaJsonHandler(service);
         }
       });
       ACTIVE_SERVERS.add(jsonServer);

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
index 6cb4508..fbc91e6 100644
--- a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
@@ -24,7 +24,8 @@ import org.apache.calcite.avatica.ConnectionSpec;
 import org.apache.calcite.avatica.Meta;
 import org.apache.calcite.avatica.jdbc.JdbcMeta;
 import org.apache.calcite.avatica.remote.Service.ErrorResponse;
-import org.apache.calcite.avatica.server.AvaticaHandler;
+import org.apache.calcite.avatica.remote.Service.Response;
+import org.apache.calcite.avatica.server.AvaticaJsonHandler;
 import org.apache.calcite.avatica.server.AvaticaProtobufHandler;
 import org.apache.calcite.avatica.server.HttpServer;
 import org.apache.calcite.avatica.server.Main;
@@ -34,8 +35,6 @@ import org.apache.calcite.avatica.util.ArrayImpl;
 import com.google.common.base.Throwables;
 import com.google.common.cache.Cache;
 
-import org.eclipse.jetty.server.handler.AbstractHandler;
-
 import org.junit.AfterClass;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -45,6 +44,8 @@ import org.junit.runners.Parameterized.Parameters;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.sql.Array;
 import java.sql.Connection;
@@ -55,6 +56,7 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
@@ -66,6 +68,7 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -81,6 +84,8 @@ public class RemoteMetaTest {
 
   private final HttpServer server;
   private final String url;
+  private final int port;
+  private final Driver.Serialization serialization;
 
   @Parameters
   public static List<Object[]> parameters() throws Exception {
@@ -91,15 +96,15 @@ public class RemoteMetaTest {
     // Bind to '0' to pluck an ephemeral port instead of expecting a certain one to be free
 
     final HttpServer jsonServer = Main.start(mainArgs, 0, new HandlerFactory() {
-      @Override public AbstractHandler createHandler(Service service) {
-        return new AvaticaHandler(service);
+      @Override public AvaticaJsonHandler createHandler(Service service) {
+        return new AvaticaJsonHandler(service);
       }
     });
     params.add(new Object[] {jsonServer, Driver.Serialization.JSON});
     ACTIVE_SERVERS.add(jsonServer);
 
     final HttpServer protobufServer = Main.start(mainArgs, 0, new HandlerFactory() {
-      @Override public AbstractHandler createHandler(Service service) {
+      @Override public AvaticaProtobufHandler createHandler(Service service) {
         return new AvaticaProtobufHandler(service);
       }
     });
@@ -112,7 +117,8 @@ public class RemoteMetaTest {
 
   public RemoteMetaTest(HttpServer server, Driver.Serialization serialization) {
     this.server = server;
-    final int port = server.getPort();
+    this.port = this.server.getPort();
+    this.serialization = serialization;
     url = "jdbc:avatica:remote:url=http://localhost:" + port + ";serialization="
         + serialization.name();
   }
@@ -452,6 +458,54 @@ public class RemoteMetaTest {
     }
   }
 
+  @Test public void testServerAddressInResponse() throws Exception {
+    ConnectionSpec.getDatabaseLock().lock();
+    try {
+      URL url = new URL("http://localhost:" + this.port);
+      AvaticaHttpClient httpClient = new AvaticaHttpClientImpl(url);
+      byte[] request;
+
+      Service.OpenConnectionRequest jsonReq = new Service.OpenConnectionRequest(
+          UUID.randomUUID().toString(), Collections.<String, String>emptyMap());
+      switch (this.serialization) {
+      case JSON:
+        request = JsonService.MAPPER.writeValueAsBytes(jsonReq);
+        break;
+      case PROTOBUF:
+        ProtobufTranslation pbTranslation = new ProtobufTranslationImpl();
+        request = pbTranslation.serializeRequest(jsonReq);
+        break;
+      default:
+        throw new IllegalStateException("Should not reach here");
+      }
+
+      byte[] response = httpClient.send(request);
+      Service.OpenConnectionResponse openCnxnResp;
+      switch (this.serialization) {
+      case JSON:
+        openCnxnResp = JsonService.MAPPER.readValue(response,
+            Service.OpenConnectionResponse.class);
+        break;
+      case PROTOBUF:
+        ProtobufTranslation pbTranslation = new ProtobufTranslationImpl();
+        Response genericResp = pbTranslation.parseResponse(response);
+        assertTrue("Expected an OpenConnnectionResponse, but got " + genericResp.getClass(),
+            genericResp instanceof Service.OpenConnectionResponse);
+        openCnxnResp = (Service.OpenConnectionResponse) genericResp;
+        break;
+      default:
+        throw new IllegalStateException("Should not reach here");
+      }
+
+      String hostname = InetAddress.getLocalHost().getHostName();
+
+      assertNotNull(openCnxnResp.rpcMetadata);
+      assertEquals(hostname + ":" + this.port, openCnxnResp.rpcMetadata.serverAddress);
+    } finally {
+      ConnectionSpec.getDatabaseLock().unlock();
+    }
+  }
+
   /** Factory that provides a {@link JdbcMeta}. */
   public static class FullyRemoteJdbcMetaFactory implements Meta.Factory {
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica-server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java
index 13e9f7d..3504e02 100644
--- a/avatica-server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java
@@ -44,7 +44,7 @@ public class HandlerFactoryTest {
   public void testJson() {
     Handler handler = factory.getHandler(service, Serialization.JSON);
     assertTrue("Expected an implementation of the AvaticaHandler, "
-        + "but got " + handler.getClass(), handler instanceof AvaticaHandler);
+        + "but got " + handler.getClass(), handler instanceof AvaticaJsonHandler);
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
index 6859763..df03b03 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
@@ -18,6 +18,7 @@ package org.apache.calcite.avatica;
 
 import org.apache.calcite.avatica.remote.AvaticaRuntimeException;
 import org.apache.calcite.avatica.remote.Service.ErrorResponse;
+import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
 
 import java.util.Collections;
 import java.util.List;
@@ -36,14 +37,16 @@ public class AvaticaClientRuntimeException extends RuntimeException {
   private final String sqlState;
   private final AvaticaSeverity severity;
   private final List<String> serverExceptions;
+  private final RpcMetadataResponse metadata;
 
   public AvaticaClientRuntimeException(String errorMessage, int errorCode, String sqlState,
-      AvaticaSeverity severity, List<String> serverExceptions) {
+      AvaticaSeverity severity, List<String> serverExceptions, RpcMetadataResponse metadata) {
     super(errorMessage);
     this.errorCode = errorCode;
     this.sqlState = sqlState;
     this.severity = severity;
     this.serverExceptions = serverExceptions;
+    this.metadata = metadata;
   }
 
   public AvaticaClientRuntimeException(String message, Throwable cause) {
@@ -52,6 +55,7 @@ public class AvaticaClientRuntimeException extends RuntimeException {
     sqlState = ErrorResponse.UNKNOWN_SQL_STATE;
     severity = AvaticaSeverity.UNKNOWN;
     serverExceptions = Collections.singletonList("");
+    metadata = null;
   }
 
   public int getErrorCode() {
@@ -70,6 +74,10 @@ public class AvaticaClientRuntimeException extends RuntimeException {
     return serverExceptions;
   }
 
+  public RpcMetadataResponse getRpcMetadata() {
+    return metadata;
+  }
+
   @Override public String toString() {
     StringBuilder sb = new StringBuilder(64);
     sb.append(getClass().getSimpleName()).append(": ")

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
index cfcf305..9408a7b 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
@@ -31,6 +31,7 @@ public class AvaticaSqlException extends SQLException {
 
   private final String errorMessage;
   private final List<String> stackTraces;
+  private final String remoteServer;
 
   /**
    * Construct the Exception with information from the server.
@@ -38,12 +39,14 @@ public class AvaticaSqlException extends SQLException {
    * @param errorMessage A human-readable error message.
    * @param errorCode An integer corresponding to a known error.
    * @param stackTraces Server-side stacktrace.
+   * @param remoteServer The host:port where the Avatica server is located
    */
   public AvaticaSqlException(String errorMessage, String sqlState, int errorCode,
-      List<String> stackTraces) {
+      List<String> stackTraces, String remoteServer) {
     super("Error " + errorCode + " (" + sqlState + ") : " + errorMessage, sqlState, errorCode);
     this.errorMessage = errorMessage;
     this.stackTraces = Objects.requireNonNull(stackTraces);
+    this.remoteServer = remoteServer;
   }
 
   public String getErrorMessage() {
@@ -57,6 +60,13 @@ public class AvaticaSqlException extends SQLException {
     return stackTraces;
   }
 
+  /**
+   * @return The host:port for the remote Avatica server. May be null.
+   */
+  public String getRemoteServer() {
+    return remoteServer;
+  }
+
   // printStackTrace() will get redirected to printStackTrace(PrintStream), don't need to override.
 
   @Override public void printStackTrace(PrintStream stream) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/3be816f4/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/Helper.java b/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
index 54be099..27c6056 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
@@ -46,8 +46,12 @@ public class Helper {
       // The AvaticaClientRuntimeException contains extra information about what/why
       // the exception was thrown that we can pass back to the user.
       AvaticaClientRuntimeException rte = (AvaticaClientRuntimeException) e;
+      String serverAddress = null;
+      if (null != rte.getRpcMetadata()) {
+        serverAddress = rte.getRpcMetadata().serverAddress;
+      }
       return new AvaticaSqlException(message, rte.getSqlState(), rte.getErrorCode(),
-          rte.getServerExceptions());
+          rte.getServerExceptions(), serverAddress);
     }
     return new SQLException(message, e);
   }