Spring Boot 2.7.5 + Angular 15 as a single war - java

I'm working on a full-stack app having spring boot v2.7.5 as the backend and Angular v15 as the front end. I use IntelliJ IDEA IDE for development. Locally, spring boot runs on http://localhost:8080 and angular runs on http://localhost:4200. I use Gradle to build the project a single war file and which would be deployed on an external tomcat server.
Following is the project structure:
I have 3 build.gradle files, 1 for frontend , 1 for backend, and 1 for global. When I run the global build.gradle file, it would call call build.gradle from fronend folder which builds angular project and copies all the build files and put them into backend/src/main/resources/static folder. Next, build.gradle from the backend gets called which would build the final war file to be deployed on the external tomcat server.
The reason I'm putting frontend build files (index.html, some .js files) into backend/src/main/resources/static is the fact that Spring Boot Serves static content from that location. more details .
So the static directory looks like this after adding frontend build files:
When I try to access http://localhost:8080, it loads index.html from the static folder.
So far it is good. When I click the login button, internally it calls the backend API and moves to the next page (home page i.e., http://localhost:8080/fe/appInstances).
Now if I refresh the page, it gives me the following 404 Whitelabel Error Page.
I understand that since this is spring-boot as it is looking for a definition of the http://localhost:8080/fe/appInstances API endpoint in the java code.
To fix this, I have created the following IndexController.java class which should redirect all the frontend rest endpoints to index.html which is present in main/resources/static folder.
IndexController.java
#Controller
public class IndexController {
#GetMapping("/")
public String index() {
return "redirect:/index";
}
#GetMapping("/fe/*")
public String anyFrontEndApi() {
return "index";
}
}
But now, I get the following Whitelabel error page about Circular view path [index]: would dispatch back to the current handler URL [/fe/index] again.
I have tried changing #Controller to #RestController and changing the return type to ModelandView or something like this. But irrespective of all, it is still giving me the Whitelabel Error Page about Circular view path...
#RestController
public class IndexController {
#GetMapping("/")
public String index() {
return "redirect:/index";
}
#GetMapping("/fe/*")
public ModelAndView anyFrontEndApi() {
ModelAndView mv = new ModelAndView();
mv.setViewName("index");
return mv;
}
}
Am I missing something here? Can someone please suggest me a fix for this?
PS: #justthink addressed this situation here. But I don't know how to do reverse proxy way.

We had this situation of page refresh for Angular and Springboot and we resolved this by adding the below Configuration class extending WebMvcConfigurerAdapter
#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");
}
});
}
}
So basically, we are telling Springboot that if we have the resource, use the same if not then redirect it to index.html.
Now, to handle the path in Angular, it depends on how you would have written your routes. If the path is available, you show the page, if not, display 404 page.
Hope this helps.
Update 1:
WebMvcConfigurerAdapter is deprecated. If this causes any trouble, then instead of extending the class WebMvcConfigurerAdapter, you can implement WebMvcConfigurer

If you see the whitelabel error says that "this application has no explicit mapping for /error".
That means if no path is matched with the paths that are defined in controller mappings, it forwards the request to "/error" route. So we can override this default behaviour.
Spring provides ErrorController interface to override this functionality
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError() {
return "forward:/";
}
}

Related

Is there a way to server static files in Java Webflux?

Hi everyone i am searching now the full day and i do not found a solution.
I could server static file in a mvc spring application without problems but with webflux i do not found a way how i can serve them.
I put in ressource a folder with the name static and in there its a simple html file.
My configuration looks like:
#Configuration
#EnableWebFlux
#CrossOrigin(origins = "*", allowedHeaders = "*")
public class WebConfig implements WebFluxConfigurer {
#Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions.resources("/", new ClassPathResource("static/"));
}
When i start the application and go to localhost i just received a 404 response.
I also try it with adding:
spring.webflux.static-path-pattern = /**
spring.web.resources.static-locations = classpath:/static/
to the application.properties but i still received the 404 not found.
Even when i added Thymeleaf to my dependencies i still get 404.
Hopefully someone knows what to do.
What i think you are missing is basically to tell on what type (GET) of request you want to serve data.
Here is an old pice of code i found that i have used when i served a react application from a public folder in the resource folder.
When doing a GET against /* we fetch the index.html. If the index is containing javascript that does returning requests they are caught in the second router, serving whatever is in the public folder.
#Configuration
public class HtmlRoutes {
#Bean
public RouterFunction<ServerResponse> htmlRouter(#Value("classpath:/public/index.html") Resource html) {
return route(GET("/*"), request -> ok()
.contentType(MediaType.TEXT_HTML)
.bodyValue(html)
);
}
#Bean
public RouterFunction<ServerResponse> imgRouter() {
return RouterFunctions
.resources("/**", new ClassPathResource("public/"));
}
}

Why doesn't my spring boot error controller work?

I have a simple spring boot web application (running under Tomcat) and I cannot figure out how to get an error handler to work. So far I've created the web application and controllers and they all work fine. That is, they display Thymeleaf templates. Upon error. I get the default white label error page.
I first disabled this white label error page by adding an EnableAutoConfiguration annotation, thus:
#SpringBootApplication(scanBasePackages="au.com.sample")
#EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class SampleWebApp extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SampleWebApp.class);
}
}
Now, if I browse to an unknown page I get a generic 404 HTML error page containing:
<html>...<body><h1>HTTP Status 404 – Not Found</h1></body></html>
So that step worked. Now when I try and add my own error controller it does not trigger when I browse to an end point that does not exist:
#Controller
#RequestMapping("/")
public class ErrorHandlingController implements ErrorController {
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping(/"error")
public ModelAndView handleError() {
ModelAndView mav = new ModelAndView();
mav.addObject("title", "Error");
mav.setViewName("layout");
return mav;
}
}
"layout" is my existing thymeleaf template that works fine for my other end points.
I've tried several variations of the error controller, eg. changing the endpoint to "error" (not "/error"), using #RestController instead of #Controller but with no luck. In every case, I get the same generic 404 HTML instead of my hand-crafted template.
Can anyone see what I'm doing wrong or have any tips on how to track down my problem.
To show a custom error page for a specific status is pretty easy in Spring Boot. Just add an <error-code>.html into the src/main/resources/templates/errors directory to have it resolved. Or add a generic error.html as a fallback. See the reference guide for this feature (see also this).
If you want to add your own error handling just add a bean of type ErrorController or if you want to only add attributes add an ErrorAttributes typed bean to your configuration. See this section for more information.
A late answer to the original question of why Spring doesn't call your error controller: I think you are excluding the magic that makes the custom ErrorController work. In a Spring-Boot v2.1.6 app I had to allow (NOT exclude) the ErrorMvcAutoConfiguration class, then it used a custom MyController-implements-ErrorController class on page-not-found situations etc. It seems to be an either-or situation. I think you should only exclude the ErrorMvc.. class if you are using Thymeleaf templates AND you have published template files in a templates/ directory, because in that case you don't really need a custom error controller. HTH.

Exclude Angluar2 route from Spring Web MVC application

I have a spring-boot application that has a few views set up. I also have bundled an Angular2 app. When I load the Angular2 app, all works fine, however, when I try to deep link to a route within the application, Spring MVC is intercepting the call, failing to find an associated view and returning the error page.
http://localhost:8080/index.html will load the Angular2 application which then re-writes the URL to be just http://localhost:8080/. If I then navigate to the route I want e.g. http://localhost:8080/invite/12345, then the route loads and works as expected. Hitting http://localhost:8080/invite/12345 directly returns the standard Spring MVC error page.
However, if I run the Angular2 app as a standalone application (not served up by spring-boot), then hitting that link directly works as expected. it loads the index.html, fires the route and shows me the data I want.
How can I, via Java configuration, tell Spring to ignore the /invite/** path (and other paths too as my Angular2 app grows) so I can deep-link to routes within my Angular2 application. Here's the current Java configuration:
#SpringBootApplication
#EnableResourceServer
public class AuthserverApplication extends WebMvcAutoConfigurationAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthserverApplication.class, args);
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
registry.addViewController("/success").setViewName("success");
}
#Bean
public ViewResolver getViewResolver() {
final InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setSuffix(".html");
return resolver;
}
}
This project is inheriting from spring-boot 1.3.3.RELEASE:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
Based on your answer, I config like this and it's worked.
#RequestMapping(value = "/*", method = RequestMethod.GET)
#Override
public String index()
{
return "index";
}
#Override
#RequestMapping(value = "/login/*", method = RequestMethod.GET)
public String login()
{
return "redirect:/#/login";
}
So we can access to localhost:8080/angular-app/login/ without 404 error.
So the way I got round this in the end was to link directly to the index.html page so that it forced the angular2 app to load e.g.:
http://localhost:8080/index.html/#/invite/12345

Spring Boot - serving pure html and static content

So far I have spent many hours to done this and still can not figure it out. How to serve pure .html pages. This is the project:
https://github.com/robson021/Invoice-Writer
Thymeleaf engine works fine, but if I try to return "regular" .html file I got error.
Whitelabel Error Page (...) There was an unexpected error
(type=Internal Server Error, status=500). Exception parsing document:
template="test", line 6 - column 3
I acknowledge that this is caused because my "test.html" file does not look like Thymeleaf file. However I tried to remove Thymeleaf form Maven's POM (or create new project form spring initializer in InteliJ, project without Thymeleaf, only Web) and put that .html files into different directories (static, public, WEB-INF) and still failed... I also tried to configure project manually with Java classes. Unfortunetely got 404 or 500 erorros.
Since this is the school project and goal is to make your front-end independet, I want to use pure html with AngularJS. No .jsp or themplate engines.
Can anyone tell me how to make it work is Spring Boot project?
Edit:
my controller:
#Controller
public class TestController {
#RequestMapping("/test")
public String goToTestPage() {
return "test";
}
}
main class:
#SpringBootApplication
#EnableAutoConfiguration
public class InvoiceWriterApplication {
public static void main(String\[\] args) {
SpringApplication.run(InvoiceWriterApplication.class, args);
}
}
and project structure: http://i.stack.imgur.com/vCqiQ.png
Replace #Controller with #RestController - (or just add #ResponseBody with the #Contorller on the controller class) - to convert a controller into a REST controller.
This is because #Controller annotation alone will result in the return value "home" to be mapped to a template file.
Also for JPA repositories to work you need to use #EnableJpaRepositories.

Spring MVC 1 to 1 mapping of a URL to .HTML without exposing path

I am using Spring 4.1.5 with Boot 1.2 on a webservice that does not serve up any JSPs. I don't want to add a JSP servlet but I want it to serve up a single canary page that shows in a prettier html type format the information that would be provided at the /manage/health endpoint.
I have a file in webapp/canary/canary.html I want to serve this up from the url: www.mywebservice.com:9343/canary, exactly like that, NOT canary.html
I tried doing this:
#Configuration
public class CanaryConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/canary")
.addResourceLocations("/canary/canary.html");
}
}
That doesn't work however.
It is expecting the handler to provide a file name. So in otherwords the location should be something like: /canary/
and the handler would something like: /canary/**
With that, the URL www.mywebservice.com:9343/canary/canary.html would work like a charm.
HOWEVER, I want the URL to resolve www.mywebservice.com:9343/canary to webapp/canary/canary.html without me having to type the html.
This is really easy in a jsp servlet because you can set the suffix ect...
I looked at ResourceResolver but it didn't make sense to me how I would link that into my current configuration.
It looks like what I want:
Provides mechanisms for resolving an incoming request to an actual Resource and for obtaining the public URL path that clients should use when requesting the resource.
See: ResourceResolver Documentation
Any help would be very beneficial.
Also I am very aware that I can put html in the resources/static and several other places that are automatically configured. That always requires the .html to be typed, which is not what I want in this case so that won't work. Thanks!
You can use view controllers to do it. Here is a sample of it. Hope this helps.
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/canary").setViewName("/canary/canary.html");
}
}
Note: if you are using tomcat, you might have to configure jsp servlet to server html files.
Related post here.
For information sake, the selected answer is the same as the following:
#Controller
public class CanaryController {
#RequestMapping(value="/canary", method=RequestMethod.GET)
public String getCanary() {
return "/canary/canary.html";
}
}
The above code will work as long as canary(or whatever file/folder) is in your webapp folder.
When I tried this I was trying to set the suffix to .html in my YAML (.yml) file and it wasn't working to I thought that it needed to return to a servlet if it is not a RestController. I was mistaken.

Categories

Resources