javafluttersocketsflutter-httpntrip

Socket / Http is working in Java Android but not in Dart / Flutter


I am using a Ntrip client wrote in Java that is making requests using Socket.

This works fine with this code :

public void run() {
        Log.d(TAG, "Run network client with server " + nServer + " and port " + nPort + " and mount point : " + nMountpoint);
        Log.d(TAG, "Running with username : " + nUsername + " password : " + nPassword);
        try {
            //Log.i(NTAG, "Creating socket");
            SocketAddress sockaddr = new InetSocketAddress(nServer, nPort);
            nsocket = new Socket();
            nsocket.connect(sockaddr, 10000); // 10 second connection timeout
            if (nsocket.isConnected()) {
                nsocket.setSoTimeout(20000); // 20 second timeout once data is flowing
                nis = nsocket.getInputStream();
                nos = nsocket.getOutputStream();
                Log.i(TAG, "Socket created, streams assigned");
                handler.sendMessage(handler.obtainMessage(MSG_NETWORK_CONNECTED, "Connected"));
                if (nProtocol.equals("ntripv1")) {
                    // Build request message
                    Log.i(TAG, "This is a NTRIP connection");
                    String requestmsg = "GET /" + nMountpoint + " HTTP/1.0\r\n";
                    requestmsg += "User-Agent: NTRIP LefebureAndroidNTRIPClient/20120614\r\n";
                    requestmsg += "Accept: */*\r\n";
                    requestmsg += "Connection: close\r\n";
                    if (nUsername.length() > 0) {
                        requestmsg += "Authorization: Basic " + ToBase64(nUsername + ":" + nPassword);
                    }
                    requestmsg += "\r\n";
                    nos.write(requestmsg.getBytes());
                    //Log.i("Request", requestmsg);
                    Log.v(TAG, requestmsg);
                } else {
                    Log.i(TAG, "This is a raw TCP/IP connection");
                }

                //Log.i(NTAG, "Waiting for inital data...");
                Log.v(TAG, "Waiting for inital data...");
                byte[] buffer = new byte[4096];
                for(int read = nis.read(buffer, 0, 4096); read != -1; read = nis.read(buffer, 0, 4096)) {
                    byte[] tempdata = new byte[read];
                    System.arraycopy(buffer, 0, tempdata, 0, read);
                    Log.v("NTripService", "Got data: " + new String(tempdata));
                    Log.v("NTripService", "Got data: " + Arrays.toString(tempdata));
                    handler.sendMessage(handler.obtainMessage(101, tempdata));
                }
            }
        } catch (SocketTimeoutException ex) {
            Log.d(TAG, "Time out : " + ex.getMessage());
            handler.sendMessage(handler.obtainMessage(MSG_NETWORK_TIMEOUT));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                nis.close();
                nos.close();
                nsocket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Log.i(TAG, "Finished");
            handler.sendMessage(handler.obtainMessage(MSG_NETWORK_FINISHED));
        }
    }

I would like to do the equivalent in Flutter without the native code, so i have tried :

Future<void> connectSocket() async {
    try {
      _socket = await Socket.connect("caster.centipede.fr", 2101, timeout: const Duration(seconds: 3));
      //_socket!.setOption(SocketOption.tcpNoDelay, true);
      _subscription = _socket!.listen(dataHandler,
          onError: errorHandler, onDone: doneHandler, cancelOnError: false);
      print("Socket connected : ${_socket!.address}");
      String requestmsg = "GET /PRSRTCM3_G4 HTTP/1.1\r\n";
      requestmsg += 'Ntrip-Version: Ntrip/2.0\r\n';
      requestmsg += "Accept: */*\r\n";
      requestmsg += 'Connection: close\r\n';
    if (_username.isNotEmpty) {
      String encoded = base64.encode(utf8.encode("$_username:$_password"));
      requestmsg += "Authorization: Basic $encoded";
      }
      requestmsg += "\r\n";
      print("Request msg : $requestmsg");
      _socket!.write(requestmsg);
      print("Socket wrote ");
    } catch (error) {
      print("Could not connect : $error");
    }
  }

  void dataHandler(data) {
    print("data : ${String.fromCharCode(data)}");
  }

  void errorHandler(error, StackTrace trace) {
    print("error : $error");
  }

  void doneHandler() {
    print("Destroying socket");
    _socket?.destroy();
    _subscription?.cancel();
  }

But this is not working, the socket is closing itself after a very short time (like 2sec) after it's connected and it has nothing to do with the message written (even without it it's closing).

I have also tried using http :

 String encoded = base64.encode(utf8.encode("$_username:$_password"));
          http.Response response =
              await http.get(Uri.parse("http://caster.centipede.fr:2101/PRSRTCM3_G4"), headers: {
            HttpHeaders.authorizationHeader: "Basic $encoded",
          });

          print(
              "Response is : ${response.statusCode} - ${response.body} - ${response.reasonPhrase}");
        } catch (error) {
          print("Error : $error");
        }

And i am getting a 403 - - Forbidden message in the logs.

Note : if i use https i have a handshake error and i have also tried the :

class DevHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) {
        print("Bad certificate for host : $host and port : $port");
        return true;
      };
  }
}

But this doesn't help either.

The same request is working fine in Java, can you tell me what am i doing wrong please ?

I would like to avoid using native code in my project and especially understand what is wrong with Flutter / dart here ?

Thanks a lot in advance

EDIT :

Here is the log i get in Flutter with the socket :

I/flutter (14751): Socket connected : InternetAddress('147.100.179.214', IPv4)
I/flutter (14751): Request msg : GET /PRSRTCM3_G4 HTTP/1.1
I/flutter (14751): Ntrip-Version: Ntrip/2.0
I/flutter (14751): Accept: */*
I/flutter (14751): Connection: close
I/flutter (14751): Authorization: Basic Y2VudGlwZWRlOmNlbnRpcGVkZQ==
I/flutter (14751): Socket wrote 
I/flutter (14751): Destroying socket

Here is the caster i am trying to reach but there is no public API.


Solution

  • Strangely, I can't get Postman to work either. But this modified version of your Dart code does. (There are 3 or 4 changes - the User Agent, the line break after the auth header, the encoding and decoding of the bytes.)

    import 'dart:convert';
    import 'dart:io';
    import 'dart:typed_data';
    
    Future<void> main() async {
      final _username = 'centipede';
      final _password = 'centipede';
    
      final _socket = await Socket.connect(
        'caster.centipede.fr',
        2101,
        timeout: const Duration(seconds: 3),
      );
      final _subscription = _socket.listen(
        dataHandler,
        onError: errorHandler,
        onDone: doneHandler,
        cancelOnError: false,
      );
      print('Socket connected : ${_socket.address}');
      var requestmsg = 'GET /PRSRTCM3_G4 HTTP/1.1\r\n';
      requestmsg += 'User-Agent: NTRIP LefebureAndroidNTRIPClient/20120614';
      requestmsg += 'Accept: */*\r\n';
      requestmsg += 'Connection: close\r\n';
      if (_username.isNotEmpty) {
        final encoded = base64.encode(utf8.encode('$_username:$_password'));
        requestmsg += 'Authorization: Basic $encoded\r\n';
      }
      requestmsg += '\r\n';
      print('Request msg : $requestmsg');
      _socket.add(utf8.encode(requestmsg));
      print('Socket wrote ');
    }
    
    void dataHandler(Uint8List data) {
      print('data : ${utf8.decode(data)}');
    }
    
    void errorHandler(error, StackTrace trace) {
      print('error : $error');
    }
    
    void doneHandler() {
      print('Destroying socket');
    }
    

    Update - I got the server to respond to Postman by changing the user agent - or the order of headers, but the response isn't HTTP!!? It starts:

    SOURCETABLE 200 OK
    Server: NTRIP BKG Caster 2.0.37/2.0
    Date: Tue, 20 Jun 2023 19:34:59 GMT
    Connection: close
    Content-Type: text/plain
    Content-Length: 95593