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

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 ?

Related

How to setup TestExecutionListener in the application.properties

I'm running a spring boot project with a large number of test and I want to use extent report. I have created a TestExecutionListener and I use the setting in one test.
I don't want to copy the same annotation in all test.
Where do I need to set the TestExecutionListener? and how?
Is it possible to set up in the application.properties?
The spring-test module declares all default TestExecutionListeners in its META-INF/spring.factories properties file.
Creating a simple Spring application:
MyBean:
#Component
public class MyBean {
public void doSomething() {
System.out.println("-- in MyBean.doSomething() method --");
}
}
AppConfig:
#Configuration
#ComponentScan
public class AppConfig {
}
Writing a TestExecutionListener:
public class MyListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestClass()");
}
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
System.out.println("MyListener.prepareTestInstance()");
}
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestMethod()");
}
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestMethod()");
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestClass");
}
}
Writing JUnit test:
A TextExecutionListener can be registered via #TestExecutionListeners annotation:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#TestExecutionListeners(value = {MyListener.class,
DependencyInjectionTestExecutionListener.class})
#TestPropertySource("/test.properties")
public class MyTests {
#Autowired
private MyBean myBean;
#Test
public void testDoSomething() {
myBean.doSomething();
}
}
Detailed infomation: https://www.logicbig.com/tutorials/spring-framework/spring-core/test-execution-listener.html
EDIT:
Declaring test property sources
Test properties files can be configured via the locations or value attribute of #TestPropertySource as shown in the following example.
Both traditional and XML-based properties file formats are supported — for example, "classpath:/com/example/test.properties" or "file:///path/to/file.xml".
Each path will be interpreted as a Spring Resource. A plain path — for example, "test.properties" — will be treated as a classpath resource that is relative to the package in which the test class is defined. A path starting with a slash will be treated as an absolute classpath resource, for example: "/org/example/test.xml". A path which references a URL (e.g., a path prefixed with classpath:, file:, http:, etc.) will be loaded using the specified resource protocol. Resource location wildcards (e.g. */.properties) are not permitted: each location must evaluate to exactly one .properties or .xml resource.
#ContextConfiguration
#TestPropertySource("/test.properties")
public class MyTests {
// class body...
}
More info: https://docs.spring.io/autorepo/docs/spring-framework/4.2.0.RC2/spring-framework-reference/html/integration-testing.html
https://www.baeldung.com/spring-test-property-source

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());
}
}

How to Configure Servlet Mapping and Resource Handler in Spring MVC

I have created sample Spring MVC REST Maven project with following folder structure
ResourceHandlerRegistry configuration as follows
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.raju.spring_app")
public class RootConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static_res/*").addResourceLocations("/WEB-INF/html/static_res/");
}
//Other methods
}
Servlet mapping as follows
public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected String[] getServletMappings() {
return new String[] { "/", "/static_res/*" };
}
//Other Methods
}
The problem is whenever I tried to access resource
http://localhost:8080/spring4_rest_angular_demo/static/css/app.css
I got 404 error.
I want to keep this folder structure to get css IntelliSense suggestions
in index.jsp file.
<link href="static_res/css/app.css" rel="stylesheet"></link>
Few corrections :
Replace
return new String[] { "/", "/static_res/*" };
with
return new String[] { "/" };
and
registry.addResourceHandler("/static_res/*")
with
registry.addResourceHandler("/static_res/**")
Also, the right path is
http://localhost:8080/spring4_rest_angular_demo/static_res/css/app.css
and not
http://localhost:8080/spring4_rest_angular_demo/static/css/app.css
With Spring 3.0.4.RELEASE and higher you can use
<mvc:resources mapping="/resources/**" location="/public-resources/"/>
As seen in http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-static-resources
Also, you should avoid putting pages in WEB-INF. Put the folder with html/css/js higher in hierarchy, under the web app folder. Generally, in WEB-INF there should be only configuration xml files.

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()

Resources