feat(ext-vite): recognise navigations by Sec-Fetch-Mode, read every header in place
A path that is no file falls back to index.html when the request is a navigation: Sec-Fetch-Mode: navigate, or an Accept naming text/html for older clients. That check, Accept-Encoding and If-None-Match are all matched on the header bytes through the new Request.headerView(name), so serving still allocates nothing. navigationOnly(false) drops the check for an app that wants every GET miss to get the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
680bbca8c7
commit
003fd6d1f0
+25
-7
@@ -1,5 +1,6 @@
|
||||
package dev.relism.flash.ext.vite;
|
||||
|
||||
import dev.relism.fpr.core.ByteView;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
@@ -33,24 +34,41 @@ class AssetsTest {
|
||||
}
|
||||
}
|
||||
try (URLClassLoader loader = new URLClassLoader(new URL[]{jar.toUri().toURL()}, null)) {
|
||||
assertEquals(Set.of("/", "/index.html", "/assets/a-12345678.css"), new Assets(loader, "/").byPath.keySet());
|
||||
assertEquals(Set.of("/", "/index.html", "/assets/a-12345678.css"), new Assets(loader, "/", true).byPath.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void noBuildFailsTheBootNamingThePlugin() throws IOException {
|
||||
try (URLClassLoader empty = new URLClassLoader(new URL[0], null)) {
|
||||
assertTrue(assertThrows(IllegalStateException.class, () -> new Assets(empty, "/")).getMessage().contains("flash-ext-vite-maven-plugin"));
|
||||
assertTrue(assertThrows(IllegalStateException.class, () -> new Assets(empty, "/", true)).getMessage().contains("flash-ext-vite-maven-plugin"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void gzipIsAcceptedUnlessRefused() {
|
||||
assertTrue(Assets.acceptsGzip("gzip, deflate, br"));
|
||||
assertTrue(Assets.acceptsGzip("br;q=1.0, gzip;q=0.5"));
|
||||
assertFalse(Assets.acceptsGzip("gzip;q=0, br"));
|
||||
assertFalse(Assets.acceptsGzip("gzip;q=0.000"));
|
||||
assertFalse(Assets.acceptsGzip("br"));
|
||||
assertTrue(Assets.acceptsGzip(view("gzip, deflate, br")));
|
||||
assertTrue(Assets.acceptsGzip(view("br;q=1.0, GZIP;q=0.5")));
|
||||
assertFalse(Assets.acceptsGzip(view("gzip;q=0, br")));
|
||||
assertFalse(Assets.acceptsGzip(view("gzip;q=0.000")));
|
||||
assertFalse(Assets.acceptsGzip(view("br")));
|
||||
assertFalse(Assets.acceptsGzip(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchingIgnoresAsciiCaseAndFindsTheFirstMatch() {
|
||||
byte[] html = "text/html".getBytes();
|
||||
assertEquals(0, Assets.indexOf(view("Text/HTML,*/*"), html, 0));
|
||||
assertEquals(12, Assets.indexOf(view("application/text/html"), html, 0));
|
||||
assertEquals(-1, Assets.indexOf(view("text/htm"), html, 0));
|
||||
assertEquals(-1, Assets.indexOf(null, html, 0));
|
||||
}
|
||||
|
||||
private static ByteView view(String text) {
|
||||
byte[] bytes = text.getBytes();
|
||||
return new ByteView() {
|
||||
@Override public int length() { return bytes.length; }
|
||||
@Override public byte byteAt(int i) { return bytes[i]; }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -16,6 +16,9 @@ class ViteExtensionTest {
|
||||
@RegisterExtension
|
||||
static final FlashTest app = FlashTest.of(flash -> flash.install(new ViteExtension()));
|
||||
|
||||
@RegisterExtension
|
||||
static final FlashTest everything = FlashTest.of(flash -> flash.install(new ViteExtension().navigationOnly(false)));
|
||||
|
||||
@RegisterExtension
|
||||
static final FlashTest nested = FlashTest.of(flash -> flash.install(new ViteExtension().basePath("/app/")));
|
||||
|
||||
@@ -41,6 +44,7 @@ class ViteExtensionTest {
|
||||
for (String route : new String[]{"/content/2", "/users/ada.lovelace"}) {
|
||||
app.request().header("Accept", NAVIGATION).get(route).expectStatus(200).expectHeader("Cache-Control", "no-cache").expectBodyContains("spa");
|
||||
}
|
||||
app.request().header("Sec-Fetch-Mode", "navigate").get("/content/2").expectStatus(200).expectBodyContains("spa");
|
||||
app.request().header("Accept", "application/json").get("/api/nope").expectStatus(404);
|
||||
app.request().header("Accept", "*/*").get("/assets/missing.js").expectStatus(404);
|
||||
app.get("/content/2").expectStatus(404);
|
||||
@@ -48,6 +52,13 @@ class ViteExtensionTest {
|
||||
nested.get("/app").expectStatus(200).expectBodyContains("spa");
|
||||
}
|
||||
|
||||
/** Opted out of the navigation check, every GET that is no file gets the page, as nginx's try_files would. */
|
||||
@Test
|
||||
void withoutTheNavigationCheckEveryMissGetsTheIndex() {
|
||||
everything.request().header("Accept", "application/json").get("/api/nope").expectStatus(200).expectBodyContains("spa");
|
||||
everything.get("/assets/missing.js").expectStatus(200).expectBodyContains("spa");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnchangedFileIsNotSentAgainAHashedOneIsNeverAsked() {
|
||||
String etag = app.get("/favicon.svg").header("ETag");
|
||||
|
||||
Reference in New Issue
Block a user