Skip to content

Commit 30cf92c

Browse files
fix: wrong existence check in getStaticResource (CP: 25.0) (#24285) (CP: 24.10) (#24286)
This PR cherry-picks changes from the original PR #24285 to branch 24.10. --- #### Original PR description > This PR cherry-picks changes from the original PR #24283 to branch 25.0. > > A conflict in `flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java` was resolved manually: the new `getStaticResource_jarUrlOnJetty12_returnsUrlInsteadOfThrowing` test was rewritten in JUnit 4 idioms (`@Rule TemporaryFolder`, `Assert.*`) since 25.0 has not adopted the JUnit 5 migration that landed on `main`. > > --- > > > On Jetty 12.1.9, requests for static resources packaged inside a JAR (e.g. `vaadinPush.js` from `flow-push`) fail with > > `FileSystemNotFoundException`. `VaadinServletService.getStaticResource` verifies the URL returned by `ServletContext.getResource` via `Path.of(url.toURI())`, which for a `jar:file:...!/entry` URI requires the JAR's NIO `FileSystem` to already be mounted in the JVM-wide cache. Jetty 12.1.8 incidentally kept those filesystems mounted during resource resolution; 12.1.9 no longer does, so `getFileSystem` throws and the existing `catch (URISyntaxException)` lets the unchecked exception escape, producing HTTP 500. > > > > Probe the URL with `URL.openStream()` instead. `JarURLConnection` and `FileURLConnection` use `java.util.jar.JarFile` / `java.io.File` directly and are independent of the NIO `FileSystems` cache, so the check works uniformly for `file:` and `jar:file:` URLs and on every Jetty 12 build. The catch is broadened to `IOException`, covering both missing files (the original Jetty 12 workaround) and missing JAR entries. Co-authored-by: Marco Collovati <marco@vaadin.com>
1 parent ace9144 commit 30cf92c

2 files changed

Lines changed: 61 additions & 10 deletions

File tree

flow-server/src/main/java/com/vaadin/flow/server/VaadinServletService.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
import jakarta.servlet.ServletContext;
2121
import jakarta.servlet.http.HttpServletRequest;
2222

23+
import java.io.IOException;
2324
import java.io.InputStream;
2425
import java.net.MalformedURLException;
25-
import java.net.URISyntaxException;
2626
import java.net.URL;
27-
import java.nio.file.Files;
28-
import java.nio.file.Path;
2927
import java.util.List;
3028
import java.util.Objects;
3129
import java.util.Optional;
@@ -325,13 +323,15 @@ static URL getStaticResource(ServletContext servletContext, String path)
325323
URL url = servletContext.getResource(path);
326324
if (url != null && Optional.ofNullable(servletContext.getServerInfo())
327325
.orElse("").contains("jetty/12.")) {
328-
// Making sure that resource exists before returning it. Jetty
329-
// 12 may return URL for non-existing resource.
330-
try {
331-
if (!Files.exists(Path.of(url.toURI()))) {
332-
url = null;
333-
}
334-
} catch (URISyntaxException e) {
326+
// Jetty 12 may return URLs for non-existing resources. Probe the
327+
// URL by opening a stream: this works uniformly for file: URLs
328+
// and for jar:file:...!/entry URLs without requiring a NIO
329+
// FileSystem to be mounted for the JAR (which Path.of(jarUri)
330+
// would need, breaking on Jetty 12.1.9 where classpath JARs are
331+
// no longer kept mounted in the JVM-wide cache).
332+
try (InputStream probe = url.openStream()) {
333+
// resource exists
334+
} catch (IOException e) {
335335
url = null;
336336
}
337337
}

flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,26 @@
66

77
import java.io.IOException;
88
import java.net.MalformedURLException;
9+
import java.net.URI;
910
import java.net.URL;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
1014
import java.util.Collections;
1115
import java.util.HashMap;
1216
import java.util.List;
1317
import java.util.Map;
18+
import java.util.UUID;
1419
import java.util.concurrent.locks.ReentrantLock;
20+
import java.util.jar.JarEntry;
21+
import java.util.jar.JarOutputStream;
1522

1623
import org.junit.After;
1724
import org.junit.Assert;
1825
import org.junit.Before;
26+
import org.junit.Rule;
1927
import org.junit.Test;
28+
import org.junit.rules.TemporaryFolder;
2029
import org.mockito.Mockito;
2130

2231
import com.vaadin.flow.di.Instantiator;
@@ -48,6 +57,9 @@ public String getThemeUrl() {
4857
private TestVaadinServletService service;
4958
private VaadinServlet servlet;
5059

60+
@Rule
61+
public TemporaryFolder tempFolder = new TemporaryFolder();
62+
5163
@Before
5264
public void setup() throws Exception {
5365
mocks = new MockServletServiceSessionSetup();
@@ -313,6 +325,45 @@ protected List<VaadinRequestInterceptor> createVaadinRequestInterceptors()
313325
request.getAttribute("ended"));
314326
}
315327

328+
@Test
329+
public void getStaticResource_jarUrlOnJetty12_returnsUrlInsteadOfThrowing()
330+
throws Exception {
331+
// Reproduces the regression caused by Jetty 12.1.9 where
332+
// ServletContext.getResource() returns a jar:file:... URL for
333+
// resources packaged inside a JAR (e.g. vaadinPush.js in flow-push).
334+
// Path.of(jar:URI) throws FileSystemNotFoundException because no
335+
// FileSystem has been opened for the JAR, and the existence check in
336+
// VaadinServletService.getStaticResource catches only
337+
// URISyntaxException, so the unchecked exception escapes and the
338+
// request fails with HTTP 500.
339+
String resourcePath = "META-INF/resources/VAADIN/static/push/vaadinPush.js";
340+
Path jarFile = tempFolder
341+
.newFile("flow-push-" + UUID.randomUUID() + ".jar").toPath();
342+
try (JarOutputStream jar = new JarOutputStream(
343+
Files.newOutputStream(jarFile))) {
344+
jar.putNextEntry(new JarEntry(resourcePath));
345+
jar.write("// push".getBytes(StandardCharsets.UTF_8));
346+
jar.closeEntry();
347+
}
348+
349+
URL jarResourceUrl = URI
350+
.create("jar:" + jarFile.toUri() + "!/" + resourcePath).toURL();
351+
352+
ServletContext servletContext = Mockito.mock(ServletContext.class);
353+
when(servletContext.getServerInfo()).thenReturn("jetty/12.1.9");
354+
when(servletContext.getResource("/VAADIN/static/push/vaadinPush.js"))
355+
.thenReturn(jarResourceUrl);
356+
357+
URL resolved = VaadinServletService.getStaticResource(servletContext,
358+
"/VAADIN/static/push/vaadinPush.js");
359+
360+
Assert.assertNotNull(
361+
"An existing resource served from a JAR by Jetty 12 must be returned, "
362+
+ "not lost to FileSystemNotFoundException",
363+
resolved);
364+
Assert.assertEquals(jarResourceUrl, resolved);
365+
}
366+
316367
static class ExceptionThrowingRequestHandler implements RequestHandler {
317368

318369
@Override

0 commit comments

Comments
 (0)