You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by wf...@apache.org on 2015/09/16 04:55:52 UTC

aurora git commit: Convert all of our servlet implementations to jax-rs endpoints.

Repository: aurora
Updated Branches:
  refs/heads/master 44e472642 -> 9dff05714


Convert all of our servlet implementations to jax-rs endpoints.

Reviewed at https://reviews.apache.org/r/38332/


Project: http://git-wip-us.apache.org/repos/asf/aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/aurora/commit/9dff0571
Tree: http://git-wip-us.apache.org/repos/asf/aurora/tree/9dff0571
Diff: http://git-wip-us.apache.org/repos/asf/aurora/diff/9dff0571

Branch: refs/heads/master
Commit: 9dff05714b3e317f9473105a6ebbcd4d1dee100d
Parents: 44e4726
Author: Bill Farner <wf...@apache.org>
Authored: Tue Sep 15 19:55:46 2015 -0700
Committer: Bill Farner <wf...@apache.org>
Committed: Tue Sep 15 19:55:46 2015 -0700

----------------------------------------------------------------------
 .../aurora/build/CoverageReportCheck.groovy     |   8 +-
 .../common/net/http/handlers/AbortHandler.java  |  27 +-
 .../common/net/http/handlers/AssetHandler.java  | 191 ----------
 .../net/http/handlers/ContentionPrinter.java    |  26 +-
 .../common/net/http/handlers/HealthHandler.java |  35 +-
 .../common/net/http/handlers/LogConfig.java     |  83 ++--
 .../common/net/http/handlers/QuitHandler.java   |  29 +-
 .../http/handlers/StringTemplateServlet.java    |  96 -----
 .../net/http/handlers/TextResponseHandler.java  |  58 ---
 .../net/http/handlers/ThreadStackPrinter.java   |  21 +-
 .../net/http/handlers/TimeSeriesDataSource.java |  35 +-
 .../common/net/http/handlers/VarsHandler.java   |  16 +-
 .../net/http/handlers/VarsJsonHandler.java      |  33 +-
 .../net/http/handlers/AssetHandlerTest.java     | 378 -------------------
 .../net/http/handlers/VarsHandlerTest.java      |   7 +-
 config/legacy_untested_classes.txt              |   2 +-
 .../scheduler/http/JettyServerModule.java       |  97 +++--
 17 files changed, 216 insertions(+), 926 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/buildSrc/src/main/groovy/org/apache/aurora/build/CoverageReportCheck.groovy
----------------------------------------------------------------------
diff --git a/buildSrc/src/main/groovy/org/apache/aurora/build/CoverageReportCheck.groovy b/buildSrc/src/main/groovy/org/apache/aurora/build/CoverageReportCheck.groovy
index b996d45..b471756 100644
--- a/buildSrc/src/main/groovy/org/apache/aurora/build/CoverageReportCheck.groovy
+++ b/buildSrc/src/main/groovy/org/apache/aurora/build/CoverageReportCheck.groovy
@@ -85,13 +85,15 @@ class CoverageReportCheck extends DefaultTask {
       def isAnonymous = { c -> c.@name ==~ /.*\$\d+/ }
       def methodFilter = isAnonymous(cls) ? { m -> m.@name != '<init>' } : { true }
 
-      def matchedMethods = cls.method.findAll(methodFilter)
-      if (isAnonymous(cls) && matchedMethods.isEmpty()) {
+      // Always ignore static code, it should not count as test coverage.
+      def matchedMethods = cls.method.findAll({ m -> m.@name != '<clinit>' }).findAll(methodFilter)
+      if (matchedMethods.isEmpty()) {
         // Ignore anonymous classes that only have a constructor. This will avoid tripping for
         // things like TypeLiteral and Clazz.
         if (cls.@name in legacyClassesWithoutCoverage) {
           return 'Please remove ' + cls.@name + ' from the legacyClassesWithoutCoverage list' \
-              + ', this check does not apply for constructor-only anonymous classes.'
+              + ', this check does not apply for constructor-only anonymous classes' \
+              + ' or classes with only static class initialization code.'
         } else {
           return null
         }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AbortHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AbortHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AbortHandler.java
index e97bd82..42e668d 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AbortHandler.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AbortHandler.java
@@ -14,13 +14,14 @@
 package org.apache.aurora.common.net.http.handlers;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.base.Preconditions;
 import com.google.inject.Inject;
@@ -29,7 +30,8 @@ import com.google.inject.name.Named;
 /**
  * A servlet that provides a way to remotely terminate the running process immediately.
  */
-public class AbortHandler extends HttpServlet {
+@Path("/abortabortabort")
+public class AbortHandler {
 
   /**
    * A {@literal @Named} binding key for the QuitHandler listener.
@@ -53,19 +55,12 @@ public class AbortHandler extends HttpServlet {
     this.abortListener = Preconditions.checkNotNull(abortListener);
   }
 
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public void abort(@Context HttpServletRequest req) throws IOException {
     LOG.info(String.format("Received abort HTTP signal from %s (%s)",
         req.getRemoteAddr(), req.getRemoteHost()));
 
-    resp.setContentType("text/plain");
-    PrintWriter writer = resp.getWriter();
-    try {
-      writer.println("Aborting process NOW!");
-      writer.close();
-      abortListener.run();
-    } catch (Exception e) {
-      LOG.log(Level.WARNING, "Abort failed.", e);
-    }
+    abortListener.run();
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AssetHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AssetHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AssetHandler.java
deleted file mode 100644
index 7a44f07..0000000
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/AssetHandler.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * Licensed 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.aurora.common.net.http.handlers;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
-import com.google.common.io.ByteSource;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-
-import org.apache.aurora.common.quantity.Amount;
-import org.apache.aurora.common.quantity.Time;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Servlet that is responsible for serving an asset.
- *
- * @author William Farner
- */
-public class AssetHandler extends HttpServlet {
-
-  @VisibleForTesting
-  static final Amount<Integer, Time> CACHE_CONTROL_MAX_AGE_SECS = Amount.of(30, Time.DAYS);
-  private static final String GZIP_ENCODING = "gzip";
-
-  private final StaticAsset staticAsset;
-
-  public static class StaticAsset {
-    private final ByteSource byteSource;
-    private final String contentType;
-    private final boolean cacheLocally;
-
-    private byte[] gzipData = null;
-    private String hash = null;
-
-    /**
-     * Creates a new static asset.
-     *
-     * @param byteSource Source of the asset.
-     * @param contentType HTTP content type of the asset.
-     * @param cacheLocally If {@code true} the asset will be loaded once and stored in memory, if
-     *    {@code false} it will be loaded on each request.
-     */
-    public StaticAsset(ByteSource byteSource,
-        String contentType, boolean cacheLocally) {
-      this.byteSource = checkNotNull(byteSource);
-      this.contentType = checkNotNull(contentType);
-      this.cacheLocally = cacheLocally;
-    }
-
-    public String getContentType() {
-      return contentType;
-    }
-
-    public synchronized byte[] getRawData() throws IOException {
-      byte[] zipData = getGzipData();
-      GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(zipData));
-      return ByteStreams.toByteArray(in);
-    }
-
-    public synchronized byte[] getGzipData() throws IOException {
-      byte[] data = gzipData;
-      // Ensure we don't double-read after a call to getChecksum().
-      if (!cacheLocally || gzipData == null) {
-        load();
-        data = gzipData;
-      }
-      if (!cacheLocally) {
-        gzipData = null;
-      }
-
-      return data;
-    }
-
-    public synchronized String getChecksum() throws IOException {
-      if (hash == null) {
-        load();
-      }
-      return hash;
-    }
-
-    private void load() throws IOException {
-      ByteArrayOutputStream gzipBaos = new ByteArrayOutputStream();
-      GZIPOutputStream gzipStream = new GZIPOutputStream(gzipBaos);
-      try (InputStream inputStream = byteSource.openStream()) {
-        ByteStreams.copy(inputStream, gzipStream);
-      }
-      gzipStream.flush();  // copy() does not flush or close output stream.
-      gzipStream.close();
-      gzipData = gzipBaos.toByteArray();
-
-      // Calculate a checksum of the gzipped data.
-      hash = Base64.encodeBase64String(DigestUtils.md5(gzipData)).trim();
-    }
-  }
-
-  /**
-   * Creates a new asset handler.
-   *
-   * @param staticAsset The asset to serve.
-   */
-  public AssetHandler(StaticAsset staticAsset) {
-    this.staticAsset = checkNotNull(staticAsset);
-  }
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-
-    OutputStream responseBody = resp.getOutputStream();
-
-    if (checksumMatches(req)) {
-      resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-    } else {
-      setPayloadHeaders(resp);
-
-      boolean gzip = supportsGzip(req);
-      if (gzip) {
-        resp.setHeader("Content-Encoding", GZIP_ENCODING);
-      }
-
-      InputStream in = new ByteArrayInputStream(
-          gzip ? staticAsset.getGzipData() : staticAsset.getRawData());
-      ByteStreams.copy(in, responseBody);
-    }
-
-    Closeables.close(responseBody, /* swallowIOException */ true);
-  }
-
-  private void setPayloadHeaders(HttpServletResponse resp) throws IOException {
-    resp.setStatus(HttpServletResponse.SC_OK);
-    resp.setContentType(staticAsset.getContentType());
-    resp.setHeader("Cache-Control", "public,max-age=" + CACHE_CONTROL_MAX_AGE_SECS);
-
-    String checksum = staticAsset.getChecksum();
-    if (checksum != null) {
-      resp.setHeader("ETag", checksum);
-    }
-  }
-
-  private boolean checksumMatches(HttpServletRequest req) throws IOException {
-    // TODO(William Farner): Change this to more fully comply with
-    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
-    // Specifically - a response to 'If-None-Match: *' should include ETag as well as other
-    // cache-related headers.
-    String suppliedETag = req.getHeader("If-None-Match");
-    if ("*".equals(suppliedETag)) {
-      return true;
-    }
-
-    String checksum = staticAsset.getChecksum();
-    // Note - this isn't a completely accurate check since the tag we end up matching against could
-    // theoretically be the actual tag with some extra characters appended.
-    return (checksum != null) && (suppliedETag != null) && suppliedETag.contains(checksum);
-  }
-
-  private static boolean supportsGzip(HttpServletRequest req) {
-    String header = req.getHeader("Accept-Encoding");
-    return (header != null)
-        && Iterables.contains(Splitter.on(",").trimResults().split(header), GZIP_ENCODING);
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ContentionPrinter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ContentionPrinter.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ContentionPrinter.java
index 1f8c453..8f14626 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ContentionPrinter.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ContentionPrinter.java
@@ -13,12 +13,6 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.primitives.Longs;
-
-import javax.servlet.http.HttpServletRequest;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadInfo;
 import java.lang.management.ThreadMXBean;
@@ -26,18 +20,30 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Longs;
+
 /**
  * HTTP request handler that prints information about blocked threads.
  *
  * @author William Farner
  */
-public class ContentionPrinter extends TextResponseHandler {
+@Path("/contention")
+public class ContentionPrinter {
   public ContentionPrinter() {
     ManagementFactory.getThreadMXBean().setThreadContentionMonitoringEnabled(true);
   }
 
-  @Override
-  public Iterable<String> getLines(HttpServletRequest request) {
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  public String getContention() {
     List<String> lines = Lists.newLinkedList();
     ThreadMXBean bean = ManagementFactory.getThreadMXBean();
 
@@ -67,7 +73,7 @@ public class ContentionPrinter extends TextResponseHandler {
       }
     }
 
-    return lines;
+    return String.join("\n", lines);
   }
 
   private static List<String> getThreadInfo(ThreadInfo t, StackTraceElement[] stack) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/HealthHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/HealthHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/HealthHandler.java
index cc5ad4d..731151c 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/HealthHandler.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/HealthHandler.java
@@ -13,27 +13,24 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
 import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 /**
  * A servlet that provides a crude mechanism for monitoring a service's health.  If the servlet
  * returns {@link #IS_HEALTHY} then the containing service should be deemed healthy.
  *
  * @author John Sirois
  */
-public class HealthHandler extends HttpServlet {
+@Path("/health")
+public class HealthHandler {
 
   /**
    * A {@literal @Named} binding key for the Healthz servlet health checker.
@@ -49,8 +46,6 @@ public class HealthHandler extends HttpServlet {
 
   private static final String IS_NOT_HEALTHY = "SICK";
 
-  private static final Logger LOG = Logger.getLogger(HealthHandler.class.getName());
-
   private final Supplier<Boolean> healthChecker;
 
   /**
@@ -66,17 +61,9 @@ public class HealthHandler extends HttpServlet {
     this.healthChecker = Preconditions.checkNotNull(healthChecker);
   }
 
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-
-    resp.setContentType("text/plain");
-    PrintWriter writer = resp.getWriter();
-    try {
-      writer.println(Boolean.TRUE.equals(healthChecker.get()) ? IS_HEALTHY : IS_NOT_HEALTHY);
-    } catch (Exception e) {
-      writer.println(IS_NOT_HEALTHY);
-      LOG.log(Level.WARNING, "Health check failed.", e);
-    }
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  public String getHealth() {
+    return healthChecker.get() ? IS_HEALTHY : IS_NOT_HEALTHY;
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/LogConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/LogConfig.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/LogConfig.java
index 5520fb6..aaaa348 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/LogConfig.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/LogConfig.java
@@ -13,33 +13,38 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import java.io.IOException;
+import java.io.StringWriter;
 import java.util.List;
+import java.util.Optional;
 import java.util.logging.Handler;
 import java.util.logging.Level;
 import java.util.logging.LogManager;
 import java.util.logging.Logger;
 import java.util.logging.LoggingMXBean;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
-import com.google.inject.Inject;
 
 import org.antlr.stringtemplate.StringTemplate;
-import org.apache.commons.lang.StringUtils;
-
 import org.apache.aurora.common.base.Closure;
+import org.apache.aurora.common.util.templating.StringTemplateHelper;
+import org.apache.aurora.common.util.templating.StringTemplateHelper.TemplateException;
+import org.apache.commons.lang.StringUtils;
 
 /**
  * Servlet that allows for dynamic adjustment of the logging configuration.
  *
  * @author William Farner
  */
-public class LogConfig extends StringTemplateServlet {
+@Path("/logconfig")
+public class LogConfig {
   private static final List<String> LOG_LEVELS = Lists.newArrayList(
       Level.SEVERE.getName(),
       Level.WARNING.getName(),
@@ -51,43 +56,45 @@ public class LogConfig extends StringTemplateServlet {
       "INHERIT" // Display value for a null level, the logger inherits from its ancestor.
   );
 
-  @Inject
-  public LogConfig(@CacheTemplates boolean cacheTemplates) {
-    super("logconfig", cacheTemplates);
-  }
+  private final StringTemplateHelper template =
+      new StringTemplateHelper(getClass(), "logconfig", false);
+
+  @POST
+  @Produces(MediaType.TEXT_HTML)
+  public String post(
+      @FormParam("logger") String loggerName,
+      @FormParam("level") String loggerLevel) throws TemplateException {
+
+    Optional<String> configChange = Optional.empty();
+    if (loggerName != null && loggerLevel != null) {
+      Logger logger = Logger.getLogger(loggerName);
+      Level newLevel = loggerLevel.equals("INHERIT") ? null : Level.parse(loggerLevel);
+      logger.setLevel(newLevel);
+      if (newLevel != null) {
+        maybeAdjustHandlerLevels(logger, newLevel);
+      }
 
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-    displayPage(req, resp, true);
+      configChange = Optional.of(String.format("%s level changed to %s", loggerName, loggerLevel));
+    }
+
+    return displayPage(configChange);
   }
 
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-    displayPage(req, resp, false);
+  @GET
+  @Produces(MediaType.TEXT_HTML)
+  public String get() throws TemplateException {
+    return displayPage(Optional.empty());
   }
 
-  protected void displayPage(final HttpServletRequest req, HttpServletResponse resp,
-      final boolean posted) throws ServletException, IOException {
-    writeTemplate(resp, new Closure<StringTemplate>() {
+  protected String displayPage(Optional<String> configChange) throws TemplateException {
+    StringWriter writer = new StringWriter();
+
+    template.writeTemplate(writer, new Closure<StringTemplate>() {
       @Override public void execute(StringTemplate stringTemplate) {
         LoggingMXBean logBean = LogManager.getLoggingMXBean();
 
-        if (posted) {
-          String loggerName = req.getParameter("logger");
-          String loggerLevel = req.getParameter("level");
-          if (loggerName != null && loggerLevel != null) {
-            Logger logger = Logger.getLogger(loggerName);
-            Level newLevel = loggerLevel.equals("INHERIT") ? null : Level.parse(loggerLevel);
-            logger.setLevel(newLevel);
-            if (newLevel != null) {
-              maybeAdjustHandlerLevels(logger, newLevel);
-            }
-
-            stringTemplate.setAttribute("configChange",
-                String.format("%s level changed to %s", loggerName, loggerLevel));
-          }
+        if (configChange.isPresent()) {
+          stringTemplate.setAttribute("configChange", configChange.get());
         }
 
         List<LoggerConfig> loggerConfigs = Lists.newArrayList();
@@ -99,6 +106,8 @@ public class LogConfig extends StringTemplateServlet {
         stringTemplate.setAttribute("levels", LOG_LEVELS);
       }
     });
+
+    return writer.toString();
   }
 
   private void maybeAdjustHandlerLevels(Logger logger, Level newLevel) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/QuitHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/QuitHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/QuitHandler.java
index 4ce3c97..40872e2 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/QuitHandler.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/QuitHandler.java
@@ -13,14 +13,14 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.base.Preconditions;
 import com.google.inject.Inject;
@@ -30,7 +30,8 @@ import com.google.inject.name.Named;
  * A servlet that provides a way to remotely signal the process to initiate a clean shutdown
  * sequence.
  */
-public class QuitHandler extends HttpServlet {
+@Path("/quitquitquit")
+public class QuitHandler {
   private static final Logger LOG = Logger.getLogger(QuitHandler.class.getName());
 
   /**
@@ -53,19 +54,13 @@ public class QuitHandler extends HttpServlet {
     this.quitListener = Preconditions.checkNotNull(quitListener);
   }
 
-  @Override
-  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String quit(@Context HttpServletRequest req) {
     LOG.info(String.format("Received quit HTTP signal from %s (%s)",
         req.getRemoteAddr(), req.getRemoteHost()));
 
-    resp.setContentType("text/plain");
-    PrintWriter writer = resp.getWriter();
-    try {
-      writer.println("Notifying quit listener.");
-      writer.close();
-      new Thread(quitListener).start();
-    } catch (Exception e) {
-      LOG.log(Level.WARNING, "Quit failed.", e);
-    }
+    new Thread(quitListener).start();
+    return "Notifying quit listener.";
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/StringTemplateServlet.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/StringTemplateServlet.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/StringTemplateServlet.java
deleted file mode 100644
index 60e0abb..0000000
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/StringTemplateServlet.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Licensed 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.aurora.common.net.http.handlers;
-
-import com.google.common.base.Preconditions;
-import com.google.inject.BindingAnnotation;
-
-import org.apache.aurora.common.base.Closure;
-import org.apache.aurora.common.base.MorePreconditions;
-import org.apache.aurora.common.util.templating.StringTemplateHelper;
-import org.apache.aurora.common.util.templating.StringTemplateHelper.TemplateException;
-
-import org.antlr.stringtemplate.StringTemplate;
-
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A base class for servlets that render using the string template templating system.  Subclasses
- * can call one of the {@link #writeTemplate} methods to render their content with the associated
- * template.
- */
-public abstract class StringTemplateServlet extends HttpServlet {
-  private static final String CONTENT_TYPE_TEXT_HTML = "text/html";
-
-  /**
-   * A {@literal @BindingAnnotation} that allows configuration of whether or not
-   * StringTemplateServlets should cache their templates.
-   */
-  @BindingAnnotation
-  @Retention(RetentionPolicy.RUNTIME)
-  @Target({ElementType.PARAMETER, ElementType.METHOD})
-  public @interface CacheTemplates {}
-
-  private static final Logger LOG = Logger.getLogger(StringTemplateServlet.class.getName());
-
-  private final StringTemplateHelper templateHelper;
-
-  /**
-   * Creates a new StringTemplateServlet that expects to find its template located in the same
-   * package on the classpath at '{@code templateName}.st'.
-   *
-   * @param templateName The name of the string template to use.
-   * @param cacheTemplates {@code true} to re-use loaded templates, {@code false} to reload the
-   *     template for each request.
-   */
-  protected StringTemplateServlet(String templateName, boolean cacheTemplates) {
-    templateHelper = new StringTemplateHelper(getClass(), templateName, cacheTemplates);
-  }
-
-  protected final void writeTemplate(
-      HttpServletResponse response,
-      Closure<StringTemplate> parameterSetter) throws IOException {
-
-    writeTemplate(response, CONTENT_TYPE_TEXT_HTML, HttpServletResponse.SC_OK, parameterSetter);
-  }
-
-  protected final void writeTemplate(
-      HttpServletResponse response,
-      String contentType,
-      int status,
-      Closure<StringTemplate> parameterSetter) throws IOException {
-
-    Preconditions.checkNotNull(response);
-    MorePreconditions.checkNotBlank(contentType);
-    Preconditions.checkArgument(status > 0);
-    Preconditions.checkNotNull(parameterSetter);
-
-    try {
-      templateHelper.writeTemplate(response.getWriter(), parameterSetter);
-      response.setStatus(status);
-      response.setContentType(contentType);
-    } catch (TemplateException e) {
-      LOG.log(Level.SEVERE, "Unknown exception.", e);
-      response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TextResponseHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TextResponseHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TextResponseHandler.java
deleted file mode 100644
index 23068eb..0000000
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TextResponseHandler.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Licensed 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.aurora.common.net.http.handlers;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-/**
- * A handler that responds to all requests in HTML format.
- *
- * @author William Farner
- */
-public abstract class TextResponseHandler extends HttpServlet {
-  private final String textContentType;
-
-  public TextResponseHandler() {
-    this("text/plain");
-  }
-
-  public TextResponseHandler(String textContentType) {
-    this.textContentType = textContentType;
-  }
-
-  /**
-   * Returns the lines to be printed as the body of the response.
-   *
-   * @return An iterable collection of lines to respond to the request with.
-   */
-  public abstract Iterable<String> getLines(HttpServletRequest request);
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-
-    resp.setContentType(textContentType);
-    resp.setStatus(HttpServletResponse.SC_OK);
-    PrintWriter responseBody = resp.getWriter();
-    for (String line : getLines(req)) {
-      responseBody.println(line);
-    }
-    responseBody.close();
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ThreadStackPrinter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ThreadStackPrinter.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ThreadStackPrinter.java
index 5dd8804..ab3af86 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ThreadStackPrinter.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/ThreadStackPrinter.java
@@ -13,20 +13,27 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import com.google.common.collect.Lists;
-
-import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.Map;
 
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
 /**
  * HTTP handler to print the stacks of all live threads.
  *
  * @author William Farner
  */
-public class ThreadStackPrinter extends TextResponseHandler {
-  @Override
-  public Iterable<String> getLines(HttpServletRequest request) {
+@Path("/threads")
+public class ThreadStackPrinter {
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  public String getThreadStacks() {
     List<String> lines = Lists.newLinkedList();
     for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
       Thread t = entry.getKey();
@@ -36,6 +43,6 @@ public class ThreadStackPrinter extends TextResponseHandler {
         lines.add("    " + s.toString());
       }
     }
-    return lines;
+    return Joiner.on("\n").join(lines);
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TimeSeriesDataSource.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TimeSeriesDataSource.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TimeSeriesDataSource.java
index e87fe2c..8039def 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TimeSeriesDataSource.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/TimeSeriesDataSource.java
@@ -13,15 +13,14 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.List;
 
 import javax.annotation.Nullable;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
@@ -33,7 +32,6 @@ import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.net.MediaType;
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 import com.google.inject.Inject;
@@ -45,7 +43,8 @@ import org.apache.aurora.common.stats.TimeSeriesRepository;
 /**
  * A servlet that provides time series data in JSON format.
  */
-public class TimeSeriesDataSource extends HttpServlet {
+@Path("/graphdata/")
+public class TimeSeriesDataSource {
 
   @VisibleForTesting static final String TIME_METRIC = "time";
 
@@ -96,21 +95,17 @@ public class TimeSeriesDataSource extends HttpServlet {
     ResponseStruct response = new ResponseStruct(
         ImmutableList.<String>builder().add(TIME_METRIC).addAll(names).build(),
         FluentIterable.from(Iterables2.zip(tsData, 0)).filter(sinceFilter).toList());
+    // TODO(wfarner): Let the jax-rs provider handle serialization.
     return gson.toJson(response);
   }
 
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-
-    resp.setContentType(MediaType.JSON_UTF_8.toString());
-    PrintWriter out = resp.getWriter();
-    try {
-      out.write(getResponse(req.getParameter(METRICS), req.getParameter(SINCE)));
-    } catch (MetricException e) {
-      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-      out.write(e.getMessage());
-    }
+  @GET
+  @Produces(MediaType.APPLICATION_JSON)
+  public String getData(
+      @QueryParam(METRICS) String metrics,
+      @QueryParam(SINCE) String since) throws MetricException {
+
+    return getResponse(metrics, since);
   }
 
   @VisibleForTesting

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsHandler.java
index bf04525..d1f23de 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsHandler.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsHandler.java
@@ -16,9 +16,13 @@ package org.apache.aurora.common.net.http.handlers;
 import java.util.Collections;
 import java.util.List;
 
-import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
@@ -32,7 +36,8 @@ import org.apache.aurora.common.stats.Stat;
  *
  * @author William Farner
  */
-public class VarsHandler extends TextResponseHandler {
+@Path("/vars")
+public class VarsHandler {
 
   private static final Function<Stat, String> VAR_PRINTER = new Function<Stat, String>() {
     @Override public String apply(Stat stat) {
@@ -52,10 +57,11 @@ public class VarsHandler extends TextResponseHandler {
     this.statSupplier = Preconditions.checkNotNull(statSupplier);
   }
 
-  @Override
-  public Iterable<String> getLines(HttpServletRequest request) {
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  public String getVars() {
     List<String> lines = Lists.newArrayList(Iterables.transform(statSupplier.get(), VAR_PRINTER));
     Collections.sort(lines);
-    return lines;
+    return Joiner.on("\n").join(lines);
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsJsonHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsJsonHandler.java b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsJsonHandler.java
index e97ec60..850c784 100644
--- a/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsJsonHandler.java
+++ b/commons/src/main/java/org/apache/aurora/common/net/http/handlers/VarsJsonHandler.java
@@ -13,14 +13,13 @@
  */
 package org.apache.aurora.common.net.http.handlers;
 
-import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.Map;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
@@ -45,9 +44,10 @@ import org.apache.aurora.common.stats.Stat;
  * If the optional URL parameter 'pretty' is used, the output will be pretty-printed
  * (similar to the above example).
  *
- * @author William Farner
+ * TODO(wfarner): Handle this request in VarsHandler.
  */
-public class VarsJsonHandler extends HttpServlet {
+@Path("/vars.json")
+public class VarsJsonHandler {
 
   private final Supplier<Iterable<Stat<?>>> statSupplier;
 
@@ -67,21 +67,14 @@ public class VarsJsonHandler extends HttpServlet {
     for (Stat<?> var : statSupplier.get()) {
       vars.put(var.getName(), var.read());
     }
+    // TODO(wfarner): Let the jax-rs provider handle serialization.
     return getGson(pretty).toJson(vars);
   }
 
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-      throws ServletException, IOException {
-
-    resp.setContentType("application/json");
-    resp.setStatus(HttpServletResponse.SC_OK);
-    PrintWriter responseBody = resp.getWriter();
-    try {
-      responseBody.print(getBody(req.getParameter("pretty") != null));
-    } finally {
-      responseBody.close();
-    }
+  @GET
+  @Produces(MediaType.APPLICATION_JSON)
+  public String getVars(@QueryParam("pretty") boolean pretty) {
+    return getBody(pretty);
   }
 
   private Gson getGson(boolean pretty) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/test/java/org/apache/aurora/common/net/http/handlers/AssetHandlerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/org/apache/aurora/common/net/http/handlers/AssetHandlerTest.java b/commons/src/test/java/org/apache/aurora/common/net/http/handlers/AssetHandlerTest.java
deleted file mode 100644
index 740c42f..0000000
--- a/commons/src/test/java/org/apache/aurora/common/net/http/handlers/AssetHandlerTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/**
- * Licensed 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.aurora.common.net.http.handlers;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import com.google.common.io.ByteSource;
-import com.google.common.io.ByteStreams;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.apache.aurora.common.testing.easymock.EasyMockTest;
-
-import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-import static org.easymock.EasyMock.createMockBuilder;
-import static org.easymock.EasyMock.expect;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author William Farner
- */
-public class AssetHandlerTest extends EasyMockTest {
-
-  private static final String TEST_DATA = "here is my great test data";
-  // Checksum of the gzipped TEST_DATA.
-  private static final String TEST_DATA_CHECKSUM = "ePvVhtAeVRu85KSOLKL0oQ==";
-  private static final String CONTENT_TYPE = "text/plain";
-
-  private ByteSource byteSource;
-
-  @Before
-  public void setUp() {
-    byteSource = createMock(ByteSource.class);
-  }
-
-  private static class Request {
-    private final HttpServletRequest req;
-    private final HttpServletResponse resp;
-    private final ByteArrayOutputStream responseBody;
-
-    Request(HttpServletRequest req, HttpServletResponse resp, ByteArrayOutputStream responseBody) {
-      this.req = req;
-      this.resp = resp;
-      this.responseBody = responseBody;
-    }
-  }
-
-  private Request doGet(String suppliedChecksum, String supportedEncodings,
-      int expectedResponseCode, boolean expectRead) throws Exception {
-    HttpServletRequest req = createMock(HttpServletRequest.class);
-    HttpServletResponse resp = createMock(HttpServletResponse.class);
-
-    if (expectRead) {
-      expect(byteSource.openStream()).andReturn(new ByteArrayInputStream(TEST_DATA.getBytes()));
-    }
-
-    expect(req.getHeader("If-None-Match")).andReturn(suppliedChecksum);
-
-    resp.setStatus(expectedResponseCode);
-    if (expectedResponseCode == SC_OK) {
-      expect(req.getHeader("Accept-Encoding")).andReturn(supportedEncodings);
-      resp.setHeader("Cache-Control", "public,max-age=" + AssetHandler.CACHE_CONTROL_MAX_AGE_SECS);
-      resp.setHeader("ETag", TEST_DATA_CHECKSUM);
-      resp.setContentType(CONTENT_TYPE);
-
-      if (supportedEncodings != null && supportedEncodings.contains("gzip")) {
-        resp.setHeader("Content-Encoding", "gzip");
-      }
-    }
-    return new Request(req, resp, expectPayload(resp));
-  }
-
-  @Test
-  public void testCached() throws Exception {
-
-    // First request - no cached value
-    Request test1 = doGet(
-        null,  // No local checksum.
-        null,  // No encodings supported.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Second request - client performs conditional GET with wrong checksum.
-    Request test2 = doGet(
-        "foo", // Wrong checksum.
-        null,  // No encodings supported.
-        SC_OK,
-        false   // No read.
-    );
-
-    // Third request - client performs conditional GET with correct checksum.
-    Request test3 = doGet(
-        TEST_DATA_CHECKSUM,  // Correct checksum.
-        null,  // No encodings supported.
-        SC_NOT_MODIFIED,
-        false   // No read.
-    );
-
-    control.replay();
-
-    AssetHandler handler = new AssetHandler(new AssetHandler.StaticAsset(byteSource, CONTENT_TYPE, true));
-
-    handler.doGet(test1.req, test1.resp);
-    assertThat(new String(test1.responseBody.toByteArray()), is(TEST_DATA));
-
-    handler.doGet(test2.req, test2.resp);
-    assertThat(new String(test2.responseBody.toByteArray()), is(TEST_DATA));
-
-    handler.doGet(test3.req, test3.resp);
-    assertThat(new String(test3.responseBody.toByteArray()), is(""));
-  }
-
-  @Test
-  public void testCachedGzipped() throws Exception {
-
-    // First request - no cached value
-    Request test1 = doGet(
-        null,  // No local checksum.
-        "gzip",  // Supported encodings.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Second request - client performs conditional GET with wrong checksum.
-    Request test2 = doGet(
-        "foo", // Wrong checksum.
-        "gzip,fakeencoding",  // Supported encodings.
-        SC_OK,
-        false   // No read.
-    );
-
-    // Third request - client performs conditional GET with correct checksum.
-    Request test3 = doGet(
-        TEST_DATA_CHECKSUM,  // Correct checksum.
-        "gzip,deflate",  // Supported encodings.
-        SC_NOT_MODIFIED,
-        false   // No read.
-    );
-
-    control.replay();
-
-    AssetHandler handler = new AssetHandler(new AssetHandler.StaticAsset(byteSource, CONTENT_TYPE, true));
-
-    handler.doGet(test1.req, test1.resp);
-    assertThat(unzip(test1.responseBody), is(TEST_DATA));
-
-    handler.doGet(test2.req, test2.resp);
-    assertThat(unzip(test2.responseBody), is(TEST_DATA));
-
-    handler.doGet(test3.req, test3.resp);
-    assertThat(new String(test3.responseBody.toByteArray()), is(""));
-  }
-
-  @Test
-  public void testUncached() throws Exception {
-
-    // First request - no cached value
-    Request test1 = doGet(
-        null,  // No local checksum.
-        null,  // No encodings supported.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Second request - client performs conditional GET with wrong checksum.
-    Request test2 = doGet(
-        "foo", // Wrong checksum.
-        null,  // No encodings supported.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Third request - client performs conditional GET with correct checksum.
-    Request test3 = doGet(
-        TEST_DATA_CHECKSUM,  // Correct checksum.
-        null,  // No encodings supported.
-        SC_NOT_MODIFIED,
-        true   // Triggers a data read.
-    );
-
-    control.replay();
-
-    AssetHandler handler = new AssetHandler(new AssetHandler.StaticAsset(byteSource, CONTENT_TYPE, false));
-
-    handler.doGet(test1.req, test1.resp);
-    assertThat(new String(test1.responseBody.toByteArray()), is(TEST_DATA));
-
-    handler.doGet(test2.req, test2.resp);
-    assertThat(new String(test2.responseBody.toByteArray()), is(TEST_DATA));
-
-    handler.doGet(test3.req, test3.resp);
-    assertThat(new String(test3.responseBody.toByteArray()), is(""));
-  }
-
-  @Test
-  public void testUncachedGzipped() throws Exception {
-
-    // First request - no cached value
-    Request test1 = doGet(
-        null,  // No local checksum.
-        "gzip",  // Supported encodings.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Second request - client performs conditional GET with wrong checksum.
-    Request test2 = doGet(
-        "foo", // Wrong checksum.
-        "gzip,fakeencoding",  // Supported encodings.
-        SC_OK,
-        true   // Triggers a data read.
-    );
-
-    // Third request - client performs conditional GET with correct checksum.
-    Request test3 = doGet(
-        TEST_DATA_CHECKSUM,  // Correct checksum.
-        "gzip,deflate",  // Supported encodings.
-        SC_NOT_MODIFIED,
-        true   // Triggers a data read.
-    );
-
-    control.replay();
-
-    AssetHandler handler = new AssetHandler(new AssetHandler.StaticAsset(byteSource, CONTENT_TYPE, false));
-
-    handler.doGet(test1.req, test1.resp);
-    assertThat(unzip(test1.responseBody), is(TEST_DATA));
-
-    handler.doGet(test2.req, test2.resp);
-    assertThat(unzip(test2.responseBody), is(TEST_DATA));
-
-    handler.doGet(test3.req, test3.resp);
-    assertThat(new String(test3.responseBody.toByteArray()), is(""));
-  }
-
-  private static ByteArrayOutputStream expectPayload(HttpServletResponse resp) throws Exception {
-    ByteArrayOutputStream responseBody = new ByteArrayOutputStream();
-    expect(resp.getOutputStream()).andReturn(new FakeServletOutputStream(responseBody));
-    return responseBody;
-  }
-
-  private static String unzip(ByteArrayOutputStream streamData) throws IOException {
-    ByteArrayInputStream in = new ByteArrayInputStream(streamData.toByteArray());
-    GZIPInputStream unzip = new GZIPInputStream(in);
-    return new String(ByteStreams.toByteArray(unzip));
-  }
-
-  private static class FakeServletOutputStream extends ServletOutputStream {
-    private final OutputStream realStream;
-
-    FakeServletOutputStream(OutputStream realStream) {
-      this.realStream = realStream;
-    }
-
-    @Override
-    public void write(int b) throws IOException {
-      realStream.write(b);
-    }
-
-    @Override
-    public void write(byte[] b) throws IOException {
-      realStream.write(b);
-    }
-
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-      realStream.write(b, off, len);
-    }
-
-    @Override
-    public void flush() throws IOException {
-      realStream.flush();
-    }
-
-    @Override
-    public void close() throws IOException {
-      realStream.close();
-    }
-
-    @Override
-    public void print(String s) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(boolean b) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(char c) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(int i) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(long l) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(float f) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void print(double d) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println() throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(String s) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(boolean b) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(char c) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(int i) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(long l) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(float f) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public void println(double d) throws IOException {
-      throw new UnsupportedOperationException("Not implemented");
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/commons/src/test/java/org/apache/aurora/common/net/http/handlers/VarsHandlerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/org/apache/aurora/common/net/http/handlers/VarsHandlerTest.java b/commons/src/test/java/org/apache/aurora/common/net/http/handlers/VarsHandlerTest.java
index 34f62fb..6c4feaf 100644
--- a/commons/src/test/java/org/apache/aurora/common/net/http/handlers/VarsHandlerTest.java
+++ b/commons/src/test/java/org/apache/aurora/common/net/http/handlers/VarsHandlerTest.java
@@ -20,14 +20,12 @@ import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 
 import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import org.apache.aurora.common.stats.Stat;
 import org.junit.Before;
 import org.junit.Test;
 
-import org.apache.aurora.common.stats.Stat;
-
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -72,7 +70,6 @@ public class VarsHandlerTest extends StatSupplierTestBase {
   }
 
   private void checkOutput(List<String> expectedLines) {
-    assertEquals(expectedLines,
-        ImmutableList.copyOf(vars.getLines(request)));
+    assertEquals(String.join("\n", expectedLines), vars.getVars());
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/config/legacy_untested_classes.txt
----------------------------------------------------------------------
diff --git a/config/legacy_untested_classes.txt b/config/legacy_untested_classes.txt
index 7b891b2..88a71df 100644
--- a/config/legacy_untested_classes.txt
+++ b/config/legacy_untested_classes.txt
@@ -9,9 +9,9 @@ org/apache/aurora/scheduler/async/OfferQueue$OfferQueueImpl$2
 org/apache/aurora/scheduler/base/Conversions$1
 org/apache/aurora/scheduler/base/Conversions$2
 org/apache/aurora/scheduler/base/Conversions$3
-org/apache/aurora/scheduler/base/Conversions$4
 org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl
 org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl$1
+org/apache/aurora/scheduler/http/AbortCallback
 org/apache/aurora/scheduler/http/JerseyTemplateServlet
 org/apache/aurora/scheduler/http/Maintenance
 org/apache/aurora/scheduler/http/Maintenance$1

http://git-wip-us.apache.org/repos/asf/aurora/blob/9dff0571/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
index c503dcc..f9b0687 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
@@ -17,6 +17,7 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.EnumSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -24,15 +25,19 @@ import javax.annotation.Nonnegative;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javax.servlet.ServletContextListener;
-import javax.servlet.http.HttpServlet;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
 import com.google.common.net.HostAndPort;
 import com.google.common.util.concurrent.AbstractIdleService;
 import com.google.inject.AbstractModule;
@@ -56,7 +61,6 @@ import org.apache.aurora.common.net.http.handlers.ContentionPrinter;
 import org.apache.aurora.common.net.http.handlers.HealthHandler;
 import org.apache.aurora.common.net.http.handlers.LogConfig;
 import org.apache.aurora.common.net.http.handlers.QuitHandler;
-import org.apache.aurora.common.net.http.handlers.StringTemplateServlet;
 import org.apache.aurora.common.net.http.handlers.ThreadStackPrinter;
 import org.apache.aurora.common.net.http.handlers.TimeSeriesDataSource;
 import org.apache.aurora.common.net.http.handlers.VarsHandler;
@@ -143,8 +147,6 @@ public class JettyServerModule extends AbstractModule {
         .annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))
         .toInstance(Suppliers.ofInstance(true));
 
-    bindConstant().annotatedWith(StringTemplateServlet.CacheTemplates.class).to(true);
-
     final Optional<String> hostnameOverride = Optional.fromNullable(HOSTNAME_OVERRIDE.get());
     if (hostnameOverride.isPresent()) {
       try {
@@ -195,6 +197,48 @@ public class JettyServerModule extends AbstractModule {
     }
   };
 
+  private static final Set<String> LEADER_ENDPOINTS = ImmutableSet.of(
+      "api",
+      "cron",
+      "locks",
+      "maintenance",
+      "mname",
+      "offers",
+      "pendingtasks",
+      "quotas",
+      "slaves",
+      "utilization"
+  );
+
+  private static final Multimap<Class<?>, String> JAX_RS_ENDPOINTS =
+      ImmutableMultimap.<Class<?>, String>builder()
+          .put(AbortHandler.class, "abortabortabort")
+          .put(ContentionPrinter.class, "contention")
+          .put(Cron.class, "cron")
+          .put(Locks.class, "locks")
+          .put(LogConfig.class, "logconfig")
+          .put(Maintenance.class, "maintenance")
+          .put(Mname.class, "mname")
+          .put(Offers.class, "offers")
+          .put(PendingTasks.class, "pendingtasks")
+          .put(QuitHandler.class, "quitquitquit")
+          .put(Quotas.class, "quotas")
+          .put(Services.class, "services")
+          .put(Slaves.class, "slaves")
+          .put(StructDump.class, "structdump")
+          .put(ThreadStackPrinter.class, "threads")
+          .put(TimeSeriesDataSource.class, "graphdata")
+          .put(Utilization.class, "utilization")
+          .put(VarsHandler.class, "vars")
+          .put(VarsJsonHandler.class, "vars.json")
+          .build();
+
+  private static String allOf(Set<String> paths) {
+    return "^(?:"
+        + Joiner.on("|").join(Iterables.transform(paths, path -> "/" + path))
+        + ").*$";
+  }
+
   // TODO(ksweeney): Factor individual servlet configurations to their own ServletModules.
   @VisibleForTesting
   static ServletContextListener makeServletContextListener(
@@ -207,22 +251,18 @@ public class JettyServerModule extends AbstractModule {
         return parentInjector.createChildInjector(
             childModule,
             new JerseyServletModule() {
-              private void registerJerseyEndpoint(String indexPath, Class<?> servlet) {
-                filter(indexPath + "*").through(LeaderRedirectFilter.class);
-                filter(indexPath + "*").through(GuiceContainer.class, GUICE_CONTAINER_PARAMS);
-                bind(servlet);
-              }
-
-              private void registerServlet(String pathSpec, Class<? extends HttpServlet> servlet) {
-                bind(servlet).in(Singleton.class);
-                serve(pathSpec).with(servlet);
-              }
-
               @Override
               protected void configureServlets() {
                 bind(HttpStatsFilter.class).in(Singleton.class);
                 filter("*").through(HttpStatsFilter.class);
+
                 bind(LeaderRedirectFilter.class).in(Singleton.class);
+                filterRegex(allOf(LEADER_ENDPOINTS))
+                    .through(LeaderRedirectFilter.class);
+
+                bind(GuiceContainer.class).in(Singleton.class);
+                filterRegex(allOf(ImmutableSet.copyOf(JAX_RS_ENDPOINTS.values())))
+                    .through(GuiceContainer.class, GUICE_CONTAINER_PARAMS);
 
                 filterRegex("/assets/.*").through(new GzipFilter());
                 filterRegex("/assets/scheduler(?:/.*)?").through(LeaderRedirectFilter.class);
@@ -232,28 +272,9 @@ public class JettyServerModule extends AbstractModule {
                         "resourceBase", STATIC_ASSETS_ROOT,
                         "dirAllowed", "false"));
 
-                bind(GuiceContainer.class).in(Singleton.class);
-                registerJerseyEndpoint("/cron", Cron.class);
-                registerJerseyEndpoint("/locks", Locks.class);
-                registerJerseyEndpoint("/maintenance", Maintenance.class);
-                registerJerseyEndpoint("/mname", Mname.class);
-                registerJerseyEndpoint("/offers", Offers.class);
-                registerJerseyEndpoint("/pendingtasks", PendingTasks.class);
-                registerJerseyEndpoint("/quotas", Quotas.class);
-                registerJerseyEndpoint("/services", Services.class);
-                registerJerseyEndpoint("/slaves", Slaves.class);
-                registerJerseyEndpoint("/structdump", StructDump.class);
-                registerJerseyEndpoint("/utilization", Utilization.class);
-
-                registerServlet("/abortabortabort", AbortHandler.class);
-                registerServlet("/contention", ContentionPrinter.class);
-                registerServlet("/health", HealthHandler.class);
-                registerServlet("/logconfig", LogConfig.class);
-                registerServlet("/quitquitquit", QuitHandler.class);
-                registerServlet("/threads", ThreadStackPrinter.class);
-                registerServlet("/graphdata/", TimeSeriesDataSource.class);
-                registerServlet("/vars", VarsHandler.class);
-                registerServlet("/vars.json", VarsJsonHandler.class);
+                for (Class<?> jaxRsHandler : JAX_RS_ENDPOINTS.keySet()) {
+                  bind(jaxRsHandler);
+                }
               }
             });
       }
@@ -353,7 +374,7 @@ public class JettyServerModule extends AbstractModule {
           new ServletContextHandler(server, "/", ServletContextHandler.NO_SESSIONS);
 
       servletHandler.addServlet(DefaultServlet.class, "/");
-      servletHandler.addFilter(GuiceFilter.class,  "/*", EnumSet.allOf(DispatcherType.class));
+      servletHandler.addFilter(GuiceFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
       servletHandler.addEventListener(servletContextListener);
 
       HandlerCollection rootHandler = new HandlerCollection();