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
So, I have written a REST API using Spring(java), which is secured using Basic Authentication and also responsible for handling the social-logins. Below is the configuration for facebook login.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//autowired
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Bean
public ProviderSignInController providerSignInController() {
return new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(),
new FacebookSignInAdapter());
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
;
}
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment.getProperty("facebook.clientId"),
environment.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator());
}
}
Dependency used:
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-facebook</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
Now my frontend is written using React.js and running at https://localhost:3000. It has a button Signin using Facebook which sends a POST request to https://localhost:8443/signin/facebook. /signin/facebook is the URL provided by Spring-Security. The REST API returns a redirect url, to which the browser is blocking for CORS issue. I understand the CORS and have configured at my backend(that's why frontend is able to send request).
Access to XMLHttpRequest at 'https://www.facebook.com/v2.5/dialog/oauth?client_id=2198xxxxxx91&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8443%2Fsignin%2Ffacebook&state=xxxx' (redirected from 'https://localhost:8443/signin/facebook') from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So, what is the resolution to this? I googled it, and read somewhere that the CORS is handled by backend, not the frontend. But the backend is already handling the CORS. what should be the configuration for this?
I have example code below, why is the process method in MockEndpoint.whenAnyExchangeReceived NOT executed?
I expect the response is "Expected Body from mock remote http call", but the actual response is what passed in request("Camel rocks").
public class CamelMockRemoteHttpCallTest extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.to("http://abc/bcd")
;
}
};
}
#Override
public String isMockEndpointsAndSkip() {
return "http://abc/bcd";
}
#Test
public void testSimulateErrorUsingMock() throws Exception {
MockEndpoint http = getMockEndpoint("mock:http://abc/bcd");
http.whenAnyExchangeReceived(new Processor() {
public void process(Exchange exchange) throws Exception {
exchange.getOut().setBody("Expected Body from mock remote http call"); //why this line doesn't execute
}
});
String response = template.requestBody("direct:start", "Camel rocks", String.class);
assertEquals("Expected Body from mock remote http call", response); //failed, the actual response is "Camel rocks"
}
}
I have added some breakpoints to your test and it seems, that automatically created mock endpoint is mock://http:abc/bcd, not mock:http://abc/bcd.
To find, why is this happening, you can look to method org.apache.camel.impl.InterceptSendToMockEndpointStrategy#registerEndpoint, which is called as part of mock endpoint auto registration. There is // removed from http URI. And then to org.apache.camel.util.URISupport#normalizeUri method, where is // added for mock uri prefix.
There is also nice comment in implementation of InterceptSendToMockEndpointStrategy, but I couldn't find it mentioned in documentation.
// create mock endpoint which we will use as interceptor
// replace :// from scheme to make it easy to lookup the mock endpoint without having double :// in uri
When you change it to getMockEndpoint("mock://http:abc/bcd"), the test passes.
The best way to avoid these issues, is pass false as second parameter of getMockEndpoint() method, if you expect already created endpoint. This will throw exception, if mock endpoint does not exists. Otherwise is new mock endpoint created on demand.
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 use cxf to call a remote web service.(integrate with mule and spring).
I want to add an Outbound Interceptor to my classes which add some headers to my request.
My config is :
<chaining-router>
<cxf:outbound-endpoint ref="facilityAuthenticationCoreEndpoint">
<cxf:outInterceptors>
<spring:bean class="com.mycompany.webservice.client.transformer.SoapHeaderEnricher"/>
</cxf:outInterceptors>
</cxf:outbound-endpoint>
<vm:outbound-endpoint address="vm://serviceResponseJob"/>
</chaining-router>
SoapHeaderEnricher class is extending from AbstractSoapInterceptor and has a handleMessage(Message message) method.
public class SoapHeaderEnricher extends AbstractSoapInterceptor {
public SoapHeaderEnricher() {
}
public void handleMessage(SoapMessage message) throws Fault {
do something here.
}
}
But when i run the service, the web service is called and a wrong response is received cause of my handleMessage method is never called.
Is there anybody who knows the trick?
Sincerely.