From b84738f5a5130b5adbc0d14ad2c683a9bbfb4bdb Mon Sep 17 00:00:00 2001 From: Jan Ullrich Date: Fri, 2 Feb 2024 17:47:59 +0000 Subject: [PATCH] WIP: websocket communication --- services/streak/build.gradle.kts | 10 +-- .../codecentric/habitcentric/Application.kt | 2 + .../streak/messaging/WebSocketConfig.kt | 34 +++++++++ .../streak/messaging/WebSocketController.kt | 32 +++++++++ .../src/main/resources/application.yaml | 6 +- services/ui/package-lock.json | 11 +++ services/ui/package.json | 1 + services/ui/src/App.tsx | 2 + services/ui/src/Socket.tsx | 70 +++++++++++++++++++ services/ui/src/index.tsx | 10 +-- 10 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketConfig.kt create mode 100644 services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketController.kt create mode 100644 services/ui/src/Socket.tsx diff --git a/services/streak/build.gradle.kts b/services/streak/build.gradle.kts index 3124ec2b..4e0b3a9c 100644 --- a/services/streak/build.gradle.kts +++ b/services/streak/build.gradle.kts @@ -64,10 +64,12 @@ extra["springModulithVersion"] = "1.1.0" dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") - implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") - implementation("org.springframework.boot:spring-boot-starter-security") +// implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") +// implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-websocket") +// implementation("org.springframework.security:spring-security-messaging") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.flywaydb:flyway-core") implementation("org.jetbrains.kotlin:kotlin-reflect") @@ -83,7 +85,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.kafka:spring-kafka-test") testImplementation("org.springframework.modulith:spring-modulith-starter-test") - testImplementation("org.springframework.security:spring-security-test") +// testImplementation("org.springframework.security:spring-security-test") testImplementation("io.kotest:kotest-assertions-core:5.8.0") testImplementation("org.springframework.boot:spring-boot-testcontainers") testImplementation("org.testcontainers:junit-jupiter") @@ -92,7 +94,7 @@ dependencies { intTestImplementation("org.springframework.kafka:spring-kafka-test") intTestImplementation("org.springframework.modulith:spring-modulith-starter-test") intTestImplementation("org.springframework.boot:spring-boot-starter-test") - intTestImplementation("org.springframework.security:spring-security-test") +// intTestImplementation("org.springframework.security:spring-security-test") intTestImplementation("org.springframework.boot:spring-boot-testcontainers") intTestImplementation("org.testcontainers:junit-jupiter") intTestImplementation("org.testcontainers:kafka") diff --git a/services/streak/src/main/kotlin/de/codecentric/habitcentric/Application.kt b/services/streak/src/main/kotlin/de/codecentric/habitcentric/Application.kt index f3eb120e..c2883270 100644 --- a/services/streak/src/main/kotlin/de/codecentric/habitcentric/Application.kt +++ b/services/streak/src/main/kotlin/de/codecentric/habitcentric/Application.kt @@ -2,8 +2,10 @@ package de.codecentric.habitcentric import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication +@EnableScheduling class StreakApplication fun main(args: Array) { diff --git a/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketConfig.kt b/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketConfig.kt new file mode 100644 index 00000000..691e386a --- /dev/null +++ b/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketConfig.kt @@ -0,0 +1,34 @@ +package de.codecentric.habitcentric.streak.messaging + +import org.springframework.context.annotation.Configuration +import org.springframework.messaging.simp.config.MessageBrokerRegistry +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker +import org.springframework.web.socket.config.annotation.StompEndpointRegistry +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer + +@Configuration +@EnableWebSocketMessageBroker +//@EnableWebSocketSecurity +class WebSocketConfig : WebSocketMessageBrokerConfigurer { + override fun registerStompEndpoints(registry: StompEndpointRegistry) { + // HTTP URL for WebSocket connection + registry.addEndpoint("/streak-socket").setAllowedOrigins("*") + } + + override fun configureMessageBroker(registry: MessageBrokerRegistry) { + // register queue prefix meant for the application + registry.setApplicationDestinationPrefixes("/app") + + // Configure spring internal broker to use /topic and /queue as prefix + // There is no special meaning for the broker, it is more like a convention to differentiate + // /topic as pub-sub and /queue as point to point messaging + registry.enableSimpleBroker("/topic", "/queue") + } + +// @Bean +// fun configSecurity(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager> { +// messages.simpDestMatchers("/app").permitAll() +// +// return messages.build() +// } +} diff --git a/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketController.kt b/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketController.kt new file mode 100644 index 00000000..559a204d --- /dev/null +++ b/services/streak/src/main/kotlin/de/codecentric/habitcentric/streak/messaging/WebSocketController.kt @@ -0,0 +1,32 @@ +package de.codecentric.habitcentric.streak.messaging + +import org.springframework.messaging.Message +import org.springframework.messaging.handler.annotation.MessageMapping +import org.springframework.messaging.handler.annotation.SendTo +import org.springframework.messaging.simp.SimpMessagingTemplate +import org.springframework.messaging.simp.annotation.SubscribeMapping +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Controller +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@Controller +class WebSocketController(val simpMessagingTemplate: SimpMessagingTemplate) { + + @MessageMapping("/helloApp") + @SendTo("/topic/hello") + fun appMessage(message: Message): String { + return message.payload + } + + @SubscribeMapping("/ticker") + fun sub(): String { + return LocalDateTime.now().format(DateTimeFormatter.ISO_TIME); + } + + @Scheduled(fixedDelay = 2000) + fun emitTime() { + val time = LocalDateTime.now().format(DateTimeFormatter.ISO_TIME) + simpMessagingTemplate.convertAndSend("/topic/ticker", time); + } +} diff --git a/services/streak/src/main/resources/application.yaml b/services/streak/src/main/resources/application.yaml index 600dcea8..cd72bc10 100644 --- a/services/streak/src/main/resources/application.yaml +++ b/services/streak/src/main/resources/application.yaml @@ -4,7 +4,7 @@ spring: datasource: driver-class-name: org.postgresql.Driver - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:10001}/${DB_NAME:postgres} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:10005}/${DB_NAME:postgres} username: ${DB_USER:postgres} password: ${DB_PASSWORD:postgres} @@ -32,5 +32,9 @@ spring: group-id: streak value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer +management: + tracing: + enabled: false + server: port: 9005 diff --git a/services/ui/package-lock.json b/services/ui/package-lock.json index 27950781..591fb03c 100644 --- a/services/ui/package-lock.json +++ b/services/ui/package-lock.json @@ -12,6 +12,7 @@ "@heroicons/react": "1.0.6", "@icons-pack/react-simple-icons": "9.3.0", "@popperjs/core": "2.11.8", + "@stomp/stompjs": "^7.0.0", "@tailwindcss/forms": "0.5.7", "@types/node": "20.11.16", "@types/react": "18.2.52", @@ -1509,6 +1510,11 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@stomp/stompjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz", + "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -7901,6 +7907,11 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "@stomp/stompjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz", + "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==" + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", diff --git a/services/ui/package.json b/services/ui/package.json index 515ad609..ae4d705b 100644 --- a/services/ui/package.json +++ b/services/ui/package.json @@ -8,6 +8,7 @@ "@heroicons/react": "1.0.6", "@icons-pack/react-simple-icons": "9.3.0", "@popperjs/core": "2.11.8", + "@stomp/stompjs": "^7.0.0", "@tailwindcss/forms": "0.5.7", "@types/node": "20.11.16", "@types/react": "18.2.52", diff --git a/services/ui/src/App.tsx b/services/ui/src/App.tsx index 03eef9d8..f28eae68 100644 --- a/services/ui/src/App.tsx +++ b/services/ui/src/App.tsx @@ -7,6 +7,7 @@ import { AuthProvider } from "react-oidc-context"; import { oidc } from "./auth/config/oidc"; import { Route, Routes } from "react-router-dom"; import RequireAuthWhenOidcIsEnabled from "./auth/RequireAuthWhenOidcIsEnabled"; +import Socket from "./Socket"; function App() { return ( @@ -16,6 +17,7 @@ function App() { } /> + } /> { + // let accessToken = auth.user?.access_token || ""; + // console.log(auth); + // console.log("Token", accessToken); + + const client = new Client({ + brokerURL: "ws://localhost:9005/streak-socket", + // connectHeaders: { + // Authentication: accessToken, + // }, + onWebSocketError: (e) => { + console.log("ws error:", e); + }, + onWebSocketClose: (e) => { + console.log("ws close:", e); + }, + debug: (str) => { + console.log("debug:", str); + }, + onConnect: () => { + client.subscribe("/app/ticker", (message) => + console.log(`Received app ticker: ${message.body}`) + ); + client.subscribe("/topic/ticker", (message) => + console.log(`Received ticker: ${message.body}`) + ); + client.subscribe("/topic/hello", (message) => + console.log(`Received hello: ${message.body}`) + ); + + client.publish({ + destination: "/app/helloApp", + body: "Hi from stomp!", + }); + + // client.publish({ destination: "/topic/test01", body: "First Message" }); + }, + onStompError: (frame) => { + console.log("Broker reported error: " + frame.headers["message"]); + console.log("Additional details: " + frame.body); + }, + onChangeState: (s) => { + ActivationState; + console.log("connection state: ", s); + }, + }); + client.activate(); + setWsOutput("hi!"); + + // client.subscribe("/topic", (message) => { + // setWsOutput(message.body); + // }); + // + return () => { + console.trace(); + console.log("destroy?"); + client.deactivate(); + }; + }, []); + + return
{wsOutput}
; +} + +export default Socket; diff --git a/services/ui/src/index.tsx b/services/ui/src/index.tsx index 210c601b..639ae856 100644 --- a/services/ui/src/index.tsx +++ b/services/ui/src/index.tsx @@ -20,11 +20,11 @@ enableMsw().then(() => { const container = document.getElementById("root"); const root = createRoot(container!); root.render( - - - - - + // + + + + // ); });