带有 Spring-boot 后端的 Flutter websocket

Posted

技术标签:

【中文标题】带有 Spring-boot 后端的 Flutter websocket【英文标题】:Flutter websocket with Spring-boot backend 【发布时间】:2019-11-16 03:11:15 【问题描述】:

好的,Flutter 在食谱中有 WebSocket 配方 (here)。这对 websocket.org 测试服务器非常有效。

问题是我想连接我自己的 WebSocket 服务器。所以我首先使用了 SpringBoot 中的this tutorial。

尝试从应用程序(我在这里使用模拟器)向 Spring Boot 后端发出请求无效。然后我开始修补并从 Spring Boot 后端删除 STOMP,并留下一个简单的 WebSocket 传递字符串。它在使用邮递员甚至网页时有效,但在应用程序中无效

当前状态在这个 GitHub 上(包括 spring boot 和 flutter 项目):https://github.com/Flavsditz/websocket_sandbox

这里有人有什么建议吗?

我很感激!

【问题讨论】:

【参考方案1】:

经过一番考虑,我发现了问题:

问题是我的spring-boot服务器在localhost,但是flutter(也是android模拟器)有自己的环回服务。 所以在 Flutter 程序中调用 localhost 指的是另一个地方,而不是我想要的地方。

我已将 localhost 替换为 ip 10.0.2.2,这是为帮助开发而设置的主机 PC 的别名。

欲了解更多信息,请查看此答案:here

当然,如果您想在真实设备上进行测试,则需要为外部发布后端,所以这个答案可能会更好:here

【讨论】:

【参考方案2】:

flutter-websocket_test/lib/message_page.dart 中,第 6-7 行有以下内容:

  final WebSocketChannel channel = IOWebSocketChannel.connect(
    Uri(scheme: "ws", host: "locahost", port: 8080, path: "/socket"),

您使用的是locahost 而不是localhost,因此请尝试更改它,看看它是否有效。

【讨论】:

感谢您指出这一点,但这确实只是我准备示例时的一个错字。【参考方案3】:

感谢您的解决方案,如果您想在真实设备中进行测试,请补充说明,

    真实设备和电脑都必须在同一个网络中。 (在我的情况下,我使用从手机到电脑的热点)

    使用 cmd 从电脑获取 IP,输入 ipconfig 获取 IP。 (在我的情况下是IPv4 Address. . . . . . . . . . . : 192.168.43.423

    现在粘贴您的 IP 而不是 localhost 例如。

    IOWebSocketChannel.connect(Uri(scheme: "ws",host: "192.168.43.423",port: 8080,path: "/socket")) 谢谢

【讨论】:

【参考方案4】:

对于那些使用

stomp_dart_client: ^0.3.7

用sockjs,记得把token传给header

initClient() async 
    try 

      if (_stompClient != null && _stompClient.connected) 
        return;
      

      SharedPreferences _prefs = await SharedPreferences.getInstance();
      String token = _prefs.getString('access_token');
      User currentUser = User.fromPrefJson(jsonDecode(_prefs.get('current_user')));
      phone = currentUser.phone;
      if (token != null) 
        String requestUrl = '$baseUrl/websocket/tracker?access_token=$token'; // please note <<<<<
        StompClient stompClient = StompClient(
            config: StompConfig.SockJS(
                url: requestUrl,
                stompConnectHeaders: 
                  'Authorization' : 'Bearer $token', // please note <<<<<
                ,
                webSocketConnectHeaders: 
                  'Authorization' : 'Bearer $token', // please note <<<<<
                ,
                onStompError: (StompFrame frame) 
                  print('A stomp error occurred in web socket connection :: $frame.body');
                ,
                onWebSocketError: (dynamic frame) 
                  print('A Web socket error occurred in web socket connection :: $frame.toString()');
                ,
                onDebugMessage: (dynamic frame) 
                  print('A debug error occurred in web socket connection :: $frame.toString()');
                ,
                onConnect: (StompClient client, StompFrame connectFrame) 
                  print('$client.toString() connected with the following frames $connectFrame.body');
                  _stompClient = client;

                  clientUnSubscribeFn = _stompClient.subscribe(
                      destination: '/topic/client',  headers: ,
                      callback: (frame) 
                        // Received a frame for this subscription
                        print(frame.body);
                        clientController.add(frame.body);
                      
                  );
                
            )
        );
        stompClient.activate();
      
     catch(e) 
      print('An error occurred $e.toString()');
    
  


  sendClientMessage(String msg) async 
      if (_stompClient != null && _stompClient.connected) 
      _stompClient.send(
         destination: '/topic/client',
         body: msg,
        headers: 
     );
  
  

别忘了更新 Spring 安全配置和 web socket 配置

@Configuration
public class WebsocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer 

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) 
        messages
            .nullDestMatcher().authenticated()
            .simpDestMatchers("/topic/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
           .simpSubscribeDestMatchers("/topic/**").authenticated()
            .simpDestMatchers("/topic/**").authenticated()
            // message types other than MESSAGE and SUBSCRIBE
            .simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
            // catch all
            .anyMessage().denyAll();
    

    /**
     * Disables CSRF for Websockets.
     */
    @Override
    protected boolean sameOriginDisabled() 
        return true;
    



// spring security configs for http

@Override
    public void configure(HttpSecurity http) throws Exception 
        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
        .and()
            .headers()
            .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
        .and()
            .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
        .and()
            .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
        .and()
            .frameOptions()
            .deny()
        .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/websocket/tracker").hasAnyAuthority(
                AuthoritiesConstants.ADMIN, AuthoritiesConstants.MANAGER, AuthoritiesConstants.STAFF,
                AuthoritiesConstants.CLIENT, AuthoritiesConstants.DRIVER
            )
            .antMatchers("/websocket/**").permitAll()
            .httpBasic()
        .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    

干杯

【讨论】:

以上是关于带有 Spring-boot 后端的 Flutter websocket的主要内容,如果未能解决你的问题,请参考以下文章

spring-boot:数据库中断后jdbc重新连接

flutte的第一个hello world程序

带有 Kafka 后端的 NodeJS API

带有 TensorFlow 后端的 Keras 不使用 GPU

带有 C# 后端的电子 GUI

Spring Cloud Vault 和带有 Vault 后端的 Spring Cloud Config 之间的区别