-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creating a Resource for an entry in a nested jar file in Jetty 12 #9973
Comments
Quick answer / example: Create/Open Path exampleJar = Path.of("example.jar");
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
{
// Create Resource from jar/zip, mounted to root of jar/zip
Resource jarFileResource = resourceFactory.newJarFileResource(exampleJar.toUri());
// access as Resource
Resource manifestResource = jarFileResource.resolve("/META-INF/MANIFEST.MF");
try (InputStream inputStream = manifestResource.newInputStream())
{
// read from input stream
}
// Create Resource from jar/zip URI
URI uri = URI.create("jar:file:/example.jar!/");
Resource uriResource = resourceFactory.newResource(uri);
// access as Path object
Path manifestPath = uriResource.getPath().resolve("/META-INF/MANIFEST.MF");
// create new Resource from Path object
PathResource resource = (PathResource)resourceFactory.newResource(manifestPath);
try (InputStream inputStream = resource.newInputStream())
{
// read from input stream
}
} A
Once you have the Now to get to details ... In Jetty 11 and earlier, this was all done via the java.net.URL and associated protocol/stream handing in the JVM. One behavior of this decision is at the JVM level, namely this means once a jar is opened via a Lets take your example URL of
|
Note also, that the Example: try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
{
URI uri = URI.create("jar:file:/example.jar!/webapp/base/");
Resource baseResource = resourceFactory.newResource(uri);
// this will result in a null
Resource manifestResource = baseResource.resolve("../../META-INF/MANIFEST.MF");
// this will work if that content exists in the `webapp/base/` directory
Resource resource = resourceFactory.resolve("index.html");
try (InputStream inputStream = resource.newInputStream())
{
// read from input stream
}
} |
Also of note, if you have a WebAppContext webappContext = new WebAppContext();
URI baseURI = URI.create("jar:file:/example.war!/");
Resource baseResource = webappContext.getResourceFactory().newResource(baseURI);
webappContext.setWarResource(baseResource); This will let the lifecycle of the |
Thanks very much, @joakime. It looks the
Does this move to the JVM's built-in zipfs support prevent the old URL-based approach from being used? Our nested, executable jars don't support NIO FileSystems at the moment and I suspect that's a bigger piece of work than we have time for right now. Two alternatives are to continue with a URL-based approach (if that's possible in Jetty 12) or perhaps to start using |
Note that The default Note that you don't need the nested jar in a jar in a jar ... to have an executable war.
We have a URL based Resource implementation available. ResourceFactory.registerResourceFactory("jar", new URLResourceFactory()); This will incur a heavy performance hit (open / access / close for every resource request), and introduce all of the known URL bugs and vulnerabilities that exist in the JVM you are using.
I would strongly encourage you to investigate that spring bug sooner than later, as many projects and libraries are using zipfs and FileSystem now. Most making the changes due the mass deprecation of various URL classes/methods in the JDK due to all of the various bugs in the URL class that cannot be fixed there, but are already fixed in existing URI and |
@wilkinsona did @joakime's explanation answered your question or do you need more help? |
I'm still stuck at the moment. #9984 is at least part of that. Looking at the Jetty 11 code and how we called it, I think what we're missing is a |
If you want to, we can arrange a meeting so I can help you live review all the changes you made to upgrade to Jetty 12, advise you on how to best use our new API, and help you with any difficulty you may have. |
That hasn't appeared to be the case in my testing of Spring Boot's executable jars that support nesting one jar within another. Unfortunately, like the JDK's While the |
Ok, got it. Let's just hope ResourceFactory.registerResourceFactory("jar", new URLResourceFactory()); and see how it works for you. |
The PR about the NPE in Please keep us posted. |
Thanks for the NPE fix. Unfortunately, it's not sufficient to reach parity with Jetty 11. Things now get a little further but the lack of support for detecting if the URL points to a directory still leaves us blocked. This is the failure that I'm seeing:
If we overcome that, I suspect that listing not being supported will be the next problem. |
That a So lets see where this could be coming from. |
@wilkinsona looking at ... ... the URLs from ... protected final List<URL> getUrlsOfJarsWithMetaInfResources() {
return this.staticResourceJars.getUrls();
} All need to end with a slash to give the right hint that they are a directory, not a resource. |
Also ... That is wrong as well. private Resource createResource(URL url) throws Exception {
if ("file".equals(url.getProtocol())) {
File file = new File(url.toURI());
if (file.isFile()) {
return Resource.newResource("jar:" + url + "!/META-INF/resources");
}
}
return Resource.newResource(url + "META-INF/resources");
} Each of those |
We're populating things. We have some common code that we use with Jetty, Tomcat, and Undertow to find all the jars with I now see a different failure that can be reproduced with this code: new URLResourceFactory().newResource("jar:file:example.war!/WEB-INF/lib/resources.jar!/META-INF/resources/").resolve("nested-resource.txt"); It fails with |
Signed-off-by: Ludovic Orban <[email protected]>
I've created a new We're probably not going to include it in the official Jetty 12 codebase, but you should be able to take this class and import it into your codebase. Could you please see if it helps, eventually adapting it? |
That |
@joakime I agree But if the Spring team is willing to take ownership of this resource factory until they come up with a |
I spent a bit of time looking at this yesterday, and I don't think it's going to be possible. To be able to plug in our support transparently (as we would require and as we can do with I think we're stuck in a bit of a corner. On one side, we have to stick with a It would be much appreciated if you would reconsider the removal of the URL-based |
We have one, it is the |
This is possible, but you need to implement your own scheme/protocol, you can't override There are a host of bugs you are tickling with your choice of using |
Thanks, @joakime.
I've fixed that but hit another problem. Please see this comment above for details. From that comment, a single-line reproducer: new URLResourceFactory().newResource("jar:file:example.war!/WEB-INF/lib/resources.jar!/META-INF/resources/").resolve("nested-resource.txt"); This throws |
This is what I was getting at when I said we need to be able to plug in our support transparently. Requiring a custom scheme doesn't meet that requirement. If someone's working with a |
Merged PR #9995 into |
@wilkinsona Have you had the opportunity to test Jetty 12 again after #9995 got merged? What's your current status and plan w.r.t the Spring / Jetty 12 integration? Is there anything else we can do to help? |
I think we need to look at improving the performance of I've open issue #10057 |
@joakime @lorban I think the ball is a bit in our court. I've long argued that we need good |
Thanks for asking, @lorban. #9995 hasn't really helped, unfortunately. We end up with a
Jetty tries to resolve
Thanks, @gregw. You probably won't be surprised to hear that I agree wholeheartedly with this. In a previous comment, you also said this:
I don't want to split hairs, but, as far as I know, neither
Given this situation in the JDK, from my admittedly biased perspective, I think Jetty 12 really needs to continue to support URLs as it did in earlier versions. |
@wilkinsona you should not resolve paths starting with a "/", unless you want the whole uri to be replaced with your resolved path. This is akin to how Path path = Path.of("/some/path/");
Path resolvedWithSlash = path.resolve("/nested-meta-inf-resource.txt");
Path resolvedWithoutSlash = path.resolve("nested-meta-inf-resource.txt");
It is also how URI uri = URI.create("file:/some/path/spring-boot-server-tests-app-jetty.jar!/BOOT-INF/lib/spring-boot-server-tests-app-resources.jar!/META-INF/resources/");
URI resolvedWithSlash = uri.resolve("/nested-meta-inf-resource.txt");
URI resolvedWithoutSlash = uri.resolve("nested-meta-inf-resource.txt");
Jetty 11 was very lenient about the effects of missing and extra slashes, but Jetty 12 is not anymore; as you noticed earlier when @joakime told you If you could give me some directions about what tests to run in which branch of Spring Boot and how to run them, we could avoid these inefficient back-and-forth messages. As @gregw pointed out, we're committed to making sure Spring works at least as well as it used to without too much effort from your side. |
It's not my code that's doing it, it's Jetty's. Here's the stack:
This was the result of an HTTP GET requests for My work on upgrading to Jetty 12 is in this branch. Running |
@wilkinsona (edit: oops) for the sake of clarity, can you confirm what branch we should run the same test on to see how it is currently working for jetty-11? |
Assuming you meant me, any of |
Opened PR #10058 to address |
Note that I ran the tests against the springboot 3.1.x branch and I can see that our JarFileResource is being using, but that it's connection class is |
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
* use URIUtil.addPaths() instead of URI.resolve() * Better Connection / InputStream locks * Removing URLResource.close() * Adding URLResourceFactory.setReadTimeout() * restore existence check in isDirectory * Simplify URLResource.resolve --------- Signed-off-by: Ludovic Orban <[email protected]> Signed-off-by: Joakim Erdfelt <[email protected]> Co-authored-by: Ludovic Orban <[email protected]>
@wilkinsona With the merge of PR #10058 and the release of Jetty I've been able to build + test that set of spring-boot test cases you referenced with a fork of your |
@wilkinsona see also the PR at wilkinsona/spring-boot#7 |
@wilkinsona I think we can close this issue, open new issues for any new concerns with your spring-boot effort |
👍 thanks for all the assistance thus far. |
Jetty version
Jetty 12.0.0.Beta2
Java version
17
Question
I'm working on upgrading Spring Boot to Jetty 12. Spring Boot supports executable jars and wars that can be run using
java -jar
. This results in URLs pointing to jars nested within a jar or war. For example, this URL taken from our test suite:Much of the path doesn't matter. A shorter example would be:
With Jetty 11 and earlier, I could create a
Resource
that points to theMETA-INF/resources
entry withinspring-boot-server-tests-app-resources.jar!
by callingResource.newResource(url + "META-INF/resources")
. I am struggling to find the Jetty 12 equivalent of this code.I have a
WebAppContext
to hand so I have tried callingResourceFactory resourceFactory = ResourceFactory.of(handler)
and callingResourceFactory.newResource
. This works for situations wherespring-boot-server-tests-app-resources.jar
is available directly on the file system but not for the case where it's nested within a war.How can I get this working for nested jars using Jetty 12 please?
The text was updated successfully, but these errors were encountered: