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】:另一种解决方案(更改/添加/删除 myurl1
、myurl2
、... 使用您的路线):
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
中提供静态内容:
看看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.html
在src/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注解的使用全局异常捕获自定义异常捕获