まえがき
もともとgRPCだったプロジェクトを、Flutter webに対応させるためにConnectRPCに乗り換えた際につまった話。
バージョン | |
---|---|
go | v1.2.4 |
flutter_sdk | v3.24.3 |
Connect-dart | v0.3.0 |
ConnectRPCについて
ConnectRPCとは...
Connect is a family of libraries for building browser and gRPC-compatible HTTP APIs: you write a short Protocol Buffer schema and implement your application logic, and Connect generates code to handle marshaling, routing, compression, and content type negotiation. It also generates an idiomatic, type-safe client in any supported language.
要約すると...
gRPC互換のHTTP APIを構築するためのライブラリ群で、proto書いて自動生成で型安全なコードも生成してくれるやつ(詳しくは自分で調べてください)
詰まったところ
gRPCで実装していたコードをConnectに置き換えた後、Flutter webd通信をすると
[invalid_argument] protocol error: promised 246 bytes, received 331
at Object.throw_ [as throw]
こんな感じでエラーを吐く。
ライブラリの当該箇所の実装を見ると
if (expLength != null && actLength > expLength) {
throw ConnectException(
Code.invalidArgument,
'protocol error: promised $expLength bytes, received $actLength',
);
}
つまり、プロトコルが本来受け取る予定だったデータ量(246バイト)と、実際に届いたデータ量(331バイト)が一致していないときにエラーを吐く。
このとき、実際受け取るデータ量が多いということは、本来受け取る予定だったデータ量というのは圧縮された値でありそう。
原因: ブラウザが自動で gzip を展開する
- サーバーは Content-Encoding: gzip を付けてレスポンスを返す
- ブラウザ (Fetch / XMLHttpRequest) はこのヘッダーを見て自動で解凍する
- Connect の 5 バイトエンベロープには 圧縮後サイズ が入る
- Dart 側で合計されるのは 解凍後のサイズ
そのため、宣言サイズ 246 バイトと伸長後 331 バイトが一致せず、invalid_argument 例外が発生する。
暫定の解決策
サーバー側のハンドラーの圧縮を無効化する
p, h = hogehogeconnect.NewFugaServiceHandler(
&hogefuga.Server{},
connect.WithCompressMinBytes(math.MaxInt)
)
WithCompressMinBytes(math.MaxInt)
を追加してgzipを無効化する。
まとめ
-
Flutter Web ではブラウザが Content-Encoding: gzip を自動展開
-
エンベロープのサイズは「圧縮後バイト数」なので、展開後と一致しなくなる
-
暫定策として圧縮を無効化するか、HTTP の Content-Encoding を外すことで解決
ライブラリ側(Connect-dart のブラウザ版)が、このパターンに未対応なのが直接原因
Connect プロトコル自体は本来 Connect-Content-Encoding ヘッダー(または Streaming-Content-Encoding)を使い、ブラウザに解凍させない設計だが、Go 実装は歴史的理由で HTTP 標準の Content-Encoding も出す ため、ズレが発生する
参考文献
https://connectrpc.com/docs/protocol/
https://connectrpc.com/docs/go/serialization-and-compression/