LoginSignup
25
12

More than 5 years have passed since last update.

node で grpc を使ってみる + TypeScript でブラウザ実行しようとした

Last updated at Posted at 2018-05-10

grpcを手元で動かすのにシンプルな例を探していたが、全然見つからなかったのでメモがてら書いておく。

(Googleの公式サンプル、非常にわかりづらすぎる… https://grpc.io/docs/tutorials/basic/node.html)

まず通信定義を書く。 service の rpc を定義する。

helloworld.proto
syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

そのサーバーを実装する。事前に npm install -S grpc などしておく。

server.js
const PROTO_PATH = __dirname + '/helloworld.proto'
const grpc = require('grpc')
const { helloworld } = grpc.load(PROTO_PATH)

function sayHello(call, callback) {
  callback(null, { message: 'Hello ' + call.request.name })
}

const server = new grpc.Server()
server.addService(helloworld.Greeter.service, { sayHello })
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure())
server.start()

node server.js で localhost:50051 にサーバーが立つ。

これを呼び出すクライアントを書く。今回は grpc-caller を使う。(npm install しておく)

client.js
const path = require('path')
const caller = require('grpc-caller')

const PROTO_PATH = __dirname + '/helloworld.proto'
const client = caller('0.0.0.0:50051', PROTO_PATH, 'Greeter')

client.sayHello({ name: 'Bob' }, (err, res) => {
  console.log(res)
})

grpcサーバーを立てた状態で node client.js で実行すると { message: 'Hello Bob' } と出るはず。

ここまでは簡単

ブラウザで実行する

ここから先、調べたが typescript の例しかなかったので、 typescript を使う。バニラJS のみでシンプルに確認作業を終えたかったが、確かにgrpcの目的としては型がない環境を想定しないので、ここは諦める。

grpc は native module なので、ブラウザで呼び出すために grpc-web-client を使う。

ついでに、helloworld.proto からクライアントコードも生成する必要がある。(この辺 github のリポジトリ名が grpc-web だったりして別パッケージ化と思ったが同一のようだった)

ってことで、 protoc コマンドをインストールして、ついでにその typescript plugin をいれる。

brew install protobuf # Mac
npm install -S ts-protoc-gen

こんなシェルスクリプト書く

gen.sh
# Path to this plugin
PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts"

# Directory to write generated code to (.js and .d.ts files)
OUT_DIR="./out"

protoc \
    --plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
    --js_out="import_style=commonjs,binary:${OUT_DIR}" \
    --ts_out="service=true:${OUT_DIR}" \
    helloworld.proto

sh gen.sh で実行すると out にこんなコードが出力される

⋊> ~/s/protobuf-playground on master  tree out                                                                                   23:44:20
out
├── helloworld_pb.d.ts
├── helloworld_pb.js
├── helloworld_pb_service.d.ts
└── helloworld_pb_service.js

これを Typescript から使うコードはこうなる。

import { grpc } from 'grpc-web-client'
import { HelloRequest } from './out/helloworld_pb'
import { Greeter, GreeterClient } from './out/helloworld_pb_service'
const HOST = 'http://localhost:50051'

const req = new HelloRequest()
req.setName('johndoe')

const client = new GreeterClient('http://localhost:50051')
client.sayHello(req, (err, ret) => {
  if (err) {
    throw err
  }
  console.log(ret)
})

yarn tsc --init でtsconfig.jsonを生成

webpack と ts-loader いれて雑にビルド設定を書く(フロントエンドのいつものアレ)

webpack.config.json
module.exports = {
  mode: 'development',
  entry: './web-client.ts',
  output: {
    path: __dirname + '/web',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js']
  },
  module: {
    rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]
  }
}

で、ここで自分はこういうエラーがでたが問題はなかった(tsconfigの設定が悪そう。moduleResolution とかあのへん。今回は本題ではないので追跡しない)

ERROR in /Users/mz/sandbox/protobuf-playground/web-client.ts
./web-client.ts
[tsl] ERROR in /Users/mz/sandbox/protobuf-playground/web-client.ts(1,22)
      TS2307: Cannot find module 'grpc-web-client'.

で, 適当に index.html 置いてこの js を読み込むと動くはず…

ここで謝らないといけないことがある。これでおそらく実行できているのだが、localhostでhttpのみの環境でやっていたので、実際に grpc の疎通を確認できなかった。(gprcはhttp/2必須)

こんなエラーが出て頓挫した。

fetch.js:42 OPTIONS http://localhost:50051/helloworld.Greeter/SayHello 0 ()
Fetch.send @ fetch.js:42
Fetch.sendMessage @ fetch.js:70
GrpcClient.send @ client.js:230
unary @ unary.js:35
sayHello @ helloworld_pb_service.js:33
(anonymous) @ web-client.ts:12
./web-client.ts @ bundle.js:811
__webpack_require__ @ bundle.js:20
(anonymous) @ bundle.js:69
(anonymous) @ bundle.js:72
detach.js:22 Uncaught Error: Response closed without headers
    at Object.onEnd (helloworld_pb_service.js:41)
    at eval (unary.js:26)
    at Array.eval (client.js:182)
    at runCallbacks (detach.js:10)
    at eval (detach.js:34)

たぶん http/2 だったら動いてる感じのエラーなので、あとで確認して追記する。h2o とオレオレ証明書とかそういう作業になると思う

感想

動作確認だけだったらとりあえず最初の grpc-caller の例でよくて、 gprc-server の開発の初手としてはややこしいブラウザは後回しで良さそう。結局やるのはそうなんだけど…

あと、どこ探しても Golang + TypeScript の例しか見つからなかった。いろんな例を眺めてコードちょっとずつ書きながら、いろんな情報の継ぎ接ぎで書いたので参照元とか思い出せない…

ここまでのコードはこれ https://github.com/mizchi-sandbox/protobuf-playground

25
12
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
25
12