Spring MVC Spring catch all route for index.html

lqfhib0f  于 2023-10-24  发布在  Spring
关注(0)|答案(9)|浏览(197)

我正在为一个基于react的单页面应用程序开发一个spring后端,我使用react-router进行客户端路由。
在index.html页面旁边,后端在路径/api/**上提供数据。
为了从应用程序的根路径/上的src/main/resources/public/index.html提供index.html,我添加了一个资源处理程序

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/").addResourceLocations("/index.html");
}

我想做的是在没有其他路由匹配的时候提供index.html页面,例如,当我调用/api以外的路径时。
如何在 Spring 配置这种全方位路由?

pkbketx9

pkbketx91#

由于我的react应用程序可以使用根作为转发目标,这最终为我工作

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/{spring:\\w+}")
            .setViewName("forward:/");
      registry.addViewController("/**/{spring:\\w+}")
            .setViewName("forward:/");
      registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
            .setViewName("forward:/");
  }
}

老实说,我不知道为什么它必须完全在这个特定的格式,以避免无限转发循环。

c9x0cxw0

c9x0cxw02#

我在Sping Boot 应用程序中托管了一个基于聚合物的PWA,沿着静态Web资源(如图像),以及“/API/."下的REST API。我希望客户端应用程序处理PWA的URL路由。以下是我的用途:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    /**
     * Ensure client-side paths redirect to index.html because client handles routing. NOTE: Do NOT use @EnableWebMvc or it will break this.
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // Map "/"
        registry.addViewController("/")
                .setViewName("forward:/index.html");

        // Map "/word", "/word/word", and "/word/word/word" - except for anything starting with "/api/..." or ending with
        // a file extension like ".js" - to index.html. By doing this, the client receives and routes the url. It also
        // allows client-side URLs to be bookmarked.

        // Single directory level - no need to exclude "api"
        registry.addViewController("/{x:[\\w\\-]+}")
                .setViewName("forward:/index.html");
        // Multi-level directory path, need to exclude "api" on the first part of the path
        registry.addViewController("/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}")
                .setViewName("forward:/index.html");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
    }
}

这也适用于Angular和React应用程序。

c6ubokkw

c6ubokkw3#

避免@EnableWebMvc

默认情况下,Spring-Boot在src/main/resources中提供静态内容:

  • /META-INF/resources/
  • /资源/
  • /static/
  • /public/

看看thisthis;

或保留@EnableWebMvc并覆盖addViewControllers

你指定了@EnableWebMvc吗?看看这个:Java Spring Boot: How to map my app root (“/”) to index.html?
要么删除@EnableWebMvc,要么重新定义addViewControllers

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("forward:/index.html");
}

或者定义一个Controller来捕获/

你可以看看这个spring-boot-reactjs sample project on github
它可以使用控制器做你想做的事情:

@Controller
public class HomeController {

    @RequestMapping(value = "/")
    public String index() {
        return "index";
    }

}

index.html低于src/main/resources/templates

3mpgtkmj

3mpgtkmj4#

我在我的spring Boot 应用程序中使用react和react-router,它就像创建一个Map到/和我网站的子树(如/users/**)的控制器一样简单。

@Controller
public class SinglePageAppController {
    @RequestMapping(value = {"/", "/users/**", "/campaigns/**"})
    public String index() {
        return "index";
    }
}

API调用不会被这个控制器捕获,资源会被自动处理。

ryoqjall

ryoqjall5#

通过观察这个问题找到了答案

@Bean
public EmbeddedServletContainerCustomizer notFoundCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
        }
    };
}
btxsgosb

btxsgosb6#

另一种解决方案(更改/添加/删除myurl1myurl2,...与您的路由):

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class SinglePageAppController {

    /**
     * If the user refreshes the page while on a React route, the request will come here.
     * We need to tell it that there isn't any special page, just keep using React, by
     * forwarding it back to the root.
     */
    @RequestMapping({"/myurl1/**", "/myurl2/**"})
    public String forward(HttpServletRequest httpServletRequest) {
        return "forward:/";
    }
}

**注意:**使用public String index()也可以,但前提是使用模板。WebMvcConfigurerAdapter已弃用。

dphi5xsq

dphi5xsq7#

经过多次尝试,我发现下面的解决方案是最简单的。它基本上可以绕过所有的Spring处理,这是很难处理的。

@Component
public class StaticContentFilter implements Filter {
    
    private List<String> fileExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico");
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }
    
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String path = request.getServletPath();
        
        boolean isApi = path.startsWith("/api");
        boolean isResourceFile = !isApi && fileExtensions.stream().anyMatch(path::contains);
        
        if (isApi) {
            chain.doFilter(request, response);
        } else if (isResourceFile) {
            resourceToResponse("static" + path, response);
        } else {
            resourceToResponse("static/index.html", response);
        }
    }
    
    private void resourceToResponse(String resourcePath, HttpServletResponse response) throws IOException {
        InputStream inputStream = Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream(resourcePath);
        
        if (inputStream == null) {
            response.sendError(NOT_FOUND.value(), NOT_FOUND.getReasonPhrase());
            return;
        }
        
        inputStream.transferTo(response.getOutputStream());
    }
}
brc7rcf0

brc7rcf08#

为了回答你的具体问题,其中涉及在所有情况下提供单页应用程序(SPA)除了/API**路线这里是我做了修改Petri的答案。
我有一个名为polymer的模板,其中包含SPA的index.html。因此,挑战变成了让我们将除了/API和/public-API之外的所有路由转发到该视图。
在我的WebMvcConfigurerAdapter中,我覆盖了addViewControllers并使用了正则表达式:^((?!/API/|/public-api/).)$
在本例中,您需要正则表达式:^((?!/API/).)
$

public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/{spring:^((?!/api/).)*$}").setViewName("polymer");
    super.addViewControllers(registry);
}

这导致能够命中http://localhosthttp://localhost/community来提供我的SPA,SPA进行的所有其余调用都被成功路由到http://localhost/api/postshttp://localhost/public-api/posts等。

1zmg4dgp

1zmg4dgp9#

我想分享一个基于Jurass答案的解决方案。
Sping Boot 3.1 + SPA Angular应用程序在/resources/static文件夹中。
下面是过滤器:

private Filter staticResourceFilter() {
    return (request, response, chain) -> {
        String path = ((HttpServletRequest) request).getRequestURI();

        boolean isApi = path.startsWith("/api/v1");
        boolean isStaticResource = path.matches(".*\\.(js|css|ico|html)");

        if (isApi || isStaticResource) {
            chain.doFilter(request, response);
        } else {
            request.getRequestDispatcher("/index.html").forward(request, response);
        }
    };
}

以及它如何在Spring Security过滤器链中使用:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .cors(withDefaults())
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(authorizeConfig -> authorizeConfig
            .requestMatchers("/index.html", "/*.js", "/*.css", "/*.ico",
                "/api/v1/auth/login",
                // others routes...
            ).permitAll()
            .anyRequest().fullyAuthenticated()
        )
        .addFilterBefore(staticResourceFilter(), AuthorizationFilter.class)
        // others security stuff (oauth2, etc.)
    return http.build();
}

所有不是API调用或静态资源的请求都将被转发到/index.html页面,因此Angular可以接管路由过程。

相关问题