Skip to content
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

Can't load static resource created by extension #28474

Closed
FroMage opened this issue Oct 10, 2022 · 27 comments · Fixed by #45031 · May be fixed by #30820
Closed

Can't load static resource created by extension #28474

FroMage opened this issue Oct 10, 2022 · 27 comments · Fixed by #45031 · May be fixed by #30820
Labels
Milestone

Comments

@FroMage
Copy link
Member

FroMage commented Oct 10, 2022

Describe the bug

In my extension, I created a static resource with:

    @BuildStep
    public void processModel(BuildProducer<GeneratedResourceBuildItem> output,
            BuildProducer<AdditionalStaticResourceBuildItem> staticResources) {

        String path = "/routes.js";
        output.produce(new GeneratedResourceBuildItem("META-INF/resources" + path,
                "//hello".getBytes(StandardCharsets.UTF_8)));
        staticResources.produce(new AdditionalStaticResourceBuildItem(path, false));
}

When I try to test this:

@QuarkusTest
public class MyTest {
    @Test
    public void testJsRouting() {
        given()
                .when().get("/routes.js")
                .then()
                .statusCode(200)
                .body(is("//hello"));
    }
}

I'm getting the following exception:

ERROR: HTTP Request to /routes.js failed, error id: 0c1267fb-df47-4c0c-b97f-f7943ca4ef19-1
java.lang.IllegalStateException: Invalid url protocol: quarkus
    at io.vertx.core.file.impl.FileResolverImpl.unpackUrlResource(FileResolverImpl.java:247)
    at io.vertx.core.file.impl.FileResolverImpl.resolveFile(FileResolverImpl.java:174)
    at io.vertx.core.impl.VertxImpl.resolveFile(VertxImpl.java:794)
    at io.vertx.core.file.impl.FileSystemImpl$20.perform(FileSystemImpl.java:1136)
    at io.vertx.core.file.impl.FileSystemImpl$20.perform(FileSystemImpl.java:1134)
    at io.vertx.core.file.impl.FileSystemImpl$BlockingAction.handle(FileSystemImpl.java:1175)
    at io.vertx.core.file.impl.FileSystemImpl$BlockingAction.handle(FileSystemImpl.java:1157)
    at io.vertx.core.impl.ContextBase.lambda$null$0(ContextBase.java:137)
    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
    at io.vertx.core.impl.ContextBase.lambda$executeBlocking$1(ContextBase.java:135)
    at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
    at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:834)

This is because those are served via a Vert.x StaticHandler, which accesses them via the file system, and obtains the URL to the resource via the QuarkusClassLoader: https://github.com/eclipse-vertx/vert.x/blob/master/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java#L173 and then attempts to open it in https://github.com/eclipse-vertx/vert.x/blob/bbb4ddc417a604697d2ff4f5a8eda2e74113697e/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java#L225

The problem is that QuarkusClassLoader serves it using a MemoryClassPathElement https://github.com/quarkusio/quarkus/blob/main/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java#L86 that has a quarkus: URL scheme, which is registered via a UrlStreamHandler at https://github.com/quarkusio/quarkus/blob/main/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java#L131 but Vert.x doesn't know about it and doesn't know how to deal with it.

I'm not sure what to do to fix this, frankly.

Expected behavior

No response

Actual behavior

No response

How to Reproduce?

No response

Output of uname -a or ver

No response

Output of java -version

No response

GraalVM version (if different from Java)

No response

Quarkus version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

@geoand
Copy link
Contributor

geoand commented Oct 10, 2022

I'll take a look tomorrow

@geoand
Copy link
Contributor

geoand commented Oct 11, 2022

@FroMage do you have your sample code anywhere?

@FroMage
Copy link
Member Author

FroMage commented Oct 11, 2022

Not already, but I guess I could push it. You can't reproduce?

@geoand
Copy link
Contributor

geoand commented Oct 11, 2022

My point is that I don't want to try to reproduce and fail :)

@FroMage
Copy link
Member Author

FroMage commented Oct 11, 2022

Well, meet me half way :) This code should be enough, and you have to add a test for this anyway, so giving you a full project won't make your test materialise. And if it doesn't fail to reproduce, I'll give you a full project? Come to think of it, how do we even write tests for extensions?

@geoand
Copy link
Contributor

geoand commented Oct 11, 2022

Come to think of it, how do we even write tests for extensions?

The deployment module tests are usually the answer

@geoand
Copy link
Contributor

geoand commented Oct 11, 2022

The easiest thing for me would be to just use your extension code instead of coming up with a new one

@FroMage
Copy link
Member Author

FroMage commented Oct 12, 2022

Well, OK, so I found how to write an extension test to have a special build step today:

package io.quarkus.vertx.http;

import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem;
import io.restassured.RestAssured;

public class AdditionalStaticResourceTest {

    @RegisterExtension
    static final QuarkusUnitTest config = new QuarkusUnitTest()
            .addBuildChainCustomizer(buildCustomizer());

    static Consumer<BuildChainBuilder> buildCustomizer() {
        return new Consumer<BuildChainBuilder>() {
            @Override
            public void accept(BuildChainBuilder builder) {
                builder.addBuildStep(new BuildStep() {
                    @Override
                    public void execute(BuildContext context) {
                        String path = "/routes.js";
                        

                        context.produce(new GeneratedResourceBuildItem("META-INF/resources" + path,
                                "//hello".getBytes(StandardCharsets.UTF_8)));
                        context.produce(new AdditionalStaticResourceBuildItem(path, false));
                    }
                }).produces(GeneratedResourceBuildItem.class)
                .produces(AdditionalStaticResourceBuildItem.class)
                .build();
            }
        };
    }

    @Test
    public void testNonApplicationEndpointDirect() {
        RestAssured.given()
        .when().get("/routes.js")
        .then()
        .statusCode(200)
        .body(Matchers.is("//hello"));
    }
}

Here's your reproducer ;)

@geoand
Copy link
Contributor

geoand commented Oct 13, 2022

Where would routes.js be found in this test?

@FroMage
Copy link
Member Author

FroMage commented Oct 13, 2022

It's not found, it's generated by the extension:

context.produce(new GeneratedResourceBuildItem("META-INF/resources" + path,
                                "//hello".getBytes(StandardCharsets.UTF_8)));

@geoand
Copy link
Contributor

geoand commented Oct 13, 2022

Ah, okay, I misread that.

@FroMage
Copy link
Member Author

FroMage commented Oct 13, 2022

You are on way too many issues at the same time, this tells me ;)

@geoand
Copy link
Contributor

geoand commented Oct 13, 2022

There are a few ways to deal with this:

  1. The extension actually generates a file as well (that's what excludeFromDevCL is used for) - see this discussion
  2. We could make Vert.x use the URLStreamHandler of a URL for protocols it does not know

@FroMage
Copy link
Member Author

FroMage commented Oct 14, 2022

Huh, I thought GeneratedResourceBuildItem would generate the file. What does it do then? And how do I generate it if not using that?

@geoand
Copy link
Contributor

geoand commented Oct 14, 2022

It does generates it in prod mode, in the other modes it's just kept "in memory" backed by the QuarkusClassLoader.

@FroMage
Copy link
Member Author

FroMage commented Oct 14, 2022

Oh. So it's not clear what you're suggesting I do for 1. then ;)

@geoand
Copy link
Contributor

geoand commented Oct 14, 2022

You would have to actually persist it on the disk yourself

@FroMage
Copy link
Member Author

FroMage commented Oct 17, 2022

If I knew where to put it, it could be an option. I suppose this depends on the build system. Or we could stop the Quarkus CL from defining its own protocol perhaps? Is this even used by anything other than the vert.x static handler?

@geoand
Copy link
Contributor

geoand commented Oct 17, 2022

The protocol is not used AFAIK - but even if we do, I don't think Vert.x will work

@FroMage
Copy link
Member Author

FroMage commented Oct 17, 2022

Well, if Quarkus itself creates the file and stops using this protocol, then Vert.x will find it on disk, no?

@ia3andy
Copy link
Contributor

ia3andy commented Feb 21, 2024

@FroMage you need to be cautious here, because when using the classloader instead of the FS, there is a cache preventing update of the file.

FYI, I am working on a very similar issue with the Web Bundler: quarkiverse/quarkus-web-bundler#171

@cescoffier
Copy link
Member

Any news about this one?
If the file is generated at build time it should work.

@FroMage
Copy link
Member Author

FroMage commented Nov 22, 2024

If the file is generated at build time it should work.

Generated how?

At the time, it was generated (see reproducer) #28474 (comment) but only in prod mode.

In DEV mode it was just stored by a classloader which would not serve it back.

@FroMage
Copy link
Member Author

FroMage commented Nov 22, 2024

Is it documented anywhere?

Looking at https://quarkus.io/guides/writing-extensions I can only find mention of GeneratedResourceBuildItem which frankly feels like it should do the same as GeneratedStaticResourceBuildItem and the difference is not obvious.

@ia3andy
Copy link
Contributor

ia3andy commented Dec 2, 2024

@mcruzdev Could you have a look to the guide?

@mcruzdev
Copy link
Contributor

mcruzdev commented Dec 2, 2024

@mcruzdev Could you have a look to the guide?

I'm going to add the documentation for it today. 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment