Return static index.html when static file with path not exists - spring

I would like to serve a static web application files with spring:
Static
index.html
file.js
I have created config for this situation:
public class StaticContextConfig implements WebMvcConfigurer {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
{
"classpath:/static/"
};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
}
It works fine when first page is like "localhost/", but when my web app use "localhost/page1" it returns 404. I tried to config it with controller and checking files if they exists, but without success.
Thanks for your attention and potential help.

Related

Accessing static resources in Spring Boot v2.1.7.RELEASE

I am very new to Spring-Boot and Bootstrap.
I'm trying to load static resources, and this is what my project structure looks like.
I saw that Spring-boot-starter lets "/" to access "/static", so if I add tag like on the screenshot I uploaded,
<script src="/js/app/index.js"></script>
then it should load file from /src/main/resources/static/js/app/index.js.
I tried every solution I saw on stackoverflow and google, but I couldn't find the answer.
Please help me.
Also, my Application.java file is as described below.
#EnableJpaAuditing
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
If I Build my application and load it, web browser console messages says
net::ERR_ABORTED 404 on every static resources that I try to load.
Spring boot will automatically serve any resources under the directories :
/META-INF/resources/
/resources/
/static/
/public
but you have a sub folder /app in your project for these static asssets. So if you keep everything like :
/static/css/
/static/js/
and so on it should work. Or else you could provide your own folder structure for the static assets by customizing the bean like :
#Configuration
public class StaticConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/custom/");
}
}

Why is my browser downloading file instead of rendering SpringBoot & Sitemesh output?

I'm trying to use SpringBoot with Freemarker and Sitemesh.
When I go to a URL at the moment the request is handled by the application, data loaded and HTML output generated, but for some reason the browser has decided it wants to download the file (which contains the correct content) rather than rendering it as a page.
This was working a while back, trouble is I'm not sure which change I've made has broken it!
Sitemesh filter:
#WebFilter
public class SitemeshFilter extends ConfigurableSiteMeshFilter {
private static final Logger LOG = Logger.getLogger(SitemeshFilter.class);
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
LOG.debug("SiteMeshFilter creation");
builder.addDecoratorPath("/*", "/templates/main.ftl")
.addExcludedPath("/h2console/*");
}
}
Application:
#ServletComponentScan
#SpringBootApplication
#EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class ClubManagementApplication {
private static Logger LOG = Logger.getLogger(ClubManagementApplication.class);
public static void main(String[] args) {
SpringApplication.run(ClubManagementApplication.class, args);
}
}
Snippet of controller:
#Controller
public class ClubController {
#Autowired
ClubService clubService;
#RequestMapping(value = {"Club/{id}","club/{id}"})
public ModelAndView viewClub(#PathVariable("id") int clubId) {
ModelAndView mv = new ModelAndView("club");
....
return mv;
}
}
EDIT:
From the HttpServletRequest object in controller...
accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
In the response headers:
Content-Type : application/octet-stream;charset=UTF-8
I guess the content type is the problem....just gotta find why it's being set like that.
In case someone else stumbles on this question, I changed my template file from an ftl to a html extension and suddenly it has woken up.
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
LOG.debug("SiteMeshFilter creation");
//builder.addDecoratorPath("/*", "/templates/main.ftl");
builder.addDecoratorPath("/*", "/templates/main.html");
}

Spring catch all route for index.html

I'm developing a spring backend for a react-based single page application where I'm using react-router for client-side routing.
Beside the index.html page the backend serves data on the path /api/**.
In order to serve my index.html from src/main/resources/public/index.html on the root path / of my application I added a resource handler
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/").addResourceLocations("/index.html");
}
What I want to is to serve the index.html page whenever no other route matches, e.g. when I call a path other than /api.
How do I configure such catch-all route in spring?
Since my react app could use the root as forward target this ended up working for me
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/**/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
.setViewName("forward:/");
}
}
To be honest I have no idea why it has to be exactly in this specific format to avoid infinite forwarding loop.
I have a Polymer-based PWA hosted inside of my Spring Boot app, along with static web resources like images, and a REST API under "/api/...". I want the client-side app to handle the URL routing for the PWA. Here's what I use:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Ensure client-side paths redirect to index.html because client handles routing. NOTE: Do NOT use #EnableWebMvc or it will break this.
*/
#Override
public void addViewControllers(ViewControllerRegistry registry) {
// Map "/"
registry.addViewController("/")
.setViewName("forward:/index.html");
// Map "/word", "/word/word", and "/word/word/word" - except for anything starting with "/api/..." or ending with
// a file extension like ".js" - to index.html. By doing this, the client receives and routes the url. It also
// allows client-side URLs to be bookmarked.
// Single directory level - no need to exclude "api"
registry.addViewController("/{x:[\\w\\-]+}")
.setViewName("forward:/index.html");
// Multi-level directory path, need to exclude "api" on the first part of the path
registry.addViewController("/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}")
.setViewName("forward:/index.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
}
}
This should work for Angular and React apps as well.
Avoid #EnableWebMvc
By default Spring-Boot serves static content in src/main/resources:
/META-INF/resources/
/resources/
/static/
/public/
Take a look at this and this;
Or keep #EnableWebMvc and override addViewControllers
Did you specify #EnableWebMvc ? Take a look a this: Java Spring Boot: How to map my app root (“/”) to index.html?
Either you remove #EnableWebMvc, or you can re-define addViewControllers:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
Or define a Controller to catch /
You may take a look a this spring-boot-reactjs sample project on github:
It does what you want using a Controller:
#Controller
public class HomeController {
#RequestMapping(value = "/")
public String index() {
return "index";
}
}
Its index.html is under src/main/resources/templates
I use react and react-router in my spring boot app, and it was as easy as creating a controller that has mapping to / and subtrees of my website like /users/**
Here is my solution
#Controller
public class SinglePageAppController {
#RequestMapping(value = {"/", "/users/**", "/campaigns/**"})
public String index() {
return "index";
}
}
Api calls aren't caught by this controller and resources are handled automatically.
Found an answer by looking at this question
#Bean
public EmbeddedServletContainerCustomizer notFoundCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
}
};
}
Another solution (change/add/remove myurl1, myurl2, ... with your routes):
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
#Controller
public class SinglePageAppController {
/**
* If the user refreshes the page while on a React route, the request will come here.
* We need to tell it that there isn't any special page, just keep using React, by
* forwarding it back to the root.
*/
#RequestMapping({"/myurl1/**", "/myurl2/**"})
public String forward(HttpServletRequest httpServletRequest) {
return "forward:/";
}
}
Note: Using public String index() also works fine, but only if you use templates. And the use of WebMvcConfigurerAdapter is deprecated.
To answer your specific question which involves serving up the Single Page App (SPA) in all cases except the /api route here is what I did to modify Petri's answer.
I have a template named polymer that contains the index.html for my SPA. So the challenge became let's forward all routes except /api and /public-api to that view.
In my WebMvcConfigurerAdapter I override addViewControllers and used the regular expression: ^((?!/api/|/public-api/).)*$
In your case you want the regular expression: ^((?!/api/).)*$
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:^((?!/api/).)*$}").setViewName("polymer");
super.addViewControllers(registry);
}
This results in being able to hit http://localhost or http://localhost/community to serve up my SPA and all of the rest calls that the SPA makes being successfully routed to http://localhost/api/posts, http://localhost/public-api/posts, etc.
After lot of tries I've found the following solution as most simple one. It will basically bypass all the Spring handling which was so difficult to deal with.
#Component
public class StaticContentFilter implements Filter {
private List<String> fileExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico");
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = request.getServletPath();
boolean isApi = path.startsWith("/api");
boolean isResourceFile = !isApi && fileExtensions.stream().anyMatch(path::contains);
if (isApi) {
chain.doFilter(request, response);
} else if (isResourceFile) {
resourceToResponse("static" + path, response);
} else {
resourceToResponse("static/index.html", response);
}
}
private void resourceToResponse(String resourcePath, HttpServletResponse response) throws IOException {
InputStream inputStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream(resourcePath);
if (inputStream == null) {
response.sendError(NOT_FOUND.value(), NOT_FOUND.getReasonPhrase());
return;
}
inputStream.transferTo(response.getOutputStream());
}
}

Spring Boot url mappings order for controllers and static pages

I have a Spring Boot web application which is meant to serve both static and controller based (ModelAndView) pages. Problem is that a controller can serve something like /{string} and a static page must be served with /test.
The problem is that the controller mapping takes precedence, and I need to avoid that. If the user hits /test, he must be forwarded to the test.html static page.
I tried to use the order property of ViewControllerRegistry in this way, with no success:
#Configuration
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("forward:/test.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE); // but I tried with 0 and -1 as well: annotated controllers should have order equals to 0
}
}
This is my SpringBootApplication class:
#SpringBootApplication
public class VipApplication {
public static void main(String[] args) {
SpringApplication.run(VipApplication.class, args);
}
}
And this is the controller code:
#Controller
public class VipController {
#RequestMapping(value = "/{string}")
public ModelAndView vip(#PathVariable("string") String string) {
ModelAndView mv = new ModelAndView("mypage");
return mv;
}
}
How can I reorder the mappings to make sure static pages are considered before annotated controllers?
(I'm not sure, but) I suggest to override WebMvcConfigurerAdapter.addResourceHandlers() method and configure order of resource handler by invoking ResourceHandlerRegistry.setOrder()

Spring-boot serving content outside context root and getting content as resources

I've added new resources handler "outside context root" with following ConfigurerAdapter:
#Configuration
public class DynamicAssetsConfigurer extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dynamicassets/**")
.addResourceLocations("file:/var/dynamicassets/");
super.addResourceHandlers(registry);
}
}
Handler works fine when making requests to server, but I would like to access the content of the "/dynamicassets/**" as resources. Like I can access "static assets" (assets directory inside context root):
#Inject
private WebApplicationContext webContext;
public Resource[] getStaticAssetResources() {
// returns array of ServletResource objects
return webContext.getResources( "/assets/*.*");
}
public Resource[] getDynamicAssetResources() {
// this fails to get resources, because
// dynamicassets not found from context
return webContext.getResources( "/dynamicassets/*.*");
}
How could I access files in "/dynamicassets/" as ServletResource objects ?

Resources