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
This is my TuckeyRewriteFilter filter class
public class TuckeyRewriteFilter extends org.tuckey.web.filters.urlrewrite.UrlRewriteFilter {
#Override
protected void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
String confPath = filterConfig.getInitParameter("confPath");
ServletContext context = filterConfig.getServletContext();
try {
final URL confUrl = getClass().getClassLoader().getResource(confPath);
final InputStream config = getClass().getClassLoader().getResourceAsStream(confPath);
Conf conf = new Conf(context, config, confPath, confUrl.toString(), false);
checkConf(conf);
} catch (Throwable e) {
throw new ServletException(e);
}
}
}
This is my springboot main class
public class AdminWebApplication {
public static final String REWRITE_FILTER_NAME = "rewriteFilter";
public static final String REWRITE_FILTER_CONF_PATH = "urlrewrite.xml";
#Autowired
ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(AdminWebApplication.class, args);
}
#Bean
public ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
applicationContext.getAutowireCapableBeanFactory().autowireBean(objectMapper);
return objectMapper;
}
#Bean
public FilterRegistrationBean rewriteFilterConfig() {
FilterRegistrationBean reg = new FilterRegistrationBean();
reg.setName(REWRITE_FILTER_NAME);
reg.setFilter(new TuckeyRewriteFilter());
reg.addInitParameter("confPath", REWRITE_FILTER_CONF_PATH);
reg.addInitParameter("confReloadCheckInterval", "-1");
reg.addInitParameter("statusPath", "/redirect");
reg.addInitParameter("statusEnabledOnHosts", "*");
reg.addInitParameter("logLevel", "WARN");
return reg;
}
}
This is my urlrewrite.xml
this file is from resources folder
configuration is works fine, loading login page, but still I have to pass /#login, then it redirect to /login URL, but on browser refresh I ma getting 404 error.
index.html, I have added , I don't want extra domain name after my port id.
<urlrewrite default-match-type="wildcard">
<rule>
<from>/login</from>
<to>/login.html</to>//As of now only configured for login page.
</rule>
<rule>
<from>/contact</from>
<to>/index.html</to>
</rule>
</urlrewrite>
In your js router file (config phase) you need to add:
$locationProvider.hashPrefix('');
$locationProvider.html5Mode(true);
and in your index.html file, you need to add:
<base href="/">
Update 1
After implementing above on client side, you need to configure url mapping on server side as well. With # , the server ignores whatever is followed after it.
http://localhost:8080/#i_will_be_ignored/and_so_do_i
So, when you configure angularjs to remove # from URLs, your above request
http://localhost:8080/i_will_be_ignored/and_so_do_i
will now hit server with #path('i_will_be_ignored') . Now, that will give you 404 because you never mapped any API for it. Thats the reason, you are getting 404 on page refresh.
You need to map all the URLs that doesn't match to index.html followed by the unmatched url. Once that's done, angularjs will take it from there and redirects to appropriate route . I hope this will help you.
Something like this will help you out here
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
i have a problem with rest API in angularjs.
web.xml
<servlet>
<servlet-name>SpringServlet</servlet-name>
<servlet-class>com.mycompany.xxxx.userInterface.servlet.SpringServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet-mapping>
<servlet-name>SpringServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
SpringServlet.class
#Override
public void init() throws ServletException
{
super.init();
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
SpringBeanReader.getInstance().setApplicationContext(applicationContext);
//SpringBeanReader.getInstance().getApplicationContext().getBean(")
}
I have problems when I call api.
controller.js
.service('ApiCall', ['$http', function ($http){
//Get All Employees
this.getEmployees = function () {
console.log('dentro service.js');
return $http.get("/utenti/all");
}
}])
.controller("DataTable",['$scope','ApiCall',
function($scope,ApiCall){
$scope.persons = ApiCall.getEmployees();
}])
Controller.class
#Path("/utenti")
public class UtentiController {
#Path("/all")
#GET
#Produces("application/json")
public JSONArray getAll() {
/**** code for JSON array ***/
}
The code "$http.get("/utenti/all");" return error
HTTP Status [404] – [Not Found]
Type Status Report
Message /foodplan/utenti/all
Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
Thanks to all.
I am using Tomcat 6.0.36 and the welcome-page is /Login.jsp
I have a filter in place so that it can display a different login page for mobile devices.
It works with URL mywebsite.com/Login.jsp, but the filter is bypassed when the URL is just mywebsite.com.
Is there a way to force it to execute?
I have found this page but it doesn't work in my case:
How to map a filter for welcome-file in web.xml for Tomcat?
Thanks
My web.xml:
<welcome-file-list>
<welcome-file>/Login.jsp</welcome-file>
</welcome-file-list>
...
<filter>
<display-name>LoginPageFilter</display-name>
<filter-name>LoginPageFilter</filter-name>
<filter-class>filters.LoginPageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginPageFilter</filter-name>
<url-pattern>/Login.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
The filter - I had deleted it and put one quickly back together
public class LoginPageFilter implements Filter
{
public LoginPageFilter() { }
public void init ( FilterConfig fConfig ) throws ServletException { }
public void doFilter ( ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException,
ServletException
{
System.out.println ( "Filter being executed" );
chain.doFilter(request, response);
}
public void destroy() { }
}
If the URL is
http://localhost:8080/gymfit/Login.jsp
then the message is printed to the console.
When the URL is
http://localhost:8080/gymfit/
the same page is displayed but the message is not printed out to the console
look at this line, this means only the request to '/Login.jsp' will the filter being executed
<url-pattern>/Login.jsp</url-pattern>
if you want to apply this filter to all the path, change the config to:
<url-pattern>/*</url-pattern>