Hi stackoverflow memebers,
I have finished migration to springboot 3.2.3 and jetty 12.0.6 and I have met an issue with a proper registration of DefaultServlet's baseResource - previously I didn't have such kind of the issue - springboot 2.7.X and older jetty for the same configuration.
I customize JettyServletWebServerFactory, adding own configuration, registering servlets etc but one thing stopped working - definition of baseResource as nested jar resources, caused - unexpected error - 404 for launching service as java -jar.
The funny fact is that it works for invocation of service as intellij idea launcher - looks like baseResource isn't served as nested resources.
below the source code:
package com.example.demo;
import jakarta.servlet.ServletRegistration;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import java.net.URL;
@SpringBootApplication
public class DemoApplication implements WebServerFactoryCustomizer<JettyServletWebServerFactory> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void customize(JettyServletWebServerFactory factory) {
DefaultServlet defaultServlet = new DefaultServlet();
factory.addInitializers(ctx -> {
ServletRegistration sr = ctx.addServlet("swagger-ui", defaultServlet);
sr.addMapping("/swagger-ui/*");
URL externalForm = DemoApplication.class.getResource("/static/swagger-ui/");
LOGGER.info("### resources: " + externalForm.toExternalForm());
sr.setInitParameter("baseResource", externalForm.toExternalForm());
sr.setInitParameter("pathInfoOnly", "true");
});
LOGGER.info("WebServer customized with swagger-ui");
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
whole code on repo: https://github.com/lmetrak/springboot3-jetty12-compilation
Would You have a look and explain me what I am doing wrong?
The URL for your base directory is not accessible by Jetty 12.
Looks like you are using a version of Spring-Boot with the internal nested FileSystem support, cool! But that means you'll need to disable the alias checks.
What does this mean?
Well, the URL produced by your classloader lookup has nested references.
### resources: jar:nested:/home/joakim/code/jetty/github/springboot3-jetty12-compilation/target/demo-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/static/swagger-ui/
Notice the doubly-nested resource? specifically the two !/
sections? and the unique nested:
scheme in that URL?
This confuses an internal security / safety mechanism in Jetty that attempts to figure out if your requested path is an alias to a real path.
This is the work of the org.eclipse.jetty.server.AliasCheck
implementations assigned to the ServletContextHandler
.
Basically, the ServletContextHandler
itself has a baseResource, which is provided by spring-boot, and is configured as something like file:/tmp/jetty-docbase.8080.1564834896253426295/
, and that is the one used by the default Alias Checkers.
When you introduced another Base Resource that is unique to the additional DefaultServlet
that you are adding, the existing Alias Checkers are not going to allow those references to be used, hence the 404 you got.
Here's the code for your customize method that does this.
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
import org.eclipse.jetty.util.resource.Resource;
import org.springframework.core.io.ClassPathResource;
@Override
public void customize(JettyServletWebServerFactory factory) {
factory.addInitializers(ctx -> {
ServletContextHandler servletContextHandler = ServletContextHandler.getServletContextHandler(ctx);
LOGGER.info("### baseResource: {}", servletContextHandler.getBaseResource());
ServletHolder holder = new ServletHolder("swatter-ui", new DefaultServlet());
servletContextHandler.addServlet(holder, "/swagger-ui/*");
try {
URL externalForm = new ClassPathResource("/static/swagger-ui/").getURL();
LOGGER.info("### resources: " + externalForm.toExternalForm());
holder.setInitParameter("baseResource", externalForm.toExternalForm());
Resource base = servletContextHandler.newResource(externalForm);
servletContextHandler.addAliasCheck(new AllowedResourceAliasChecker(servletContextHandler, base));
} catch (IOException e) {
throw new RuntimeException("Unable to find /static/swagger-ui");
}
});
LOGGER.info("WebServer customized with swagger-ui");
}