forked from quarkusio/quarkus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WebSockets Next: make it possible to store user data in a connection
- resolves quarkusio#43772
- Loading branch information
Showing
8 changed files
with
396 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
...ment/src/test/java/io/quarkus/websockets/next/test/connection/ConnectionUserDataTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package io.quarkus.websockets.next.test.connection; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
||
import java.net.URI; | ||
import java.util.List; | ||
|
||
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.OnTextMessage; | ||
import io.quarkus.websockets.next.OpenConnections; | ||
import io.quarkus.websockets.next.UserData.TypedKey; | ||
import io.quarkus.websockets.next.WebSocket; | ||
import io.quarkus.websockets.next.WebSocketConnection; | ||
import io.quarkus.websockets.next.test.utils.WSClient; | ||
import io.vertx.core.Vertx; | ||
|
||
public class ConnectionUserDataTest { | ||
|
||
@RegisterExtension | ||
public static final QuarkusUnitTest test = new QuarkusUnitTest() | ||
.withApplicationRoot(root -> { | ||
root.addClasses(MyEndpoint.class, WSClient.class); | ||
}); | ||
|
||
@Inject | ||
Vertx vertx; | ||
|
||
@TestHTTPResource("/end") | ||
URI baseUri; | ||
|
||
@Inject | ||
OpenConnections connections; | ||
|
||
@Test | ||
void testConnectionData() { | ||
try (WSClient client = WSClient.create(vertx).connect(baseUri)) { | ||
assertEquals("5", client.sendAndAwaitReply("bar").toString()); | ||
assertNotNull(connections.stream().filter(c -> c.userData().get(TypedKey.forString("username")) != null).findFirst() | ||
.orElse(null)); | ||
assertEquals("FOOMartin", client.sendAndAwaitReply("foo").toString()); | ||
assertEquals("0", client.sendAndAwaitReply("bar").toString()); | ||
} | ||
} | ||
|
||
@WebSocket(path = "/end") | ||
public static class MyEndpoint { | ||
|
||
@OnOpen | ||
void onOpen(WebSocketConnection connection) { | ||
connection.userData().put(TypedKey.forInt("baz"), 5); | ||
connection.userData().put(TypedKey.forLong("foo"), 42l); | ||
connection.userData().put(TypedKey.forString("username"), "Martin"); | ||
connection.userData().put(TypedKey.forBoolean("isActive"), true); | ||
connection.userData().put(new TypedKey<List<String>>("list"), List.of()); | ||
} | ||
|
||
@OnTextMessage | ||
public String onMessage(String message, WebSocketConnection connection) { | ||
if ("bar".equals(message)) { | ||
return connection.userData().size() + ""; | ||
} | ||
try { | ||
connection.userData().get(TypedKey.forString("foo")).toString(); | ||
throw new IllegalStateException(); | ||
} catch (ClassCastException expected) { | ||
} | ||
if (!connection.userData().get(TypedKey.forBoolean("isActive")) | ||
|| !connection.userData().get(new TypedKey<List<String>>("list")).isEmpty()) { | ||
return "NOK"; | ||
} | ||
if (connection.userData().remove(TypedKey.forLong("foo")) != 42l) { | ||
throw new IllegalStateException(); | ||
} | ||
if (connection.userData().remove(TypedKey.forInt("baz")) != 5) { | ||
throw new IllegalStateException(); | ||
} | ||
String ret = message.toUpperCase() + connection.userData().get(TypedKey.forString("username")); | ||
connection.userData().clear(); | ||
return ret; | ||
} | ||
|
||
} | ||
|
||
} |
103 changes: 103 additions & 0 deletions
103
extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/Connection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package io.quarkus.websockets.next; | ||
|
||
import java.time.Instant; | ||
|
||
import io.smallrye.common.annotation.CheckReturnValue; | ||
import io.smallrye.mutiny.Uni; | ||
|
||
/** | ||
* WebSocket connection. | ||
* | ||
* @see WebSocketConnection | ||
* @see WebSocketClientConnection | ||
*/ | ||
public interface Connection extends BlockingSender { | ||
|
||
/** | ||
* | ||
* @return the unique identifier assigned to this connection | ||
*/ | ||
String id(); | ||
|
||
/** | ||
* | ||
* @param name | ||
* @return the value of the path parameter or {@code null} | ||
* @see WebSocketClient#path() | ||
*/ | ||
String pathParam(String name); | ||
|
||
/** | ||
* @return {@code true} if the HTTP connection is encrypted via SSL/TLS | ||
*/ | ||
boolean isSecure(); | ||
|
||
/** | ||
* @return {@code true} if the WebSocket is closed | ||
*/ | ||
boolean isClosed(); | ||
|
||
/** | ||
* | ||
* @return the close reason or {@code null} if the connection is not closed | ||
*/ | ||
CloseReason closeReason(); | ||
|
||
/** | ||
* | ||
* @return {@code true} if the WebSocket is open | ||
*/ | ||
default boolean isOpen() { | ||
return !isClosed(); | ||
} | ||
|
||
/** | ||
* Close the connection. | ||
* | ||
* @return a new {@link Uni} with a {@code null} item | ||
*/ | ||
@CheckReturnValue | ||
default Uni<Void> close() { | ||
return close(CloseReason.NORMAL); | ||
} | ||
|
||
/** | ||
* Close the connection with a specific reason. | ||
* | ||
* @param reason | ||
* @return a new {@link Uni} with a {@code null} item | ||
*/ | ||
Uni<Void> close(CloseReason reason); | ||
|
||
/** | ||
* Close the connection and wait for the completion. | ||
*/ | ||
default void closeAndAwait() { | ||
close().await().indefinitely(); | ||
} | ||
|
||
/** | ||
* Close the connection with a specific reason and wait for the completion. | ||
*/ | ||
default void closeAndAwait(CloseReason reason) { | ||
close(reason).await().indefinitely(); | ||
} | ||
|
||
/** | ||
* | ||
* @return the handshake request | ||
*/ | ||
HandshakeRequest handshakeRequest(); | ||
|
||
/** | ||
* | ||
* @return the time when this connection was created | ||
*/ | ||
Instant creationTime(); | ||
|
||
/** | ||
* | ||
* @return the user data associated with this connection | ||
*/ | ||
UserData userData(); | ||
} |
59 changes: 59 additions & 0 deletions
59
extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/UserData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package io.quarkus.websockets.next; | ||
|
||
/** | ||
* Mutable user data associated with a connection. Implementations must be thread-safe. | ||
*/ | ||
public interface UserData { | ||
|
||
/** | ||
* | ||
* @param <VALUE> | ||
* @param key | ||
* @return the value or {@code null} if no mapping is found | ||
*/ | ||
<VALUE> VALUE get(TypedKey<VALUE> key); | ||
|
||
/** | ||
* Associates the specified value with the specified key. An old value is replaced by the specified value. | ||
* | ||
* @param <ConnectionData.VALUE> | ||
* @param key | ||
* @param value | ||
* @return the previous value associated with {@code key}, or {@code null} if no mapping exists | ||
*/ | ||
<VALUE> VALUE put(TypedKey<VALUE> key, VALUE value); | ||
|
||
/** | ||
* | ||
* @param <VALUE> | ||
* @param key | ||
*/ | ||
<VALUE> VALUE remove(TypedKey<VALUE> key); | ||
|
||
int size(); | ||
|
||
void clear(); | ||
|
||
/** | ||
* @param <TYPE> The type this key is used for. | ||
*/ | ||
record TypedKey<TYPE>(String value) { | ||
|
||
public static TypedKey<Integer> forInt(String key) { | ||
return new TypedKey<>(key); | ||
} | ||
|
||
public static TypedKey<Long> forLong(String key) { | ||
return new TypedKey<>(key); | ||
} | ||
|
||
public static TypedKey<String> forString(String key) { | ||
return new TypedKey<>(key); | ||
} | ||
|
||
public static TypedKey<Boolean> forBoolean(String key) { | ||
return new TypedKey<>(key); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.