Spring 捕获 index.html 的所有路由

Posted

技术标签:

【中文标题】Spring 捕获 index.html 的所有路由【英文标题】:Spring catch all route for index.html 【发布时间】:2017-01-12 21:40:46 【问题描述】:

我正在为基于 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以外的路径时。

如何在春季配置这样的包罗万象的路线?

【问题讨论】:

【参考方案1】:

经过多次尝试,我发现以下解决方案是最简单的解决方案。它基本上会绕过所有很难处理的 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());
    

【讨论】:

【参考方案2】:

另一种解决方案(更改/添加/删除 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

【讨论】:

【参考方案3】:

我在我的 Spring Boot 应用程序中托管了一个基于 Polymer 的 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 应用程序。

【讨论】:

这对我不起作用,我们有到 /resources/static/index.html 的 webapp 路径但仍然无法正常工作,我得到的只是来自任何 RestController 的随机视图 谢谢,这对我有用 - 只需将 resourceLocation 更改为 classpath:/static/ 即可使其与我的 React 应用程序一起运行。请注意,从 Spring 5.x.x 开始。扩展 WebMvcConfigurerAdapter 已弃用,而必须实施 WebMvcConfigurer 这比当前投票率最高的答案更简洁,解释得更好:)【参考方案4】:

要回答您在所有情况下都提供单页应用程序 (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://localhost 或 http://localhost/community 来提供我的 SPA,并且 SPA 发出的所有其余调用都成功路由到 http://localhost/api/posts、http://localhost/public-api/posts 等。

【讨论】:

这工作从/ 开始并在 SPA 中导航,但不可收藏/并且无法在刷新后保留。【参考方案5】:

避免使用@EnableWebMvc

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

/META-INF/resources/ /资源/ /静态/ /公共/

看看this和this;

或者保留@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.htmlsrc/main/resources/templates下面

【讨论】:

我认为这应该被标记为你的问题的答案。 我在模板文件夹中有probe.html,而不是index.html。如果我按照这个例子,我得到:“没有找到 GET /probe 的处理程序”我也尝试使用 index.html,但我得到了同样的错误。 这并不能解决问题所要求的客户端路由。它只为根路径服务index.html。它不会将 /campaigns/33 之类的 URL 重定向到 index.html【参考方案6】:

由于我的反应应用程序可以使用根作为前向目标,这最终对我有用

@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:/");
  

说实话,我不知道为什么它必须完全采用这种特定格式以避免无限转发循环。

【讨论】:

谢谢!这适用于 Angular 4 RouterModule + Spring Boot Embedded Container 类型设置;) 我遇到了第三个addViewController 方法调用的问题。我的情况是我有未提供的 webfonts 文件夹。我认为这是因为正则表达式只指定了 js 和 css 扩展。我最终删除了第三个并添加了一个新的资源处理程序。 您好,代码中的spring前缀是什么意思? 正则表达式不适用于连字符 (-) 和下划线 (_)。您可以将 \\w 替换为 ^[a-zA-Z\d-_] 以使其适用于具有这些字符的 URL @hguser spring 是分配匹配部分的变量名。但是,它仅用于在 AntMatcher (docs.spring.io/spring-framework/docs/current/javadoc-api/org/…) 中启用正则表达式的使用,并且由于未使用它而不能满足目的。您可以将其更改为其他任何内容,但其目的是启用与正则表达式的匹配。要使用 is,在路径中使用 spring 重定向或转发,然后您将使用匹配的任何内容。最后一个条目不明确,因为两个匹配项都分配了同一个变量...【参考方案7】:

我在我的 Spring Boot 应用程序中使用 react 和 react-router,它就像创建一个映射到 / 和我网站的子树(如 /users/**)的控制器一样简单 这是我的解决方案

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

此控制器不会捕获 API 调用,并且会自动处理资源。

【讨论】:

这对我不起作用。我得到“没有为 GET /index 找到处理程序” 尝试“index.html”而不是“index” 我当前的项目遵循这种方法,但我们有像“/portal/**”、“/portal/user/**”、“/portal/profile/**”这样的网址。正常导航工作。但是当我们刷新 url 时,说“/portal/user”,我得到错误 Circular view path [index.html]: will dispatch back to the current handler URL。如何处理这个,有什么想法吗?【参考方案8】:

通过查看this question找到答案

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

【讨论】:

以上是关于Spring 捕获 index.html 的所有路由的主要内容,如果未能解决你的问题,请参考以下文章

Spring Webflux 将所有 404 页面重定向到 index.html

Spring boot异常统一处理方法:@ControllerAdvice注解的使用全局异常捕获自定义异常捕获

Spring boot异常统一处理方法:@ControllerAdvice注解的使用全局异常捕获自定义异常捕获

Spring Boot 替代索引页面

在 Spring Boot 中无法显示 index.html 页面

在 Spring Boot 中将未知请求重定向到 index.html