【问题标题】:Springboot/Angular2 - How to handle HTML5 urls?Spring Boot/Angular 2 - 如何处理 HTML5 url?
【发布时间】:2016-11-25 18:43:30
【问题描述】:

我相信这是一个简单的问题,但我找不到答案,或者至少在搜索中使用了正确的术语。

我正在同时设置Angular2Springboot。默认情况下,Angular 将使用像 localhost:8080\dashboardlocalhost:8080\dashboard\detail 这样的路径。

如果可能的话,我想避免使用路径作为哈希值。正如 Angular documentation 所说:

路由器的provideRouter 函数将LocationStrategy 设置为PathLocationStrategy,使其成为默认策略。如果我们愿意,我们可以在引导过程中使用覆盖切换到 HashLocationStrategy。

然后……

几乎所有 Angular 2 项目都应该使用默认的 HTML 5 样式。它生成的 URL 更易于用户理解。它保留了稍后进行服务器端渲染的选项。

问题是当我尝试访问localhost:8080\dashboard 时,Spring 会寻找一些映射到该路径的控制器,而它不会。

Whitelabel Error Page
There was an unexpected error (type=Not Found, status=404).
No message available

我最初认为我的所有服务都在localhost:8080\api 下,我所有的静态都在localhost:8080\app 下。但是我如何告诉 Spring 忽略对这个 app 路径的请求呢?

Angular2 或 Boot 是否有更好的解决方案?

【问题讨论】:

  • 你的角度路线应该看起来像 localhost:8080\#dashboard 和 localhost:8080\#dashboard\detail
  • 嗨@tashi,如果可能的话,我想避免使用哈希...我更新了主题以反映这一点..我第一次没有说清楚..
  • 不要只使用html样式

标签: spring angular spring-boot


【解决方案1】:

我有一个解决方案给你,你可以添加一个ViewController 来将请求从 Spring boot 转发到 Angular。

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

@Controller
public class ViewController {

@RequestMapping({ "/bikes", "/milages", "/gallery", "/tracks", "/tracks/{id:\\w+}", "/location", "/about", "/tests","/tests/new","/tests/**","/questions","/answers" })
   public String index() {
       return "forward:/index.html";
   }
}

在这里,我已经重定向了我所有的 angular2(“/bikes”、“/milages”、“/gallery”、“/tracks”、“/tracks/{id:\w+}”、“/location”、“/ about", "/tests","/tests/new","/tests/**","/questions","/answers") 到我的 SPA 您可以对您的预测执行相同的操作,还可以将 404 错误页面重定向到索引页面作为进一步的步骤。 享受吧!

【讨论】:

  • 如果我使用这种方法,我总是会得到一个完整的页面刷新:(
  • @Hans 不,你不应该刷新整页你还有另一个问题
  • @AndroidLover 不行,只有当我用 f5 重新加载或按输入新的 url 时才能刷新整页。但这就是应该的样子。我想错了……
  • @bielas 当然,你有。你甚至有几种方法可以做到这一点。恕我直言,最自然的是自定义 Spring MVC 配置。 stackoverflow.com/a/46854105/270371
  • 这个答案有效。但是,我相信这是最好的答案:stackoverflow.com/questions/38516667/… 谢谢@davidxxx
【解决方案2】:

您可以通过提供自定义 ErrorViewResolver 将所有未找到的资源转发到您的主页。您需要做的就是将其添加到您的 @Configuration 类中:

@Bean
ErrorViewResolver supportPathBasedLocationStrategyWithoutHashes() {
    return new ErrorViewResolver() {
        @Override
        public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
            return status == HttpStatus.NOT_FOUND
                    ? new ModelAndView("index.html", Collections.<String, Object>emptyMap(), HttpStatus.OK)
                    : null;
        }
    };
}

【讨论】:

  • 补充说明,ErrorViewResolver 是一个需要由具有@Configuration 注解的类实现的接口,除此之外,这是一个很好的动态解决方案,处理错误处理的责任Angular 应用,在 Spring Boot 应用中运行。
  • 由于我使用的是 Angular 6,我不得不使用“index”而不是“index.html”。
  • 这就是我最终寻求的解决方案。返回重定向类型的HttpStatus 会更干净,而不是 OK,但在语义上更有意义。
  • 应使用 HttpStatus.NOT_FOUND 而不是 OK
【解决方案3】:

为了更简单,你可以直接实现 ErrorPageRegistrar..

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
    }

}

这会将请求转发到 index.html。

@Controller
@RequestMapping("/")
public class MainPageController {

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping({ "/" })
    public String forward() {
        return "forward:/";
    }
}

【讨论】:

  • 这个解决方案抛出异常
  • 请帮我处理异常堆栈跟踪
【解决方案4】:

这些是您需要遵循的三个步骤:

  1. 实现您自己的 TomcatEmbeddedServletContainerFactory bean 并设置 RewriteValve

      import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;  
      ...
      import org.apache.catalina.valves.rewrite.RewriteValve; 
      ... 
    
      @Bean TomcatEmbeddedServletContainerFactory servletContainerFactory() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.setPort(8080);
        factory.addContextValves(new RewriteValve());
        return factory;
      }
    
  2. 将rewrite.conf 文件添加到应用程序的WEB-INF 目录并指定重写规则。这是一个示例 rewrite.conf 内容,我在 Angular 应用程序中使用它来利用 Angular 的 PathLocationStrategy(基本上我只是将所有内容重定向到 index.html,因为我们只是使用 Spring Boot 来提供静态 Web 内容,否则您需要在 RewriteCond 规则中过滤掉您的控制器):

      RewriteCond %{REQUEST_URI} !^.*\.(bmp|css|gif|htc|html?|ico|jpe?g|js|pdf|png|swf|txt|xml|svg|eot|woff|woff2|ttf|map)$
      RewriteRule ^(.*)$ /index.html [L]
    
  3. 从你的路由声明中去掉 useHash(或将其设置为 false):

      RouterModule.forRoot(routes)
    

      RouterModule.forRoot(routes, {useHash: false})

【讨论】:

  • 我的应用是一个带有嵌入式 tomcat 的独立 jar。 WEB-INF 目录应该在哪里?我只知道我的 /src/main/resources/public 文件夹,我将所有 Angular 4 静态 html 放在其中。
【解决方案5】:

在我的 Spring Boot 应用程序(版本 1 和 2)中,我的静态资源位于一个位置:

src/main/resources/static

static是Spring Boot识别的加载静态资源的文件夹。

那么思路就是自定义Spring MVC配置。
更简单的方法是使用 Spring Java 配置。

我实现WebMvcConfigurer 来覆盖addResourceHandlers()。 我在当前的ResourceHandlerRegistry 中添加了一个单个 ResourceHandler
处理程序映射在每个请求上,我将 classpath:/static/ 指定为资源位置值(如果需要,您当然可以添加其他值)。
我添加了一个自定义 PathResourceResolver 匿名类来覆盖 getResource(String resourcePath, Resource location)
返回资源的规则如下:如果资源存在并且是可读的(所以它是一个文件),我返回它。否则,默认情况下我会返回 index.html 页面。这是处理 HTML 5 url 的预期行为。

Spring Boot 1.X 应用程序:

扩展org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter是办法。
类是WebMvcConfigurer接口的适配器 使用空方法允许子类仅覆盖他们感兴趣的方法。

这里是完整的代码:

import java.io.IOException;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.resource.PathResourceResolver;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

       
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    registry.addResourceHandler("/**/*")
        .addResourceLocations("classpath:/static/")
        .resourceChain(true)
        .addResolver(new PathResourceResolver() {
            @Override
            protected Resource getResource(String resourcePath,
                Resource location) throws IOException {
                  Resource requestedResource = location.createRelative(resourcePath);
                  return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
                : new ClassPathResource("/static/index.html");
            }
        });
    }
}

Spring Boot 2.X 应用程序:

org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter 已弃用。
直接实现 WebMvcConfigurer 是现在的方式,因为它仍然是一个接口,但它现在具有默认方法(通过 Java 8 基线实现)并且可以直接实现而无需适配器。

这里是完整的代码:

import java.io.IOException;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

      registry.addResourceHandler("/**/*")
        .addResourceLocations("classpath:/static/")
        .resourceChain(true)
        .addResolver(new PathResourceResolver() {
            @Override
            protected Resource getResource(String resourcePath,
                Resource location) throws IOException {
                Resource requestedResource = location.createRelative(resourcePath);
                return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
                : new ClassPathResource("/static/index.html");
            }
        });
    }
}

编辑以解决一些 cmets:

对于那些将其静态资源存储在另一个位置作为src/main/resources/static 的人,因此更改addResourcesLocations() 的var args 参数的值。
例如,如果您在 staticpublic 文件夹中都有静态资源(未尝试):

  registry.addResourceHandler("/**/*")
    .addResourceLocations("classpath:/static/", "/public")

【讨论】:

  • 应该WebMvcConfig 扩展WebMvcConfigurerAdapter 而不是实现WebMvcConfigurer,因为它是一个接口?
  • 如果您使用 Spring Boot 1,是的,您应该使用 WebMvcConfigurerAdapter 。但在 Spring Boot 2 中,它已被弃用,WebMvcConfigurer 仍然是一个接口,但它现在具有默认方法(通过 Java 8 基线实现)并且可以直接实现而无需适配器。
  • 我更新了,根据版本做了明确的区分。
  • 我已经从安全配置中排除了我的 Angular 应用程序 URL。一切都很好,除了图像部分。我在资产中有图像,现在没有显示。此外,我在公用文件夹中有其他静态 html,现在无法使用。
  • 这是最好的解决方案,应该是公认的答案。
【解决方案6】:

您可以使用以下方式转发未映射到 Angular 的所有内容:

@Controller
public class ForwardController {

    @RequestMapping(value = "/**/{[path:[^\\.]*}")
    public String redirect() {
        // Forward to home page so that route is preserved.
        return "forward:/";
    }
} 

来源:https://stackoverflow.com/a/44850886/3854385

我的 Angular 的 Spring Boot 服务器也是一个网关服务器,API 调用 /api 在 Angular 页面前面没有登录页面,你可以使用类似的东西。

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

/**
 * This sets up basic authentication for the microservice, it is here to prevent
 * massive screwups, many applications will require more secuity, some will require less
 */

@EnableOAuth2Sso
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .logout().logoutSuccessUrl("/").and()
                .authorizeRequests()
                .antMatchers("/api/**").authenticated()
                .anyRequest().permitAll().and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }
}

【讨论】:

  • 就我而言,我需要的只是 ForwardController 中的 @RequestMapping(value = "/{:[^\\.]*}")
【解决方案7】:

使用 index.html 转发所有 Angular 路由。包括基础href。

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

@Controller
public class ViewController {

@RequestMapping({ "jsa/customer","jsa/customer/{id}",})
   public String index() {
       return "forward:/index.html";
   }
}

在我的例子中,jsa 是基本的 href。

【讨论】:

    【解决方案8】:

    我用一个普通的旧 filter 做到了:

    public class PathLocationStrategyFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
    
            if(request instanceof HttpServletRequest) {
                HttpServletRequest servletRequest = (HttpServletRequest) request;
    
                String uri = servletRequest.getRequestURI();
                String contextPath = servletRequest.getContextPath();
                if(!uri.startsWith(contextPath + "/api") && 
                    !uri.startsWith(contextPath + "/assets") &&
                    !uri.equals(contextPath) &&
                    // only forward if there's no file extension (exclude *.js, *.css etc)
                    uri.matches("^([^.]+)$")) {
    
                    RequestDispatcher dispatcher = request.getRequestDispatcher("/");
                    dispatcher.forward(request, response);
                    return;
                }
            }        
    
            chain.doFilter(request, response);
        }
    }
    

    然后在web.xml:

    <web-app>
        <filter>
            <filter-name>PathLocationStrategyFilter</filter-name>
            <filter-class>mypackage.PathLocationStrategyFilter</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>PathLocationStrategyFilter</filter-name>
            <url-pattern>*</url-pattern>
        </filter-mapping>
    </web-app>
    

    【讨论】:

      猜你喜欢
      • 2016-12-01
      • 2017-12-17
      • 2018-08-10
      • 2019-03-01
      • 2017-09-13
      • 2017-01-13
      • 2017-05-15
      • 2021-04-17
      相关资源
      最近更新 更多