Springboot / Angular2 - jak obsługiwać adresy URL HTML5?

Uważam, że to proste pytanie, ale nie mogłem znaleźć odpowiedzi lub przynajmniej użyć poprawnych terminów w wyszukiwaniu.

Przygotowuję Angular2 i Springboot razem. Domyślnie Angular będzie używać ścieżek takich jak localhost:8080\dashboard i localhost:8080\dashboard\detail.

Chciałbym unikać używania path jako hashów, jeśli to możliwe. Jak wynika z Dokumentacji :

Funkcja provideRouter ustawia LocationStrategy na PathLocationStrategy, czyniąc ją domyślną strategia. Możemy przełączyć się na HashLocationStrategy z nadpisaniem podczas procesu bootstrapowania, jeśli wolimy.

I wtedy...

Prawie wszystkie projekty Angular 2 powinny używać domyślnego stylu HTML 5. Tworzy adresy URL, które są łatwiejsze do zrozumienia dla użytkowników. I zachowuje opcję późniejszego renderowania po stronie serwera.

Problem polega na tym, że gdy spróbuję uzyskać dostęp localhost:8080\dashboard, Spring będzie szukał jakiegoś kontrolera mapującego tę ścieżkę, który to Nie będę.

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

Początkowo myślałem, aby wszystkie moje usługi były pod localhost:8080\api i wszystkie moje statyczne Pod localhost:8080\app. Ale jak mam powiedzieć Spring ' owi, żeby zignorował prośby do tej ścieżki app?

Czy jest lepsze rozwiązanie z Angular2 lub Boot?

Author: Felipe S., 2016-07-22

8 answers

Mam dla Ciebie rozwiązanie, możesz dodać ViewController Aby przesłać żądania do Angular Z Spring boot.

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";
   }
}
Tutaj przekierowałem wszystkie moje angular2 ("/rowery", "/kilometry", "/Galeria", "/Tory", "/Tory/{id:\w+}", "/Lokalizacja", "/o", "/testy","/testy/nowe","/testy/**","/pytania","/odpowiedzi") do mojego SPA Możesz zrobić to samo dla wstępnego projektu, a także przekierować stronę błędu 404 na stronę indeksu jako kolejny krok. Smacznego!
 52
Author: AndroidLover,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 21:52:37

W moich aplikacjach Spring Boot (Wersja 1 i 2) Moje zasoby statyczne są w jednym miejscu :

src/main/resources/static

static jako folder rozpoznawany przez Spring Boot do ładowania zasobów statycznych.

Następnie chodzi o dostosowanie konfiguracji Spring MVC.
Prostszym sposobem jest użycie konfiguracji Spring Java.

Implementuję WebMvcConfigurer, aby zastąpić addResourceHandlers(). Dodaję singiel ResourceHandler do prądu ResourceHandlerRegistry.
Handler jest mapowany na każde żądanie i określam classpath:/static/ jako wartość lokalizacji zasobów (możesz oczywiście dodać inne, jeśli jest to wymagane).
Dodaję niestandardową klasę PathResourceResolver anonymous, aby nadpisać getResource(String resourcePath, Resource location).
A reguła zwracania zasobu jest następująca: jeśli zasób istnieje i jest czytelny( więc jest to plik), zwracam go. W przeciwnym razie domyślnie zwracam stronę index.html. Co jest oczekiwanym zachowaniem obsługi adresów URL HTML 5.

Sprężynowy But 1.X Zastosowanie:

Rozszerzenie org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter jest drogą.
klasa jest adapter interfejsu {[5] } z pustymi metodami pozwalającymi podklasom nadpisywać tylko te metody, którymi są zainteresowani.

Oto Pełny kod:

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 Zastosowanie:

org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter był przestarzały.
Implementacja bezpośrednio WebMvcConfigurer jest teraz sposobem, ponieważ jest nadal interfejsem, ale ma teraz domyślne metody (możliwe dzięki linii bazowej Java 8) i może być zaimplementowana bezpośrednio bez potrzeby stosowania adaptera.

Oto Pełny kod:

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

EDIT to address some comments:

Dla tych, którzy przechowują swoje zasoby statyczne w innej lokalizacji jako src/main/resources/static, należy zmienić wartość parametru var args z addResourcesLocations().
Na przykład, jeśli masz zasoby statyczne zarówno w static, jak i w folderze public (Nie próbowano):

  registry.addResourceHandler("/**/*")
    .addResourceLocations("classpath:/static/", "/public")
 52
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-07 16:21:22

Możesz przesłać wszystkie Nie znalezione zasoby do swojej strony głównej, dostarczając Niestandardowy ErrorViewResolver. Wszystko, co musisz zrobić, to dodać to do swojej klasy @ 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;
        }
    };
}
 16
Author: Dmitry Serdiuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-27 09:48:07

Możesz przesłać wszystko, co nie jest odwzorowane na kątowe, używając czegoś takiego:

@Controller
public class ForwardController {

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

Źródło: https://stackoverflow.com/a/44850886/3854385

Mój Spring Boot server dla angular jest również serwerem bramy z wywołaniami API do /api, aby nie mieć strony logowania przed stronami angular, możesz użyć czegoś takiego.

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());
    }
}
 9
Author: Loren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 23:07:02

Aby to uprościć, możesz po prostu zaimplementować ErrorPageRegistrar bezpośrednio..

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {

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

}

To przekaże żądania do indeksu.html.

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

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping({ "/" })
    public String forward() {
        return "forward:/";
    }
}
 3
Author: Akhil Bojedla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-15 20:48:24

Oto trzy kroki, które musisz wykonać:

  1. Zaimplementuj własny tomcatembeddedservletcontainerfactory bean i skonfiguruj 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. Dodaj przepisanie.plik conf do katalogu WEB-INF Twojej aplikacji i określ reguły przepisywania. Oto przykład przepisania.conf content, który używam w aplikacji angular, aby skorzystać z PathLocationStrategy Angulara (zasadniczo po prostu Przekierowuję wszystko do indeksu.html jak my wystarczy użyć spring boot, aby obsługiwać statyczną zawartość sieci, w przeciwnym razie musisz odfiltrować Kontrolery w regule 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. Pozbądź się useHash (lub ustaw go na false) z deklaracji routingu:

      RouterModule.forRoot(routes)
    

Lub

      RouterModule.forRoot(routes, {useHash: false})
 0
Author: Krzysztof Miernik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-08 10:23:32

Prześlij wszystkie trasy kątowe z indeksem.html. Łącznie z bazą 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";
   }
}

W moim przypadku JSA to baza href.

 0
Author: Anis Mulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-23 05:57:49

Zrobiłem to zwykłym starym filtrem :

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

Następnie w 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>
 0
Author: fidke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-07 00:26:42