I have a single page Angular app with Spring Boot. It looks like the following:
src
main
java
controller
HomeController
CustomerController
OtherController
webapp
js/angular-files.js
index.html
Spring boot correctly defaults to webapp folder and serves index.html file.
What I am looking to do is:
For every local REST request not starting with /api overwrite and redirect to default webapp/index.html. I plan to serve anything /api to the spring controllers.
Is there a way to prefix all controllers with API so that I do not have to write API every time?
e.g.
#RequestMapping("/api/home") can write shorthand in code #RequestMapping("/home")
or
#RequestMapping("/api/other-controller/:id") can write shorthand #RequestMapping("/other-controller/:id")
I'm looking for every API request, e.g. 1) http://localhost:8080/api/home keep API with API and resolve to correct controller and return JSON, however if someone enters a URL like http:///localhost/some-url or http:///localhost/some-other/123/url then it will serve the index.html page and keep the URL.
Alternative ways to do it: try adding #ErrorViewResolver:
Springboot/Angular2 - How to handle HTML5 urls?
If you're tired of trying to solve this problem by following so many conflicting solutions - look here!!
After hours upon hours trying to follow all the scattered advice from dozens of stack overflow and blog posts, I've finally found the minimum PURE spring boot + angular 6 application to always redirect to index.html after a refresh on a non-root page WHILE maintaining all your REST API endpoint paths. No #EnableWebMvc, no #ControllerAdvice, no changes to application.properties, no custom ResourceHandlerRegistry modifications, just simplicity:
Very important pre-requisite
You *must* include the output of ng build into Spring's resources/static folder. You can accomplish this via the maven-resources-plugin. Learn here: Copying multiple resource directories to independent target directories with maven
Code
#Controller
#SpringBootApplication
public class MyApp implements ErrorController {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String error() {
return "forward:/index.html";
}
#Override
public String getErrorPath() {
return PATH;
}
}
Reasoning
Including the output of ng-build into resources/static at build time allows spring view redirects ("forward:/index.html") to succeed. It seems spring cannot redirect to anything outside of the resources folder so if you're trying to access pages at the root of the site, it won't work.
With default functionality (i.e. no additions of #EnableWebMvc or changes to application.properties) navigating to / automatically serves the index.html (iff it was included in the resources/static folder) so no need to make changes there.
With default functionality (as stated above), any error encountered in a spring boot app routes to /error and implementing ErrorController overrides that behavior to - you guessed it - route to index.html which allows Angular to take over the routing.
Remarks
Don't settle for the HashLocationStrategy to get over this problem as it is not recommended by Angular: https://angular.io/guide/router#which-strategy-is-best
For every local REST request not starting with /api overwrite and redirect to default webapp/index.html. I plan to serve anything /api to the spring controllers.
Update 15/05/2017
Let me re-phrase your query for other readers. (Correct me, if misunderstood)
Background
Using Spring Boot and Serving static resources from classpath
Requirement
All 404 non api requests should be redirected to index.html.
NON API - means Requests in which URL doesn't start with /api.
API - 404 should throw 404 as usual.
Sample Response
/api/something - will throw 404
/index.html - will server index.html
/something - will redirect to index.html
My Solution
Let the Spring MVC throw exceptions, if any handler is not available for the given resource.
Add following to application.properties
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Add a ControllerAdvice as follows
#ControllerAdvice
public class RedirectOnResourceNotFoundException {
#ExceptionHandler(value = NoHandlerFoundException.class)
public Object handleStaticResourceNotFound(final NoHandlerFoundException ex, HttpServletRequest req, RedirectAttributes redirectAttributes) {
if (req.getRequestURI().startsWith("/api"))
return this.getApiResourceNotFoundBody(ex, req);
else {
redirectAttributes.addFlashAttribute("errorMessage", "My Custom error message");
return "redirect:/index.html";
}
}
private ResponseEntity<String> getApiResourceNotFoundBody(NoHandlerFoundException ex, HttpServletRequest req) {
return new ResponseEntity<>("Not Found !!", HttpStatus.NOT_FOUND);
}
}
You can customize the error message as you like.
Is there a way to prefix all controllers with api so that I do not have to write api every time.
For this, you can create a BaseController and set the RequestMapping path to /api
Example
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RequestMapping("/api")
public abstract class BaseController {}
And extend this BaseController and make sure you do not annotate child class with #RequestMapping
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class FirstTestController extends BaseController {
#RequestMapping(path = "/something")
public String sayHello() {
return "Hello World !!";
}
}
Previous Answer
You can create a Filter which redirects to /index.html if request path doesn't startsWith /api.
// CODE REMOVED. Check Edit History If you want.
Try this instead
#SpringBootApplication
#Controller
class YourSpringBootApp {
// Match everything without a suffix (so not a static resource)
#RequestMapping(value = "/**/{path:[^.]*}")
public String redirect() {
// Forward to home page so that route is preserved.(i.e forward:/intex.html)
return "forward:/";
}
}
#Controller
public class RedirectController {
/*
* Redirects all routes to FrontEnd except: '/', '/index.html', '/api', '/api/**'
*/
#RequestMapping(value = "{_:^(?!index\\.html|api).*$}")
public String redirectApi() {
return "forward:/";
}
}
Too late on this thread, but thought it might help someone
Tried many solutions, but this looked pretty straight forward and great to me
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;
import java.io.IOException;
#Configuration
public class MvcConfiguration 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");
}
});
}
}
Credits: https://keepgrowing.in/java/springboot/make-spring-boot-surrender-routing-control-to-angular/
The solution that works to me is to overwrite the BasicErrorController of Spring Boot:
#Component
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes, new ErrorProperties());
}
#RequestMapping(produces = "text/html")
#Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NOT_FOUND) {
return new ModelAndView("forward:/");
} else {
return super.errorHtml(request, response);
}
}
}
The method errorHtml only intercepts not found requests and is transparent for responses 404 (not found) from the api.
Most reasonable solution, imho, for Spring Boot 2+ (code is in Kotlin):
#Component
class ForwardErrorsToIndex : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest?,
status: HttpStatus?,
model: MutableMap<String, Any>?): ModelAndView {
return ModelAndView("forward:/index.html")
}
}
For whole application, you can add context path in application.properties
server.contextPath=/api
It will append "/api" to every requested URL after http://localhost:8080/api/home
For Redirection,
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/", "/home");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
super.addViewControllers(registry);
}
Put this bunch of code in WebMVCConfig.java
In the #Configuration bean you can add a ServletRegistrationBean to make the spring server for the /api/* resquest only, then in the Controller you don't need to add it.
#Bean
public ServletRegistrationBean dispatcherRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet());
registration.addUrlMappings("/api/*");
registration.setLoadOnStartup(1);
registration.setName("mvc-dispatcher");
return registration;
}
I don't know why, but the root url "/" would not resolve without adding a little more code. This is what I ended up with.
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
#EnableWebMvc
#Configuration
public class MvcConfiguration 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");
}
});
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");
}
});
}
}
Ok, let's start with the simple part of your question:
Is there a way to prefix all controllers with api so that I do not have to write api every time?
The answer is yes, just mark your controller with a "global" #RequestMapping annotation, for example:
#RestController
#RequestMapping("/api")
public class ApiController{
#RequestMapping("/hello")
public String hello(){
return "hello simple controller";
}
#RequestMapping("/hello2")
public String hello2(){
return "hello2 simple controller";
}
}
In the example above you can invoke hello method with this URL: /api/hello
and the second method with this URL: /api/hello2
This is how I didn't have to mark each method with /api prefix.
Now, to the more complex part of your question:
is how to achieve a redirect if the request doesn't start with /api prefix?
You can do it by returning an HTTP status code (302) of Redirect, after all, angularJs "speaks" REST natively, thus you can't force a redirect from Java/Spring code like you use to.
Then just return an HTTP message with the status code of 302, and on your angularJS do the actual redirection.
For example:
On AngularJS:
var headers = {'Content-Type':'application/json', 'Accept':'application/json'}
var config = {
method:'GET'
url:'http://localhost:8080/hello',
headers:headers
};
http(config).then(
function onSuccess(response){
if(response.status == 302){
console.log("Redirect");
$location("/")
}
}, function onError(response){
console.log("An error occured while trying to open a new game room...");
});
On Spring:
#RestController
#RequestMapping("/api")
public class ApiController{
#RequestMapping("/hello")
public ResponseEntity<String> hello(){
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "application/json");
return new ResponseEntity<String>("", header, HttpStatus.FOUND);
}
}
of course, you'll need to custom it to your project.
All you need to try is put the index.html to src/main/resources/static/
See Example: https://github.com/reflexdemon/shop/tree/master/src/main/resources/static
In my package.josn I try to copy it to this location.
See PackageJSON: https://github.com/reflexdemon/shop/blob/master/package.json#L14
I tried many solution and stackoverflow is last place that I can ask about my problem.
I've created application with Spring Boot on backend that also serves my frontend.
My HomeController looks as follows
#Controller
public class HomeController {
#RequestMapping(value = "/*", method={RequestMethod.GET, RequestMethod.HEAD})
public String fallback() {
return "index";
}
}
And here are some antMatchers:
http.csrf().disable()
.authorizeRequests()
.antMatchers("/resources/**", "/built/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/registration","/about","/garages/**/*").permitAll()
.antMatchers("/", "/home").permitAll()
Everything works perfect when I'm trying to reach
/registration, /about or just /
but when I'm trying to get
/garages/5
I see empty page and in Network Tab (in mozilla) it show that bundle is trying be taken from
http://localhost:8080/garages/built/bundle.js And status 404
Which for /about page it looks as follows:
http://localhost:8080/built/bundle.js (this is correct one).
Is anybody here able to point any issue that I'm constantly somehow ommitting which causes my problem with proper redirection?
EDIT
If you have your static assets on the resources/static folder, this works:
#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");
}
});
}
}
Solution credits and further explanation:
https://stackoverflow.com/a/46854105
My spring boot mvc project interacts with a database via a repository interface, which works nicely using Spring boot default configurations:
spring:
datasource:
url: jdbc:mysql://localhost/some_schema
username:
...
#Configuration
#EnableJpaRepositories(basePackages = {"my.path.to.repository"})
public class Application extends WebMvcConfigurerAdapter {
....
Now depending on some runtime condition, I need to interact with an identical second database (same schema) in a separate location. The solutions I found all point to creating a separate repository package per datasource.
Since the databases are identical, however, is there an elegant way to avoid duplicating the repository package for each added datasource?
You can accomplish this with a Spring AbstractRoutingDataSource.
Roughly:
public class ChooseOneDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
if (***some runtime condition***) {
return "dataSource1";
} else {
return "dataSource2";
}
}
}
And in your conguration:
#Bean
#ConfigurationProperties(prefix = "dataSource1")
DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "dataSource2")
DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
#Bean
DataSource dataSource() {
AbstractRoutingDataSource dataSource = new ChooseOneDataSource();
Map<Object,Object> resolvedDataSources = new HashMap<>();
resolvedDataSource.put("dataSource1", dataSource1());
resolvedDataSource.put("dataSource2", dataSource2());
dataSource.setDefaultTargetDataSource(dataSource1()); // << default
dataSource.setTargetDataSources(resolvedDataSources);
return dataSource;
}
For more info/examples:
http://fizzylogic.nl/2016/01/24/Make-your-Spring-boot-application-multi-tenant-aware-in-2-steps/
https://spring.io/blog/2007/01/23/dynamic-datasource-routing/
I am wiring a AngularJS and spring-boot application together by hand for the first time. The issues I am running into is my #RestController is not returning the index page:
#RestController
public class IndexController {
#RequestMapping("/")
public String index(){
System.out.println("Looking in the index controller.........");
return "index";
}
}
Directory:
It keeps rendering the default 404 error page:
----------------UPDATE 1------------------
I have added a configuration file:
#Configuration
public class IndexPageConfiguration {
#Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/app/");
resolver.setSuffix(".html");
return resolver;
}
}
RestController
#RestController
public class IndexController {
#RequestMapping("/")
public String index(){
System.out.println("Looking in the index controller.........");
return "index";
}
}
main class:
#SpringBootApplication(scanBasePackages = { "com.serviceImpl","com.service","com.config" },exclude = { ErrorMvcAutoConfiguration.class })
public class SpringCrudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCrudApplication.class, args);
}
}
The above main class is still returning the default 404 error page.
On the other hand, Spring will automatically look for the index.html page if you put it directly under webapp folder. So you don't need any configuration.
This is just another way to do it.
You need to configure InternalRosourceViewResolver to let the spring know your jsp location
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/app/");
resolver.setSuffix(".html");
return resolver;
}
So Spring will append and append location and suffix to your View returned.
I think it is good idea to keep your views separately in any other folder and configure your folder location according to it.
If you want to continue with your current set up
you should return "/app/index.html" from your controller.
Spring boot provides White label error page to hide your stack trace when a Server side error/ exception occurs, this will help us from protecting our code from intruders.
If you want to get rid of white label error.
In your #SpringBootApplication specify excludes ErrorMvcAutoConfiguration.class
#SpringBootApplication(scanBasePackages = { "com.ekart.app" }, exclude = { ErrorMvcAutoConfiguration.class })
If you are not using #SpringBootApplication annotatio, you should supply same same excludes in #EnableAutoConfiguration annotation
We're using angular-route to map URLs to templates. The application works in a way that if for e.g. we're navigating from http://servername/appName to http://servername/appName/page1, the URL on the browser changes and the templates loads successfully.
The problem is that when the page is refreshed (or accessing directly http://servername/appName/page1), we're getting 404 error from the server. It seems like the default handler does not map unknown URLs to the default app page.
How can we make make the server return the default app page for all these angularjs URLs?
The code is below:
#Controller
public class HomeController {
#RequestMapping("/")
public String home() {
return "/WEB-INF/views/nbcalendar.html";
}
}
app configuration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.appname.web"})
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
You can set a error handler in the web.xml configuration. Note! This web.xml configures your app inside the Servlet container (E.G. tomcat), it's not a Spring-MVC setting.
Add something like:
<error-page>
<error-code>404</error-code>
<location>/MY_HANDLER</location>
</error-page>
Where MY_HANDLER either is your default location, or something like a jsp that logs the event then forwards to the default location.
Hope that helps.
You can use wildcard expressions in your #RequestMapping to match your application and all it's subpages.
#RequestMapping("/**")
public String home() {
return "/WEB-INF/views/nbcalendar.html";
}
I was able to resolve it by adding the additional URLs to the home controller:
#Controller
public class HomeController {
private static String DEFUALT_PAGE = "/WEB-INF/views/main.jsp";
#RequestMapping("/")
public String home() {
return DEFUALT_PAGE;
}
#RequestMapping("/welcome")
public String welcome() {
return DEFUALT_PAGE;
}
#RequestMapping("/cluster/{id}")
public String cluster() {
return DEFUALT_PAGE;
}
}
This isn't a generic solution and requires to add any new URL manually, but it's working well