Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて

Df4978f14401325586e9e286b140ac4c?s=47 n1215
March 27, 2021

ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて

PHPerKaigi 2021 の発表資料です

サンプルコード: https://github.com/n1215/grpc-web-chat

Df4978f14401325586e9e286b140ac4c?s=128

n1215

March 27, 2021
Tweet

Transcript

  1. for PHPerKaigi 2021 ブラウザから始めるgRPC      〜 gRPC-WebにPHPを添えて 2021年3⽉27⽇ (⼟) 株式会社Nextat 中榮健⼆

    Nextat Inc. 1
  2. ⾃⼰紹介 from 京都 - 中榮健⼆ (なかえけんじ) - twitter: @n_1215  -

    株式会社Nextat 取締役 - Laravel中⼼にECサイトやシステム開発 - ここ最近はLambdaを使ったりUnityを使ったりPHP以外の仕事も増加 Nextat Inc. 2
  3. 発表概要 1. gRPCとは 2. gRPCとPHP 3. gRPC-Webとは 4. サンプルアプリ ブラウザ側実装

    / C#サーバ実装 5. サンプルアプリ PHPサーバ実装 6. まとめ Nextat Inc. 3
  4. 1. gRPCとは Nextat Inc. 4

  5. 1-1. gRPCとは Google製のRPCフレームワーク → gpc.io RPC = Remote Procedure Call

    (遠隔⼿続呼出) 'g' の意味はリリース毎に違う てっきり Google の g かと 1.0 'g' stands for 'gRPC', 1.1 'g' stands for 'good', ... ハイパフォーマンス Nextat Inc. 5
  6. gRPCの通信プロトコルとRPCの種類 over HTTP/2を前提に策定されている データがバイナリ TCPコネクションの使い回し ネットワークリソース利⽤効率 サーバ・クライアント間のRPCは4種類 HTTP/2だから双⽅向通信が可能 Nextat Inc.

    6
  7. (1) Unary RPCs 1リクエスト / 1レスポンス 多くのWebアプリ開発者が慣れ親しんだもの Nextat Inc. 7

  8. (2) Server streaming RPCs 1リクエストに対しサーバが複数回のレスポンスを返す 送信完了までクライアントがストリームからメッセージを読む サーバープッシュ Nextat Inc. 8

  9. (3) Client streaming RPCs 複数回のリクエストを送信しサーバが1回レスポンスを返す 送信完了までサーバがストリームからメッセージを読む データのアップロードなどに利⽤可能 Nextat Inc. 9

  10. (4) Bidirectional streaming RPCs リクエストとレスポンスが多対多 双⽅向ストリーミング 順序に決まりはない チャットなどに利⽤可能 Nextat Inc.

    10
  11. 1-2. Protocol Buffers (Protobuf) gRPCが利⽤するIDL 兼 メッセージ交換⽤のバイナリフォーマット IDL = Interface

    Definition Language インタフェース記述⾔語 プログラミング⾔語に依存しない .protoファイルから⾔語実装を⾃動⽣成できる gRPCとは独⽴して使うこともできる 例)REST API + リクエストボディやレスポンスボディにProtocol Buffers Nextat Inc. 11
  12. Protocol Buffers 定義ファイル の書式 Message → リクエストやレスポンスのデータ構造を記述 Service → RPCの定義を記述

    // service.proto syntax = "proto3"; package service; service Echo { rpc Ping (Message) returns (Message) { } } message Message { string msg = 1; } Nextat Inc. 12
  13. protoc (Protocol Compiler) .protoファイルをprotocでビルドして各⾔語の実装を⽣成する 拡張が容易 例) PHPのgRPCクライアントの⽣成 $ protoc -I

    . --php_out=. --grpc_out=. \ --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin ./service.proto Nextat Inc. 13
  14. gRPCのメリット パフォーマンス、ネットワークリソースの効率的利⽤ 双⽅向通信 クライアント側の実装が⾔語ごとに⾃動⽣成できる サーバ側もスキーマが定まるので型のエラーが起きにくい コンパイル時のエラーないし静的解析で検知しやすい 定義ファイルの内容がそのままAPIの定義となる 実装変更の前に定義ファイルの変更が必要になるため、両者が乖離しにくい Nextat Inc.

    14
  15. gRPCのデメリット 開発中に通信の中⾝を確認しにくい HTTP/2は実質暗号化通信が必須 バイナリフォーマット 定義ファイルから各⾔語の実装をビルドするのが少し⼿間 Nextat Inc. 15

  16. gRPCの使いどころ マイクロサービスのバックエンドでのサービス間通信 公式クライアントしかないスマホアプリ⽤のサーバ ゲームサーバ チャットやバトルシステムの通信⽅式をまとめられる ex. MagicOnion ブラウザでの利⽤を想定する⼀般向けの公開APIにそのまま⽤いるのは⾟い Nextat Inc.

    16
  17. 2. gRPCとPHP Nextat Inc. 17

  18. PHP界隈のgRPC事情 PHP界隈ではgRPC関連の発表は少ない(⽇本のカンファレンスで年1くらい) php grpc-client in phpcon2018 PHPによるgRPCクライアント実装のお話 "PHPでのgRPCサーバはできない” PHPでもgRPCサーバを⽴てたいだけの⼈⽣だった(Laravel JP

    Conf 2019) "PHPerには⻭ブラシで船舶を磨く⾃由が与えられている" サーバ側もUnary RPCは可能だが、"ストリーミングはまだ試してません" RoadRunner、 spiral/php-grpc PHPでgRPCってどこまでいけるの?(PHP Conference 2019) "Unary RPCはできる" "Streaming RPCはまだはやかった" Swoole、 Hyperf Nextat Inc. 18
  19. PHP界隈のgRPC事情 PHPによるgRPCクライアントは公式サポート リクエスト使い捨てのPHPの通常の動作⽅式では⻑寿命なStreamingができない ドキュメント(PHP⽤): サーバ側はNode.JSを使ってね Google Groupでのとある発⾔ : PHPでgRPCサーバ作っても特殊な構成になるし あまり役に⽴たないよね(意訳)Serers

    in PHP? 参考: なぜPHPはgRPCサーバーがサポートされていないのか? Nextat Inc. 19
  20. "Works across languages and platforms" Automatically generate idiomatic client and

    server stubs for your service in a variety of languages and platforms https://grpc.io/ Nextat Inc. 20
  21. だが、PHPでのサーバ実装は駄⽬……っ! 公式の対応にPHPer涙⽬ Nextat Inc. 21

  22. PHPでgRPCサーバを実現する試み 通常のPHPの構成でダメなら通常とは違う構成でやればいい PHP-FPM以外の選択肢 Swoole、RoadRunnerなど、新しいアプリケーションサーバ・HTTPサーバによ る試験的な実装が⾒つかる 資料の後半で⼀部を紹介 Nextat Inc. 22

  23. PHP界隈におけるgRPCの現状 マイクロサービスなどの⽂脈で、PHPでgRPCクライアントを実装するのは問題ない が、通常のPHP+JavaScriptによるWeb開発の代替技術としての採⽤には難 (1) PHPによるgRPCサーバの実装が困難 Unary RPCは問題ないものの、Streaming RPCがネックになる 実装状況を調査した結果は2年前とあまり変わらず (2)

    gRPCはそのままではブラウザから利⽤しにくい 解決策 (1) → Unary RPCのみで我慢するか、Streamingに対応した実装を待つ (2) → gRPCに準じた通信をブラウザから利⽤するための gRPC-Web Nextat Inc. 23
  24. 3. gRPC-Webとは ようやく本編 Nextat Inc. 24

  25. gRPC-Web ブラウザからgRPCに準じた通信を扱うためのJSライブラリ+プロトコル https://github.com/grpc/grpc-web gRPC公式GitHubリポジトリのdoc/PROTOCOL-WEB.mdに仕様あり gRPC over HTTP2 との主な違い HTTP/2特有の振る舞いやフレームには依存せず、HTTP/1でも利⽤できる HTTP/1やクロスブラウザサポートのため、Base64などのテキストエンコードを

    ⾏う場合がある RPCの⽅式はUnary RPC、Server Streaming RPCのみ Client Streaming、Bidirectional Streamingは未サポート Nextat Inc. 25
  26. Content-Typeは2種類 application/grpc-web-text base64でテキストにエンコードされる 今回のサンプルアプリはこちらを利⽤ application/grpc-web デフォルトのフォーマットはProtobuf(application/grpc-web+proto) バイナリフォーマットよりJSONのほうがブラウザ上の処理が軽いという話 もあり、application/grpc-web+jsonなども想定されている Nextat Inc.

    26
  27. Proxy gRPCサーバとブラウザが直接通信するわけではない 間にgRPCとgRPC-Webを変換するProxyが必要 Envoy Nginx moduleもある 参考: gRPC-Web is Generally

    Available Nextat Inc. 27
  28. gRPC-Webのロードマップ roadmap.md streaming-roadmap.md ブラウザのStreamサポートに応じてClient StreamingやBidi Streamingを追加 Envoy不要のIn-process Proxy(Python、Java、Node、C++ etc.) ブラウザがネイティブにgRPCを扱えるようになれば、gRPC-Webはオプション

    に WHATWG Streams API (Chromiumベースのブラウザではもう使える) fetch() upload streaming (ChromeのOrigin Trialには⼊っている) もう少し時間は掛かりそう Nextat Inc. 28
  29. 4. サンプルアプリ ブラウザ側実装 + C#サーバ実装 Nextat Inc. 29

  30. お題: 簡易チャット チャットルームにメッセージを投稿できる 他の⼈が投稿したメッセージをリアルタイムに確認できる チャットルームは⼀つだけ 過去ログの閲覧やチャットメッセージの永続化は特に考えない Nextat Inc. 30

  31. RPCの設計とProtobuf定義(スキーマ駆動は良いぞ!) 双⽅向ストリーミングは(今の)gRPC-Webでは使えないが、なくても問題ない メッセージ投稿はUnary RPC メッセージ購読にServer Streaming RPC 定義ファイルは次ページ Nextat Inc.

    31
  32. // chat.proto syntax = "proto3"; option csharp_namespace = "GrpcWebChat"; import

    "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; package GrpcWebChat; service Chat { rpc SendMessage (SendMessageRequest) returns (google.protobuf.Empty); rpc Subscribe (google.protobuf.Empty) returns (stream ChatMessage); } message SendMessageRequest { string body = 1; string name = 2; } message ChatMessage { string body = 1; string name = 2; google.protobuf.Timestamp date = 3; } Nextat Inc. 32
  33. ブラウザ側TypeScript実装(1) mode は grpc-web-text を選択 mode: grpc-web はServer Streaming⾮対応 ブラウザがStreamingのデータをバイナリで受け取ることができないため

    gRPC クライアントコード⽣成 protoc -I . -I /opt/include ./chat.proto \ --grpc-web_out=import_style=typescript,mode=grpcwebtext:/out/grpc-web-text \ --js_out=import_style=commonjs:/out/grpc-web-text \ --plugin=protoc-gen-grpc-web=/usr/local/bin/grpc_web_plugin gRPC-Web⽤のprotocプラグインを⽤いて⾃動コード⽣成 JavaScript + TypeScript型定義(d.ts)、TypeScriptのコードが⽣成される Nextat Inc. 33
  34. ブラウザ側TypeScript実装(2) ⾃動⽣成されたChatClientを利⽤するだけ // メッセージ購読の処理のイメージ import { ChatMessage } from './pb-web/chat_pb'

    import { ChatClient } from './pb-web/ChatServiceClientPb' const client = new ChatClient('https://localhost:9000') const messageStream = client .subscribe(new Empty()) as ClientReadableStream<ChatMessage> messageStream.on('data', (chatMessage: ChatMessage) => { console.log('data', chatMessage) }) messageStream.on('status', (status: Status) => { console.log('status', status) }) messageStream.on('end', () => { console.log('stream end', new Date()) }) Nextat Inc. 34
  35. サーバ側C#実装(1) ASP.NET Core を利⽤ gRPCとgRPC-Webに対応したパッケージあり gRPC-Web実装はIn-process Proxy⽅式 サーバ内にgRPC-Webへの変換を組み込む形式なので、Envoy不要 C#のHTTPサーバ(Kestrel)⾃体がgRPC-Web形式でやりとり ドキュメントを⽤意しているMicrosoftはgRPC-Webガチ勢なのか?

    そういえば クライアント側:TypeScript + サーバ側:C# ですね アンダース・ヘルスバーグ先⽣に感謝の祈りを捧げながら実装します Nextat Inc. 35
  36. サーバ側C#実装(2) ChatService.csが⾃動⽣成できるので、処理を埋めていく データストアを使わない簡易的なPub/Subに Reactive Extensions (Rx)を利⽤ ChatService.csのメッセージ投稿処理 public override Task<Empty>

    SendMessage(SendMessageRequest request, ServerCallContext context) { var chatMessage = new ChatMessage { Body = request.Body, Name = request.Name, Date = new Timestamp { Seconds = DateTimeOffset.Now.ToUnixTimeSeconds() } }; _chatMessageSubject.OnNext(chatMessage); return Task.FromResult(new Empty()); } Nextat Inc. 36
  37. ChatService.csのメッセージ購読処理 public override Task Subscribe( Empty request, IServerStreamWriter<ChatMessage> responseStream, ServerCallContext

    context ) { return _chatMessageSubject.Do(chatMessage => { responseStream.WriteAsync(chatMessage); }).ToTask(); } Nextat Inc. 37
  38. 動いた! 最低ノルマ達成。最悪PHP実装は動かなくても許されるはず! Nextat Inc. 38

  39. content-type: application/grpc-web-text Nextat Inc. 39

  40. 5. サンプルアプリ PHPサーバ実装 Nextat Inc. 40

  41. "PHPでgRPCサーバ" に再挑戦 In-process Proxy⽅式にする場合もStreamingは必須 どうせならEnvoyでProxyすることにして、gRPCサーバに再挑戦 Nextat Inc. 41

  42. 候補1. Spiral Framework (RoadRunner)     https://spiral.dev/ RoadRunner (Golang製のPHPアプリケーションサーバ) を利⽤

    前⾯のGoサーバがgRPCのリクエストを受け、PHPのworkerに振る 開発元は Spiral Scout プレゼン資料(ロシア語): RoadRunner Unaryに加えてServer Streaming RPCに対応している(?) https://spiral.dev/docs/grpc-streaming gRPCのサービスはPHPではなくGo実装で、PHPは裏のJobとして動くのみ これをPHP実装と⾔い張るのは厳しい Nextat Inc. 42
  43. 今回は⻭ブラシ(PHP)で船舶を磨くことが ⽬的なのであって 船舶を効率良く磨く⽅法は求めていない spiral/php-grpc も Streaming周りの進展はなさそう だったため、⾒送り Nextat Inc. 43

  44. 候補2. Swoole https://www.swoole.co.uk/ コルーチンベースの⾮同期並⾏実⾏ライブラリ(PHP拡張/C⾔語) PHPのコードでHTTPサーバを実装することが可能。HTTP/2にも対応 Unary RPCのサンプル実装が存在 上記を元に2回レスポンスのデータを送ればOKかと思いきや⽅法が不明 \Swoole\Http\Response::end() は名前の通り2回以上は呼べない

    チャンク⽤の\Swoole\Http\Response::write() はHTTP/2⾮対応 \Swoole\Http\Server::send()でバイナリデータを送ればいけるのか? Nextat Inc. 44
  45. 困った…… どちらかで⾏けるだろうと思っていた またServer Streaming RPCの前に涙を飲むしかないのか? Nextat Inc. 45

  46. そんなところに救世主が! Nextat Inc. 46

  47. 候補3. Amp https://amphp.org/ イベント駆動な並⾏処理のフレームワーク Event Loop、Promise、Coroutine、Stream PHPのコードでHTTPサーバを実装可能。HTTP/2対応 HTTP/2のHeader圧縮(HPACK)などもPHPで書かれている → PHPで制御できる部分がかなり多そう

    Nextat Inc. 47
  48. Amp HTTP Serverによるストリーミング amphp/http-server のリポジトリにStreamのコード例あり(下に抜粋) examples/stream.php StreamとProducerを使って複数回データを送っている ⾮同期処理の記述におけるyieldが特徴的 $server =

    new HttpServer($servers, new CallableRequestHandler(function (Request $request) { // We stream the response here, one line every 100 ms. return new Response(Status::OK, [ "content-type" => "text/plain; charset=utf-8", ], new IteratorStream(new Producer(function (callable $emit) { for ($i = 0; $i < 30; $i++) { yield new Delayed(100); yield $emit("Line {$i}\r\n"); } }))); }), $logger, (new Options)->withoutCompression()); Nextat Inc. 48
  49. それだよAmp!!! キミにきめた!!! 時間を空けてテキストデータを複数のDATAフレームで送ることが可能だと確認 あとはレスポンスをgRPCの仕様に合わせれば…… Nextat Inc. 49

  50. gRPCのレスポンスの仕様 公式の gRPC over HTTP2 を⾒る ABNF(拡張バッカス・ナウア記法)で記述されている Response → (Response-Headers

    *Length-Prefixed-Message Trailers) / Trailers-Only Length-Prefixed-Message → Compressed-Flag Message-Length Message Compressed-Flag → 0 / 1 # encoded as 1 byte unsigned integer Message-Length → {length of Message} # encoded as 4 byte unsigned integer (big endian) Message → *{binary octet} Protocol Buffersのバイナリをそのまま送るだけではダメ レスポンスボディに相当するLength-Prefixed-Messageを構成する必要がある 参考: https://gkuga.hatenablog.com/entry/2019/12/14/005653 Nextat Inc. 50
  51. Length-Prefixed-Message を⽣成 pack()と⽂字列操作関数を使って簡易的に実装 $serializedMessage = $message->serializeToString(); $lengthPrefixedMessage = pack('c', 0x00)

    . pack('N', strlen($serializedMessage)) . $serializedMessage; これをAmpのHTTPサーバからStreamのデータとして返す Nextat Inc. 51
  52. Wiresharkによる通信内容の確認 HTTP/2の通信はそのままでは確認が困難なので、Wiresharkの出番 有名なネットワークプロトコル解析⽤のソフト TLS鍵情報のログを参照すれば、ブラウザからのHTTP/2通信内容を⾒られる 参考: HTTP/2 over TLS の通信をダンプする⽅法 gRPCにも対応している

    protoファイルの検索パスを指定すればより詳細な情報も出せる模様 参考: https://grpc.io/blog/wireshark/ ⾮常にお世話になりました Nextat Inc. 52
  53. ⼀旦 GET / に対しStreamでレスポンスを返し、ブラウザとWiresharkで確認 Nextat Inc. 53

  54. (GRPC)(PROTOBUF) Wiresharkのお墨付き! これで勝ったも同然! Nextat Inc. 54

  55. C#実装を参考にChatServiceを分離 先程のLenght-Prefix-Messageの処理とAmpのStream関連クラスを使い ServerStreamWriterを作成 // ChatService の メッセージ購読の処理 public function subscribe(GPBEmpty

    $request, ServerStreamWriter $streamWriter) { $this->chatMessageSubject->subscribe( function (ChatMessage $chatMessage) use ($streamWriter) { $this->logger->debug("emit", [$chatMessage->getBody()]); $streamWriter->write($chatMessage); }, null, function () use ($streamWriter) { $streamWriter->complete(); } ); return new Success(); } Nextat Inc. 55
  56. RPCごとのルーティングを追加 Amp HTTP Server⽤のルータを追加 各RPCは POST /{ サービス名}/{ メソッド名} に対応

    $router = new Amp\Http\Server\Router(); $chatService = new ChatService( new Rx\Subject\Subject(), $logger ); $router->addRoute( 'POST', '/GrpcWebChat.Chat/SendMessage', new SendMessageRequestHandler($chatService, $requestBodyDeserializer, $responseFactory) ); $router->addRoute( 'POST', '/GrpcWebChat.Chat/Subscribe', new SubscribeRequestHandler($chatService, $responseFactory) ); Nextat Inc. 56
  57. Envoyの設定 EnvoyのProxy設定で少しハマった ローカル環境だったので⾃⼰署名証明書の設定に⼿間取る Envoyのタイムアウト系の設定ですぐにメッセージの購読が途切れたりしていた Nextat Inc. 57

  58. 完成したもの https://github.com/n1215/grpc-web-chat/tree/main/server-amphp 先に作ったブラウザ側のgRPC-Webのチャットクライアントアプリで動作を確認 Unary RPC、Server Streaming RPCが可能 厳密なgRPCサーバにはなっていない Client Streaming、Bidirectional

    Streamingは未検証 grpc-timeoutなど、リクエストヘッダ周りは完全無視 サーバ側のgRPC Serviceのコード⾃動⽣成には⼿を出していない protobufのMessageはPHPクライアント⽤のものを利⽤ Nextat Inc. 58
  59. PHPサーバ側のログ Nextat Inc. 59

  60. x-powered-by: PHP8.0.0 Nextat Inc. 60

  61. 6. まとめ gRPCとProtocol Buffersを使ったスキーマ駆動はいいぞ gRPC-Web⽤のプロキシを⽤意すればブラウザからでも使える In-process ProxyかEnvoyが⼿軽 gRPC-Web⽤に⾃動コード⽣成するのは簡単 将来的にはgRPCがネイティブにブラウザから使えるようになる展望 PHPのgRPCサーバは公式サポートがない

    が、Server Streaming RPCに対応したPHP製サーバは作れる PHP製gRPCサーバとgRPC-Webの組み合わせも夢じゃない C#のgRPC-Webサーバ実装はめっちゃ楽でした Nextat Inc. 61
  62. 最近の国内PHP系カンファレンスのgRPC関連発表 php grpc-client in phpcon2018 (2018/12) gRPCクライアント実装は可能だが、通常はPHPでgRPCサーバはできない PHPでもgRPCサーバを⽴てたいだけの⼈⽣だった (2019/02) サーバ側もUnary

    RPCは可能だが、Streaming RPCは未検証 PHPでgRPCってどこまでいけるの?(2019/12) Streaming RPCはまだはやかった 本発表 (2021/03) Server Streaming RPCもいけるやん! ← イマココ to be continued... Nextat Inc. 62
  63. 次のどなたかの発表を楽しみにしています!! Nextat Inc. 63

  64. PR: ITエンジニアを募集しています 株式会社Nextat nextat.co.jp 受託開発 業務システム、ECサイト、スマホアプリ... 本社は京都ですが、東京・名古屋などリモートワーク実績あり 設計の話に付き合ってくれる⽅⼤歓迎! Nextat Inc.

    64
  65. ご清聴ありがとうございました Nextat Inc. 65