javawebsocketquarkusjava-websocket

Quarkus WebSockets Next Intercepting HTTP GET Requests when using the @WebSocket annotation


After moving from quarkus-websocket to quarkus-websockets-next, any HTTP GET to a @WebSocket path now errors with

"Connection" header must be "Upgrade"

This is because the new extension intercepts every matching request and treats it as a WebSocket handshake.

In the old quarkus-websocket model, @ServerEndpoint only handled true WebSocket upgrades; plain GETs to the same URL would fall through to JAX-RS resources.

With quarkus-websockets-next, @WebSocket(path="/…") installs a single Vert.x handler for all HTTP methods on that path. A standard GET—lacking the required Connection: Upgrade header—gets caught and rejected before any REST logic can run.

Below is a minimal Quarkus project showing:

  1. Legacy (quarkus-websockets):

    • A REST @GET /chat endpoint
    • A JSR-356 WebSocket @ServerEndpoint("/chat")
      GET works (200 OK), WS works
  2. Next (quarkus-websockets-next):

    • Same REST resource
    • New @WebSocket(path = "/chat")
      Any GET /chat now fails with
"Connection" header must be "Upgrade"

Sample reproduction using:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-websockets</artifactId>
</dependency>

ChatResource.java:

@Path("/chat")
@ApplicationScoped
public class ChatResource {
    @GET
    public String hello() {
        return "Hello from REST!";
    }
}

ChatEndpoint.java:

@ServerEndpoint("/chat")
@ApplicationScoped
public class ChatEndpoint {
    @OnOpen
    public void onOpen(Session session) { /*...*/ }

    @OnMessage
    public void onMessage(Session session, String msg) {
        session.getAsyncRemote().sendText("Echo:" + msg);
    }
}

Behavior

GET http://localhost:8080/chat → 200 OK with “Hello from REST!”

ws://localhost:8080/chat → WebSocket handshake succeeds

With the new quarkus-websockets-next:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-websockets-next</artifactId>
</dependency>

ChatResource.java (unchanged):

@Path("/chat")
@ApplicationScoped
public class ChatResource {
    @GET
    public String hello() {
        return "Hello from REST!";
    }
}

ChatSocket.java:

@WebSocket(path = "/chat")
@ApplicationScoped
public class ChatSocket {
    @OnOpen
    public void onOpen(WebSocketConnection conn) { /*...*/ }

    @OnMessage
    public void onMessage(WebSocketConnection conn, String msg) {
        conn.sendText("Echo:" + msg).subscribe().with(r -> {}, t -> {});
    }
}

Behavior

GET http://localhost:8080/chat → fails with

"Connection" header must be "Upgrade"

ws://localhost:8080/chat → WebSocket handshake succeeds

Is this expected from the quarkus-websockets-next or is this a bug? Because I am implementing some endpoints for a standard specification where I can have the endpoints something like this:

/queries/{queryName}/events

As per the specification, it should do the following:

Returns all events that match the query or creates a new Websocket subscription.

This was working with the quarkus-websockets and now failing the GET request with quarkus-websockets-next so bit confusing if this is an issue that needs a fix.


Solution

  • It's a bug in WebSockets Next. We tried to fix this problem in https://github.com/quarkusio/quarkus/pull/43439 (Quarkus 3.15.2+) but the fix is not quite correct, in the sense that the next route is used but WS Next still attempts to upgrade the request at some point; i.e. it's a timing issue.

    The problem should be fixed in https://github.com/quarkusio/quarkus/pull/47583.

    In any case, I also checked the RFC 6455 and it states that the opening handshake must be HTTP GET with specific headers (e.g. Upgrade: websocket and Connection: Upgrade) and the server should return an HTTP response with an appropriate error code (such as 400) if the handshake violates the requiremenets. So I'm not really sure if the required behavior (ignoring non-websocket requests) is guaranteed for other implementations...