I will write about how to use gRPC to communicate between Go's API server and Dart client (assuming Flutter).
Hibiya Music Festival Osanpo App 2020 Development Behind the Scenes / Server Edition DeNA's 20 new graduates and 21 graduates I referred to the article of the person, so I will share it.
Also, why is it nice to use gRPC? A session by Nao Minami of Cloud Native Days Tokyo 2020 "Real World Migration from HTTP to gRPC" It's easy to understand when you listen to, so I'll share this as well. You can jump to the archived video from the link.
To automatically generate from a .proto file, you need to use the protoc command and peripheral plugins, but I don't want to install various things locally, so I launch a Docker container and generate code for each language. You can check the latest version of Current Protocol Buffers from the GitHub repository (https://github.com/protocolbuffers/protobuf/releases).
Dockerfile
FROM golang:1.15.0
ENV DEBIAN_FRONTEND=noninteractive
ARG PROTO_VERSION=3.13.0
WORKDIR /proto
COPY ./proto .
RUN mkdir /output /output/server /output/client
RUN apt-get -qq update && apt-get -qq install -y \
unzip
RUN curl -sSL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTO_VERSION}/protoc-${PROTO_VERSION}-linux-x86_64.zip -o protoc.zip && \
unzip -qq protoc.zip && \
cp ./bin/protoc /usr/local/bin/protoc && \
cp -r ./include /usr/local
# Go
RUN go get -u github.com/golang/protobuf/protoc-gen-go
# Dart
RUN apt-get install apt-transport-https
RUN sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
RUN sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
RUN apt-get update
RUN apt-get install dart -y
ENV PATH="${PATH}:/usr/lib/dart/bin/"
ENV PATH="${PATH}:/root/.pub-cache/bin"
RUN pub global activate protoc_plugin
Run the following script inside this container
protoc.sh
#!/bin/sh
set -xe
SERVER_OUTPUT_DIR=/output/server
CLIENT_OUTPUT_DIR=/output/client
protoc --version
protoc -I=/proto/protos hoge.proto fuga.proto\
--go_out=plugins="grpc:${SERVER_OUTPUT_DIR}" \
--dart_out="grpc:${CLIENT_OUTPUT_DIR}"
#timestamp in proto file.Required when importing proto etc.
protoc -I=/proto/protos timestamp.proto wrappers.proto\
--dart_out="grpc:${CLIENT_OUTPUT_DIR}"
When using docker-compose, if you set it to execute protoc.sh with command, the code will be automatically generated at startup. Synchronize the directory containing the proto file and protoc.sh with volumes, the output directory on the server side, and the output directory on the client side.
docker-compose.yml
version: '3.8'
services:
proto:
build:
context: .
dockerfile: docker/proto/Dockerfile
command: ./protoc.sh
volumes:
- ./proto:/proto
- ./client:/output/client
- ./server:/output/server
However, when specifying the go package on the proto file side, for example
hoge.proto
syntax = "proto3";
package hoge.hoge;
option go_package = "hoge/fuga/foo/bar";
service Hoge{
}
If so, it will be output to / output / server / hoge / fuga / foo
on the container side.
`` `hoge.pb.go``` is generated in the volumes specified in docker-compose.yml. Since there is Clinet's Inerface definition in it (it is easy to find if you search in the file with ctx), we will implement the method by looking at that part.
go:hoge.pb.go
// HogeClient is the client API for Hoge service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HogeClient interface {
CreateHoge(ctx context.Context, in *CreateHogeMessage, opts ...grpc.CallOption) (*CreateHogeResponse, error)
}
Create HogeController as a class that implements HogeClient. The point to note here is that variable-length arguments are received after the third argument, but it is not necessary to write opts in the implementation method.
hoge_controller.go
type HogeController struct{
}
func (ctrl HogeController) CreateHoge(ctx context.Context, in *CreateHogeMessage) (*CreateHogeResponse, error){
// TODO: return *CreateHogeResponse, error
}
Register the HogeController instance created above with the gRPC server instance with RegisterHogeServer ().
main.go
func main() {
listenPort, err := net.Listen("tcp", ":8000")
if err != nil {
log.Fatalln(err)
}
server := grpc.NewServer()
hogeCtrl := NewHogeController()
pb.RegisterHogeServer(server, &hogeCtrl)
if err := server.Serve(listenPort); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
The client is `hoge.pb.dart```,
hoge.pbgrpc.dart```, ``
hoge.pbjson.dart, `` `hoge. If you are using an enum. pbenum.dart
is generated.
If timestamp.dart is used on the proto file side, an error will occur in the generated path, so simply change the import statement in `hoge.pb.dart``` to ```import'timestamp.pb.dart' You can use it by changing it like as $ 1;
`. In the case of Unary Call, you can communicate with the following functions.
hoge.dart
Future<void> createHoge(dartSideParam1, dartSideParam2, dartSideParam3) async {
final channel = ClientChannel('localhost',
port: 8000,
options:
const ChannelOptions(credentials: ChannelCredentials.insecure()));
final grpcClient = KitchenClient(channel,
options: CallOptions(timeout: Duration(seconds: 10)));
try {
await grpcClient.createHoge(CreateHogerMessage()
..param1 = dartSideParam1
..param2 = dartSideParam2
..param3 = dartSideParam3;
await channel.shutdown();
} catch (error) {
developer.log('Caught error: $error');
await channel.shutdown();
return Future.error(error);
}
}
It was a way to communicate between Go and Dart using gRPC. In some businesses, we are implementing using Server Streaming method communication, so I would like to share that information at a later date.
Recommended Posts