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
}
}
実行すると下のようにレスポンスが返る事がわかります。
参考URL
遠隔手続き呼出し - Wikipedia
iOSエンジニアの為のgrpc-swift入門 - Speaker Deck