gRPCを使ってgoのサーバーにSwiftでアクセスする処理を実装しました。
Swiftは5.2、Go言語はgo1.14.4、protobufは3.12.3を使っています。
gRPCとは
gRPCとはGoogleが開発したRPCを実現するためのプロトコルのことです。
RPCはremote procedure callの略で、別のサーバーのメソッドなどを呼び出すことができる技術です。
要するにgRPCを使えば今までRESTなどでAPI通信した箇所を置き換えることができます。
今回はSwiftからgRPCを使ってサーバーにアクセスしてみたいと思います。
Go言語でgRPCサーバーを作る
サーバー立ち上げは下サイトを参考にさせて頂きました。
https://tech.smartcamp.co.jp/entry/2019/03/28/175137
Go言語とprotobufが入っていない場合はbrewでインストールします。
protobufは
brew install go brew install protobuf
下コマンドで必要なライブラリをインストールします。
go get -u google.golang.org/grpc go get -u github.com/golang/protobuf/protoc-gen-go
データ構造を示すprotoファイルを作成します。
pingerというフォルダを作ってpinger.proto
というファイルを作って下のように記述します。
syntax = "proto3"; package pinger; service Pinger { rpc Ping(Empty) returns (Pong) {} } message Empty {} message Pong { string text = 1; }
protoファイルを作ったら、下コマンドでgo言語のファイルを生成します。
protoc ./pinger/pinger.proto --go_out=plugins=grpc:.
もし下エラーになった場合、goのライブラリへのパスが足りていない可能性があります。
PATH="${PATH}:${HOME}/go/bin"
でパスを追加すると解消することがあります。
protoc-gen-go: program not found or is not executable Please specify a program using absolute path or make sure the program is available in your PATH system variable --go_out: protoc-gen-go: Plugin failed with status code 1.
protocコマンドがうまく動けばpingerフォルダにpinger.pb.go
というファイルが生成されます。
最後にserverを作成します。
カレントディレクトリにserver.go
というファイルを作って下のように記述します。
package main import ( "context" "log" "net" "./pinger" "google.golang.org/grpc" ) func main() { listener, err := net.Listen("tcp", ":5300") if err != nil { log.Fatalf("failed to listen: %v\n", err) return } grpcSrv := grpc.NewServer() pinger.RegisterPingerServer(grpcSrv, &server{}) log.Printf("Pinger service is running!") grpcSrv.Serve(listener) } type server struct{} func (s *server) Ping(ctx context.Context, req *pinger.Empty) (*pinger.Pong, error) { pong := &pinger.Pong{ Text: "pong", } return pong, nil }
ファイルを作ってから下コマンドを実行するとサーバーが起動します。
go run server.go
起動したら動作確認をします。
今いるディレクトリに下のようなcommand.goを作成します。
package main import ( "context" "fmt" "os" "./pinger" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:5300", grpc.WithInsecure()) if err != nil { fmt.Fprintf(os.Stderr, "grpc.Dial: %v\n", err) return } defer conn.Close() client := pinger.NewPingerClient(conn) req := &pinger.Empty{} pong, err := client.Ping(context.Background(), req) if err != nil { fmt.Fprintf(os.Stderr, "Ping: %v\n", err) return } fmt.Fprintf(os.Stdout, "Pong: %s\n", pong.Text) }
下コマンドを実行してPong: pong
という表示になれば問題なく動いています。
go run command.go
Swiftからサーバーのメソッドを呼び出す
次はSwiftでクライアントを作ります。
適当なプロジェクトを作成したらCocoaPodsでgRPC-Swiftをインストールします。
target 'MyApp' do use_frameworks! pod 'gRPC-Swift', '1.0.0-alpha.12' end
次は下コマンドでprotocのswiftプラグインをインストールします。
git clone https://github.com/grpc/grpc-swift.git cd grpc-swift make plugins
生成したプラグインはパスの通っている場所に移動します。
今回は一旦試したいだけなのでgrpc-swiftのパスを追加するだけにしました。
PATH="${PATH}:${HOME}/XXXX/grpc-swift"
ここまで終わったらクライアント用のファイルを生成します。
protoc pinger/pinger.proto --swift_out=. --grpc-swift_out=.
pinger.pb.swift
とpinger.grpc.swift
というファイルが作られるので、これをプロジェクトに追加します。
追加したら実際に通信を行います。
AppDelegateを下のように修正します。
import UIKit import GRPC import NIO @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) let channel = ClientConnection.insecure(group: group).connect(host: "localhost", port: 5300) let client = Pinger_PingerClient(channel: channel) let request = Pinger_Empty() let call = client.ping(request) print(try! call.response.wait()) return true } }
実行すると下のようにレスポンスが返る事がわかります。