Embedded Tomcat directory listing for spring-boot application - java

I have a spring boot application with embedded Tomcat.
I wanted to expose some images files & folders from a different location via tomcat directory listing. So I added the below in my configuration file called
public class AppConfig extends WebMvcConfigurerAdapter
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**").addResourceLocations("file:///xxx/yyy/images/");
}
}
I can now access individual image(s), if I know the name.
Example: localhost:8080/images/file.jpg.
But since the directory listing is false by default, I can't access the images listing through "localhost:8080/images/" to know the all the available images.
I tried the below option to add the listings as well, but did not work.
public class MyApplication implements ServletContextInitializer{
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("listings", "true");
}
}

Updated for Spring 2.1
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${tomcat.file.base}") // C:\\some\\parent\\child
String tomcatBaseDir;
#Override
public void customize(TomcatServletWebServerFactory factory) {
// customize the factory here
TomcatContextCustomizer tomcatContextCustomizer = new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
String parentFolder = tomcatBaseDir.substring(0,tomcatBaseDir.lastIndexOf("\\"));
String childFolder = tomcatBaseDir.substring(tomcatBaseDir.lastIndexOf("\\") + 1);
context.setDocBase(parentFolder);
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
defServlet.addInitParameter("readOnly", "false");
defServlet.addMapping("/"+ childFolder + "/*");
}
};
factory.addContextCustomizers(tomcatContextCustomizer);
}
}

In an identical way to SpringBoot Embedded Tomcat JSPServlet Options you can use an EmbeddedServletContainerCustomizer #Bean to look up the default servlet and configure its init parameters.
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
}
}
private void customizeTomcat(TomcatEmbeddedServletContainerFactory tomcat) {
tomcat.addContextCustomizers(new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
}
});
}
};
}
Kudos to Andy Wilkinson.

In springboot /** is mapped to ResourceHttpRequestHandler. The call never gets delegated to DefaultServlet for the listings to take effect. I had to make two more adjustments to Mark's solution get it to work.
Add a different mapping to the DefaultServlet -> "/static/*"
The docbase from where the static contents are served is a tmp folder. I had to set it to the folder where the static contents are present.
public void customize(Context context) {
context.setDocBase("../../../mytest");
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
defServlet.addInitParameter("readOnly", "false");
defServlet.addMapping("/static/*");
}
Deployment folder structure
/myhome/mytest
----myapp.jar
----/tomcat/webapps
----/static
--------All static files go here
application.yml
server :
tomcat :
basedir : tomcat
Current working dir to run the app /myhome/mytest
url to test : http://localhost:8080/static

Related

Spring Boot Context Path Ignored With External Tomcat

I have set the context path for tomcat as follows:
#Component
public class CustomContainer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setContextPath("/capripol");
factory.setPort(8080);
}
}
Navigating to localhost:8080/capripol works fine and I am prompted with my login screen, however after logging in my forms and controllers do not append to the context path, so instead of navigating to /capripol/MainMenu etc. they navigate to /MainMenu. How do I set the context path such that my form actions and controllers will be appended do it - why is the tomcat factory context path not setting?
Edit: My Application class
#SpringBootApplication
public class CapripolApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(CapripolApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CapripolApplication.class);
}
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "classpath:/images/")
.setCachePeriod(0);
}
}
}
A few ways to do it. You can add it to each controller, usefully if you want to change the context path
#Controller
#RequestMapping(value = "/foo")
public class bar{
#GetMapping(value = "/bar")
public void stuff(){
//doing stuff
}
}
Or you can put it in your application.properties / yml
server.servlet.contextPath=/foo/*
There are technically some other more round about ways to do it, especially if you are using an older version of Spring, but I would think the application properties is what you are looking for.

Why Spring boot Filter init function execute in different order pack as jar and war package

I run the Spring boot in the different way , jar and war package to run the code.
I need to run some code in the filter init method, I run with jar ,it work.
However I run with war, it's order not right.
I want to know why and how to run in war get the result like run with jar way.
so , I write some code like these, and run in the different way , found it has different result.
SpringBootWebApplication.java
#ComponentScan(value = "com", excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class))
#SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer implements WebMvcConfigurer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootWebApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootWebApplication.class, args);
}
#Bean
public FilterRegistrationBean hrFilterRegistration() {
FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new TestFilter());
registration.addUrlPatterns("/*");
registration.setName("testFiter");
registration.setOrder(1);
return registration;
}
}
TestBean.java
#Component
public class TestBean {
public static String aaa = "nothing";
public TestBean() {
System.out.println(aaa);
}
}
TestFilter.java
public class TestFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) {
TestBean.aaa = "good thing";
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
System.out.println("filter dofilter");
}
#Override
public void destroy() {
System.out.println("filter destory");
}
}
when I run with jar,I get the result:
good thing
when I run with war,I get the result:
nothing
I want to run with war, get the result:
good thing
The best way will be to use #Order annotation that can specify the order of running filters (but can be applied not only to filters)
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html

Spring Boot - Update ResourceHandlerRegistry without restarting app

In my Spring Boot app that will be running locally the user needs to be able to drag photos into the web page from the operating system and have them displayed. The path on the OS to the files can be set at startup with:
#SpringBootApplication
public class UploaderApplication extends WebMvcConfigurerAdapter {
private String workingDir = "/Users/example/Desktop/";
public static void main(String[] args) {
SpringApplication.run(UploaderApplication.class, args);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dir/**")
.addResourceLocations("file://" + workingDir)
.setCachePeriod(0);
System.out.println(workingDir);
}
But I need to give the user the ability to update the directory where the files are coming from after the app is running, since the absolute path won't always be known when the application starts. If I send a GET request from the browser with a new working directory path entered by the user, how can I update the registry?
Thank you.
This is how I decided to solve it. A user can fill out a form to update a database with the directories they will be uploading from. When the application loads it gets the list of directories and creates identifiers for them that can then be used in the html.
#SpringBootApplication
public class boxApplication extends WebMvcConfigurerAdapter {
#Autowired private TemplateRepository templateRepository;
public static void main(String[] args) {
SpringApplication.run(boxApplication.class, args);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
Template template = templateRepository.findById(1);
for (UploadDirectory local: template.getLocalDirectories()){
registry.addResourceHandler("/dir" + local.getId() + "/**")
.addResourceLocations("file://" + local.getDirectory())
.setCachePeriod(0);
}
}
}

Amdatu: How to make ExceptionMapper (#Provider) to work?

I'm trying to manage all my exceptions with an ExceptionMapper, as i saw in multiple documentation and examples. However, it doesn't seem to work, at least in my conditions.
I'm in a OSGI environment, using the Felix Witheboard pattern, with Amdatu Wink, so i don't have a web.xml and everything is supposed to be managed by itself.
I tried to register my ExceptionMapper as a service as i did with my web services, with no results.
#Component(immediate=true, provide={Object.class})
#Provider
public class SessionTimeoutExeptionHandler implements ExceptionMapper<SessionTimeoutException>{
public Response toResponse(SessionTimeoutException arg0) {
Response toReturn = Response.status(Status.FORBIDDEN)
.entity("session_timeout")
.build();
return toReturn;
};
}
Don't pay attention to the Response itself, i was just playing around.
My code is never called, how am i supposed to setup that provider?
You have to register the Provider in a javax.ws.rs.core.Application. That Application should be registered as a service with a higher service ranking than the default one created by the Amdatu Wink bundle.
The following is a working example.
The Exception Mapper itself:
#Provider
public class SecurityExceptionMapper implements ExceptionMapper<SecurityException>{
#Override
public Response toResponse(SecurityException arg0) {
return Response.status(403).build();
}
}
The Application:
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
public class MyApplication extends Application {
#Override
public Set<Object> getSingletons() {
Set<Object> s = new HashSet<Object>();
s.add(new JacksonJsonProvider());
s.add(new SecurityExceptionMapper());
return s;
}
}
Activator setting the service ranking property.
public class Activator extends DependencyActivatorBase{
#Override
public void destroy(BundleContext arg0, DependencyManager arg1) throws Exception {
}
#Override
public void init(BundleContext arg0, DependencyManager dm) throws Exception {
Properties props = new Properties();
props.put(Constants.SERVICE_RANKING, 100);
dm.add(createComponent().setInterface(Application.class.getName(), props).setImplementation(MyApplication.class));
}
}

How can I register a secondary servlet with Spring Boot?

I have an extra servlet I need to register in my application. However with Spring Boot and its Java Config, I can't just add servlet mappings in a web.xml file.
How can I add additional servlets?
Also available is the ServletRegistrationBean
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new FooServlet(),"/someOtherUrl/*");
}
Which ended up being the path I took.
Just add a bean for the servlet. It'll get mapped to /{beanName}/.
#Bean
public Servlet foo() {
return new FooServlet();
}
You can register multiple different servlet with different ServletRegistrationBean like #Bean in Application class and you can register a servlet has multiple servlet mapping;
#Bean
public ServletRegistrationBean axisServletRegistrationBean() {
ServletRegistrationBean registration = new ServletRegistrationBean(new AxisServlet(), "/services/*");
registration.addUrlMappings("*.jws");
return registration;
}
#Bean
public ServletRegistrationBean adminServletRegistrationBean() {
return new ServletRegistrationBean(new AdminServlet(), "/servlet/AdminServlet");
}
We can also register the Servlet as follow way:
#Configuration
public class ConfigureWeb implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerServlet(servletContext);
}
private void registerServlet(ServletContext servletContext) {
log.debug("register Servlet");
ServletRegistration.Dynamic serviceServlet = servletContext.addServlet("ServiceConnect", new ServiceServlet());
serviceServlet.addMapping("/api/ServiceConnect/*");
serviceServlet.setAsyncSupported(true);
serviceServlet.setLoadOnStartup(2);
}
}
If you're using embedded server, you can annotate with #WebServlet your servlet class:
#WebServlet(urlPatterns = "/example")
public class ExampleServlet extends HttpServlet
From #WebServlet:
Annotation used to declare a servlet.
This annotation is processed by the container at deployment time, and
the corresponding servlet made available at the specified URL
patterns.
And enable #ServletComponentScan on a base class:
#ServletComponentScan
#EntityScan(basePackageClasses = { ExampleApp.class, Jsr310JpaConverters.class })
#SpringBootApplication
public class ExampleApp
Please note that #ServletComponentScan will work only with embedded server:
Enables scanning for Servlet components (filters, servlets, and
listeners). Scanning is only performed when using an embedded web
server.
More info: The #ServletComponentScan Annotation in Spring Boot
This way worked for me, having a servlet called WS01455501EndpointFor89
#Bean
public ServletRegistrationBean<WS01455501EndpointFor89> servletRegistrationBeanAlt(ApplicationContext context) {
ServletRegistrationBean<WS01455501EndpointFor89> servletRegistrationBean = new ServletRegistrationBean<>(new WS01455501EndpointFor89(),
"/WS01455501Endpoint");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
Also available in the BeanDefinitionRegistryPostProcessor
package bj;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#SpringBootApplication
class App implements BeanDefinitionRegistryPostProcessor {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("myServlet", new RootBeanDefinition(ServletRegistrationBean.class,
() -> new ServletRegistrationBean<>(new HttpServlet() {
#Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("hello world");
}
}, "/foo/*")));
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
}

Categories