View Javadoc
1   package cn.home1.oss.lib.webmvc.api;
2   
3   import static com.google.common.collect.Sets.newLinkedHashSet;
4   import static java.lang.Boolean.parseBoolean;
5   import static java.util.Arrays.asList;
6   import static java.util.stream.Collectors.toList;
7   import static lombok.AccessLevel.PRIVATE;
8   
9   import lombok.AllArgsConstructor;
10  import lombok.NoArgsConstructor;
11  import lombok.NonNull;
12  import lombok.SneakyThrows;
13  import lombok.extern.slf4j.Slf4j;
14  
15  import org.apache.commons.io.FileUtils;
16  import org.eclipse.jetty.http.HttpMethod;
17  import org.eclipse.jetty.server.ConnectionFactory;
18  import org.eclipse.jetty.server.Connector;
19  import org.eclipse.jetty.server.ForwardedRequestCustomizer;
20  import org.eclipse.jetty.server.HttpConfiguration;
21  import org.eclipse.jetty.server.HttpConnectionFactory;
22  import org.eclipse.jetty.server.NCSARequestLog;
23  import org.eclipse.jetty.server.RequestLog;
24  import org.eclipse.jetty.server.Server;
25  import org.eclipse.jetty.server.ServerConnector;
26  import org.eclipse.jetty.server.Slf4jRequestLog;
27  import org.eclipse.jetty.server.handler.HandlerCollection;
28  import org.eclipse.jetty.server.handler.MovedContextHandler;
29  import org.eclipse.jetty.server.handler.RequestLogHandler;
30  import org.eclipse.jetty.server.handler.gzip.GzipHandler;
31  import org.eclipse.jetty.util.thread.QueuedThreadPool;
32  import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
33  import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
34  import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
35  import org.springframework.core.env.Environment;
36  
37  import java.io.File;
38  import java.util.Collection;
39  
40  public interface Jetty9ServerCustomizers {
41  
42    @SuppressWarnings("unchecked")
43    static <T> Collection<T> connectorConnectionFactories(final Connector connector,
44      final Class<T> ofType) {
45      final Collection<T> connectionFactories = newLinkedHashSet();
46      final ConnectionFactory defaultConnectionFactory = connector.getDefaultConnectionFactory();
47      if (defaultConnectionFactory != null
48        && ofType.isAssignableFrom(defaultConnectionFactory.getClass())) {
49        connectionFactories.add((T) defaultConnectionFactory);
50      }
51      connectionFactories.addAll(connector.getConnectionFactories().stream()
52        .filter(connectionFactory -> ofType.isAssignableFrom(connectionFactory.getClass()))
53        .map(connectionFactory -> (T) connectionFactory).collect(toList()));
54      return connectionFactories;
55    }
56  
57    static <T> Collection<T> serverConnectionFactories(final Server server, final Class<T> ofType) {
58      return newLinkedHashSet(asList(server.getConnectors()).stream()
59        .flatMap(connector -> connectorConnectionFactories(connector, ofType).stream())
60        .collect(toList()));
61    }
62  
63    static Collection<ServerConnector> serverConnectors(final Server server) {
64      return newLinkedHashSet(
65        asList(server.getConnectors()).stream().filter(c -> c instanceof ServerConnector)
66          .map(connector -> (ServerConnector) connector).collect(toList()));
67    }
68  
69    static JettyEmbeddedServletContainerFactory jetty(
70      final ConfigurableEmbeddedServletContainer container) {
71      if (container instanceof JettyEmbeddedServletContainerFactory) {
72        return (JettyEmbeddedServletContainerFactory) container;
73      } else {
74        return null;
75      }
76    }
77  
78    @AllArgsConstructor(access = PRIVATE)
79    class RedirectToHostRoot implements JettyServerCustomizer {
80  
81      @NonNull
82      private final String host;
83      @NonNull
84      private final String root;
85  
86      @Override
87      public void customize(final Server server) {
88        final HandlerCollection handlers = new HandlerCollection();
89        final MovedContextHandler movedContextHandler = new MovedContextHandler();
90        movedContextHandler.setContextPath("/");
91        movedContextHandler.setNewContextURL(this.root);
92        movedContextHandler.setPermanent(true);
93        movedContextHandler.setDiscardPathInfo(false);
94        movedContextHandler.setDiscardQuery(false);
95        movedContextHandler.setVirtualHosts(new String[]{this.host});
96        handlers.addHandler(movedContextHandler);
97  
98        asList(server.getHandlers()).forEach(handlers::addHandler);
99        server.setHandler(handlers);
100     }
101   }
102 
103   /**
104    * see:
105    * http://stackoverflow.com/questions/3539143/redirect-non-www-version-of-domain-to-www-in-jetty
106    *
107    * @param container container
108    * @param host      host
109    * @param root      root
110    */
111   static void redirectRootDomainToHostRoot( //
112     final ConfigurableEmbeddedServletContainer container, final String host, final String root //
113   ) {
114     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
115     if (jetty == null) {
116       return;
117     }
118 
119     jetty.addServerCustomizers(new RedirectToHostRoot(host, root));
120   }
121 
122   @AllArgsConstructor(access = PRIVATE)
123   class AccessLog implements JettyServerCustomizer {
124 
125     private String applicationName;
126 
127     @Override
128     public void customize(final Server server) {
129       final HandlerCollection handlers = new HandlerCollection();
130       // final RequestLog requestLog = slf4jLog();
131       final RequestLog requestLog = ncsaRequestLog(applicationName);
132       final RequestLogHandler logHandler = new RequestLogHandler();
133       logHandler.setRequestLog(requestLog);
134       handlers.addHandler(logHandler);
135 
136       asList(server.getHandlers()).forEach(handlers::addHandler);
137       server.setHandler(handlers);
138     }
139 
140     public static RequestLog slf4jLog() {
141       final Slf4jRequestLog requestLog = new Slf4jRequestLog();
142       return requestLog;
143     }
144 
145     @SneakyThrows
146     public static RequestLog ncsaRequestLog(String applicationName) {
147       final String logPath = "./logs";
148       FileUtils.forceMkdir(new File(logPath));
149 
150       final String logPattern = logPath + "/" + applicationName + "_access.yyyy_mm_dd.log";
151       final NCSARequestLog requestLog = new NCSARequestLog(logPattern);
152       requestLog.setLogDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
153       requestLog.setRetainDays(30);
154       requestLog.setAppend(true);
155       requestLog.setExtended(true);
156       requestLog.setLogTimeZone("GMT+8");
157       requestLog.setLogLatency(true);
158       return requestLog;
159     }
160   }
161 
162   static void accessLog(final Environment environment,
163     final ConfigurableEmbeddedServletContainer container) {
164     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
165     if (jetty == null) {
166       return;
167     }
168     String applicationName =
169       environment.getProperty("spring.application.name", "$spring.application.name");
170     jetty.addServerCustomizers(new AccessLog(applicationName));
171   }
172 
173   @NoArgsConstructor(access = PRIVATE)
174   @Slf4j
175   class Gzip implements JettyServerCustomizer {
176 
177     @Override
178     public void customize(final Server server) {
179       try {
180         Class.forName("org.eclipse.jetty.server.handler.gzip.GzipHandler");
181       } catch (final ClassNotFoundException ignored) {
182         log.debug("org.eclipse.jetty.server.handler.gzip.GzipHandler is not in classpath.", ignored);
183         return;
184       }
185 
186       final HandlerCollection handlers = new HandlerCollection();
187 
188       final GzipHandler gzipHandler = new GzipHandler();
189       gzipHandler.addIncludedMethods(HttpMethod.GET.asString(), HttpMethod.POST.asString(),
190         HttpMethod.PUT.asString(), HttpMethod.DELETE.asString());
191       gzipHandler.setCompressionLevel(5);
192       handlers.addHandler(gzipHandler);
193 
194       asList(server.getHandlers()).forEach(handlers::addHandler);
195       server.setHandler(handlers);
196     }
197   }
198 
199   /**
200    * 最后添加gzip.
201    *
202    * @param environment environment
203    * @param container   container
204    */
205   static void gzip( //
206     final Environment environment, final ConfigurableEmbeddedServletContainer container //
207   ) {
208     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
209     final Boolean gzipEnabled =
210       parseBoolean(environment.getProperty("spring.http.gzip.enabled", "false"));
211     if (gzipEnabled && jetty != null) {
212       jetty.addServerCustomizers(new Gzip());
213     }
214   }
215 
216   @NoArgsConstructor(access = PRIVATE)
217   class ForwardedRequest implements JettyServerCustomizer {
218 
219     @Override
220     public void customize(final Server server) {
221       final Collection<HttpConnectionFactory> httpConnectionFactories =
222         serverConnectionFactories(server, HttpConnectionFactory.class);
223       httpConnectionFactories.forEach(httpConnectionFactory -> {
224         final HttpConfiguration httpConfiguration = httpConnectionFactory.getHttpConfiguration();
225         httpConfiguration.addCustomizer(new ForwardedRequestCustomizer());
226       });
227     }
228   }
229 
230   static void forwardedRequest(final Environment environment,
231     final ConfigurableEmbeddedServletContainer container) {
232     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
233     if (jetty != null) {
234       jetty.addServerCustomizers(new ForwardedRequest());
235     }
236   }
237 
238   @NoArgsConstructor(access = PRIVATE)
239   class HideServerInfo implements JettyServerCustomizer {
240 
241     @Override
242     public void customize(final Server server) {
243       final Collection<HttpConnectionFactory> httpConnectionFactories =
244         serverConnectionFactories(server, HttpConnectionFactory.class);
245       httpConnectionFactories.forEach(httpConnectionFactory -> {
246         final HttpConfiguration httpConfiguration = httpConnectionFactory.getHttpConfiguration();
247         httpConfiguration.setSendServerVersion(false);
248         httpConfiguration.setSendXPoweredBy(false);
249         httpConfiguration.setSendDateHeader(false);
250       });
251     }
252   }
253 
254   static void hideServerInfo(final Environment environment,
255     final ConfigurableEmbeddedServletContainer container) {
256     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
257     if (jetty != null) {
258       jetty.addServerCustomizers(new HideServerInfo());
259     }
260   }
261 
262   @NoArgsConstructor(access = PRIVATE)
263   class NullSessionIdManager implements JettyServerCustomizer {
264 
265     @Override
266     public void customize(final Server server) {
267       server.setSessionIdManager(null);
268     }
269   }
270 
271   static void nullSessionIdManager(final Environment environment,
272     final ConfigurableEmbeddedServletContainer container) {
273     final JettyEmbeddedServletContainerFactory jetty = jetty(container);
274     if (jetty != null) {
275       jetty.addServerCustomizers(new NullSessionIdManager());
276     }
277   }
278 
279   @Deprecated
280   @AllArgsConstructor
281   class Pool implements JettyServerCustomizer {
282 
283     // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.maxThreads:200}")
284     private final String maxThreads;
285     // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.minThreads:8}")
286     private final String minThreads;
287     // @org.springframework.beans.factory.annotation.Value("${jetty.threadPool.idleTimeout:60000}")
288     private final String idleTimeout;
289 
290     @Override
291     public void customize(final Server server) {
292       final QueuedThreadPool threadPool = server.getBean(QueuedThreadPool.class);
293       threadPool.setMaxThreads(Integer.parseInt(this.maxThreads));
294       threadPool.setMinThreads(Integer.parseInt(this.minThreads));
295       threadPool.setIdleTimeout(Integer.parseInt(this.idleTimeout));
296     }
297 
298     public static Pool smallPool() {
299       return new Pool("1", "8", "60000");
300     }
301   }
302 }