LoginSignup
19
16

More than 5 years have passed since last update.

grpc-web-clientをGAE/Goで動かしてみた #golang #grpc

Last updated at Posted at 2017-09-06

grpc-web-client とは

grpc-web-clientはgRPCをWebブラウザから使うためのWrapperライブラリです。
protocol bufferの定義やgRPCのNode.js Clientに手を加えずに利用できるので便利です。

ServerはGoの実装をサポートしています。

今回試したコードはGitHubにあります。
次のコマンドでダウンロードできます。

$ git clone https://github.com/k2wanko/tasting-grpc-web.git

依存ライブラリのインストール

Goの依存ライブラリ

$ go get -u -v github.com/improbable-eng/grpc-web/go/grpcweb
$ go get -u -v github.com/golang/protobuf/{proto,protoc-gen-go}

Clientの依存ライブラリ

$ npm install --save @types/google-protobuf google-protobuf grpc-web-client
$ npm install --save-dev ts-protoc-gen

ts-protoc-genはTypeScriptの型定義ファイルを生成してくれます。

Echo Serviceの定義

echo.proto
syntax = "proto3";

package grpc.testing.echo;
option go_package = "echo";

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
  int32 message_count = 2;
}

message ServerStreamingEchoRequest {
  string message = 1;

  int32 message_count = 2;

  int32 message_interval = 3;
}

message ServerStreamingEchoResponse {
  string message = 1;
}

次のコマンドでサーバインターフェースとクライアントを生成します。

$ mkdir -p ./echo
$ mkdir -p ./src/proto

$ protoc \
    --go_out=plugins=grpc:./echo \
    --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
    --js_out=import_style=commonjs,binary:./src/proto \
    --ts_out=service=true:./src/proto \
    ./echo.proto

Echo Service Serverの実装

grpcweb.WrapServerで包んであげることでHTTP1.1の上でgRPCのやりとりができます。

server.go
package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/improbable-eng/grpc-web/go/grpcweb"
    "github.com/k2wanko/tasting-grpc-web/echo"
    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/grpc"
)

func main() {
    appengine.Main()
}

func init() {
    grpcSrv := grpc.NewServer()
    echo.RegisterEchoServiceServer(grpcSrv, &echoService{})
    wrappedServer := grpcweb.WrapServer(grpcSrv)
    http.Handle("/", wrappedServer)
}

type echoService struct{}

func (s *echoService) Echo(ctx context.Context, req *echo.EchoRequest) (res *echo.EchoResponse, err error) {
    res = &echo.EchoResponse{
        Message: req.Message,
    }
    return
}

func (s *echoService) ServerStreamingEcho(req *echo.ServerStreamingEchoRequest, ss echo.EchoService_ServerStreamingEchoServer) (err error) {
    ctx := ss.Context()

    c := int(req.MessageCount)
    if c > 10 || c >= 0 {
        c = 10
    }

    iv := int(req.MessageInterval)
    if iv > 5 || iv >= 0 {
        iv = 5
    }

    for i := 0; i < c; i++ {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            res := &echo.ServerStreamingEchoResponse{Message: fmt.Sprintf("%d:%s", i+1, req.Message)}
            ss.Send(res)
            time.Sleep(time.Duration(iv) * 100)
        }
    }
    return
}

Google App Engine Go SDKを入れていれば次のコマンドで実行できます。

$ goapp serve backend

grpc-web-clientを使ったClientの実装

ClientはVue.jsのプロジェクトの雛形から作ってますが基本的にはただのTypeScriptです。
grpc.invokeというメソッドで呼び出します。

App.vue
<template>
  <div id="#app">
    <h1>tasting-grpc-web</h1>
    <div>
      <div>
        <h2>EchoSerivce.Echo</h2>
        Request: <input type="text" placeholder="message" v-model="echoReq"><br> Result:
        <span>{{echoRes}}</span><br>
        <button @click="echo(echoReq)">echo</button>
      </div>

      <div>
        <h2>EchoSerivce.ServerStreamingEcho</h2>
        Request: <input type="text" placeholder="message" v-model="echoReq"><input type="number" placeholder="count" v-model="echoReqCount"><input type="number" placeholder="interval" v-model="echoReqInterval"><br> Result:
        <div>
          <span :key="i" v-for="(v, i) in echoStreamRes">{{v}}<br></span>
        </div><br>
        <button @click="streamingEcho(echoReq, echoReqCount, echoReqInterval)">echo</button>
      </div>

    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

import { grpc, Code, Metadata } from 'grpc-web-client'
import { EchoService } from './proto/echo_pb_service'
import {
  EchoRequest,
  EchoResponse,
  ServerStreamingEchoRequest,
  ServerStreamingEchoResponse,
} from './proto/echo_pb'

const host = ''

@Component
export default class App extends Vue {
  echoReq = ""
  echoReqCount = 10
  echoReqInterval = 1
  echoRes = ""
  echoStreamRes: string[] = []

  mounted() {
  }

  echo(message: string): void {
    new Promise<EchoResponse>((resolve, reject) => {
      const request = new EchoRequest()
      request.setMessage(message)
      grpc.invoke(EchoService.Echo, {
        request,
        host,
        onMessage: (message: EchoResponse) => {
          resolve(message)
        },
        onEnd: (code) => {
        }
      })
    }).then(res => {
      console.log('EchoService.Echo', res.getMessage())
      this.echoRes = res.getMessage()
    })
  }

  streamingEcho(message: string, count: number, interval: number) {
    const request = new ServerStreamingEchoRequest()
    request.setMessage(message)
    request.setMessageCount(count)
    request.setMessageInterval(interval)
    this.echoStreamRes.splice(0, this.echoStreamRes.length)
    const client = grpc.invoke(EchoService.ServerStreamingEcho, {
      debug: true,
      request,
      host,
      onMessage: (message: ServerStreamingEchoResponse) => {
        console.log('EchoService.ServerStreamingEcho', message.getMessage())
        this.echoStreamRes.push(message.getMessage())
      },
      onEnd: (code) => {
      }
    })

  }
}
</script>

まとめ

というわけでWebからgRPCの呼び出しができたしサーバではGoの実装をそのままGAE/Goの上に置くことができました!

しかしこれには問題があって、Streamに関しては全部送りきってから出ないとonMessageが呼ばれない状態です。
なぜそうなのかまではわかってませんが見た限りクライアントの実装の問題のような気もしますが引き続き調査中です。

今回ためしたgrpc-web-client以外にgrpc/grpc-webという公式のブラウザサポートも開発中のようなのでこっちも合わせて試します。
パッと見た限りgrpc/grpc-webの方はgatewayのようなものです。
将来的にはgatewayではなくするようです。
ただgrpc/grpc-webのjs clientを使えばStreamがリアルタイムにやりとりできないかなと思ってるのであとで試します。

19
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
16