56chのブログ

そこそこ技術が好きな人

🌲【前編】GKEにgRPCサーバーをデプロイしFlutterクライアントからgRPCする

repo: Goro-o56/grpc-flutter-client (github.com)

まずはローカルで通信する

FlutterでProjectを作る。 (↓完成したもの)

image block

protoを書く

//example.proto

syntax = "proto3";

package example;

service StringService {
  rpc ProcessStrings (StringArray) returns (StringResult);
}

message StringArray {
  repeated string values = 1;
}

message StringResult {
  string value = 1;
}

Flutterのクライアントを作る。

pubspec.yamlにgrpcを追加

image block

コードを生成。

protoc --dart_out=grpc:lib/pkg/ -Iprotos protos/example.proto

example.dartからexportする(参照しやすくする。)

library example;

export 'pkg/example.pb.dart';
export 'pkg/example.pbenum.dart';
export 'pkg/example.pbgrpc.dart';
export 'pkg/example.pbjson.dart';

インターフェースが出来たので、それに依存する形でコードを書く。

import 'package:flutter/material.dart';
import 'package:grpc_flutter_golang/example.dart';
import 'package:grpc/grpc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter gRPC Client',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter gRPC Client'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _responseText = '';

  Future<void> _callGrpcService() async {
    final channel = ClientChannel(
      'localhost',
      port: 8080,
      options: const ChannelOptions(credentials: ChannelCredentials.insecure()),
    );

    final client = StringServiceClient(channel);

    try {
      final response = await client.processStrings(
        StringArray(values: ['Hello', 'World']),
      );

      setState(() {
        _responseText = 'Response from server: ${response.value}';
      });
    } catch (e) {
      print('Caught error: $e');
    }

    await channel.shutdown();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '$_responseText',
              style: Theme.of(context).textTheme.headline6,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _callGrpcService,
        tooltip: 'Call gRPC Service',
        child: Icon(Icons.send),
      ),
    );
  }
}

テストで起動してみて、大丈夫だったので進む。

Goでサーバーを作る。

repo: Goro-o56/go-grpcsrv (github.com)

ここからgRPCを使えるようにする。

https://grpc.io/docs/languages/go/quickstart/

go-grpcsrvプロジェクトを作る。(↓完成したもの)

image block

コードを生成する。先ほどと同様に

/protosで次のコマンドを実行

protoc --go_out=./pkg/grpc --go-grpc_out=./pkg/grpc example.proto

生成されたpkgをgo-grpcsrb配下に置く。

インターフェースに依存する形でコードを書く

package main

import (
	"context"
	"fmt"
	examplepb "go-grpcsrv/pkg/grpc"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
	"os"
	"os/signal"
	"strings"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	listener, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
	if err != nil {
		panic(err)
	}

	s := grpc.NewServer()

	examplepb.RegisterStringServiceServer(s, NewServer())
	reflection.Register(s) //grpcurl用にリフレクションする。

	go func() {
		log.Printf("start gRPC server port: %v", port)
		err := s.Serve(listener)
		if err != nil {
			log.Fatalf("Failed to serve: %v", err)
		}

	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("stopping gRPC server...")
	s.GracefulStop()

}

func NewServer() *StringServiceServer {
	//これはコンストラクタ。呼び出すことで実体が返される
	return &StringServiceServer{}
}

type StringServiceServer struct {
	examplepb.UnimplementedStringServiceServer
}

func (s *StringServiceServer) StringProcess(ctx context.Context, strArr *examplepb.StringArray) (*examplepb.StringResult, error) {
	str := strings.Join(strArr.Values, "")
	return &examplepb.StringResult{Value: str}, nil
}

コードはこの方のを参考にしました。

サーバーを起動し、grpcurlを送る(この際、.protoの存在するディレクトリからコマンドを叩く事。)

grpcurl -plaintext -d '{"values": ["aaa", "bbb"]}' -proto example.proto localhost:8080 example.StringService/ProcessStrings 

Flutterでリクエストを送信してこの画面が出る事を確認。

image block