Serving static content in Spring Boot with Jetty and Jersey - spring-boot

I'm running a Spring Boot application with Jetty server. The APIs are served by the Jersey servlet. The static contents are placed in src/main/static and src/main/private-static.
Issue#1 Static content in src/main/static is not being served
The content in src/main/static is public and can be accessed freely. According to this: https://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content Spring boot should automatically serve the static content, but it does not.
Issues#2 How to set the static content directory to src/main/private-static for this custom servlet that checks for authentication before serving a file.
I've created another servlet to serve static content with authentication
#Bean
public ServletRegistrationBean<HttpServlet> privateStaticContent() {
return new ServletRegistrationBean<>(new HttpServlet() {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
int status = isAuthenticated(req) ? 200 : 401;
resp.setStatus(status);
}
}, "/private-static/*");
}
Basically this servlet should serve files from the directory src/main/private-static, but I'm not sure how and where to configure this.
Issue#3 To make Jersey serve at /api endpoint, I've added this in application.properties
server.servlet.context-path=/api
But this makes all the servlets to use this /api endpoint. How can I configure this only for Jersey servlet and not for others. Is there something like server.servlet.jersey.context-path=/api ?

src/main/java/<dir>/<static-content> is not something that maven or gradle would ever see.
the src/main/java tree is only used for *.java, nothing else.
perhaps you should be using src/main/resources/<dir>/<static-content> instead?

Related

How manage tomcat servlets with Spring applications?

I am a litter bit confuse about tomcat and his purples regarding servlets.
Tomcat is a servlet container, so if we implement HttpServlet this instance will be manage by apache tomcat (will make sure to call the method based on the URL request and HTTP verb)
public class ExampleHttpServlet extends HttpServlet
{
private String mymsg;
public void init() throws ServletException {
mymsg = "Http Servlet Demo";
}
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>" + mymsg + "</h1>");
out.println("<p>" + "Hello Friends!" + "</p>");
}
public void destroy() {
// Leaving empty. Use this if you want to perform
//something at the end of Servlet life cycle.
}
}
So if we will have multiple servlets apache tomcat will find them, and based on the url will call the appropriate servlet.
But how is this working with spring boot and spring mvc if we don't implement any HttpServlet?
Let's say that we have a simple REST api. What will manage tomcat here if we don't implement any HttpServlet, or in this case will not be created any servlets, and tomcat will only forward the request to DispatcherServlet and from here DispatcherServlet identifies the controller and further the handler?
Or maybe is there created a servlet automatically for each Controller?
I found this picture but I don't see from where comes up the intermediat servlets in tomcat that forwards the request to Dispatcher.
I tried to find more details here but with not to much success. Any resource si welcome.
Thanks

How to disable interceptor call for Actuators in Springboot application

I am trying to implement Prometheus in my microservices based spring boot application, deployed over weblogic server. As part of POC,I have included the configs as part of one war. To enable it, i have set below config -
Application.properties
management:
endpoint:
prometheus:
enabled: true
endpoints:
web:
exposure:
include: "*"
Gradle -
implementation 'io.micrometer:micrometer-registry-prometheus'
But the actuator request is getting blocked by existing interceptors. It asks to pass values in headers specific to our project. Through postman(http:localhost:8080/abc/actuator/prometheus), I am able to test my POC(with required headers) and it returns time-series data expected by Prometheus. But Prometheus is not able to scrap data on its own(with pull approach), as the call lacks headers in request.
I tried following links (link1,link2) to bypass it, but my request still got intercepted by existing interceptor.
Interceptors blocking the request are part of dependent jars.
Edited --
I have used following way to exclude all calls to interceptor -
#Configuration
public class MyConfig implements WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("**/actuator/**");
}
}
MyCustomInterceptor
#Component
public class MyCustomInterceptor implements HandlerInterceptor{
}
I have not implemented anything custom in MyCustomInterceptor(as i only want to exclude all calls to 'actuator' endpoint from other interceptors).
#Configuration
public class ActuatorConfig extends WebMvcEndpointManagementContextConfiguration {
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping mapping = super.webEndpointServletHandlerMapping(
endpointDiscoverer,
endpointMediaTypes,
corsProperties,
webEndpointProperties);
mapping.setInterceptors(null);
return mapping;
}
}
Maybe you can override with setting null. I got code from https://github.com/spring-projects/spring-boot/issues/11234
AFAIK Spring HandlerInterceptor do not intercept actuator's endpoints by default.
Spring Boot can't intercept actuator access

Does Spring Boot Actuator have a Java API?

We customize the Spring Boot Actuator Info endpoint to include the application version number generated during our Jenkins build. We're using gradle to do this:
if (project.hasProperty('BUILD_NUMBER')) {
version = "${BUILD_NUMBER}"
} else {
version = "0.0.1-SNAPSHOT"
}
That works great for adding the version to the /info endpoint, but I'd like to access it when the application starts and print it to the application log.
I'm hoping the values are exposed in some property value (similar to spring.profiles.active) or through a Java API. That way, I could do something like this:
public class MyApplication{
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
System.out.println(environment.getProperty("spring.fancy.path.to.info.version"));
}
}
Looking through the docs, I'm not finding a way to access these values easily in code. Has anyone else had luck with this?
To get exactly the same properties of an actuator endpoint that are exposed through the REST endpoints, you can inject in one of your classes an instance of the respective endpoint class. In your case, the "right" endpoint class would be the InfoEndpoint. There are analogous endpoint classes for metrics, health, etc.
The interface has changed a little between Spring Boot 1.5.x and Spring Boot 2.x. So the exact fully qualified class name or read method name may vary based on the Spring Boot version that you are using. In Boot 1.5.x, you can find most of the endpoints in the org.springframework.boot.actuate.endpoint package.
Roughly, this is how you could build a simple component for reading your version property (assuming that the name of the property inside the info endpoint is simply build.version):
#Component
public class VersionAccessor {
private final InfoEndpoint endpoint;
#Autowired
public VersionAccessor(InfoEndpoint endpoint) {
this.endpoint = endpoint;
}
public String getVersion() {
// Spring Boot 2.x
return String.valueOf(getValueFromMap(endpoint.info()));
// Spring Boot 1.x
return String.valueOf(getValueFromMap(endpoint.invoke()));
}
// the info returned from the endpoint may contain nested maps
// the exact steps for retrieving the right value depends on
// the exact property name(s). Here, we assume that we are
// interested in the build.version property
private Object getValueFromMap(Map<String, Object> info) {
return ((Map<String, Object>) info.get("build")).get("version");
}
}

Changing default URL mapping for Serving Static Content in Spring Boot

My static resources stopped working as soon as I added a new Controller (non rest) in my application with the following mapping
#RequestMapping(value = "/{postId}/{postUri:.+}", method = RequestMethod.GET)
public String viewPost(#ModelAttribute("model") ModelMap model, PathVariable("postId") String postId, PathVariable("postUri") String postUri) {
// do something
}
After debugging I found that my newly added controller method started picking up static resources, basically, it has taken precedence over the default mapping for static resources.
For example, Request to the below static resource reaches my controller instead of static resource handler.
http://localhost:7999/css/bootstrap-2a31dca112f26923b51676cb764c58d5.css
I am using spring boot 1.4
Is there a way to modify the mapping URL for serving default static content since I do not want to modify the URL of my Controller method ?
Sure thing. There is a spring.mvc.static-path-pattern that you can use to override that:
spring.mvc.static-path-pattern=/resources/**
will map classpath:/static/css/foo.css to /resources/css/foo.css.
(I've made that clearer in a862b6d)
Having said that, I could only strongly recommend to change your path there. Having a path variable that catches the root context is really a bad idea.
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-static-content
By default Spring Boot will serve static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so you can modify that behavior by adding your own WebMvcConfigurerAdapter and overriding the addResourceHandlers method.
In a stand-alone web application the default servlet from the container is also enabled, and acts as a fallback, serving content from the root of the ServletContext if Spring decides not to handle it. Most of the time this will not happen (unless you modify the default MVC configuration) because Spring will always be able to handle requests through the DispatcherServlet.
By default, resources are mapped on /** but you can tune that via spring.mvc.static-path-pattern. For instance, relocating all resources to /resources/** can be achieved as follows:
spring.mvc.static-path-pattern=/resources/**
You can also customize the static resource locations using spring.resources.static-locations (replacing the default values with a list of directory locations). If you do this the default welcome page detection will switch to your custom locations, so if there is an index.html in any of your locations on startup, it will be the home page of the application.
In addition to the ‘standard’ static resource locations above, a special case is made for Webjars content. Any resources with a path in /webjars/** will be served from jar files if they are packaged in the Webjars format.
i dint use #EnableWebMVC. This worked for me and spring boot service server static content for default
localhost:8888/ and also for localhost:8888/some/path/
#Configuration
public static class WebServerStaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/some/path/").setViewName("forward:/index.html");
}
}
I added spring.resources.static-location=file:../frontend/build in application.properties
index.html is present in the build folder
Use can also add absolute path
spring.resources.static-location=file:/User/XYZ/Desktop/frontend/build
For no controller pages:
#Controller
#RequestMapping("/feature")
public class DataTableController {
// map /feature/* to /feature/*
#RequestMapping(value="/{name}", method = RequestMethod.GET)
public ModelAndView staticPage(#PathVariable String name){
return new ModelAndView("feature/" + name);
}
}
For static resource except for HTML:
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
// map /res/ to classpath:/resources/static/
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/res/**").addResourceLocations("classpath:/static/");
}
}

How to find the togglz url when integrating with spring-boot and jersey

My application based on spring-boot and jersey. I have configured togglz in my application. I can successfully launch my web application but running gradle bootRun. From the output during the startup, I am able to see below log message. But I am not able to access http://localhost:8080/togglz. My application root path is "/" which is managed by jersey. It seems that spring-mvc works well with togglz but failed to access it when integrating with jersey. What should I do in order to let jersey to accept the url?
2016-03-30 18:40:35.191 INFO 81748 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/togglz || /togglz.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
For Jersey 2 to work along with Spring MVC endpoints in a Spring Boot application, I would suggest to make sure your application.yml (or .properties makes such distinction, something like:
...
# Spring MVC dispatcher servlet path. Needs to be different than Jersey's to enable/disable Actuator endpoints access (/info, /health, ...)
server.servlet-path: /
# Jersey dispatcher servlet
spring.jersey.application-path: /api
...
You could read more about this at my blog: http://tech.asimio.net/2016/04/05/Microservices-using-Spring-Boot-Jersey-Swagger-and-Docker.html#implement-api-endpoints-using-jersey
If you are integrating Jersey 1 with Spring MVC endpoints in a Spring Boot app, Spring Boot doesn't provide a jersey 1 starter so everything needs to be "manually" configured, but basically if your Jersey servlet is mapped to "/", you would need to configure it to let pass 404 to the servlet container for further handling (maybe a Spring MVC or plain servlet endpoint). Something like:
Jersey 1 resources configuration:
#ApplicationPath("/")
public class DemoResourcesConfig extends PackagesResourceConfig {
private static final Map<String, Object> properties() {
Map<String, Object> result = new HashMap<>();
result.put(PackagesResourceConfig.PROPERTY_PACKAGES, "com.sun.jersey;com.asimio.api.demo1.rest");
// To forward non-Jersey paths to servlet container for Spring Boot actuator endpoints to work.
result.put("com.sun.jersey.config.feature.FilterForwardOn404", "true");
result.put(JSONConfiguration.FEATURE_POJO_MAPPING, "true");
return result;
}
public DemoResourcesConfig() {
super(properties());
}
...
}
Jersey 1 resource implementation:
package com.asimio.api.demo1.rest;
...
#Component
#Path("/actors")
#Produces(MediaType.APPLICATION_JSON)
public class ActorResource {
#GET
public List<Actor> findActors() {
...
}
#GET
#Path("{id}")
public Actor getActor(#PathParam("id") String id) {
...
}
...
}
ServletContextInitializer bean
#Bean
public ServletContextInitializer servletInitializer() {
return new ServletContextInitializer() {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
final ServletRegistration.Dynamic appServlet = servletContext.addServlet("jersey-servlet", new SpringServlet());
Map<String, String> filterParameters = new HashMap<>();
// Set filter parameters
filterParameters.put("javax.ws.rs.Application", "com.asimio.api.demo1.config.DemoResourcesConfig");
appServlet.setInitParameters(filterParameters);
appServlet.setLoadOnStartup(2);
appServlet.addMapping("/*");
}
};
}
application.yml
...
# For Spring MVC to enable Endpoints access (/admin/info, /admin/health, ...) along with Jersey
server.servlet-path: /admin
...
More about Jersey 1 and Spring Boot / Cloud could be found at my blog: http://tech.asimio.net/2016/11/14/Microservices-Registration-and-Discovery-using-Spring-Cloud-Eureka-Ribbon-and-Feign.html#create-the-demo-service-1

Resources