Java tutorial
/* * Copyright (c) 2018 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.mica.config; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import javax.inject.Inject; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlets.GzipFilter; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.obiba.mica.web.filter.CachingHttpHeadersFilter; import org.obiba.mica.web.filter.StaticResourcesProductionFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.servlet.InstrumentedFilter; import com.codahale.metrics.servlets.MetricsServlet; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; import static javax.servlet.DispatcherType.FORWARD; import static javax.servlet.DispatcherType.INCLUDE; import static javax.servlet.DispatcherType.REQUEST; import static org.obiba.mica.config.JerseyConfiguration.WS_ROOT; /** * Configuration of web application with Servlet 3.0 APIs. */ @Configuration @ComponentScan({ "org.obiba.mica", "org.obiba.shiro" }) @PropertySource("classpath:mica-webapp.properties") @AutoConfigureAfter(SecurityConfiguration.class) public class WebConfiguration implements ServletContextInitializer, JettyServerCustomizer, EnvironmentAware { private static final Logger log = LoggerFactory.getLogger(WebConfiguration.class); private static final int DEFAULT_HTTPS_PORT = 8445; private static final int MAX_IDLE_TIME = 30000; private static final int REQUEST_HEADER_SIZE = 8192; @Inject private Environment env; @Inject private MetricRegistry metricRegistry; @Inject private org.obiba.ssl.SslContextFactory sslContextFactory; private int httpsPort; @Override public void setEnvironment(Environment environment) { RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "https."); httpsPort = propertyResolver.getProperty("port", Integer.class, DEFAULT_HTTPS_PORT); } @Bean EmbeddedServletContainerCustomizer containerCustomizer() throws Exception { return (ConfigurableEmbeddedServletContainer container) -> { JettyEmbeddedServletContainerFactory jetty = (JettyEmbeddedServletContainerFactory) container; jetty.setServerCustomizers(Collections.singleton(this)); }; } @Override public void customize(Server server) { customizeSsl(server); } private void customizeSsl(Server server) { SslContextFactory jettySsl = new SslContextFactory() { @Override protected void doStart() throws Exception { setSslContext(sslContextFactory.createSslContext()); super.doStart(); } }; jettySsl.setWantClientAuth(true); jettySsl.setNeedClientAuth(false); jettySsl.addExcludeProtocols("SSLv2", "SSLv3"); ServerConnector sslConnector = new ServerConnector(server, jettySsl); sslConnector.setPort(httpsPort); sslConnector.setIdleTimeout(MAX_IDLE_TIME); server.addConnector(sslConnector); } @Override public void onStartup(ServletContext servletContext) throws ServletException { log.info("Web application configuration, using profiles: {}", Arrays.toString(env.getActiveProfiles())); initAllowedMethods(servletContext); // Note: authentication filter was already added by Spring EnumSet<DispatcherType> disps = EnumSet.of(REQUEST, FORWARD, ASYNC); initMetrics(servletContext, disps); if (env.acceptsProfiles(Profiles.PROD)) { initStaticResourcesProductionFilter(servletContext, disps); initCachingHttpHeadersFilter(servletContext, disps); } initGzipFilter(servletContext, disps); log.info("Web application fully configured"); } private void initAllowedMethods(ServletContext servletContext) { log.debug("Registering Allowed Methods Filter"); FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("noTrace", new NoTraceFilter()); filterRegistration.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD, ASYNC, INCLUDE, ERROR), true, "/*"); filterRegistration.setAsyncSupported(true); } /** * Initializes the GZip filter. */ private void initGzipFilter(ServletContext servletContext, EnumSet<DispatcherType> disps) { log.debug("Registering GZip Filter"); FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("gzipFilter", new GzipFilter()); if (filterRegistration == null) { filterRegistration = (FilterRegistration.Dynamic) servletContext.getFilterRegistration("gzipFilter"); } filterRegistration.addMappingForUrlPatterns(disps, true, "*.css"); filterRegistration.addMappingForUrlPatterns(disps, true, "*.json"); filterRegistration.addMappingForUrlPatterns(disps, true, "*.html"); filterRegistration.addMappingForUrlPatterns(disps, true, "*.js"); filterRegistration.addMappingForUrlPatterns(disps, true, "/jvm/*"); filterRegistration.addMappingForUrlPatterns(disps, true, WS_ROOT + "/*"); filterRegistration.setAsyncSupported(true); } /** * Initializes the static resources production Filter. */ private void initStaticResourcesProductionFilter(ServletContext servletContext, EnumSet<DispatcherType> disps) { log.debug("Registering static resources production Filter"); FilterRegistration.Dynamic resourcesFilter = servletContext.addFilter("staticResourcesProductionFilter", new StaticResourcesProductionFilter()); resourcesFilter.addMappingForUrlPatterns(disps, true, "/favicon.ico"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/robots.txt"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/index.html"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/images/*"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/fonts/*"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/scripts/*"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/styles/*"); resourcesFilter.addMappingForUrlPatterns(disps, true, "/views/*"); resourcesFilter.setAsyncSupported(true); } /** * Initializes the caching HTTP Headers Filter. */ private void initCachingHttpHeadersFilter(ServletContext servletContext, EnumSet<DispatcherType> disps) { log.debug("Registering Caching HTTP Headers Filter"); FilterRegistration.Dynamic cachingFilter = servletContext.addFilter("cachingHttpHeadersFilter", new CachingHttpHeadersFilter()); cachingFilter.addMappingForUrlPatterns(disps, true, "/images/*"); cachingFilter.addMappingForUrlPatterns(disps, true, "/fonts/*"); cachingFilter.addMappingForUrlPatterns(disps, true, "/scripts/*"); cachingFilter.addMappingForUrlPatterns(disps, true, "/styles/*"); cachingFilter.setAsyncSupported(true); } /** * Initializes Metrics. */ private void initMetrics(ServletContext servletContext, EnumSet<DispatcherType> disps) { log.debug("Initializing Metrics registries"); servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE, metricRegistry); servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY, metricRegistry); log.debug("Registering Metrics Filter"); FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter", new InstrumentedFilter()); metricsFilter.addMappingForUrlPatterns(disps, true, "/*"); metricsFilter.setAsyncSupported(true); log.debug("Registering Metrics Servlet"); ServletRegistration.Dynamic metricsAdminServlet = servletContext.addServlet("metricsServlet", new MetricsServlet()); metricsAdminServlet.addMapping("/jvm/*"); metricsAdminServlet.setAsyncSupported(true); metricsAdminServlet.setLoadOnStartup(2); } /** * When a TRACE request is received, returns a Forbidden response. */ private static class NoTraceFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; if ("TRACE".equals(httpRequest.getMethod())) { httpResponse.reset(); httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "TRACE method not allowed"); return; } chain.doFilter(request, response); } @Override public void destroy() { } } }