flutterdartsocketsgrpcunix-socket

Using Flutter gRPC over Unix Sockets


I am new to Flutter and wanted to create a simple app using Flutter in the front and Go in the back over gRPC. I decided to use Unix Sockets (UDS) instead of TCP. Using UDS when both server and client are in Go works fine. However, when trying to create a gRPC client with Flutter over UDS I get the following error.

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: gRPC Err
or (code: 14, codeName: UNAVAILABLE, message: Error connecting: SocketException:
 Failed host lookup: 'unix:///tmp/test.socket' (OS Error: Name or service not kn
own, errno = -2), details: null, rawResponse: null, trailers: {})

I tried looking for similar issues. As far as I know, dart supports UDS starting from SDK 2.8. I tried looking for other functions instead of ClientChannel I even tried using file:///tmp/test.socket as input string.

Also, when changing both go server and flutter client to use TCP, it works fine.

Flutter code:

import 'package:flutter/material.dart';
import 'proto/src/service.pbgrpc.dart';
import 'package:grpc/grpc.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int result = 0;
  Sequence input = Sequence();
  SumServiceClient client = SumServiceClient(ClientChannel(
      'unix:///tmp/test.socket',
      options:
          const ChannelOptions(credentials: ChannelCredentials.insecure())));

  void _callGrpcService() async {
    input.sequence.add(10);
    var response = await client.sumSequence(input);
    setState(() {
      result = response.sum;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: Column(children: <Widget>[
          const Text(
            'gRPC example',
          ),
          Text(
            '$result',
            style: Theme.of(context).textTheme.displayMedium,
          ),
        ])),
        floatingActionButton: FloatingActionButton(
            onPressed: _callGrpcService, child: const Icon(Icons.play_arrow)));
  }
}

go server:

package main

import (
    "context"
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"

    pb "rpc/demo/proto"

    "google.golang.org/grpc"
)

type sumServer struct {
    pb.SumServiceServer
}

func (s *sumServer) SumSequence(ctx context.Context, req *pb.Sequence) (*pb.Sum, error) {
    var sum int32
    for _, element := range req.Sequence {
        sum += element
    }

    return &pb.Sum{
        Sum: sum,
    }, nil
}

const port = ":9000"

func main() {
    laddr, err := net.ResolveUnixAddr("unix", "/tmp/test.socket")
    listener, err := net.ListenUnix("unix", laddr)
    // listener, err := net.Listen("tcp", "localhost"+port)
    grpcServer := grpc.NewServer()

    signalChannel := make(chan os.Signal, 1)
    signal.Notify(signalChannel, os.Interrupt, os.Kill, syscall.SIGTERM)

    go func(c chan os.Signal) {
        sig := <-c
        log.Printf("Caught signal %s, shutting down.", sig)
        grpcServer.Stop()
        os.Exit(0)
    }(signalChannel)

    if err != nil {
        log.Fatalf("Failed to start the server %v", err)
    }

    pb.RegisterSumServiceServer(grpcServer, &sumServer{})
    log.Printf("Server started at %v", listener.Addr())
    if err := grpcServer.Serve(listener); err != nil {
        log.Fatalf("Failed to start gRPC server %v", err)
    }
}

proto file:

syntax = "proto3";

option go_package = "./proto";

package sum_service;

service SumService {
  rpc SumSequence(Sequence) returns (Sum) {}
}

message Sequence {
  repeated int32 sequence = 1;
}

message Sum {
  int32 sum = 1;
}

As far as I know, dart supports using unix sockets. Is this a problem with Flutter's socket permission? Is there a function other than ClientChannel for connecting to UDS? Or am I missing a UDS initialization function in flutter?


Solution

  • Just as I suspected. We need to translate the unix socket to an InternetAddress. Use

    final InternetAddress host = InternetAddress('tmp/test.socket', type:InternetAddressType.unix);
    

    and use host inside ClientChannel without a port.

    Or plug it in directly:

      SumServiceClient client = SumServiceClient(ClientChannel(
          InternetAddress('/tmp/test.socket', type: InternetAddressType.unix),
          options:
              const ChannelOptions(credentials: ChannelCredentials.insecure())));