Skip to content

Commit

Permalink
WebSockets Next: enable configuration of supported subprotocols
Browse files Browse the repository at this point in the history
- add WebSocketConnection#subprotocol() that can be used to obtain the
subprotocol selected by the handshake
- the values defined with quarkus.websockets-next.supported-subprotocols
contribute to the set of subprotocols passed to the HTTP server
configuration
- also add constants for handshake headers defined by the RFC
- resolves quarkusio#39465
  • Loading branch information
mkouba committed Mar 21, 2024
1 parent a949acb commit 548a49c
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import io.quarkus.websockets.next.runtime.JsonTextMessageCodec;
import io.quarkus.websockets.next.runtime.WebSocketEndpoint.ExecutionModel;
import io.quarkus.websockets.next.runtime.WebSocketEndpointBase;
import io.quarkus.websockets.next.runtime.WebSocketHttpServerOptionsCustomizer;
import io.quarkus.websockets.next.runtime.WebSocketServerRecorder;
import io.quarkus.websockets.next.runtime.WebSocketSessionContext;
import io.smallrye.mutiny.Multi;
Expand Down Expand Up @@ -200,7 +201,9 @@ public void registerRoutes(WebSocketServerRecorder recorder, HttpRootPathBuildIt
@BuildStep
AdditionalBeanBuildItem additionalBeans() {
return AdditionalBeanBuildItem.builder().setUnremovable()
.addBeanClasses(Codecs.class, JsonTextMessageCodec.class, ConnectionManager.class).build();
.addBeanClasses(Codecs.class, JsonTextMessageCodec.class, ConnectionManager.class,
WebSocketHttpServerOptionsCustomizer.class)
.build();
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkus.websockets.next.test.subprotocol;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.URI;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicBoolean;

import jakarta.inject.Inject;

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

import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.test.utils.WSClient;
import io.vertx.core.Vertx;
import io.vertx.core.http.WebSocketConnectOptions;

public class SubprotocolNotAvailableTest {

@RegisterExtension
public static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot(root -> {
root.addClasses(Endpoint.class, WSClient.class);
});

@Inject
Vertx vertx;

@TestHTTPResource("endpoint")
URI endUri;

@Test
void testConnectionRejected() {
CompletionException e = assertThrows(CompletionException.class,
() -> new WSClient(vertx).connect(new WebSocketConnectOptions().addSubProtocol("oak"), endUri));
Throwable cause = e.getCause();
assertTrue(cause instanceof WebSocketClientHandshakeException);
assertFalse(Endpoint.OPEN_CALLED.get());
}

@WebSocket(path = "/endpoint")
public static class Endpoint {

static final AtomicBoolean OPEN_CALLED = new AtomicBoolean();

@OnOpen
void open() {
OPEN_CALLED.set(true);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.quarkus.websockets.next.test.subprotocol;

import static io.quarkus.websockets.next.WebSocketConnection.HandshakeRequest.SEC_WEBSOCKET_PROTOCOL;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.net.URI;
import java.util.concurrent.ExecutionException;

import jakarta.inject.Inject;

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

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.WebSocketConnection;
import io.quarkus.websockets.next.test.utils.WSClient;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Vertx;
import io.vertx.core.http.WebSocketConnectOptions;

public class SubprotocolSelectedTest {

@RegisterExtension
public static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot(root -> {
root.addClasses(Endpoint.class, WSClient.class);
}).overrideConfigKey("quarkus.websockets-next.supported-subprotocols", "oak,larch");

@Inject
Vertx vertx;

@TestHTTPResource("endpoint")
URI endUri;

@Test
void testSubprotocol() throws InterruptedException, ExecutionException {
WSClient client = new WSClient(vertx).connect(new WebSocketConnectOptions().addSubProtocol("oak"), endUri);
assertEquals("ok", client.waitForNextMessage().toString());
}

@WebSocket(path = "/endpoint")
public static class Endpoint {

@Inject
WebSocketConnection connection;

@OnOpen
Uni<Void> open() {
if (connection.handshakeRequest().header(SEC_WEBSOCKET_PROTOCOL) == null) {
return connection.sendText("Sec-WebSocket-Protocol not set: " + connection.handshakeRequest().headers());
} else if ("oak".equals(connection.subprotocol())) {
return connection.sendText("ok");
} else {
return connection.sendText("Invalid protocol: " + connection.subprotocol());
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ default void closeAndAwait() {
*/
HandshakeRequest handshakeRequest();

/**
*
* @return the subprotocol selected by the handshake
*/
String subprotocol();

/**
*
* @return the time when this connection was created
Expand Down Expand Up @@ -172,6 +178,31 @@ interface HandshakeRequest {
*/
String query();

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-57">The WebSocket Protocol</a>.
*/
public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-58">The WebSocket Protocol</a>.
*/
public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-58">The WebSocket Protocol</a>.
*/
public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-59">The WebSocket Protocol</a>.
*/
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-60">The WebSocket Protocol</a>.
*/
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.websockets.next;

import java.time.Duration;
import java.util.List;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigPhase;
Expand All @@ -11,6 +12,13 @@
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
public interface WebSocketsRuntimeConfig {

/**
* See <a href="https://datatracker.ietf.org/doc/html/rfc6455#page-12">The WebSocket Protocol</a>
*
* @return the supported subprotocols
*/
Optional<List<String>> supportedSubprotocols();

/**
* TODO Not implemented yet.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ public HandshakeRequest handshakeRequest() {
return handshakeRequest;
}

@Override
public String subprotocol() {
return webSocket.subProtocol();
}

@Override
public Instant creationTime() {
return creationTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.websockets.next.runtime;

import java.util.List;

import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;

import io.quarkus.vertx.http.HttpServerOptionsCustomizer;
import io.quarkus.websockets.next.WebSocketsRuntimeConfig;
import io.vertx.core.http.HttpServerOptions;

@Dependent
public class WebSocketHttpServerOptionsCustomizer implements HttpServerOptionsCustomizer {

@Inject
WebSocketsRuntimeConfig config;

@Override
public void customizeHttpServer(HttpServerOptions options) {
config.supportedSubprotocols().orElse(List.of()).forEach(options::addWebSocketSubProtocol);
}

@Override
public void customizeHttpsServer(HttpServerOptions options) {
config.supportedSubprotocols().orElse(List.of()).forEach(options::addWebSocketSubProtocol);
}

}

0 comments on commit 548a49c

Please sign in to comment.