Package org.apache.aurora.scheduler.http

Source Code of org.apache.aurora.scheduler.http.JettyServerModule

/**
* 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.scheduler.http;

import java.util.EnumSet;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.Nonnegative;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.ws.rs.HttpMethod;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.twitter.common.application.modules.LifecycleModule;
import com.twitter.common.application.modules.LifecycleModule.LaunchException;
import com.twitter.common.args.Arg;
import com.twitter.common.args.CmdLine;
import com.twitter.common.base.Command;
import com.twitter.common.base.ExceptionalCommand;
import com.twitter.common.base.ExceptionalSupplier;
import com.twitter.common.base.MoreSuppliers;
import com.twitter.common.net.http.handlers.AbortHandler;
import com.twitter.common.net.http.handlers.ContentionPrinter;
import com.twitter.common.net.http.handlers.HealthHandler;
import com.twitter.common.net.http.handlers.LogConfig;
import com.twitter.common.net.http.handlers.QuitHandler;
import com.twitter.common.net.http.handlers.StringTemplateServlet;
import com.twitter.common.net.http.handlers.ThreadStackPrinter;
import com.twitter.common.net.http.handlers.TimeSeriesDataSource;
import com.twitter.common.net.http.handlers.VarsHandler;
import com.twitter.common.net.http.handlers.VarsJsonHandler;
import com.twitter.common.net.pool.DynamicHostSet.MonitorException;

import org.apache.aurora.scheduler.http.api.ApiBeta;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.util.resource.Resource;

import static java.util.Objects.requireNonNull;

import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS;
import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS;
import static com.sun.jersey.api.json.JSONConfiguration.FEATURE_POJO_MAPPING;
import static com.twitter.common.application.modules.LocalServiceRegistry.LocalService;

/**
* Binding module for scheduler HTTP servlets.
* <p>
* TODO(wfarner): Continue improvements here by simplifying serving of static assets.  Jetty's
* DefaultServlet can take over this responsibility, and jetty-rewite can be used to rewrite
* requests (for static assets) similar to what we currently do with path specs.
*/
public class JettyServerModule extends AbstractModule {
  private static final Logger LOG = Logger.getLogger(JettyServerModule.class.getName());

  // The name of the request attribute where the path for the current request before it was
  // rewritten is stored.
  static final String ORIGINAL_PATH_ATTRIBUTE_NAME = "originalPath";

  @Nonnegative
  @CmdLine(name = "http_port",
      help = "The port to start an HTTP server on.  Default value will choose a random port.")
  protected static final Arg<Integer> HTTP_PORT = Arg.create(0);

  @CmdLine(name = "enable_cors_support", help = "Enable CORS support for thrift end points.")
  private static final Arg<Boolean> ENABLE_CORS_SUPPORT = Arg.create(false);

  // More info on CORS can be found at http://enable-cors.org/index.html
  @CmdLine(name = "enable_cors_for",
      help = "List of domains for which CORS support should be enabled.")
  private static final Arg<String> ENABLE_CORS_FOR = Arg.create("*");

  private static final Map<String, String> CONTAINER_PARAMS = ImmutableMap.of(
      FEATURE_POJO_MAPPING, Boolean.TRUE.toString(),
      PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName(),
      PROPERTY_CONTAINER_RESPONSE_FILTERS, GZIPContentEncodingFilter.class.getName());

  @Override
  protected void configure() {
    bind(Runnable.class)
        .annotatedWith(Names.named(AbortHandler.ABORT_HANDLER_KEY))
        .to(AbortCallback.class);
    bind(AbortCallback.class).in(Singleton.class);
    bind(Runnable.class).annotatedWith(Names.named(QuitHandler.QUIT_HANDLER_KEY))
        .to(QuitCallback.class);
    bind(QuitCallback.class).in(Singleton.class);
    bind(new TypeLiteral<ExceptionalSupplier<Boolean, ?>>() { })
        .annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))
        .toInstance(MoreSuppliers.ofInstance(true));

    bindConstant().annotatedWith(StringTemplateServlet.CacheTemplates.class).to(true);

    LifecycleModule.bindServiceRunner(binder(), HttpServerLauncher.class);

    bind(LeaderRedirect.class).in(Singleton.class);
    LifecycleModule.bindStartupAction(binder(), RedirectMonitor.class);
  }

  static class RedirectMonitor implements ExceptionalCommand<MonitorException> {

    private final LeaderRedirect redirector;

    @Inject
    RedirectMonitor(LeaderRedirect redirector) {
      this.redirector = requireNonNull(redirector);
    }

    @Override
    public void execute() throws MonitorException {
      redirector.monitor();
    }
  }

  // TODO(wfarner): Use guava's Service to enforce the lifecycle of this.
  public static final class HttpServerLauncher implements LifecycleModule.ServiceRunner {
    private final Injector parentInjector;

    @Inject
    HttpServerLauncher(Injector parentInjector) {
      this.parentInjector = requireNonNull(parentInjector);
    }

    private static final Map<String, String> REGEX_REWRITE_RULES =
        ImmutableMap.<String, String>builder()
            .put("/(?:index.html)?", "/assets/index.html")
            .put("/apibeta/help/(.*)?", "/assets/org/apache/aurora/scheduler/storage/entities/$1")
            .put("/graphview(?:/index.html)?", "/assets/graphview/graphview.html")
            .put("/graphview/(.*)", "/assets/graphview/$1")
            .put("/(?:scheduler|updates)(?:/.*)?", "/assets/scheduler/index.html")
            .build();

    private RewriteHandler getRewriteHandler(HandlerCollection rootHandler) {
      RewriteHandler rewrites = new RewriteHandler();
      rewrites.setOriginalPathAttribute(ORIGINAL_PATH_ATTRIBUTE_NAME);
      rewrites.setRewriteRequestURI(true);
      rewrites.setRewritePathInfo(true);

      for (Map.Entry<String, String> entry : REGEX_REWRITE_RULES.entrySet()) {
        RewriteRegexRule rule = new RewriteRegexRule();
        rule.setRegex(entry.getKey());
        rule.setReplacement(entry.getValue());
        rewrites.addRule(rule);
      }

      rewrites.setHandler(rootHandler);

      return rewrites;
    }

    @Override
    public LocalService launch() throws LaunchException {
      // N.B. we explicitly disable the resource cache here due to a bug serving content out of the
      // jar under the vagrant image. C.f. https://bugs.eclipse.org/bugs/show_bug.cgi?id=364936
      Resource.setDefaultUseCaches(false);

      final Server server = new Server();
      ServletContextHandler servletHandler =
          new ServletContextHandler(server, "/", ServletContextHandler.NO_SESSIONS);

      servletHandler.addServlet(DefaultServlet.class, "/");
      servletHandler.addFilter(GuiceFilter.class,  "/*", EnumSet.allOf(DispatcherType.class));

      HandlerCollection rootHandler = new HandlerCollection();

      RequestLogHandler logHandler = new RequestLogHandler();
      logHandler.setRequestLog(new RequestLogger());

      rootHandler.addHandler(logHandler);
      rootHandler.addHandler(servletHandler);

      servletHandler.addEventListener(new GuiceServletContextListener() {
        @Override
        protected Injector getInjector() {
          return parentInjector.createChildInjector(new JerseyServletModule() {
            private void registerJerseyEndpoint(String indexPath, Class<?> servlet) {
              filter(indexPath + "*").through(LeaderRedirectFilter.class);
              filter(indexPath + "*").through(GuiceContainer.class, 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("/assets/scheduler*").through(HttpStatsFilter.class);
              bind(LeaderRedirectFilter.class).in(Singleton.class);
              filterRegex("/assets/scheduler(?:/.*)?").through(LeaderRedirectFilter.class);

              // Add CORS support for all /api endpoints.
              if (ENABLE_CORS_SUPPORT.get()) {
                bind(CorsFilter.class).toInstance(new CorsFilter(ENABLE_CORS_FOR.get()));
                filter("/api*").through(CorsFilter.class);
              }

              // NOTE: GzipFilter is applied only to /api instead of globally because the
              // Jersey-managed servlets have a conflicting filter applied to them.
              bind(GzipFilter.class).in(Singleton.class);
              filterRegex("/api(?:/.*)?").through(GzipFilter.class, ImmutableMap.of(
                  "methods", Joiner.on(',').join(ImmutableSet.of(
                      HttpMethod.GET,
                      HttpMethod.POST))));
              filterRegex("/assets/.*").through(
                  GzipFilter.class,
                  ImmutableMap.of("methods", HttpMethod.GET));

              bind(DefaultServlet.class).in(Singleton.class);
              serve("/assets*")
                  .with(DefaultServlet.class, ImmutableMap.of(
                      "resourceBase", Resource.newClassPathResource("scheduler").toString(),
                      "dirAllowed", "false"));

              bind(GuiceContainer.class).in(Singleton.class);
              registerJerseyEndpoint("/apibeta", ApiBeta.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("/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("/api", SchedulerAPIServlet.class);
              registerServlet("/threads", ThreadStackPrinter.class);
              registerServlet("/graphdata/", TimeSeriesDataSource.class);
              registerServlet("/vars", VarsHandler.class);
              registerServlet("/vars.json", VarsJsonHandler.class);
            }
          });
        }
      });

      Connector connector = new SelectChannelConnector();
      connector.setPort(HTTP_PORT.get());
      server.addConnector(connector);
      server.setHandler(getRewriteHandler(rootHandler));

      try {
        connector.open();
        server.start();
      } catch (Exception e) {
        throw new LaunchException("Failed to start jetty server: " + e, e);
      }

      Command shutdown = new Command() {
        @Override public void execute() {
          LOG.info("Shutting down embedded http server");
          try {
            server.stop();
          } catch (Exception e) {
            LOG.log(Level.INFO, "Failed to stop jetty server: " + e, e);
          }
        }
      };

      return LocalService.auxiliaryService("http", connector.getLocalPort(), shutdown);
    }
  }
}
TOP

Related Classes of org.apache.aurora.scheduler.http.JettyServerModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');