-1

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.

5
  • Define "the socket is closing." Sockets don't just close themselves. What symptoms are you observing that led you to that conclusion? And 403 - Forbidden doesn't have anything to do with sockets closing themselves. It is an authorization problem. Commented Jun 20, 2023 at 10:46
  • Regarding the 403 error, this may be caused by the server being case-sensitive to header names (incorrectly). Try with Postman and try naming your header Authorization and authorization and see if you get different results. Which, if any, of the print messages get printed in your socket code? Since this is HTTP, use a packet sniffer like wireshark to see who closes the connection - it may be the server. Commented Jun 20, 2023 at 12:24
  • @user207421 The onDone method is called automatically after the little timeout. I completely agree the 403 - Forbidden has nothing to do, but this is the error i am getting with the above code that's why i am so confused and asked a question because it makes no sense. Commented Jun 20, 2023 at 12:33
  • The authorization header, with the first letter upper or lower case doesn't change the forbidden error unfortunately (i have also tested from Postman and get the forbidden). The username and password are actually public and both equal to centipede, feel free to try it out but the server allows only one connection per IP, which is my case. I have updated the question Commented Jun 20, 2023 at 13:07
  • Try changing requestmsg += "Authorization: Basic $encoded"; to requestmsg += "Authorization: Basic $encoded\r\n"; Commented Jun 20, 2023 at 14:04

2 Answers 2

1

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
Sign up to request clarification or add additional context in comments.

3 Comments

Hey @Richard it is perfect this is the expected response thank you so much for your time. Yes this is a weird system but i am working on it for a client.. Really appreciate your help
Consider simply adding the bytes received in dataHandler into a bytes buffer api.dart.dev/stable/2.5.2/dart-io/BytesBuilder-class.html When the whole content has been received you can convert it to a string and begin parsing it. FYI, the server seems to have some test that implements userAgent.startsWithIgnoreCase('NTRIP') and rejects the request otherwise(!)
Thank you ! It's indeed what is done in Java, i will do the equivalent in Flutter but thanks for pointing out the BytesBuilder.
0

enter image description hereIt looks like you are requesting a mountpoint. via socket But not the connection to receive RTCM. When you want to get RTCM data too, you have to send GGA to ntrip. Here is an example I made app in flutter.enter image description here

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.