with mvc example and spring angular spring-boot

mvc - Springboot/Angular2: ¿Cómo manejar las URL HTML5?



spring security angular 2 (8)

Creo que esta es una pregunta simple, pero no pude encontrar una respuesta o al menos usar los términos correctos en la búsqueda.

Estoy configurando Angular2 y Springboot juntos. De forma predeterminada, Angular utilizará rutas como localhost:8080/dashboard y localhost:8080/dashboard/detail .

Me gustaría evitar usar path como hashs, si es posible. Como dice la documentation angular:

La función provideRouter del enrutador establece LocationStrategy en PathLocationStrategy, por lo que es la estrategia predeterminada. Podemos cambiar a HashLocationStrategy con una anulación durante el proceso de arranque si lo preferimos.

Y entonces...

Casi todos los proyectos de Angular 2 deberían usar el estilo HTML 5 predeterminado. Produce URL que son más fáciles de entender para los usuarios. Y conserva la opción de hacer la representación del lado del servidor más adelante.

El problema es que cuando intento acceder a localhost:8080/dashboard , Spring buscará alguna asignación de controlador a esta ruta, que no tendrá.

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

Inicialmente pensé hacer que todos mis servicios estuvieran bajo localhost:8080/api y toda mi estática bajo localhost:8080/app . Pero, ¿cómo le digo a Spring que ignore las solicitudes a esta ruta de app ?

¿Existe una mejor solución con Angular2 o Boot?


En mis aplicaciones Spring Boot (versiones 1 y 2), mis recursos estáticos están en un solo lugar:

src/main/resources/static

static es una carpeta reconocida por Spring Boot para cargar recursos estáticos.

Entonces la idea es personalizar la configuración de Spring MVC.
La forma más sencilla es usar la configuración Spring Java.

Implemento WebMvcConfigurer para anular addResourceHandlers() . Agrego un único ResourceHandler al ResourceHandlerRegistry actual.
El controlador se asigna en cada solicitud y especifico classpath:/static/ como valor de ubicación del recurso (por supuesto, puede agregar otros si es necesario).
Agrego una clase anónima PathResourceResolver personalizada para anular getResource(String resourcePath, Resource location) .
Y la regla para devolver el recurso es la siguiente: si el recurso existe y es legible (por lo que es un archivo), lo devuelvo. De lo contrario, por defecto devuelvo la página index.html . ¿Cuál es el comportamiento esperado para manejar las URL de HTML 5?

Aplicación Spring Boot 1.X:

Extender org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter es el camino.
La clase es un adaptador de la interfaz WebMvcConfigurer con métodos vacíos que permiten que las subclases anulen solo los métodos que les interesan.

Aquí está el código completo:

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

Aplicación Spring Boot 2.X:

org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter ha org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter desuso.
Implementar directamente WebMvcConfigurer es el camino ahora, ya que todavía es una interfaz pero ahora tiene métodos predeterminados (posibilitados por una línea de base de Java 8) y puede implementarse directamente sin la necesidad del adaptador.

Aquí está el código completo:

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


Estos son los tres pasos que debe seguir:

  1. Implemente su propio bean TomcatEmbeddedServletContainerFactory y configure 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. Agregue un archivo rewrite.conf al directorio WEB-INF de su aplicación y especifique las reglas de reescritura. Aquí hay un ejemplo de contenido rewrite.conf, que estoy usando en la aplicación angular para aprovechar la PathLocationStrategy del angular (básicamente simplemente redirijo todo a index.html ya que solo usamos spring boot para servir el contenido web estático, de lo contrario necesita filtrar sus controladores en la regla 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. Deshágase de useHash (o configúrelo como falso) de sus declaraciones de enrutamiento:

    RouterModule.forRoot(routes)

o

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


Lo hice con un filter antiguo simple:

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); } }

Luego en 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>


Para hacerlo más simple, puede implementar ErrorPageRegistrar directamente.

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

Esto reenviaría las solicitudes a index.html.

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


Puede reenviar todo lo que no esté asignado a Angular usando algo como esto:

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

Fuente: https://.com/a/44850886/3854385

Mi servidor Spring Boot para angular también es un servidor de puerta de enlace con las llamadas API a /api para no tener una página de inicio de sesión frente a las páginas angulares, puede usar algo como.

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()); } }


Puede reenviar todos los recursos no encontrados a su página principal proporcionando ErrorViewResolver personalizado. Todo lo que necesita hacer es agregar esto a su clase de @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; } }; }


Tengo una solución para usted, puede agregar un ViewController para reenviar solicitudes a Angular desde el arranque de Spring.

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"; } }

aquí he redirigido todos mis angular2 ("/ bikes", "/ milages", "/ gallery", "/ tracks", "/ tracks / {id: / w +}", "/ location", "/ about", "/ tests", "/ tests / new", "/ tests / **", "/ preguntas", "/ answers") a mi SPA Puede hacer lo mismo para su proyecto y también puede redirigir su página de error 404 a la página de índice como un paso más. ¡Disfrutar!


reenvíe todo el enrutamiento angular con index.html. Incluyendo base 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"; } }

En mi caso, jsa es base href.