suusan2号の戯れ

SIerでインフラSE⇛WEB系でエンジニアのおっさん

grpc-gatewayでMetadataに詰めたエラーの内容をJSONに詰めてRESTクライアントに返したい

引き続きgRPCの話。

gRPCでエラーをクライアントに返したい場合、通常だとステータスコードとエラーメッセージしか返せない。例えばアプリケーションレベルのバリデーションエラーみたいなものを返したい時、メルカリさんの資料によるとMetadataに詰めて送ると良いらしい、

speakerdeck.com

gRPCがクライアントのときは詰めたMetadataを読み出せばいいんだけど、grpc-gatewayでRESTのクライアントに返す時にはどうしたらよいか調べた。

grpc-gatewayのエラーハンドリングをカスタマイズする

実はGitHubwikiHow to customize your gateway というのがあって、そこに結構色々と書かれている。

How to customize your gateway · grpc-ecosystem/grpc-gateway Wiki · GitHub

で、Wikiから辿った先にあるブログに実際に結構丁寧にエラーレスポンスのカスタマイズ方法が乗ってるので、それを参考にすればよい。

My Code Smells!

fun run() error {
    runtime.HTTPError = CustomHTTPError
    // 省略
}

type errorBody struct {
    Error string `json:”error"`
    ErrorDetails []ErrorDetail `json:”errorDetails”`
}

type errorDetails struct {
    Field string `json:”field”`
    Message string `json:”message”`
}

// おもにgRPCのMetadataをerrorBodyのような構造に変換し、クライアントに返すためのカスタムハンドラー
func CustomHTTPError(ctx context.Context, _ *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
    const fallback = `{"error": "failed to marshal error message"}`

    w.Header().Set("Content-type", marshaler.ContentType())
    w.WriteHeader(runtime.HTTPStatusFromCode(grpc.Code(err)))

    eb := errorBody{
        Err: grpc.ErrorDesc(err),
    }

    md, _ := runtime.ServerMetadataFromContext(ctx)
    for k, v := range md.TrailerMD {
        eb.ErrorDetails = append(eb.ErrorDetails, errorDetail{
            Field:   strings.TrimSuffix(k, "-bin"), // バイナリで帰ってくる文字列は-binのprefixがつくので、クライアントが扱いやすいよう消す
            Message: v[0],                          // サーバー側では文字列としてしか入れていないが、何故かArrayで入ってくるの最初のものだけ取得する
        })
    }

    jErr := json.NewEncoder(w).Encode(eb)

    if jErr != nil {
        w.Write([]byte(fallback))
    }
}

Go書くの久しぶりすぎてこんな感じでよかったか全然自信ないけど、一応やりたいことは実現できた。

gRPCをブラウザで使いたい

引き続きgRPCについて調べている。

2017年11月現在では現状gRPCはそのままブラウザ上で動かすことが出来ない(Node実装はあるけどブラウザでは動かせない)。

現状ブラウザでgRPCを動かしたいと思ったら二つの道があるようだ。

grpc-webを使う

github.com

これはgRPCサーバーの前段にプロキシサーバーを立てることにより、ブラウザ環境でgRPCのクライアントを動かせるようにするというもの。

import {grpc, BrowserHeaders, Code} from "grpc-web-client";

// Import code-generated data structures.
import {BookService} from "../_proto/examplecom/library/book_service_pb_service";
import {QueryBooksRequest, Book, GetBookRequest} from "../_proto/examplecom/library/book_service_pb";

const queryBooksRequest = new QueryBooksRequest();
queryBooksRequest.setAuthorPrefix("Geor");
grpc.invoke(BookService.QueryBooks, {
  request: queryBooksRequest,
  host: "https://example.com",
  onMessage: (message: Book) => {
    console.log("got book: ", message.toObject());
  },
  onEnd: (code: Code, msg: string | undefined, trailers: BrowserHeaders) => {
    if (code == Code.OK) {
      console.log("all ok")
    } else {
      console.log("hit an error", code, msg, trailers);
    }
  }
});

実際に試してみると、確かにブラウザからgRPCが使えた。GitHubを見るとGo前提っぽく感じるけど、grpcwebproxyが単体で提供されているので、例えばバックエンドはGoでなくてもgRPCサーバーの前段でこのプロキシサーバーを起動させてあげれば良い。

grpc-web/go/grpcwebproxy at master · improbable-eng/grpc-web · GitHub

そうなんだけど実際に試してみると、クライアント側でセットしたメタデータがサーバーまでたどり着かない。

github.com

調べてみたら同様の症状の人がIssueを上げているよう。ここで一旦grpc-webは諦めて、次のgprc-gatewayの方を触ってみた。

grpc-gatewayを使う

こちらはgRPCのバックエンドサーバーの前段において、RESTのエンドポイントを提供するというもの。grpc-webとは違い、protoファイルにも手を加える必要がある。

 syntax = "proto3";
 package example;
+
+import "google/api/annotations.proto";
+
 message StringMessage {
   string value = 1;
 }
 
 service YourService {
-  rpc Echo(StringMessage) returns (StringMessage) {}
+  rpc Echo(StringMessage) returns (StringMessage) {
+    option (google.api.http) = {
+      post: "/v1/example/echo"
+      body: "*"
+    };
+  }
 }

grpc-gateway は、RESTのエンドポイントを作るための実装だけではなく、swagger定義も出力出来るようになっている。なので、gRPCが使える環境の場合には普通にprotoファイルから生成したクライアントを使い、RESTを使う必要がある場合にはgatewayの出力したswagger定義からRESTクライアントを生成すればよい。

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --swagger_out=logtostderr=true:. \
  path/to/your_service.proto

まとめ

grpc-web の方がprotoファイルに余計なものを書かなくてすむので後々はこっちの方向性かなーと思った。でも、現状だとまだ grpc-gateway を使った方が良いのかなーという感触。

Rails + SpringBootでgRPCする方法について色々調べた

RailsのバックエンドとしてSpring Bootを置いてgRPCでやりとりするみたいなのをどう実現するかを調べている

RubyからgRPCを使う

shiladitya-bits.github.io

クライアント側はこれが参考になった。proto からGemの中身を生成して配布するの良さそう。 クライアント側でgRPCを扱うコードはこんな感じになる

  stub = Snip::UrlSnipService::Stub.new('0.0.0.0:50052', :this_channel_is_insecure)
  req = Snip::SnipRequest.new(url: 'http://shiladitya-bits.github.io')
  resp_obj = stub.snip_it(req)

Spring BootでgRPCを使う

これを使うのが楽そう。 @GRpcServiceアノテーションを付与するだけで、 SpringgRPC を共存させることができる。

github.com

    @GRpcService
    public static class GreeterService extends  GreeterGrpc.GreeterImplBase{
        @Override
        public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
            final GreeterOuterClass.HelloReply.Builder replyBuilder = GreeterOuterClass.HelloReply.newBuilder().setMessage("Hello " + request.getName());
            responseObserver.onNext(replyBuilder.build());
            responseObserver.onCompleted();
        }
    }

実際にアプリケーションを作るにあたって

クライアントをどこで作るか

.proto からクライアントをどこで作って配布するかみたいなのを考えていた。 .proto を vendoringするためのツールを作っている方もいるみたい。

github.com

現在のところはgradleのビルドの中でRubyクライアントを作っている。クライアントが決まっていて1言語だけなら、これがコスパよいかなという気がする。

github.com

このgradleのプラグインで、Rubyクライアントも生成できそうなんだけど、なぜかCLIgrpc_tools_ruby_protoc -Iproto --ruby_out=lib --grpc_out=lib proto/*.proto としたときにできる *_services_pb.rb が生成されない。 このIssueと関連ありそうな気がするけど、コードまでは追えてない。

github.com

しょうが無いので、無理やりgradleの中でコマンドを実行してRubyのクライアントを生成するようにしている。 キレイにやるならCIで生成するとかかなぁ。

エラーハンドリング

まだ良くわかってないが、この辺を読んで見るつもり。というか、この公式のexamplesもっと早く見ておけばよかった・・・

色々資料を読んでいると、エラーの詳細を返したいときはメタデータに入れるのが一般的なのかなー。例えばバリデーション的なものをやりたいときに、gRPCからどんな風にエラーを返せばいいんだろ。

grpc/examples/ruby/errors_and_cancellation at master · grpc/grpc · GitHub

今のところの感想

なんとなく行けそうなイメージは出てきたけど、まだまだ色々調べなきゃいけないことが多い。

gRPCだけでなく、SpringBootやgradleとかもまだまだ「全然わからない。俺は雰囲気で(ry」って感じなので、ちゃんと作っていくとまだまだ色々ありそうな予感。

GoでGitHubのrepositoryをキレイにするツール作ってた

作ったのは大分前なんだけど、そういえばブログに書いてなかった。

github.com

こういうのの名前考えるのめっちゃ苦手で、好きなMGSのキャラクターから名前とったものの、結構有名っぽいOSSに同名のものがあってしくじった感ある。いい名前があればIssueください…

何をするツール?

GitHubのrepositoryのお掃除をするツール。と言っても、現在のところはReleaseを消す機能しかない。

# clean suusan2go/hoge repo releases created 1 months ago
$ raiden releases clean -r hoge -o suusan2go --months 1

GitHubのRelasesは何か特殊な構造になっていて、GitのタグとGitHubで作成されたReleaseというのが混在する形になっている。なのでGitHubAPIからReleasesを消してもタグは残っちゃうし、タグだけ消してもReleaseは残るという結構面倒な感じになっているが、このツールなら上記のコマンドで一気に消せる。

ちなみにタグだけを消す方法もあります。

# clean suusan2go/hoge repo releases created 1 months ago
$ raiden tags clean -r hoge -o suusan2go --months 1

なんでこんなの作ったの?

会社(前職)のデプロイフローのなかでタグを作ったりReleaseを作ったりしていたんだけど、デプロイの度に作っちゃうもんだから積もり積もって数千件に達していてGitHubからpullしてくるのがちょっと遅くなっていた(らしい。インフラ見てる同僚談) GitHubの画面からポチポチは無理な感じだったので、シュッっと(実際はちょっとかかったけど)Goで作ってみた。

Goに詳しい人から教えてもらって、Cobraという奴を使ってみたんだけど、結構簡単にそれっぽいCLIツールができてしまって感動。

github.com

今後の展望

放置されたIssueやPRを閉じる機能とかそのうち作ろうかな‐と思っていたものの、今の職場はGitLabなんでどうしたもんかな。 GitLab悪くないんだけど、やっぱりGitHubが恋しいよ。

4Kディスプレイを買った

4Kディスプレイを買いました。

なぜ買ったのか

前から使ってたディスプレイ(Windows機、MacBookProのサブディスプレイ兼用)に線が入った状態になっていて、買い替えたいなーと思っていましたが、処分とか色々考えると面倒で何となくそのままに。

で、Windows機を新調するタイミングで、古いPCと合わせてディスプレイも引き取ってくれるとのことで、ついでに買い換えることにしました。

検討したやつ

このあたりを検討しました。

MacBookProとWin機の兼用で4K画質で使えるかが一番大きなポイントで、後は多少値段が上がってもディスプレイの位置とかを調整できたほうが嬉しいなと思い、そのあたり一番良さそうなDellのU2718Qを購入することにしました。

買ってみて

Macから接続するとRetinaディスプレイとして扱えなくて、字が小さすぎる3840x2160かちょっと大きく感じる1920x1080しか高解像度で選べなくて、やべーこれは失敗したかなーと思いました。しかし、試しにHigh SierraにOSを上げてみるとちゃんとRetinaとして認識されてちょうどよい解像度を選択することができました。

30Hzでしか出力できないのは誤算でしたが、ゲームをしたりするわけではないので、今のところそんなに気になっていません。Mac用に4Kディスプレイを買う方は以下をよくチェックしておくと良いです。(自分はMacBook Pro Retina, 13-inch, Mid 2014でギリギリ対応してると思ったら、Mid 2014から対応してるのは15-inchモデルだった…)

support.apple.com

SwitchResXというアプリを使うと52Hzまでは上げられそう。ちょっとスクロールがかくつく感じはするので、試してみようかな。 kandrejevs.com

作業環境はこんな感じになった。 f:id:suzan2go:20171025112744j:plain

IDEやエディタとブラウザ、ターミナルを1画面に並べても十分作業できるし、買ってよかった。これは元の環境には戻れない感ある。

「ジェフ・ベゾス 果てなき野望」を読んだ

ジェフ・ベゾス 果てなき野望

ジェフ・ベゾス 果てなき野望

積んでた本を読んでみたシリーズ。きっかけは、この本を本棚から引っ張り出した娘が「パパ!パパ!」と言ってきたから。

どんな本か

Amazonの企業としての歴史を振り返り、社内でどんな事が起こっていたかが実際に社員(もしくは過去社員だった人)からのインタビューを中心にしてまとめられている。

最初の方のAmazonの成り立ちというか起業秘話みたいなところはふーんって感じでしかなかったんだけど(だからそこで止まってた)、以下のエピソードは面白かった。

  • 取り扱う商品をどのように広げていったのか
  • プライムがどう始まったのか
  • Kindleがどのように生まれたか
  • AWSがどのようにして生まれたのか

感想

とにかくベゾスすげーなということに尽きる。自分で大きなビジョンを描いたらそれを達成するために手段を選ばないという感じが本当にすごい。最近では自前で運送もやりはじめているらしいけど、この本を読んだあとだと全く違和感がないというかそりゃやるだろうな…という感じがする。Kindle fireとか個人的には何でAmazonがこれ作る必要があるんだ?と思っていたけど、この本を読んだあとだ全く違和感がないというか、そりゃやるだろうなー・・・という感じだ。

ただAmazonで働いてみたいかというと、結構しんどそうだなーと思った。本当に猛烈に働き続けなきゃいけない感じで、最近よく聞く心理的安全性とか微塵も無さそうw(あくまで本を読んでの感想です。もしかしたら最近は or 部署によっては違うのかもしれない) 一番きっついなーと思ったエピソードは、会議でベゾスに「サポートに電話をかけると何分で繋がる?」と聞かれて、サポート担当のマネージャが「1分くらいです」と適当に答えたら、ベゾスがその場でサポートに電話をかけ始めたという話。数分たっても電話が繋がらず、ベゾスは真っ赤な顔でそのマネージャを罵りまくったとのこと。

とにかくこのベゾスは滅茶苦茶キレる(怒る)人のようで、各エピソードで一回以上はブチ切れてる感じだった。それでも最近は一通りブチ切れた後「でも、よく頑張ってくれた」的なことを言ってくれるらしいが…(改めて読み返してみたらそういう記述が見つけられなかったので、もしかしたら<そうであってほしい>という自分の妄想かもしれないw)

参考

AmazonからGoogleに移ったエンジニアのぶっちゃけ話。前もこれは見たことあったんだけど、本を読んだあとだとアーヤッパソウナンスネという感じだ。 anond.hatelabo.jp

「ヨチヨチ父」という本を読んだ

ヨチヨチ父という本を読んだ。

ヨチヨチ父 とまどう日々

ヨチヨチ父 とまどう日々

これ今気がついたんだけど、「もうぬげない」と同じ作者だったんだなぁ。

もう ぬげない

もう ぬげない

きっかけ

たまたま書店で見かけてちょっと立ち読みしたら凄く共感することが多くて、衝動買いしてしまった。 いい本見つけたなーと思ったんだけど、うちの奥さん曰く結構話題になっていたらしい。

どんな本?

父親の目線で育児について書かれているエッセイ集?みたいな本。

よかったところ

母親の人が書いた育児マンガやエッセイは結構色んなものが出てる気がするけど、父親目線で書かれている本って意外とない(主観です)。この本で父親目線で書かれているエピソードや思いはとても共感することが多くて、読んでいると子供が生まれてからこれまでの嬉しかったこと辛かったことが思い起こされて、結構ぐっと来た。

特に、育児における父親の大変さを書いてくれているところが凄く良くて、自分以外の父親も子供が生まれてからの環境の変化に対して戸惑っているし、同じように大変なんだということが分かった。それを知ることができただけでも、なんていうか心が軽くなった感じがする。

もしかすると、子供が産まれる前に読んでもあんまりピンとこないかもしれない。 子供が生まれてからの環境の変化に戸惑って、疲れて、モヤモヤしているお父さん方におすすめしたいなー。