Spring捕获index.html的所有路由

时间:2016-09-05 13:42:04

标签: spring spring-mvc spring-boot single-page-application catch-all

我正在开发一个基于反应的单页面应用程序的弹簧后端,我使用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以外的路径时。

如何在春季配置这种全能路线?

8 个答案:

答案 0 :(得分:24)

由于我的反应应用程序可以使用root作为前向目标,因此最终为我工作

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

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

答案 1 :(得分:7)

避免@EnableWebMvc

默认情况下,Spring-Boot在src/main/resources

中提供静态内容
  • / META-INF /资源/
  • /资源/
  • /静态/
  • /公共/

查看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");
}

或定义控制器以捕获/

你可以看看这个spring-boot-reactjs sample project on github

使用Controller执行您想要的操作:

@Controller
public class HomeController {

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

}

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

之下

答案 2 :(得分:7)

我在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应用程序。

答案 3 :(得分:2)

我在spring boot app中使用react和react-router,就像创建一个映射到$ gulp stylelint 的控制器和我的网站的子树一样简单/ 这是我的解决方案

/users/**

此控制器未捕获Api调用,并自动处理资源。

答案 4 :(得分:1)

通过查看this question

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

答案 5 :(得分:0)

要回答您在所有情况下提供单页应用(SPA)的具体问题 / api 路线,这是我修改Petri所做的工作&# 39;答案。

我有一个名为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/posts,{{3}的所有其余呼叫等等。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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