Skip to content

Commit

Permalink
feat: Allow excluding URLs from being handled by a root mapped Vaadin…
Browse files Browse the repository at this point in the history
… Spring servlet (#14579)

This makes it possible to make services like swagger work correctly even when Vaadin is mapped to the context root.

Fixes #12949
  • Loading branch information
Artur- authored Sep 28, 2022
1 parent 672d29c commit 84bcdeb
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
server.port=8888
server.servlet.contextPath=/context
vaadin.exclude-urls=/swagger-ui/**

Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
server.port=8888
vaadin.blacklisted-packages=vaadin-spring/target/test-classes
vaadin.excludeUrls=/swagger-ui/**
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
server.port=8888
proxy.port=9999
proxy.path=/public/path
vaadin.excludeUrls=/swagger-ui/**
springdoc.swagger-ui.configUrl=/public/path/v3/api-docs/swagger-config
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
server.port=8888
vaadin.exclude-urls=/swagger-ui/**
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
server.port=8888
vaadin.exclude-urls=/swagger-ui/**
18 changes: 18 additions & 0 deletions flow-tests/vaadin-spring-tests/test-spring-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>


<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.149</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.vaadin.flow.spring.test;

import org.junit.Test;

public class SwaggerIT extends AbstractSpringTest {

@Override
protected String getTestPath() {
return "/swagger-ui.html";
}

@Test
public void swaggerUIShown() {
open();
waitUntil(
driver -> driver.getPageSource().contains("OpenAPI definition")
|| driver.getPageSource().contains("Swagger Petstore"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vaadin.exclude-urls=/swagger-ui/**
18 changes: 16 additions & 2 deletions flow-tests/vaadin-spring-tests/test-spring/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vaadin</groupId>
Expand Down Expand Up @@ -42,6 +41,12 @@
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test-spring-common</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
Expand Down Expand Up @@ -103,6 +108,15 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/SwaggerIT.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ public static String getUrlMapping(Environment environment) {
.map(conf -> conf.getUrlMapping()).orElse(null);
}

/**
* Gets the excluded URLs using the given environment.
*
* This is needed only when VaadinConfigurationProperties is not available
* for injection, e.g. when using Spring without Boot.
*
* @param environment
* the application environment
* @return the excluded URLs or null if none is defined
*/
public static List<String> getExcludedUrls(Environment environment) {
return Binder.get(environment)
.bind("vaadin", VaadinConfigurationProperties.class)
.map(conf -> conf.getExcludeUrls()).orElse(null);
}

/**
* Base URL mapping of the Vaadin servlet.
*/
Expand Down Expand Up @@ -84,6 +100,12 @@ public static String getUrlMapping(Environment environment) {
*/
private boolean launchBrowser = false;

/**
* URL patterns that should not be handled by the Vaadin servlet when mapped
* to the context root.
*/
private List<String> excludeUrls;

public static class Pnpm {
private boolean enable;

Expand Down Expand Up @@ -261,4 +283,25 @@ public List<String> getWhitelistedPackages() {
public void setWhitelistedPackages(List<String> whitelistedPackages) {
this.whitelistedPackages = new ArrayList<>(whitelistedPackages);
}

/**
* Get a list of URL patterns that are not handled by the Vaadin servlet
* when it is mapped to the context root.
*
* @return a list of url patterns to exclude
*/
public List<String> getExcludeUrls() {
return excludeUrls;
}

/**
* Set a list of URL patterns that are not handled by the Vaadin servlet
* when it is mapped to the context root.
*
* @param excludeUrls
* a list of url patterns to exclude
*/
public void setExcludeUrls(List<String> excludeUrls) {
this.excludeUrls = excludeUrls;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,28 @@
*/
package com.vaadin.flow.spring;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.ServletForwardingController;
import org.springframework.web.util.UrlPathHelper;

import com.vaadin.flow.server.VaadinServlet;

Expand All @@ -47,6 +58,83 @@
public class VaadinServletConfiguration {

static final String VAADIN_SERVLET_MAPPING = "/vaadinServlet/*";
public static final String EXCLUDED_URLS_PROPERTY = "vaadin.excludeUrls";

/**
* Gets the excluded URLs in a way compatible with both plain Spring and
* Spring Boot.
*
* @param environment
* the application environment
* @return the excluded URLs or null if none is defined
*/
private static List<String> getExcludedUrls(Environment environment) {
if (SpringUtil.isSpringBoot()) {
try {
return (List<String>) Class.forName(
"com.vaadin.flow.spring.VaadinConfigurationProperties")
.getMethod("getExcludedUrls", Environment.class)
.invoke(null, environment);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException
| SecurityException | ClassNotFoundException e) {
LoggerFactory.getLogger(RootMappedCondition.class).error(
"Unable to find excluded URLs from properties", e);
return null;
}
} else {
String value = environment.getProperty(EXCLUDED_URLS_PROPERTY);
if (value == null || value.isEmpty()) {
return Collections.emptyList();
} else {
return Arrays.stream(value.split(",")).map(url -> url.trim())
.collect(Collectors.toList());
}
}
}

private static class RootExcludeHandler extends SimpleUrlHandlerMapping {
private List<String> excludeUrls;
private AntPathMatcher matcher;
private UrlPathHelper urlPathHelper = new UrlPathHelper();

public RootExcludeHandler(List<String> excludeUrls,
Controller vaadinForwardingController) {
this.excludeUrls = excludeUrls;
matcher = new AntPathMatcher();

setOrder(Ordered.LOWEST_PRECEDENCE - 1);

// This is /** and not /* so that it is not interpreted as a
// "default handler"
// and we can override the behavior in getHandlerInternal
setUrlMap(Collections.singletonMap("/**",
vaadinForwardingController));
}

@Override
protected Object getHandlerInternal(HttpServletRequest request)
throws Exception {
if (excludeUrls != null && !excludeUrls.isEmpty()) {
String requestPath = urlPathHelper
.getPathWithinApplication(request);
for (String pattern : excludeUrls) {
if (matcher.match(pattern, requestPath)) {
getLogger().debug(
"Ignoring request to {} excluded by {}",
requestPath, pattern);
return null;
}
}
}
return super.getHandlerInternal(request);
}

protected Logger getLogger() {
return LoggerFactory.getLogger(getClass());
}

}

/**
* Makes an url handler mapping allowing to forward requests from a
Expand All @@ -56,14 +144,9 @@ public class VaadinServletConfiguration {
* servlet
*/
@Bean
public SimpleUrlHandlerMapping vaadinRootMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.LOWEST_PRECEDENCE - 1);

mapping.setUrlMap(
Collections.singletonMap("/*", vaadinForwardingController()));

return mapping;
public RootExcludeHandler vaadinRootMapping(Environment environment) {
return new RootExcludeHandler(getExcludedUrls(environment),
vaadinForwardingController());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ protected Stream<String> getExcludedPatterns() {
"com\\.vaadin\\.flow\\.spring\\.DispatcherServletRegistrationBeanConfig",
"com\\.vaadin\\.flow\\.spring\\.VaadinApplicationConfiguration",
"com\\.vaadin\\.flow\\.spring\\.VaadinServletConfiguration",
"com\\.vaadin\\.flow\\.spring\\.VaadinServletConfiguration\\$RootExcludeHandler",
"com\\.vaadin\\.flow\\.spring\\.VaadinScopesConfig",
"com\\.vaadin\\.flow\\.spring\\.VaadinSpringSecurity",
"com\\.vaadin\\.flow\\.spring\\.SpringBootAutoConfiguration",
Expand Down

0 comments on commit 84bcdeb

Please sign in to comment.