Skip to content

Commit

Permalink
Upgrade to Spring 6 and Tyrus 2
Browse files Browse the repository at this point in the history
Resolves:
#298
  • Loading branch information
joffrey-bion committed Aug 10, 2023
1 parent a90935f commit 6182dbb
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 43 deletions.
2 changes: 2 additions & 0 deletions autobahn-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ apply<org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolverPlugin>()
val websocketEngineAttribute = Attribute.of("websocket-engine", String::class.java)

kotlin {
jvmToolchain(17) // for Spring 6

jvm("jvmBuiltin") {
attributes.attribute(websocketEngineAttribute, "builtin-jvm")
}
Expand Down
47 changes: 14 additions & 33 deletions docs/websocket/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,21 @@ Krossbow allows you to use
[Spring's `WebSocketClient`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/client/WebSocketClient.html)
as transport for STOMP.

The `krossbow-websocket-spring` module provides the `SpringWebSocketClientAdapter`, which adapts any of Spring's
`WebSocketClient` to Krossbow's web socket interface.
The `krossbow-websocket-spring` module provides the `.asKrossbowWebSocketClient()` extension, which adapts any of
Spring's `WebSocketClient` to Krossbow's web socket client interface.

On top of that, some pre-configured clients are provided:

- `SpringDefaultWebSocketClient`: based on the `StandardWebSocketClient`, which relies on the
[JSR-356](https://www.oracle.com/technical-resources/articles/java/jsr356.html) (`javax.websocket.*`) web socket standard.
- `SpringSockJSWebSocketClient`: an implementation using the SockJS protocol (requires a SockJS server), based on
the standard web socket client, with the ability to fall back to other transports (like `RestTemplateXhrTransport`).
- `SpringJettyWebSocketClient`: based on the `JettyWebSocketClient` (requires Jetty dependency, see below)
For example, use `StandardWebSocketClient().asKrossbowWebSocketClient()` to wrap Spring's standard JSR-356 client.

## Usage with StompClient

### Predefined clients

To use one of the predefined Spring clients, you need to specify it when creating your `StompClient`:

```kotlin
val stompClient = StompClient(SpringDefaultWebSocketClient)
```

Or using the default SockJS client:
To use a Spring web socket client with Krossbow's `StompClient`, adapt it to a Krossbow `WebSocketClient` using
`.asKrossbowWebSocketClient()` and pass it to the `StompClient` constructor:

```kotlin
val stompClient = StompClient(SpringSockJSWebSocketClient)
val stompClient = StompClient(StandardWebSocketClient().asKrossbowWebSocketClient())
```

### Custom SpringWebSocketClient

You can also use your own `SpringWebSocketClient` by wrapping it in a `SpringWebSocketClientAdapter`:
You can of course further customize your Spring client before adapting it to Krossbow:

```kotlin
// Pure Spring configuration
Expand All @@ -43,7 +28,7 @@ val springWsClient = StandardWebSocketClient().apply {
}

// Krossbow adapter
val stompClient = StompClient(SpringWebSocketClientAdapter(springWsClient))
val stompClient = StompClient(springWsClient.asKrossbowWebSocketClient())
```

Another example of custom client, using Spring's SockJS client:
Expand All @@ -54,10 +39,10 @@ val transports = listOf(
WebSocketTransport(StandardWebSocketClient()),
RestTemplateXhrTransport(myCustomRestTemplate),
)
val sockJsClient = SockJsClient(transports)
val springSockJsWsClient = SockJsClient(transports)

// Krossbow adapter
val stompClient = StompClient(SpringWebSocketClientAdapter(sockJsClient))
val stompClient = StompClient(springSockJsWsClient.asKrossbowWebSocketClient())
```

## Dependency information
Expand All @@ -68,15 +53,11 @@ You will need to declare the following Gradle dependency to use the Spring adapt
implementation("org.hildan.krossbow:krossbow-websocket-spring:{{ git.tag }}")
```

If you're using the `SpringDefaultWebSocketClient` (or `SpringSockJSWebSocketClient` with default settings), you'll
need to add a dependency on a JSR-356 implementation, such as Tyrus:

```kotlin
implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:{{ versions.tyrus }}")
```
It transitively depends on `spring-websocket`, so you don't need to add it yourself.

If you're using the `SpringJettyWebSocketClient`, you'll need to add a dependency on Jetty's web socket client:
**Important:** if you're using Spring's `StandardWebSocketClient`, you'll also need to add a dependency on a
JSR-356 implementation, such as the Tyrus reference implementation:

```kotlin
implementation("org.eclipse.jetty.websocket:websocket-client:{{ versions.jetty }}")
implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:{{ versions.tyrus }}")
```
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ nexus-publish-plugin = "1.3.0"
okhttp = "4.11.0"
okio = "3.5.0"
slf4j = "2.0.7"
spring = "5.3.23"
tyrus = "1.17"
spring = "6.0.11"
tyrus = "2.1.3"
uuid = "0.8.0"
vyarus-github-info-plugin = "1.5.0"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package org.hildan.krossbow.websocket.sockjs

import org.hildan.krossbow.websocket.WebSocketClient
import org.hildan.krossbow.websocket.spring.SpringSockJSWebSocketClient
import org.hildan.krossbow.websocket.spring.asKrossbowWebSocketClient
import org.springframework.web.socket.client.standard.StandardWebSocketClient
import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport
import org.springframework.web.socket.sockjs.client.SockJsClient
import org.springframework.web.socket.sockjs.client.WebSocketTransport

@Suppress("FunctionName")
actual fun SockJSClient(): WebSocketClient = SpringSockJSWebSocketClient
actual fun SockJSClient(): WebSocketClient = SockJsClient(createDefaultSockJSTransports()).asKrossbowWebSocketClient()

private fun createDefaultSockJSTransports() = listOf(
WebSocketTransport(StandardWebSocketClient()),
RestTemplateXhrTransport()
)
4 changes: 4 additions & 0 deletions krossbow-websocket-spring/api/krossbow-websocket-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ public class org/hildan/krossbow/websocket/spring/SpringWebSocketClientAdapter :
public fun connect (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class org/hildan/krossbow/websocket/spring/SpringWebSocketClientKt {
public static final fun asKrossbowWebSocketClient (Lorg/springframework/web/socket/client/WebSocketClient;)Lorg/hildan/krossbow/websocket/WebSocketClient;
}

4 changes: 4 additions & 0 deletions krossbow-websocket-spring/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ plugins {

description = "A Krossbow adapter for Spring's default WebSocket client and SockJS client"

kotlin {
jvmToolchain(17)
}

dependencies {
api(projects.krossbowWebsocketCore)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("removal") // for JettyWebSocketClient

package org.hildan.krossbow.websocket.spring

import kotlinx.coroutines.CancellationException
Expand Down Expand Up @@ -28,23 +30,72 @@ import org.hildan.krossbow.websocket.WebSocketClient as KrossbowWebSocketClient
import org.springframework.web.socket.WebSocketSession as SpringWebSocketSession
import org.springframework.web.socket.client.WebSocketClient as SpringWebSocketClient

/**
* Adapts this Spring [WebSocketClient][SpringWebSocketClient] to the Krossbow
* [WebSocketClient][KrossbowWebSocketClient] interface.
*/
@Suppress("DEPRECATION")
fun SpringWebSocketClient.asKrossbowWebSocketClient(): KrossbowWebSocketClient = SpringWebSocketClientAdapter(this)

@Deprecated(
message = "The SpringDefaultWebSocketClient object is made redundant by the public adapter extension" +
".asKrossbowWebSocketClient(), prefer using that instead.",
replaceWith = ReplaceWith(
expression = "StandardWebSocketClient().asKrossbowWebSocketClient()",
imports = [
"org.springframework.web.socket.client.standard.StandardWebSocketClient",
"org.hildan.krossbow.websocket.spring.asKrossbowWebSocketClient",
],
)
)
@Suppress("DEPRECATION")
object SpringDefaultWebSocketClient : SpringWebSocketClientAdapter(StandardWebSocketClient())

@Deprecated(
message = "The JettyWebSocketClient is deprecated for removal in Spring itself, prefer the StandardWebSocketClient.",
replaceWith = ReplaceWith(
expression = "StandardWebSocketClient().asKrossbowWebSocketClient()",
imports = [
"org.springframework.web.socket.client.standard.StandardWebSocketClient",
"org.hildan.krossbow.websocket.spring.asKrossbowWebSocketClient",
],
)
)
@Suppress("DEPRECATION")
object SpringJettyWebSocketClient : SpringWebSocketClientAdapter(JettyWebSocketClient().apply { start() })

@Deprecated(
message = "The SpringDefaultWebSocketClient object is made redundant by the public adapter extension" +
".asKrossbowWebSocketClient(), prefer using that instead.",
replaceWith = ReplaceWith(
expression = "SockJsClient(listOf(WebSocketTransport(StandardWebSocketClient()), RestTemplateXhrTransport())).asKrossbowWebSocketClient()",
imports = [
"org.springframework.web.socket.client.standard.StandardWebSocketClient",
"org.springframework.web.socket.sockjs.client.SockJsClient",
"org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport",
"org.springframework.web.socket.sockjs.client.WebSocketTransport",
"org.hildan.krossbow.websocket.spring.asKrossbowWebSocketClient",
],
)
)
@Suppress("DEPRECATION")
object SpringSockJSWebSocketClient : SpringWebSocketClientAdapter(SockJsClient(defaultWsTransports()))

private fun defaultWsTransports(): List<Transport> = listOf(
WebSocketTransport(StandardWebSocketClient()),
RestTemplateXhrTransport()
)

@Deprecated(
message = "This class is internal and will become invisible in future versions, " +
"prefer the adapter extension asKrossbowWebSocketClient().",
)
open class SpringWebSocketClientAdapter(private val client: SpringWebSocketClient) : KrossbowWebSocketClient {

override suspend fun connect(url: String): WebSocketConnectionWithPingPong {
try {
val handler = KrossbowToSpringHandlerAdapter()
val springSession = client.doHandshake(handler, url).completable().await()
val springSession = client.execute(handler, url).await()
return SpringSessionToKrossbowConnectionAdapter(springSession, handler.channelListener.incomingFrames)
} catch (e: CancellationException) {
throw e // this is an upstream exception that we don't want to wrap here
Expand Down Expand Up @@ -100,7 +151,6 @@ private class KrossbowToSpringHandlerAdapter : WebSocketHandler {
override fun supportsPartialMessages(): Boolean = true
}

@Suppress("BlockingMethodInNonBlockingContext")
private class SpringSessionToKrossbowConnectionAdapter(
private val session: SpringWebSocketSession,
override val incomingFrames: Flow<WebSocketFrame>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package org.hildan.krossbow.websocket.spring

import org.hildan.krossbow.websocket.WebSocketClient
import org.hildan.krossbow.websocket.test.WebSocketClientTestSuite
import org.springframework.web.socket.client.standard.StandardWebSocketClient

class SpringDefaultWebSocketClientTest : WebSocketClientTestSuite(supportsStatusCodes = false) {

override fun provideClient(): WebSocketClient = SpringDefaultWebSocketClient
override fun provideClient(): WebSocketClient = StandardWebSocketClient().asKrossbowWebSocketClient()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package org.hildan.krossbow.websocket.spring

import org.hildan.krossbow.websocket.WebSocketClient
import org.hildan.krossbow.websocket.test.WebSocketClientTestSuite
import org.springframework.web.socket.client.standard.StandardWebSocketClient
import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport
import org.springframework.web.socket.sockjs.client.SockJsClient
import org.springframework.web.socket.sockjs.client.WebSocketTransport
import kotlin.test.Ignore

@Ignore // requires a SockJS server
class SpringSockJSWebSocketClientTest : WebSocketClientTestSuite() {

override fun provideClient(): WebSocketClient = SpringSockJSWebSocketClient
override fun provideClient(): WebSocketClient =
SockJsClient(listOf(WebSocketTransport(StandardWebSocketClient()))).asKrossbowWebSocketClient()
}
3 changes: 1 addition & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ extra:
ktor: 2.3.1
kotlinxSerialization: 1.5.1
moshi: 1.15.0
tyrus: 1.17
jetty: 9.4.51.v20230217
tyrus: 2.1.3

copyright: Copyright &copy; 2019 - 2023 Joffrey Bion

0 comments on commit 6182dbb

Please sign in to comment.