diff --git a/STOMP_README.md b/STOMP_README.md index 3f874b3..3a98068 100644 --- a/STOMP_README.md +++ b/STOMP_README.md @@ -32,14 +32,70 @@ Das System bietet folgende STOMP-Funktionalitäten: ```javascript // Mit SockJS -const socket = new SockJS('http://localhost:8080/ws'); +const socket = new SockJS('http://192.168.180.196:8080/ws'); const stompClient = Stomp.over(socket); -// Oder mit nativem WebSocket -const socket = new WebSocket('ws://localhost:8080/websocket'); +// Oder mit nativem WebSocket (WICHTIG: ws:// verwenden, nicht http://) +const socket = new WebSocket('ws://192.168.180.196:8080/websocket'); const stompClient = Stomp.over(socket); ``` +### Flutter/Dart STOMP Client + +**Für Flutter-Apps verwenden Sie diese Konfiguration:** + +```dart +import 'package:stomp_dart_client/stomp.dart'; +import 'package:stomp_dart_client/stomp_config.dart'; +import 'package:stomp_dart_client/stomp_frame.dart'; +import 'dart:convert'; + +// WICHTIG: Verwenden Sie ws:// für WebSocket-Verbindungen, NICHT http:// +final stompClient = StompClient( + config: StompConfig( + url: 'ws://192.168.180.196:8080/websocket', // Beachten Sie ws:// statt http:// + onConnect: onConnectCallback, + onWebSocketError: (dynamic error) => print('WebSocket Error: $error'), + onStompError: (StompFrame frame) => print('Stomp Error: ${frame.body}'), + onDisconnect: (StompFrame frame) => print('Disconnected'), + // Heartbeat-Konfiguration + heartbeatIncoming: Duration(seconds: 20), + heartbeatOutgoing: Duration(seconds: 20), + ), +); + +void onConnectCallback(StompFrame frame) { + print('Connected to STOMP server'); + + // Nachrichten abonnieren + stompClient.subscribe( + destination: '/topic/messages', + callback: (StompFrame frame) { + print('Received message: ${frame.body}'); + }, + ); +} + +// Verbindung herstellen +stompClient.activate(); + +// Nachricht senden +void sendMessage(String content) { + stompClient.send( + destination: '/app/message', + body: jsonEncode({ + 'content': content, + 'sender': 'FlutterApp', + }), + ); +} +``` + +**Alternative Endpunkte für Flutter:** +Falls Probleme mit `/websocket` auftreten, versuchen Sie: +- `ws://192.168.180.196:8080/stomp` (zusätzlicher Endpunkt) +- `ws://192.168.180.196:8080/ws` (SockJS-Endpunkt ohne SockJS-Protokoll) + ### 2. Verbindung herstellen ```javascript diff --git a/flutter_websocket_test.html b/flutter_websocket_test.html new file mode 100644 index 0000000..7f71fcd --- /dev/null +++ b/flutter_websocket_test.html @@ -0,0 +1,159 @@ + + + + + + Flutter WebSocket Test + + + +

Flutter WebSocket STOMP Test

+
Nicht verbunden
+ + + +

+ + +

+
+ + + +

Flutter Dart Code Beispiel:

+

+import 'package:stomp_dart_client/stomp.dart';
+import 'package:stomp_dart_client/stomp_config.dart';
+import 'package:stomp_dart_client/stomp_frame.dart';
+import 'dart:convert';
+
+// RICHTIG: ws:// für WebSocket verwenden
+final stompClient = StompClient(
+  config: StompConfig(
+    url: 'ws://192.168.180.196:8080/websocket',  // ws:// NICHT http://
+    onConnect: (StompFrame frame) {
+      print('Connected to STOMP server');
+      
+      // Nachrichten abonnieren
+      stompClient.subscribe(
+        destination: '/topic/messages',
+        callback: (StompFrame frame) {
+          print('Received: ${frame.body}');
+        },
+      );
+    },
+    onWebSocketError: (dynamic error) => print('WebSocket Error: $error'),
+    onStompError: (StompFrame frame) => print('Stomp Error: ${frame.body}'),
+    onDisconnect: (StompFrame frame) => print('Disconnected'),
+  ),
+);
+
+// Verbindung aktivieren
+stompClient.activate();
+
+// Nachricht senden
+void sendMessage(String content) {
+  stompClient.send(
+    destination: '/app/message',
+    body: jsonEncode({
+      'content': content,
+      'sender': 'FlutterApp',
+    }),
+  );
+}
+    
+ +

Verfügbare Endpunkte:

+ + +

Häufiger Fehler:

+

FALSCH: http://192.168.180.196:8080/websocket

+

RICHTIG: ws://192.168.180.196:8080/websocket

+ + diff --git a/src/main/java/de/assecutor/votianlt/config/WebSocketConfig.java b/src/main/java/de/assecutor/votianlt/config/WebSocketConfig.java index c0cc8a7..2a60a3d 100644 --- a/src/main/java/de/assecutor/votianlt/config/WebSocketConfig.java +++ b/src/main/java/de/assecutor/votianlt/config/WebSocketConfig.java @@ -5,6 +5,7 @@ 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; +import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; /** * WebSocket configuration for STOMP messaging. @@ -23,15 +24,32 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // Designate the "/app" prefix for messages that are bound to methods // annotated with @MessageMapping config.setApplicationDestinationPrefixes("/app"); + + // Set user destination prefix for user-specific messages + config.setUserDestinationPrefix("/user"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { - // Register the "/ws" endpoint for WebSocket connections - // withSockJS() enables SockJS fallback options for browsers that don't support WebSocket - registry.addEndpoint("/ws").withSockJS(); + // Register the "/ws" endpoint for WebSocket connections with SockJS fallback + registry.addEndpoint("/ws") + .setAllowedOriginPatterns("*") + .addInterceptors(new HttpSessionHandshakeInterceptor()) + .withSockJS() + .setHeartbeatTime(25000) // Set heartbeat interval + .setDisconnectDelay(5000) // Set disconnect delay + .setStreamBytesLimit(128 * 1024) // Set stream bytes limit + .setHttpMessageCacheSize(1000) // Set HTTP message cache size + .setSessionCookieNeeded(false); // Disable session cookie requirement - // Also add a plain WebSocket endpoint without SockJS for native WebSocket clients - registry.addEndpoint("/websocket"); + // Plain WebSocket endpoint without SockJS for native WebSocket clients (Flutter, mobile apps) + registry.addEndpoint("/websocket") + .setAllowedOriginPatterns("*") + .addInterceptors(new HttpSessionHandshakeInterceptor()); + + // Additional endpoint specifically for mobile/Flutter clients that might have URL issues + registry.addEndpoint("/stomp") + .setAllowedOriginPatterns("*") + .addInterceptors(new HttpSessionHandshakeInterceptor()); } } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java b/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java index 8abd6c5..2be02dd 100644 --- a/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java +++ b/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java @@ -37,10 +37,26 @@ public class SecurityConfig extends VaadinWebSecurity { new AntPathRequestMatcher("/frontend/**"), new AntPathRequestMatcher("/webjars/**"), new AntPathRequestMatcher("/h2-console/**"), - new AntPathRequestMatcher("/frontend-es5/**", "/frontend-es6/**") + new AntPathRequestMatcher("/frontend-es5/**", "/frontend-es6/**"), + // WebSocket und STOMP Endpunkte + new AntPathRequestMatcher("/ws/**"), + new AntPathRequestMatcher("/websocket/**"), + new AntPathRequestMatcher("/stomp/**"), + new AntPathRequestMatcher("/app/**"), + new AntPathRequestMatcher("/topic/**"), + new AntPathRequestMatcher("/queue/**") ).permitAll() ); + // CSRF für WebSocket-Endpunkte deaktivieren + http.csrf(csrf -> csrf + .ignoringRequestMatchers( + new AntPathRequestMatcher("/ws/**"), + new AntPathRequestMatcher("/websocket/**"), + new AntPathRequestMatcher("/stomp/**") + ) + ); + // Delegiere die Basis-Konfiguration an VaadinWebSecurity // Dies fügt automatisch .anyRequest().authenticated() hinzu super.configure(http); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fd41aae..0079313 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ server.port=${PORT:8080} +server.address=0.0.0.0 logging.level.org.atmosphere=warn spring.mustache.check-template-location=false