Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Lack of ability to configure connection timeouts #1598

Open
nailgilaziev opened this issue Jun 27, 2019 · 18 comments
Open

Lack of ability to configure connection timeouts #1598

nailgilaziev opened this issue Jun 27, 2019 · 18 comments

Comments

@nailgilaziev
Copy link

nailgilaziev commented Jun 27, 2019

On mobile connection quality can vary depending on the environment conditions.
if connection is bad for the moment sometimes connecting to a websocket servers can't be established now, and in this situation ConnectionTimeOut exception must thrown.
Actually this exception throws:
WebSocketChannelException: WebSocketChannelException: SocketException: OS Error: Connection timed out, errno = 110, address = 192.168.2.2, port = 56472
But it take around a 2min before happen.

This is too long for creating a realtime applications with websockets, and sometimes we need to fail faster.

Additionally, this 2 min behaviour is annoying because of issue #1610 that closed because issue #1614 and flutter/flutter#18204 created. (connecting and already connected states still not differentiable)

How this connection timeout can be configured in this package?

One year ago in issue #1611 nex3 answered me, that dart io implementation doesn't expose this API. But it is a superficial answer, because:

  1. HttpClient has this property
    https://api.dartlang.org/stable/2.4.0/dart-io/HttpClient/connectionTimeout.html

  2. if we use dart.io or html implementation directly we can use .timeout function on futures:

_wsFuture = WebSocket.connect(backendUrl)
        .timeout(Duration(seconds: 15))
        .then(_configureWsAfterConnecting);

But when using this package I can't manipulate with connection timeout. If connection doesn't establish in acceptable time I can't fail gracefully.

@MarcelGarus
Copy link

Any progress on this? 👀

@kavinda1995
Copy link

kavinda1995 commented Feb 13, 2020

What i was doing is create a native socket and wrap it in IOWebSocketChannel

Like this

WebSocket.connect(_SERVER_ADDRESS)
        .timeout(Duration(seconds: 20))
        .then((ws) {
      try {
        _channel = new IOWebSocketChannel(ws);
      } catch (e) {
        print(
            'Error happened when opening a new websocket connection. ${e.toString()}');
      }

@ziedReg
Copy link

ziedReg commented Feb 21, 2020

@kavinda1995 I got an error. The WebSocket that comes from connect is from flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart while the IOWebSocketChannel requires a WebSocket from flutter/bin/cache/pkg/sky_engine/lib/html/html_dart2js.dart.

@kavinda1995
Copy link

@zeidReg - No. You must import WebSocket from dart:io. Not from any other package

@ziedReg
Copy link

ziedReg commented Feb 21, 2020

@kavinda1995 I don't really get it. I imported it from dart:io but when i press Ctrl + click on the class name i go to the file flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart

@kavinda1995
Copy link

@ziedReg please provide your code snippet here. So we can elaborate 😃

@ziedReg
Copy link

ziedReg commented Feb 21, 2020

These are the imports

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/html.dart';
import 'package:my_project/constants/variables.dart';
import 'package:web_socket_channel/io.dart';

this is the method where i connect to the socket

void fetchImages() {
    if (channel == null) {
      // copying your code
      WebSocket.connect("ws://"+settings.host+":"+settings.port.toString()).timeout(Duration(seconds: 5)).then((ws) {
          try {
            channel = new HtmlWebSocketChannel(ws); // there is an error at "ws"
          } catch (e) {
          print('Error happened when opening a new websocket connection. ${e.toString()}');
        }
      });

      // what i'm working with right now
      channel = HtmlWebSocketChannel.connect("ws://"+settings.host+":"+settings.port.toString());

    }
    channel.stream.listen((message) async{
      bytes = base64Decode(message);
      image = Image.memory(bytes);
      await precacheImage(image.image, context);
      setState(() {});
    });
  }

The error is : The argument type 'WebSocket (where WebSocket is defined in ~/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart)' can't be assigned to the parameter type 'WebSocket (where WebSocket is defined in ~/flutter/bin/cache/pkg/sky_engine/lib/html/html_dart2js.dart)'. (argument_type_not_assignable at [my_project] lib/views/control/control.dart:44)

I am using HtmlWebSocketChannel because i'm working on web but i tried IOWebSocketChannel and got the same error

@kavinda1995
Copy link

Oh 😞. Couldn't see any wrong in this

@theLee3
Copy link

theLee3 commented Jul 17, 2020

@ziedReg I'm probably too late, but the issue here is that you are using HtmlWebSocketChannel (for web) while @kavinda1995 is using IOWebSocketChannel (for mobile). As noted, the WebSocket class in use is from the dart:io package, which cannot be used for web development. The two websocket implementations are not interchangeable.

@steeling
Copy link

So there really isn't a good way to do this, particularly since the dart:html package only offers a constructor that doesn't return a future

@sm2017
Copy link

sm2017 commented Apr 5, 2021

What i was doing is create a native socket and wrap it in IOWebSocketChannel

Like this

WebSocket.connect(_SERVER_ADDRESS)
        .timeout(Duration(seconds: 20))
        .then((ws) {
      try {
        _channel = new IOWebSocketChannel(ws);
      } catch (e) {
        print(
            'Error happened when opening a new websocket connection. ${e.toString()}');
      }

@kavinda1995 is your approach safe?
Consider the following scenario:

1- By running WebSocket.connect a web socket connection will be established but due to network issues, it can be established after 30 seconds
2- timeout(Duration(seconds: 20)) will completeError the feature with TimeoutException after 20 seconds
3- We have a reconnect logic in our code in the catch and do same thing for connection
4- By running WebSocket.connect again, a new web socket connection will be established without timeout
5- after 30 seconds, the first connection is established, So we have 2 web socked connection, and there is no way to close the first

@theLee3
Copy link

theLee3 commented Apr 12, 2021

@sm2017 is correct. This can lead to resource leaks. The timeout passed to the WebSocket is for the communication Stream, not the establishment of the WebSocket.

It seems to me that a viable solution could be to pass a connectionTimeout parameter to the WebSocket constructor which would be passed on to the HttpClient when establishing a connection. This could also trickle up to IOWebSocketChannel.connect making establishing web sockets much easier/safer. Unless there is a reason unbeknown to me why this is should not be.

Currently, I believe the safest way to handle timeouts when establishing a web socket in Dart is to use an HttpClient and manually upgrade to a WebSocket. Then you can create the IOWebSocketChannel with the WebSocket.

    final r = Random();
    final key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255)));

    final client = HttpClient();
    client
       .getUrl(Uri.parse(SERVER_ADDRESS))
       .timeout(Duration(seconds: 20))
       .then((request) {
           request.headers.add('Connection', 'upgrade');
           request.headers.add('Upgrade', 'websocket');
           request.headers.add('sec-websocket-version', '13');
           request.headers.add('sec-websocket-key', key);

           request.close().then((response) {
              response.detachSocket().then((socket) {
                  final webSocket = WebSocket.fromUpgradedSocket(socket, serverSide: false);
                  _channel = IOWebSocketChannel(webSocket);
               });
            });
         }).catchError((error) {
              // handle error
              return null;
         });

@julienduchow
Copy link

Thanks, works great! @theLee3 Any solution for Web though?

@theLee3
Copy link

theLee3 commented Apr 26, 2021

@julien100000 For the web, we can simply check the status of the WebSocket after the desired timeout period, and if the connection has not been established, close the socket and handle the timeout according to your needs.

    // WebSocket class from `html` library
    final webSocket = WebSocket(SERVER_ADDRESS);
    Future.delayed(Duration(seconds: 20), () {
      if (webSocket.readyState == WebSocket.CONNECTING) {
        webSocket.close();
        // handle the timeout here
      }
    });
    _channel = HtmlWebSocketChannel(webSocket);

@ycherniavskyi
Copy link

I think that I found not ideal but technically correct solution:

final httpClient = HttpClient();
httpClient.connectionTimeout = Duration(seconds: 20);
final webSocket = await WebSocket.connect(
  'some url',
  customClient: httpClient,
);
final channel = IOWebSocketChannel(webSocket);

@theLee3
Copy link

theLee3 commented May 18, 2022

@ycherniavskyi This is a good solution. I would also suggest setting httpClient.idleTimeout and/or webSocket.pingInterval according to your needs.

@Tienisto
Copy link

Tienisto commented Oct 9, 2023

I think that I found not ideal but technically correct solution:

final httpClient = HttpClient();
httpClient.connectionTimeout = Duration(seconds: 20);
final webSocket = await WebSocket.connect(
  'some url',
  customClient: httpClient,
);
final channel = IOWebSocketChannel(webSocket);

This is a viable workaround, but dart:io is not allowed in web

@ScorpiosCrux
Copy link

Maybe something has changed since this was originally posted, but you can set a timeout like this:

WebSocketChannel webSocket = WebSocketChannel.connect(Uri.parse(url));

await webSocket.ready.timeout(Duration(seconds: 10));

It then throws a TimeoutException

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests