From: Julien Lancelot Date: Tue, 6 Sep 2016 10:23:50 +0000 (+0200) Subject: SONAR-7714 Remove JSESSIONID generation from Tomcat X-Git-Tag: 6.1-RC1~160 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=79c3bfc01953b03cee1e0bf210cf11734ce9ef24;p=sonarqube.git SONAR-7714 Remove JSESSIONID generation from Tomcat --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java index 56dcf66f5ff..b15d76e0854 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java @@ -112,6 +112,7 @@ public class TomcatContexts { context.setDelegate(true); context.setJarScanner(new NullJarScanner()); context.setAllowCasualMultipartParsing(true); + context.setCookies(false); return context; } catch (ServletException e) { throw new IllegalStateException("Fail to configure webapp from " + dir, e); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java index 76f3abd0822..230bddd3cd6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java @@ -82,7 +82,7 @@ public class MasterServletFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest hsr = (HttpServletRequest) request; if (filters.length == 0) { - chain.doFilter(request, response); + chain.doFilter(hsr, response); } else { String path = hsr.getRequestURI().replaceFirst(hsr.getContextPath(), ""); GodFilterChain godChain = new GodFilterChain(chain); @@ -92,7 +92,7 @@ public class MasterServletFilter implements Filter { godChain.addFilter(filter); } } - godChain.doFilter(request, response); + godChain.doFilter(hsr, response); } } @@ -134,4 +134,5 @@ public class MasterServletFilter implements Filter { filters.add(filter); } } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java deleted file mode 100644 index 8728a7b401c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.web; - -import com.google.common.collect.ImmutableSet; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -import java.io.IOException; -import java.util.Set; - -/** - *

Profile HTTP requests using platform profiling utility.

- *

To avoid profiling of requests for static resources, the staticDirs - * filter parameter can be set in the servlet context descriptor. This parameter should - * contain a comma-separated list of paths, starting at the context root; - * requests on subpaths of these paths will not be profiled.

- * - * @since 4.1 - */ -public class ProfilingFilter implements Filter { - - private static final String CONFIG_SEPARATOR = ","; - private static final String URL_SEPARATOR = "/"; - - private static final String MESSAGE_WITH_QUERY = "%s %s?%s"; - private static final String MESSAGE_WITHOUT_QUERY = "%s %s"; - public static final org.sonar.api.utils.log.Logger Logger = Loggers.get("http"); - - private String contextRoot; - private Set staticResourceDirs; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - contextRoot = filterConfig.getServletContext().getContextPath(); - - String staticResourcesConfig = filterConfig.getInitParameter("staticDirs"); - if (StringUtils.isNotBlank(staticResourcesConfig)) { - staticResourceDirs = ImmutableSet.copyOf(staticResourcesConfig.split(CONFIG_SEPARATOR)); - } else { - staticResourceDirs = ImmutableSet.of(); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String requestUri = httpRequest.getRequestURI(); - String rootDir = getRootDir(requestUri); - - if (staticResourceDirs.contains(rootDir)) { - // Static resource, not profiled - chain.doFilter(request, response); - } else { - Profiler profiler = Profiler.createIfDebug(Logger).start(); - try { - chain.doFilter(request, response); - } finally { - if (profiler.isDebugEnabled()) { - String queryString = httpRequest.getQueryString(); - String message = String.format(queryString == null ? MESSAGE_WITHOUT_QUERY : MESSAGE_WITH_QUERY, httpRequest.getMethod(), requestUri, queryString); - profiler.stopDebug(message); - } - } - } - } else { - // Not an HTTP request, not profiled - chain.doFilter(request, response); - } - } - - private String getRootDir(String requestUri) { - String rootPath = ""; - String localPath = StringUtils.substringAfter(requestUri, contextRoot); - if (localPath.startsWith(URL_SEPARATOR)) { - int secondSlash = localPath.indexOf(URL_SEPARATOR, 1); - if (secondSlash > 0) { - rootPath = URL_SEPARATOR + localPath.substring(1, secondSlash); - } - } - return rootPath; - } - - @Override - public void destroy() { - // Nothing - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RootFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RootFilter.java new file mode 100644 index 00000000000..dec5a8d76c1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RootFilter.java @@ -0,0 +1,138 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpSession; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +/** + *

Profile HTTP requests using platform profiling utility.

+ *

To avoid profiling of requests for static resources, the staticDirs + * filter parameter can be set in the servlet context descriptor. This parameter should + * contain a comma-separated list of paths, starting at the context root; + * requests on subpaths of these paths will not be profiled.

+ * + * @since 4.1 + */ +public class RootFilter implements Filter { + + private static final String CONFIG_SEPARATOR = ","; + private static final String URL_SEPARATOR = "/"; + + private static final String MESSAGE_WITH_QUERY = "%s %s?%s"; + private static final String MESSAGE_WITHOUT_QUERY = "%s %s"; + public static final org.sonar.api.utils.log.Logger Logger = Loggers.get("http"); + + private String contextRoot; + private Set staticResourceDirs; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + contextRoot = filterConfig.getServletContext().getContextPath(); + + String staticResourcesConfig = filterConfig.getInitParameter("staticDirs"); + if (StringUtils.isNotBlank(staticResourcesConfig)) { + staticResourceDirs = ImmutableSet.copyOf(staticResourcesConfig.split(CONFIG_SEPARATOR)); + } else { + staticResourceDirs = ImmutableSet.of(); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = new ServletRequestWrapper((HttpServletRequest) request); + String requestUri = httpRequest.getRequestURI(); + String rootDir = getRootDir(requestUri); + + if (staticResourceDirs.contains(rootDir)) { + // Static resource, not profiled + chain.doFilter(httpRequest, response); + } else { + Profiler profiler = Profiler.createIfDebug(Logger).start(); + try { + chain.doFilter(httpRequest, response); + } finally { + if (profiler.isDebugEnabled()) { + String queryString = httpRequest.getQueryString(); + String message = String.format(queryString == null ? MESSAGE_WITHOUT_QUERY : MESSAGE_WITH_QUERY, httpRequest.getMethod(), requestUri, queryString); + profiler.stopDebug(message); + } + } + } + } else { + // Not an HTTP request, not profiled + chain.doFilter(request, response); + } + } + + private String getRootDir(String requestUri) { + String rootPath = ""; + String localPath = StringUtils.substringAfter(requestUri, contextRoot); + if (localPath.startsWith(URL_SEPARATOR)) { + int secondSlash = localPath.indexOf(URL_SEPARATOR, 1); + if (secondSlash > 0) { + rootPath = URL_SEPARATOR + localPath.substring(1, secondSlash); + } + } + return rootPath; + } + + @Override + public void destroy() { + // Nothing + } + + @VisibleForTesting + static class ServletRequestWrapper extends HttpServletRequestWrapper { + + ServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public HttpSession getSession(boolean create) { + throw notSupported(); + } + + @Override + public HttpSession getSession() { + throw notSupported(); + } + + private static UnsupportedOperationException notSupported() { + return new UnsupportedOperationException("Sessions are disabled so that web server is stateless"); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java index 255dab8f8e3..3ac703c5819 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java @@ -46,7 +46,7 @@ import static org.mockito.Mockito.when; public class MasterServletFilterTest { @Rule - public ExpectedException thrown = ExpectedException.none(); + public ExpectedException expectedException = ExpectedException.none(); @Before public void resetSingleton() { @@ -71,15 +71,15 @@ public class MasterServletFilterTest { public void servlet_container_should_instantiate_only_a_single_master_instance() { new MasterServletFilter(); - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Servlet filter org.sonar.server.platform.web.MasterServletFilter is already instantiated"); + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Servlet filter org.sonar.server.platform.web.MasterServletFilter is already instantiated"); new MasterServletFilter(); } @Test public void should_propagate_initialization_failure() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("foo"); + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("foo"); ServletFilter filter = mock(ServletFilter.class); doThrow(new IllegalStateException("foo")).when(filter).init(any(FilterConfig.class)); @@ -126,17 +126,20 @@ public class MasterServletFilterTest { private static int globalCount = 0; private int count = 0; + @Override public void init(FilterConfig filterConfig) throws ServletException { } + @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { globalCount++; count = globalCount; filterChain.doFilter(servletRequest, servletResponse); } + @Override public void destroy() { } - } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java deleted file mode 100644 index 93c339c9986..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.web; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.sonar.server.platform.web.ProfilingFilter; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ProfilingFilterTest { - - private ProfilingFilter filter; - private FilterChain chain; - - @Before - public void initialize() throws Exception { - FilterConfig filterConfig = mock(FilterConfig.class); - when(filterConfig.getInitParameter("staticDirs")).thenReturn("/static,/assets"); - ServletContext context = mock(ServletContext.class); - when(context.getContextPath()).thenReturn("/context"); - when(filterConfig.getServletContext()).thenReturn(context); - chain = mock(FilterChain.class); - - filter = new ProfilingFilter(); - filter.init(filterConfig); - } - - @Test - public void should_profile_service_call() throws Exception { - filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); - } - - @Test - public void should_profile_service() throws Exception { - filter.doFilter(request("POST", "/context/service", null), null, chain); - } - - @Test - public void should_profile_context_root_as_slash2() throws Exception { - filter.doFilter(request("POST", "/context", null), null, chain); - } - - @Test(expected = ServletException.class) - public void should_profile_even_when_exception() throws Exception { - Mockito.doThrow(new ServletException()).when(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); - filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); - } - - @Test - public void should_not_profile_non_http_request() throws Exception { - filter.doFilter(mock(ServletRequest.class), null, chain); - } - - @Test - public void should_not_profile_static_resource() throws Exception { - filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); - } - - @Test - public void should_profile_static_resource_if_no_config() throws Exception { - FilterConfig filterConfig = mock(FilterConfig.class); - ServletContext context = mock(ServletContext.class); - when(context.getContextPath()).thenReturn("/context"); - when(filterConfig.getServletContext()).thenReturn(context); - - filter.init(filterConfig); - filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); - } - - private HttpServletRequest request(String method, String path, String query) { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getMethod()).thenReturn(method); - when(request.getRequestURI()).thenReturn(path); - when(request.getQueryString()).thenReturn(query); - return request; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/RootFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RootFilterTest.java new file mode 100644 index 00000000000..aff2ff1b642 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RootFilterTest.java @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RootFilterTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private RootFilter filter; + private FilterChain chain; + + @Before + public void initialize() throws Exception { + FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter("staticDirs")).thenReturn("/static,/assets"); + ServletContext context = mock(ServletContext.class); + when(context.getContextPath()).thenReturn("/context"); + when(filterConfig.getServletContext()).thenReturn(context); + chain = mock(FilterChain.class); + + filter = new RootFilter(); + filter.init(filterConfig); + } + + @Test + public void should_profile_service_call() throws Exception { + filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); + } + + @Test + public void should_profile_service() throws Exception { + filter.doFilter(request("POST", "/context/service", null), null, chain); + } + + @Test + public void should_profile_context_root_as_slash2() throws Exception { + filter.doFilter(request("POST", "/context", null), null, chain); + } + + @Test(expected = ServletException.class) + public void should_profile_even_when_exception() throws Exception { + Mockito.doThrow(new ServletException()).when(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); + } + + @Test + public void should_not_profile_non_http_request() throws Exception { + filter.doFilter(mock(ServletRequest.class), null, chain); + } + + @Test + public void should_not_profile_static_resource() throws Exception { + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + } + + @Test + public void should_profile_static_resource_if_no_config() throws Exception { + FilterConfig filterConfig = mock(FilterConfig.class); + ServletContext context = mock(ServletContext.class); + when(context.getContextPath()).thenReturn("/context"); + when(filterConfig.getServletContext()).thenReturn(context); + + filter.init(filterConfig); + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + } + + @Test + public void request_used_in_chain_do_filter_is_a_servlet_wrapper_when_static_resource() throws Exception { + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(ServletRequest.class); + + verify(chain).doFilter(requestArgumentCaptor.capture(), any(ServletResponse.class)); + + assertThat(requestArgumentCaptor.getValue()).isInstanceOf(RootFilter.ServletRequestWrapper.class); + } + + @Test + public void request_used_in_chain_do_filter_is_a_servlet_wrapper_when_service_call() throws Exception { + filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(ServletRequest.class); + + verify(chain).doFilter(requestArgumentCaptor.capture(), any(ServletResponse.class)); + + assertThat(requestArgumentCaptor.getValue()).isInstanceOf(RootFilter.ServletRequestWrapper.class); + } + + @Test + public void fail_to_get_session_from_request() throws Exception { + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(ServletRequest.class); + verify(chain).doFilter(requestArgumentCaptor.capture(), any(ServletResponse.class)); + + expectedException.expect(UnsupportedOperationException.class); + ((HttpServletRequest) requestArgumentCaptor.getValue()).getSession(); + } + + @Test + public void fail_to_get_session_with_create_from_request() throws Exception { + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(ServletRequest.class); + verify(chain).doFilter(requestArgumentCaptor.capture(), any(ServletResponse.class)); + + expectedException.expect(UnsupportedOperationException.class); + ((HttpServletRequest) requestArgumentCaptor.getValue()).getSession(true); + } + + private HttpServletRequest request(String method, String path, String query) { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn(method); + when(request.getRequestURI()).thenReturn(path); + when(request.getQueryString()).thenReturn(query); + return request; + } +} diff --git a/server/sonar-web/src/main/webapp/WEB-INF/web.xml b/server/sonar-web/src/main/webapp/WEB-INF/web.xml index b86c1a99b9b..e0bd48e3257 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/web.xml +++ b/server/sonar-web/src/main/webapp/WEB-INF/web.xml @@ -53,8 +53,8 @@ org.sonar.server.platform.web.SecurityServletFilter - ProfilingFilter - org.sonar.server.platform.web.ProfilingFilter + RootFilter + org.sonar.server.platform.web.RootFilter staticDirs /images,/javascripts,/stylesheets @@ -67,7 +67,7 @@ - ProfilingFilter + RootFilter /* @@ -101,14 +101,6 @@ /static/* - - - 20 - - true - - - org.sonar.server.platform.web.PlatformServletContextListener