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

Undertow Websockets: Could not create an endpoint dynamically #19781

Closed
pjgg opened this issue Aug 30, 2021 · 4 comments · Fixed by #19948
Closed

Undertow Websockets: Could not create an endpoint dynamically #19781

pjgg opened this issue Aug 30, 2021 · 4 comments · Fixed by #19948

Comments

@pjgg
Copy link
Contributor

pjgg commented Aug 30, 2021

Describe the bug

Extensions: io.quarkus:quarkus-undertow-websockets

On version 1.11.7.Final I could create an endpoint dynamically as follow:

 ((ServerContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName()))
                    .addEndpoint(ServerEndpointConfig.Builder.create(WebsockEndpoint.class, "/simple")
                            .configurator(new WebsockEndpointConfigurator(this)).build());

However after upgrade to version 1.13.7.Final I got a NullPointerException

Caused by: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.NullPointerException
	at io.quarkus.undertow.runtime.UndertowDeploymentRecorder.bootServletContainer(UndertowDeploymentRecorder.java:528)
	at io.quarkus.deployment.steps.UndertowBuildStep$build-649634386.deploy_0(UndertowBuildStep$build-649634386.zig:238)
	at io.quarkus.deployment.steps.UndertowBuildStep$build-649634386.deploy(UndertowBuildStep$build-649634386.zig:40)
	at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:169)
	... 43 more
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
	at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:253)
	at io.quarkus.undertow.runtime.UndertowDeploymentRecorder.bootServletContainer(UndertowDeploymentRecorder.java:517)
	... 46 more
Caused by: java.lang.NullPointerException
	at io.quarkus.qe.undertow.PongLeakSample.contextInitialized(PongLeakSample.java:51)

Here I paste you a full reproducer:

import java.io.IOException;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

import javax.enterprise.inject.spi.DeploymentException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;

import org.jboss.logging.Logger;

@WebListener
public class PongLeakSample implements ServletContextListener {
    private static final Logger LOG = Logger.getLogger(PongLeakSample.class);

    private Thread pingThread;
    private Set<WebsockEndpoint> endpoints;

    public PongLeakSample() {
        endpoints = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>()));
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        pingThread.interrupt();
        endpoints.forEach(WebsockEndpoint::close);
        try {
            pingThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * @see ServletContextListener#contextInitialized(ServletContextEvent)
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            ((ServerContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName()))
                    .addEndpoint(ServerEndpointConfig.Builder.create(WebsockEndpoint.class, "/simple")
                            .configurator(new WebsockEndpointConfigurator(this)).build());
            
        } catch (DeploymentException | javax.websocket.DeploymentException e) {
            e.printStackTrace();
        }
        pingThread = new Thread(this::pingRoutine);
        pingThread.setDaemon(true);
        pingThread.start();
    }

    private void pingRoutine() {
        try {
            while (true) {
                Thread.sleep(300);
                endpoints.forEach(WebsockEndpoint::ping);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static class WebsockEndpoint extends Endpoint {

        private static final String PONG = "PONG";
        private final PongLeakSample filter;
        private Session session;

        public WebsockEndpoint(PongLeakSample filter) {
            this.filter = filter;
        }

        @Override
        public void onOpen(Session session, EndpointConfig config) {
            this.session = session;
            filter.endpoints.add(this);
        }

        @Override
        public void onClose(Session session, CloseReason closeReason) {
            filter.endpoints.remove(this);
        }

        public void ping() {
            LOG.info("ping-pong invoked!");
            session.getAsyncRemote().sendText(PONG);
        }

        public void close() {
            try {
                session.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static class WebsockEndpointConfigurator extends ServerEndpointConfig.Configurator {
        private PongLeakSample filter;

        WebsockEndpointConfigurator(PongLeakSample filter) {
            this.filter = filter;
        }

        @Override
        public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
            WebsockEndpoint endpoint = new WebsockEndpoint(filter);
            if (endpointClass.isInstance(endpoint))
                return endpointClass.cast(endpoint);
            throw new InstantiationException(
                    "Requested class is not assignable from " + WebsockEndpoint.class.getName());
        }
    }
}

Run: mvn quarkus:dev

Test: curl --include \\n --no-buffer \\n --header "Connection: Upgrade" \\n --header "Upgrade: websocket" \\n --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \\n --header "Sec-WebSocket-Version: 13" \\n http://localhost:8080/simple

Works on: 1.11.7.Final

Fails on: 1.13.7.Final (and higher)

@gastaldi
Copy link
Contributor

/cc @stuartwdouglas

@gastaldi
Copy link
Contributor

FYI you can also fetch an instance of the configured ServerContainer by doing:

ServerContainer container = (ServerContainer) ContainerProvider.getWebSocketContainer();

But then you get an error when you try to deploy:

Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: UT003017: Cannot add endpoint after deployment
	at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:253)
	at io.quarkus.undertow.runtime.UndertowDeploymentRecorder.bootServletContainer(UndertowDeploymentRecorder.java:510)

@pjgg
Copy link
Contributor Author

pjgg commented Sep 1, 2021

So do you think that is still possible to create an endpoint programmatically(dynamically), on Quarkus 1.13.7 or higher?

2021-09-01 09:18:24,874 INFO  [io.und.web.jsr] (main) UT026005: Adding programmatic server endpoint class io.quarkus.qe.undertow.PongLeakSample$WebsockEndpoint for path /simple

@rsvoboda
Copy link
Member

rsvoboda commented Sep 6, 2021

@gastaldi / @stuartwdouglas

stuartwdouglas added a commit to stuartwdouglas/quarkus that referenced this issue Sep 6, 2021
stuartwdouglas added a commit to stuartwdouglas/quarkus that referenced this issue Sep 7, 2021
@quarkus-bot quarkus-bot bot added this to the 2.3 - main milestone Sep 7, 2021
@gsmet gsmet modified the milestones: 2.3 - main, 2.2.2.Final Sep 7, 2021
gsmet pushed a commit to gsmet/quarkus that referenced this issue Sep 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants