Jetty9ServerCustomizers.java

package cn.home1.oss.lib.webmvc.api;

import static com.google.common.collect.Sets.newLinkedHashSet;
import static java.lang.Boolean.parseBoolean;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static lombok.AccessLevel.PRIVATE;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.Slf4jRequestLog;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.MovedContextHandler;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
import org.springframework.core.env.Environment;

import java.io.File;
import java.util.Collection;

public interface Jetty9ServerCustomizers {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> connectorConnectionFactories(final Connector connector,
    final Class<T> ofType) {
    final Collection<T> connectionFactories = newLinkedHashSet();
    final ConnectionFactory defaultConnectionFactory = connector.getDefaultConnectionFactory();
    if (defaultConnectionFactory != null
      && ofType.isAssignableFrom(defaultConnectionFactory.getClass())) {
      connectionFactories.add((T) defaultConnectionFactory);
    }
    connectionFactories.addAll(connector.getConnectionFactories().stream()
      .filter(connectionFactory -> ofType.isAssignableFrom(connectionFactory.getClass()))
      .map(connectionFactory -> (T) connectionFactory).collect(toList()));
    return connectionFactories;
  }

  static <T> Collection<T> serverConnectionFactories(final Server server, final Class<T> ofType) {
    return newLinkedHashSet(asList(server.getConnectors()).stream()
      .flatMap(connector -> connectorConnectionFactories(connector, ofType).stream())
      .collect(toList()));
  }

  static Collection<ServerConnector> serverConnectors(final Server server) {
    return newLinkedHashSet(
      asList(server.getConnectors()).stream().filter(c -> c instanceof ServerConnector)
        .map(connector -> (ServerConnector) connector).collect(toList()));
  }

  static JettyEmbeddedServletContainerFactory jetty(
    final ConfigurableEmbeddedServletContainer container) {
    if (container instanceof JettyEmbeddedServletContainerFactory) {
      return (JettyEmbeddedServletContainerFactory) container;
    } else {
      return null;
    }
  }

  @AllArgsConstructor(access = PRIVATE)
  class RedirectToHostRoot implements JettyServerCustomizer {

    @NonNull
    private final String host;
    @NonNull
    private final String root;

    @Override
    public void customize(final Server server) {
      final HandlerCollection handlers = new HandlerCollection();
      final MovedContextHandler movedContextHandler = new MovedContextHandler();
      movedContextHandler.setContextPath("/");
      movedContextHandler.setNewContextURL(this.root);
      movedContextHandler.setPermanent(true);
      movedContextHandler.setDiscardPathInfo(false);
      movedContextHandler.setDiscardQuery(false);
      movedContextHandler.setVirtualHosts(new String[]{this.host});
      handlers.addHandler(movedContextHandler);

      asList(server.getHandlers()).forEach(handlers::addHandler);
      server.setHandler(handlers);
    }
  }

  /**
   * see:
   * http://stackoverflow.com/questions/3539143/redirect-non-www-version-of-domain-to-www-in-jetty
   *
   * @param container container
   * @param host      host
   * @param root      root
   */
  static void redirectRootDomainToHostRoot( //
    final ConfigurableEmbeddedServletContainer container, final String host, final String root //
  ) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    if (jetty == null) {
      return;
    }

    jetty.addServerCustomizers(new RedirectToHostRoot(host, root));
  }

  @AllArgsConstructor(access = PRIVATE)
  class AccessLog implements JettyServerCustomizer {

    private String applicationName;

    @Override
    public void customize(final Server server) {
      final HandlerCollection handlers = new HandlerCollection();
      // final RequestLog requestLog = slf4jLog();
      final RequestLog requestLog = ncsaRequestLog(applicationName);
      final RequestLogHandler logHandler = new RequestLogHandler();
      logHandler.setRequestLog(requestLog);
      handlers.addHandler(logHandler);

      asList(server.getHandlers()).forEach(handlers::addHandler);
      server.setHandler(handlers);
    }

    public static RequestLog slf4jLog() {
      final Slf4jRequestLog requestLog = new Slf4jRequestLog();
      return requestLog;
    }

    @SneakyThrows
    public static RequestLog ncsaRequestLog(String applicationName) {
      final String logPath = "./logs";
      FileUtils.forceMkdir(new File(logPath));

      final String logPattern = logPath + "/" + applicationName + "_access.yyyy_mm_dd.log";
      final NCSARequestLog requestLog = new NCSARequestLog(logPattern);
      requestLog.setLogDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
      requestLog.setRetainDays(30);
      requestLog.setAppend(true);
      requestLog.setExtended(true);
      requestLog.setLogTimeZone("GMT+8");
      requestLog.setLogLatency(true);
      return requestLog;
    }
  }

  static void accessLog(final Environment environment,
    final ConfigurableEmbeddedServletContainer container) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    if (jetty == null) {
      return;
    }
    String applicationName =
      environment.getProperty("spring.application.name", "$spring.application.name");
    jetty.addServerCustomizers(new AccessLog(applicationName));
  }

  @NoArgsConstructor(access = PRIVATE)
  @Slf4j
  class Gzip implements JettyServerCustomizer {

    @Override
    public void customize(final Server server) {
      try {
        Class.forName("org.eclipse.jetty.server.handler.gzip.GzipHandler");
      } catch (final ClassNotFoundException ignored) {
        log.debug("org.eclipse.jetty.server.handler.gzip.GzipHandler is not in classpath.", ignored);
        return;
      }

      final HandlerCollection handlers = new HandlerCollection();

      final GzipHandler gzipHandler = new GzipHandler();
      gzipHandler.addIncludedMethods(HttpMethod.GET.asString(), HttpMethod.POST.asString(),
        HttpMethod.PUT.asString(), HttpMethod.DELETE.asString());
      gzipHandler.setCompressionLevel(5);
      handlers.addHandler(gzipHandler);

      asList(server.getHandlers()).forEach(handlers::addHandler);
      server.setHandler(handlers);
    }
  }

  /**
   * 最后添加gzip.
   *
   * @param environment environment
   * @param container   container
   */
  static void gzip( //
    final Environment environment, final ConfigurableEmbeddedServletContainer container //
  ) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    final Boolean gzipEnabled =
      parseBoolean(environment.getProperty("spring.http.gzip.enabled", "false"));
    if (gzipEnabled && jetty != null) {
      jetty.addServerCustomizers(new Gzip());
    }
  }

  @NoArgsConstructor(access = PRIVATE)
  class ForwardedRequest implements JettyServerCustomizer {

    @Override
    public void customize(final Server server) {
      final Collection<HttpConnectionFactory> httpConnectionFactories =
        serverConnectionFactories(server, HttpConnectionFactory.class);
      httpConnectionFactories.forEach(httpConnectionFactory -> {
        final HttpConfiguration httpConfiguration = httpConnectionFactory.getHttpConfiguration();
        httpConfiguration.addCustomizer(new ForwardedRequestCustomizer());
      });
    }
  }

  static void forwardedRequest(final Environment environment,
    final ConfigurableEmbeddedServletContainer container) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    if (jetty != null) {
      jetty.addServerCustomizers(new ForwardedRequest());
    }
  }

  @NoArgsConstructor(access = PRIVATE)
  class HideServerInfo implements JettyServerCustomizer {

    @Override
    public void customize(final Server server) {
      final Collection<HttpConnectionFactory> httpConnectionFactories =
        serverConnectionFactories(server, HttpConnectionFactory.class);
      httpConnectionFactories.forEach(httpConnectionFactory -> {
        final HttpConfiguration httpConfiguration = httpConnectionFactory.getHttpConfiguration();
        httpConfiguration.setSendServerVersion(false);
        httpConfiguration.setSendXPoweredBy(false);
        httpConfiguration.setSendDateHeader(false);
      });
    }
  }

  static void hideServerInfo(final Environment environment,
    final ConfigurableEmbeddedServletContainer container) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    if (jetty != null) {
      jetty.addServerCustomizers(new HideServerInfo());
    }
  }

  @NoArgsConstructor(access = PRIVATE)
  class NullSessionIdManager implements JettyServerCustomizer {

    @Override
    public void customize(final Server server) {
      server.setSessionIdManager(null);
    }
  }

  static void nullSessionIdManager(final Environment environment,
    final ConfigurableEmbeddedServletContainer container) {
    final JettyEmbeddedServletContainerFactory jetty = jetty(container);
    if (jetty != null) {
      jetty.addServerCustomizers(new NullSessionIdManager());
    }
  }

  @Deprecated
  @AllArgsConstructor
  class Pool implements JettyServerCustomizer {

    // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.maxThreads:200}")
    private final String maxThreads;
    // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.minThreads:8}")
    private final String minThreads;
    // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.idleTimeout:60000}")
    private final String idleTimeout;

    @Override
    public void customize(final Server server) {
      final QueuedThreadPool threadPool = server.getBean(QueuedThreadPool.class);
      threadPool.setMaxThreads(Integer.parseInt(this.maxThreads));
      threadPool.setMinThreads(Integer.parseInt(this.minThreads));
      threadPool.setIdleTimeout(Integer.parseInt(this.idleTimeout));
    }

    public static Pool smallPool() {
      return new Pool("1", "8", "60000");
    }
  }
}