CBOR API ボディ
[generate.api.cbor]enabled = truepopcornweb.toml にこれを書いて pw generate を実行すると、すべての API
エンドポイントが JSON と並んで CBOR
を話すようになります。Content-Type: application/cbor で届いたリクエストはボディを
CBOR としてデコードし、リクエストの Accept ヘッダが application/cbor を名指し
していればレスポンスも CBOR で返します。それ以外は何も変わりません。JSON・フォーム・
マルチパートのクライアントは今までどおりのバイト列を受け取り、同じハンドラが全員を
さばきます。
デフォルトで無効なのは、ほとんどの API は CBOR クライアントに出会わないうえ、この
スイッチがタダではないからです。API 型ごとに生成されるコーデックのコードがおよそ
倍になり、TinyGo や wasm のビルドはそれをサイズとして感じます。切ったままなら
pw generate は今日と同一バイトの出力を再現し、バイナリに CBOR のコードは一切
リンクされません。クライアントが組み込みデバイスや wasm モジュールで、バイナリ
ペイロードが base64-in-JSON に勝つ場面が来たら入れてください。
ハンドラは変わらない
Section titled “ハンドラは変わらない”func createUser(w http.ResponseWriter, r *http.Request) { input, err := pw.Parse[CreateUserRequest](r) if err != nil { pw.WriteProblem(w, r, err) return } user, err := store.Create(r.Context(), input) if err != nil { pw.WriteProblem(w, r, err) return } pw.WriteAPI(w, r, user)}ごく普通のハンドラですが、実はこれで CBOR 対応は全部済んでいます。ネゴシエーションは
pw generate が CreateUserRequest と user の型に対して出力するコードの中に
あり、JSON コーデックを生み出すのと同じ呼び出し箇所が CBOR コーデックも生み出す
からです。JSON クライアントは一方の分岐を、CBOR クライアントはもう一方を通ります。
curl http://localhost:8080/users \ -H 'Content-Type: application/cbor' \ -H 'Accept: application/cbor' \ --data-binary @user.cborリクエスト側では、ボディはテキストキーの CBOR map 1 つで、JSON ボディが埋めるのと
同じフィールドを埋めます。payload タグ、ネストした構造体、バリデーション、その他の
リクエストバインディングはまったく同じに振る舞い、
未知のキーは JSON デコーダと同じように読み飛ばされます。path・query・header・cookie の
入力はその横で変わらずバインドされます。+cbor の構造化構文サフィックス
(たとえば application/senml+cbor)も CBOR として扱われます。
レスポンス側のルールは意図的に狭くしてあります。CBOR が返るのは Accept ヘッダが
application/cbor を明示的に名指ししたときだけで、ワイルドカードは数えません。
ブラウザの fetch はデフォルトで Accept: */* を送るので、ワイルドカードで
フォーマットが切り替わる仕様だと、世のブラウザコードすべてに予期しないバイナリボディを
握らせることになります。CBOR が欲しいクライアントはそう言う。言わなければ JSON の
まま。レスポンスには Vary: Accept が付くので、共有キャッシュは 2 つの表現を別の
エントリとして扱います。
Problem ドキュメントは意図的な例外です。バリデーション失敗も 500 も、CBOR
クライアントに対して application/problem+json で答えます。problem ライタは
「失敗してはならないパス」に載っており、JSON のエラーはどのクライアントもすでに
パースできるからです。
プロファイルキー
Section titled “プロファイルキー”生成されるコーデックは CBOR のサブセットを実装しており、2 つのキーでさらに絞れます。
[generate.api.cbor]enabled = truereject_floats = falsesorted_keys = falseどちらもデフォルトはオフで、普通の API はオフが正解です。
reject_floats = true は float64 フィールドを生成時エラーにし、リクエストボディに
浮動小数点数が届いたらデコードエラーにします。頼む理由がなさそうな制限に見えますし、
たいていのスキーマでは実際そうです。ただ、金額や固定小数点のセンサ値をスケール済み
整数で運ぶプロトコルでは、丸めを黙って持ち込みかねないフィールド種別を、将来の編集者の
記憶力ではなくビルドに拒否させたくなります。そのためのキーです。
sorted_keys = true は map のメンバを構造体フィールド順ではなく RFC 8949 の
bytewise キー順で出力します。決定論的エンコーディングを検証するクライアント向けです。
ビルド時の帰結が 1 つあります。map[string]T フィールドは生成時に順序を約束できない
ため、このキーが立っている間は生成エラーになります。
プロファイルは生成フィンガープリントの一部です。プロファイルについて意見の合わない
2 台のマシンは異なる生成コードを作り、それは pw check がドリフトとして報告します。
つまりプロファイル変更はコミットされる目に見える出来事であって、誰かのフラグが
こっそり起こすものではありません。
CBOR ボディはデコード前に丸ごと読み込まれるため、その上限は転送サイズではなく
デコードメモリに答えるものです。だから server.max_request_body とは別のキーを
持ちます。
[server]cbor_max_body = 4194304デフォルトは 1 MiB で、net/http と fasthttp の両ランタイムが同じ値を共有し、0 は
その 1 MiB を保ちます。超過したボディはデコードが始まる前に 413 の problem
ドキュメントで拒否されます。
payload:"*" の rest マップ には対応する
CBOR の形がなく、そのルートを黙って JSON 専用にする代わりに、生成がエラーとして
報告します。そのルートの入力型を分割するか、そのプロジェクトでは CBOR を切って
ください。
明示的な Accept を付け忘れたクライアントには JSON が返ります。ネゴシエーション
ルールが正しく働いているだけなのですが、開発中は「CBOR が効いていない」ように
見えます。何より先にリクエストヘッダを確認してください。
OpenAPI ドキュメントには、スイッチを入れた
時点で application/cbor のリクエスト・レスポンス content が自動的に載ります。
生成クライアントや API コンソールは、別のアノテーションなしでこのフォーマットを
見つけられます。
使わないほうがよいとき
Section titled “使わないほうがよいとき”クライアントが全部ブラウザで、fetch と JSON を話しているなら、切ったままにして
ください。ブラウザにネイティブの CBOR エンコーダはなく、JSON パスはすでに
リフレクションなしの生成コードで、スイッチはどのリクエストも通らない分岐にバイナリ
サイズを費やすだけです。圧縮ガイドの理屈の縮小版がここでも成り立ちます。誰も使わない
能力は中立ではなく、重さです。
