GRPC starting with Go server and Dart client

Introduction

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.

Preparation

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.

Server side (Go)

`` `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)
	}
}

Client side (Dart)

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);
    }
}

Summary

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

GRPC starting with Go server and Dart client
Let's try gRPC with Go and Docker
GRPC starting with Python
Authentication process with gRPC and Firebase Authentication
Communicate between Elixir and Python with gRPC
Code-Server x Dart with ffi x Go x Clang
"First Elasticsearch" starting with a python client
Compare xml parsing speeds with Python and Go
Launch a web server with Python and Flask
Music playback server with NanoPi-NEO, MPD and OLED
System trading starting with Python 3: Investment and risk
Python with Go
Distributed environment construction with Raspberry PI series (Part 4: NFS server construction and client OS import)
Deep learning image analysis starting with Kaggle and Keras
Easy HTTP server and Systemd autostart settings in Go
Get swagger.json with Flask-RESTX (Flask-RESTPlus) without starting the server
How to make an HTTPS server with Go / Gin
Test automation starting with L-Chika (5) Webcam and OCR integration
HTTP server and HTTP client using Socket (+ web browser) --Python3
Call the C function with dart: ffi and call the Dart function callback