[インデックス 14861] ファイルの概要
このコミットは、Goのnet/http
パッケージにおいて、HTTPレスポンスのチャンク処理の前にデータをバッファリングするメカニズムを導入するものです。これにより、特に小さなレスポンスの場合に、Content-Length
ヘッダを正確に計算して設定できるようになり、パフォーマンスの向上とHTTP/1.0クライアントとの互換性改善が図られています。また、Content-Type
の自動検出(スニッフィング)も改善されています。
コミット
commit bef4cb475c0638ab5193f75f2683b35a7c7f6547
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Jan 11 10:03:43 2013 -0800
net/http: buffer before chunking
This introduces a buffer between writing from a handler and
writing chunks. Further, it delays writing the header until
the first full chunk is ready. In the case where the first
full chunk is also the final chunk (for small responses), that
means we can also compute a Content-Length, which is a nice
side effect for certain benchmarks.
Fixes #2357
R=golang-dev, dave, minux.ma, rsc, adg, balasanjay
CC=golang-dev
https://golang.org/cl/6964043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bef4cb475c0638ab5193f75f2683b35a7c7f6547
元コミット内容
ハンドラからの書き込みとチャンクの書き込みの間にバッファを導入します。さらに、最初の完全なチャンクが準備できるまでヘッダの書き込みを遅延させます。最初の完全なチャンクが最終チャンクでもある場合(小さなレスポンスの場合)、Content-Length
を計算できるようになり、特定のベンチマークにとって良い副次効果となります。
Issue #2357 を修正します。
変更の背景
この変更の主な背景には、Goのnet/http
パッケージにおけるHTTPレスポンスのチャンク処理の挙動に関する課題がありました。具体的には、Issue #2357で報告された問題に対応しています。
従来のnet/http
のチャンク処理では、ハンドラがデータを書き込むとすぐにチャンクヘッダが付与されて送信される傾向がありました。この挙動は、特に小さなレスポンスの場合にいくつかの問題を引き起こしていました。
Content-Length
の欠如: HTTP/1.1では、レスポンスボディのサイズが事前に不明な場合に「チャンク転送エンコーディング(Chunked Transfer Encoding)」を使用します。しかし、レスポンスボディが非常に小さい場合でもチャンク形式で送信されることがあり、この場合Content-Length
ヘッダは付与されません。Content-Length
ヘッダは、クライアントがレスポンスボディの終わりを正確に知るために重要であり、特にHTTP/1.0のkeep-alive
接続の維持や、一部のプロキシやキャッシュの動作に影響を与える可能性がありました。- パフォーマンスのオーバーヘッド: 小さなデータ片が頻繁に書き込まれると、そのたびにチャンクヘッダが付与され、ネットワーク上で多くの小さなパケットが送信されることになります。これは、チャンクヘッダ自体のオーバーヘッドと、TCP/IPレベルでの効率の低下を招き、パフォーマンスに悪影響を与える可能性がありました。
Content-Type
スニッフィングの課題:net/http
は、Content-Type
ヘッダが明示的に設定されていない場合に、レスポンスボディの最初の数バイトを「スニッフィング」して適切なContent-Type
を推測する機能を持っています。しかし、チャンク処理がすぐに開始されると、スニッフィングに必要な十分なデータがバッファリングされる前にヘッダが送信されてしまい、正確なContent-Type
の検出が困難になることがありました。
このコミットは、これらの問題を解決するために、ハンドラからの書き込みと実際のチャンク送信の間にバッファを導入し、ヘッダの送信を遅延させることで、より効率的で柔軟なレスポンス処理を実現することを目的としています。
前提知識の解説
HTTPチャンク転送エンコーディング (Chunked Transfer Encoding)
HTTP/1.1で導入された転送エンコーディングの一種で、メッセージボディの長さを事前に知らなくても、動的に生成されるコンテンツやストリーミングデータなどを送信できるようにするメカニズムです。
- 仕組み: メッセージボディは、複数の「チャンク」に分割されて送信されます。各チャンクは、そのチャンクのデータ長(16進数)とCRLF(改行コード)で始まり、その後に実際のデータとCRLFが続きます。メッセージの終わりは、長さが0のチャンク(
0\r\n\r\n
)で示されます。 - 利点:
- サーバーは、レスポンスボディ全体のサイズが確定するのを待たずに、データの送信を開始できます。
- 大きなファイルをストリーミングしたり、リアルタイムでデータを生成したりする際に特に有用です。
Content-Length
との関係:Transfer-Encoding: chunked
ヘッダが存在する場合、Content-Length
ヘッダは存在してはなりません。これらは互いに排他的なメカニズムです。
HTTP Content-Length
ヘッダ
HTTPメッセージボディの正確なバイト長を示すヘッダです。
- 仕組み: サーバーがレスポンスボディの全長を事前に知っている場合に設定されます。クライアントは、このヘッダの値に基づいて、メッセージボディの終わりを判断します。
- 利点:
- クライアントは、レスポンスボディの受信完了を正確に知ることができます。
- HTTP/1.0の
keep-alive
接続を維持するために重要です。 - プロキシやキャッシュがコンテンツを効率的に処理するために利用されます。
HTTP Content-Type
スニッフィング (DetectContentType
)
Content-Type
ヘッダがサーバーから提供されない、または不正確な場合に、WebブラウザやHTTPクライアントがレスポンスボディの内容を検査して、そのメディアタイプ(MIMEタイプ)を推測するプロセスです。
- 仕組み: 通常、レスポンスボディの最初の数バイト(Goの
net/http
ではデフォルトで512バイト)を読み込み、既知のファイルシグネチャやパターン(例: HTMLの<html>
タグ、JPEGのマジックナンバーなど)と照合して、適切なContent-Type
を決定します。 - Goの
net/http
におけるDetectContentType
: Goのnet/http
パッケージには、このスニッフィング機能を提供するDetectContentType
関数があります。これは、Content-Type
ヘッダが設定されていない場合に自動的に呼び出され、レスポンスボディの最初の部分に基づいて適切なMIMEタイプを推測します。 - 課題: スニッフィングは便利ですが、セキュリティ上の脆弱性(MIMEタイプ混同攻撃など)を引き起こす可能性もあります。そのため、サーバーは常に正確な
Content-Type
ヘッダを送信し、可能であればX-Content-Type-Options: nosniff
ヘッダを使用してブラウザのスニッフィングを無効にすることが推奨されます。
Go net/http
パッケージの役割
Goの標準ライブラリであるnet/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。このパッケージは、HTTPプロトコルの詳細(ヘッダの解析、ボディの読み書き、チャンク処理など)を抽象化し、開発者が高レベルなAPIでWebアプリケーションを構築できるようにします。
http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェースです。ハンドラはこのインターフェースを通じて、ステータスコード、ヘッダ、レスポンスボディをクライアントに送信します。- 自動チャンク処理:
net/http
サーバーは、Content-Length
ヘッダが設定されていない場合に、自動的にチャンク転送エンコーディングを使用してレスポンスを送信します。
技術的詳細
このコミットは、net/http
パッケージのHTTPレスポンス処理フローを大幅に再構築しています。主な変更点は、ハンドラからの書き込みと実際のネットワークへの書き込みの間に新しいバッファリング層とchunkWriter
構造体を導入したことです。
新しい書き込みフロー
変更後のレスポンス書き込みフローは以下のようになります。
*response
(ResponseWriter): ハンドラがhttp.ResponseWriter
インターフェースを通じてデータを書き込みます。(*response).w
(*bufio.Writer
):*response
は、bufferBeforeChunkingSize
(デフォルト2048バイト)のバッファを持つ*bufio.Writer
をラップします。ハンドラからの最初の書き込みは、まずこのバッファに蓄積されます。chunkWriter
:(*response).w
は、その出力先としてchunkWriter
をラップしています。chunkWriter
は、ヘッダの最終決定(Content-Length
やContent-Type
の設定)と、必要に応じたチャンクヘッダの書き込みを担当します。conn.buf
(bufio.Writer
):chunkWriter
は、最終的に接続のバッファ(conn.buf
、デフォルト4KB)に書き込みます。rwc
(net.Conn
):conn.buf
は、実際のネットワーク接続(net.Conn
)にデータをフラッシュします。
chunkWriter
の導入
chunkWriter
は、*response
構造体からチャンク処理とヘッダの最終決定ロジックを分離するために導入されました。chunkWriter
は、res
(関連する*response
へのポインタ)、header
(response.handlerHeader
のディープコピー)、wroteHeader
(ヘッダが送信されたかを示すフラグ)、chunking
(チャンク転送エンコーディングを使用するかどうか)のフィールドを持ちます。
ヘッダの遅延書き込みとContent-Length
の計算
chunkWriter.Write(p []byte)
メソッドが呼び出された際、まだヘッダが書き込まれていない場合(!cw.wroteHeader
)、cw.writeHeader(p)
が呼び出されます。cw.writeHeader(p)
は、以下の重要な処理を行います。cw.wroteHeader = true
を設定し、ヘッダが論理的に書き込まれたことを示します。response.handlerHeader
のディープコピーをcw.header
に保存します。これにより、ハンドラがWriteHeader
呼び出し後もヘッダを変更しても、既に送信されたヘッダには影響しません。Content-Length
の計算: ハンドラが終了しており(w.handlerDone
がtrue)、かつContent-Length
ヘッダが設定されておらず、かつ最初の書き込み(p
)が空でない場合、p
の長さに基づいてContent-Length
を設定します。これは、特に小さなレスポンスの場合に、チャンクではなく固定長でレスポンスを送信できるようにするための重要な改善です。Content-Type
のスニッフィング:Content-Type
ヘッダが設定されていない場合、DetectContentType(p)
を使用してp
の内容からContent-Type
を推測し、ヘッダに設定します。- 最終的なヘッダ(ステータスライン、各種ヘッダフィールド、CRLF)を
w.conn.buf
に書き込みます。
Content-Type
スニッフィングの改善
- 以前は、
response
構造体にneedSniff
フラグがあり、sniff()
メソッドが別途呼び出されていました。 - この変更により、
chunkWriter.writeHeader
内でDetectContentType(p)
が直接呼び出されるようになり、最初のデータチャンクが利用可能になった時点でContent-Type
が決定されます。これにより、スニッフィングがより確実に行われるようになります。
バッファリングの導入
response
構造体は、*bufio.Writer
であるw
フィールドを持つようになりました。このw
は、chunkWriter
をラップし、bufferBeforeChunkingSize
(2048バイト)のバッファサイズで初期化されます。- ハンドラからの書き込みは、まずこの
w
にバッファリングされます。バッファが満たされるか、Flush()
が呼び出されるか、ハンドラが終了するまで、実際のネットワークへの書き込みは遅延されます。 - これにより、小さな書き込みが結合され、チャンクヘッダの数が減り、ネットワーク効率が向上します。
ReadFrom
メソッドの変更
ReadFrom
メソッドも、新しいバッファリングとヘッダ遅延書き込みのロジックに合わせて調整されました。needsSniff()
がtrueの場合、sniffLen
(512バイト)までのデータをio.LimitReader
で読み込み、Content-Type
スニッフィングを可能にします。- その後、
w.w.Flush()
とw.cw.flush()
を呼び出して、バッファリングされたデータとヘッダをフラッシュします。 chunking
モードでない場合、かつio.ReaderFrom
インターフェースを実装している基盤の接続がある場合、直接ReadFrom
を使用して効率的なデータ転送を行います。
finishRequest
とFlush
の変更
finishRequest
メソッドは、ハンドラが終了したことを示すw.handlerDone = true
を設定し、w.w.Flush()
とw.cw.close()
を呼び出して、残りのバッファをフラッシュし、チャンク処理を終了させます(0バイトチャンクの送信など)。Flush
メソッドも、w.w.Flush()
とw.cw.flush()
を呼び出すように変更され、バッファリングされたデータを強制的にフラッシュします。
これらの変更により、net/http
サーバーは、より効率的にレスポンスを処理し、特に小さなレスポンスや動的に生成されるコンテンツにおいて、Content-Length
の利用可能性とパフォーマンスを向上させることができました。
コアとなるコードの変更箇所
このコミットは、主に以下のファイルに影響を与えています。
src/pkg/net/http/header.go
src/pkg/net/http/serve_test.go
src/pkg/net/http/server.go
src/pkg/net/http/header.go
func (h Header) clone() Header
メソッドが追加されました。これは、Header
マップのディープコピーを作成するために使用されます。response
のhandlerHeader
がchunkWriter
に渡される際に、ハンドラがヘッダを後から変更しても影響が出ないようにするために導入されました。
src/pkg/net/http/serve_test.go
- 既存のテストが、新しいバッファリングとチャンク処理の挙動に合わせて修正されています。
TestServerBufferedChunking
テストが有効化されました。このテストは、以前はIssue 2357のためにスキップされていましたが、今回の変更によって修正されたため、再度実行されるようになりました。このテストは、1バイトずつ書き込まれるチャンクレスポンスが、チャンクヘッダが付与される前にバッファリングされることを検証します。w.(Flusher).Flush()
の呼び出しが追加され、ヘッダが強制的に送信されるシナリオをテストしています。
src/pkg/net/http/server.go
このファイルが最も広範な変更を受けています。
conn
構造体:body []byte
フィールドが削除されました。これは、以前のContent-Type
スニッフィングのためのバッファでしたが、新しいchunkWriter
とresponse.w
によるバッファリングに置き換えられました。bufferBeforeChunkingSize
定数:const bufferBeforeChunkingSize = 2048
が追加されました。これは、チャンク処理の前にデータをバッファリングするサイズを定義します。chunkWriter
構造体: 新しい構造体chunkWriter
が定義されました。res *response
: 関連するresponse
へのポインタ。header Header
:response.handlerHeader
のディープコピー。wroteHeader bool
: ヘッダが送信されたかどうかのフラグ。chunking bool
: チャンク転送エンコーディングを使用するかどうかのフラグ。
chunkWriter
のメソッド:Write(p []byte) (n int, err error)
: データを書き込み、必要に応じてチャンクヘッダを追加します。ヘッダがまだ書き込まれていない場合はwriteHeader
を呼び出します。flush()
: ヘッダがまだ書き込まれていない場合はwriteHeader
を呼び出し、conn.buf
をフラッシュします。close()
: ヘッダがまだ書き込まれていない場合はwriteHeader
を呼び出し、チャンク処理を終了させるための0バイトチャンクを書き込みます。
response
構造体:wroteHeader
フィールドの意味が「論理的にヘッダが書き込まれた」に変更されました。chunking
フィールドが削除され、chunkWriter
に移されました。header
フィールドがhandlerHeader
にリネームされ、ハンドラがアクセスするヘッダを表すようになりました。w *bufio.Writer
: 新しいバッファリング層として追加されました。chunkWriter
をラップします。cw *chunkWriter
: 新しいchunkWriter
インスタンスへのポインタ。handlerDone bool
: ハンドラが終了したかどうかを示すフラグ。
response
のメソッド:Header() Header
:w.handlerHeader
を返すように変更されました。WriteHeader(code int)
: ヘッダの最終決定ロジックがchunkWriter.writeHeader
に移譲されました。ここでは、ステータスコードの設定と、handlerHeader
のクローンをcw.header
に設定する処理が行われます。ReadFrom(src io.Reader) (n int64, err error)
:Content-Type
スニッフィングとバッファリングの新しいロジックに合わせて大幅に書き換えられました。Write(data []byte) (n int, err error)
: 実際の書き込み処理がw.w.Write(data)
に委譲されるようになりました。これにより、ハンドラからの書き込みはまずresponse.w
のバッファに蓄積されます。finishRequest()
: ハンドラ終了時の処理が変更され、w.handlerDone = true
を設定し、w.w.Flush()
とw.cw.close()
を呼び出すようになりました。Flush()
:w.w.Flush()
とw.cw.flush()
を呼び出すように変更されました。Hijack()
:w.cw.flush()
を呼び出すロジックが追加され、ハイジャック前にバッファがフラッシュされるようにしました。
writeHeader
関数の削除: 以前のresponse.writeHeader
関数は削除され、そのロジックはchunkWriter.writeHeader
メソッドに統合されました。sniff
関数の削除: 以前のresponse.sniff
関数は削除され、そのロジックはchunkWriter.writeHeader
内でDetectContentType
を直接呼び出す形に統合されました。
これらの変更は、HTTPレスポンスの生成と送信の内部メカニズムを根本的に変更し、より堅牢で効率的な処理を実現しています。
コアとなるコードの解説
このコミットの核心は、net/http
サーバーがHTTPレスポンスをクライアントに送信する方法を根本的に変更した点にあります。特に、chunkWriter
構造体の導入と、response
構造体におけるバッファリング層の再構築が重要です。
chunkWriter
構造体とその役割
chunkWriter
は、HTTPレスポンスのヘッダの最終決定と、チャンク転送エンコーディングの管理を担当する新しいコンポーネントです。
- ヘッダの最終決定:
chunkWriter.writeHeader(p []byte)
メソッドがその主要な役割を担います。- このメソッドは、
response.WriteHeader
が呼び出された後、またはハンドラが最初のデータを書き込んだ際に、一度だけ呼び出されます。 - ここで、
Content-Length
ヘッダがまだ設定されておらず、かつハンドラが既に終了している(w.handlerDone
がtrue)場合、最初のデータチャンクp
の長さに基づいてContent-Length
を設定します。これにより、小さなレスポンスでもContent-Length
が付与される可能性が高まります。 Content-Type
ヘッダが設定されていない場合、DetectContentType(p)
を使用してp
の内容からContent-Type
を推測し、ヘッダに設定します。Transfer-Encoding: chunked
ヘッダの追加や、Connection: close
ヘッダの管理など、HTTPプロトコルに準拠したヘッダの調整を行います。- 最終的に、ステータスラインとすべてのヘッダフィールドを
w.conn.buf
(実際のネットワークバッファ)に書き込みます。
- このメソッドは、
- チャンク処理の管理:
chunkWriter.Write(p []byte)
メソッドは、chunking
フラグがtrueの場合、書き込まれるデータp
の前にチャンクサイズ(16進数)とCRLFを付与し、データp
の後にCRLFを付与します。chunkWriter.close()
メソッドは、チャンク処理を終了させるために、長さ0の最終チャンク(0\r\n\r\n
)を書き込みます。
response
構造体におけるバッファリング層
response
構造体は、http.ResponseWriter
インターフェースの実装であり、ハンドラからの書き込みを受け取ります。
w *bufio.Writer
:response
構造体には、w
という新しいフィールドが追加されました。これは*bufio.Writer
のインスタンスであり、その出力先はchunkWriter
です。bufio.NewWriterSize(w.cw, bufferBeforeChunkingSize)
で初期化され、bufferBeforeChunkingSize
(2048バイト)の内部バッファを持ちます。- ハンドラが
response.Write(data []byte)
を呼び出すと、データはまずこのw
の内部バッファに蓄積されます。 - このバッファリングにより、小さな書き込みが結合され、
chunkWriter
に渡されるデータの塊が大きくなります。これにより、チャンクヘッダの数が減り、ネットワーク効率が向上します。
- ヘッダの遅延:
response.Write
が呼び出されても、response.w
のバッファが満たされるか、response.Flush()
が呼び出されるか、ハンドラが終了するまで、実際のHTTPヘッダの送信は遅延されます。この遅延が、Content-Length
やContent-Type
をより正確に決定する機会を提供します。
処理の流れの要約
- ハンドラが
http.ResponseWriter.Write()
を呼び出す。 - データは
response.w
(bufio.Writer
)の内部バッファに蓄積される。 response.w
のバッファが満たされるか、Flush()
が呼び出されるか、ハンドラが終了すると、バッファの内容がchunkWriter.Write()
に渡される。chunkWriter.Write()
は、まだヘッダが送信されていない場合、chunkWriter.writeHeader()
を呼び出してヘッダを最終決定し、Content-Length
やContent-Type
を設定する。chunkWriter.Write()
は、必要に応じてチャンクヘッダを付与し、データをconn.buf
(実際のネットワークバッファ)に書き込む。conn.buf
がフラッシュされると、データがネットワーク経由でクライアントに送信される。
この新しいアーキテクチャにより、Goのnet/http
サーバーは、より柔軟かつ効率的にHTTPレスポンスを処理できるようになり、特に動的に生成されるコンテンツや小さなレスポンスにおいて、パフォーマンスとプロトコル準拠が改善されました。
関連リンク
- Go Issue 2357: https://github.com/golang/go/issues/2357
- Go CL 6964043: https://golang.org/cl/6964043
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGqiZ4O9YCHZe79tf5SAtYvUWwdhYt8sO39KQnMSZouQVCB2fQsldahfWoAPk0-UczW6tbdeUBdY5dTL6sq6yzkYa-sGjp8LTmpdEt2wRUcI7Wjmh5k6zpC4xHoR-Mf9m4ydGsk0ZROcTRhXSJrZltNcGanYqhxKz4HsWdKCzNWxs7TXdM3fluYhUTGZCSwI8zo9cR-
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEmFv5xJOANVKfM2IKChi5HlQdicwfCROOzOjPITybxuxUYyzkn1qL2blRVBLfgoZzB-6ZIlbEWVQbE6wIsJnRgc13FOSTrR5_J4qkIcNsT8Snbb7Df9uVAASxZIsbewsfpJ8ZUz_YIUrM2MkeYTpIHVyRwFM9O0EnK6sd3b9RI9GqbxjSt
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGJ8wGwGTjqtUSM69_aj3nCej5W0EBht2LRq0elYcsP_y9wroN6Nk9Rv93qhI1VmsyImSnLpLluRJvy_eHVbK_AT6SmZgIK-wUhqv3Q4kqq127LI9Iptw4lE5KfmLWgpbnMcY2fQNGiPSYo1NCnx57XJbMzp1WPq36_4_xbwrH-3ADRRtqS7KTH5BZCyx5DbW4iKdN_Bw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQETa4gV6ibfVpvM0E80N_ivDrjXeFsBdgADSRQbNZ_seGF9KAg4dczjzasiLt5YEmAPpADC0xoshGI1Ed83Cs6fnj9cCWDSdJswTELwLZdRxFWDbhxz4oOyJGrGHzw_ISnixVRDCasOWTHf-jibmC_P_2EbEu-hBEN6ZPaFRE2eNoIePX8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEEd3xcfp450Y4rXoQbvxMXN24cYWhdTOsxDB0iEdz1cnbIzq50fv4Y3NllPVz3jJ5DVldjdBX71SovetFdY90db4rsbz_Bk4vlou4lv3wEn1KxVuttskWNfHaywMEZv4X9YSiYJVT5555M
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHlJIQe5KA3zFaVVQp4FKyWw0Y9Ai13tSoAAKKKWmNs3decFhI0UKc6ozZaUK4AK9TOaDNepSayQ242BYIcge_rVUaULKUYSwvZbJjd3DLNxkE_biwt0RB-gRawWDvObxkTb_0sf6M_cQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFEcRb3AyxR9KqYzgJ1NEOAHeJIqDUr4_Zmsn9okaOiOQkcHBCuawYiUabeBCv8IRQFtjKObBGESzIyXR3uY3_MtIlRCxG1j18sYBsMz7zJBoniG6P_XaPJ_24RJdK4yw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEMdjCEh4Vi0CFicanlONmZUTmJZXINbUEHr39amIrhRhSNGHNaxahqzgJ1URywsHsQxtetCkhzK08e8QCRSziTemWvfGtUmsQ-vlgXvXdX_LsMiy-_wWKUT9vYtMLIpXnOxGY=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHIxj3VLtwjmvtULdJayeTiAA6_2hYZ24PPG0xDbbIb0v68Tro8RMR8Q8_5D4SFgtJZL7oLVkZaB-0K0hngKFh5OJUTbofgYECMkUmm8bO4meYFeQLs2BVBgjgYrOqusKeroWw=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGa1hUBk_271q_fKcPGI9ZztIYMzAfNyA72MiLMlroIsgBB6pDS7o7nlJyzj5pcvnTTdlYrGetfcaqXh_8t4VJDgIEIw5JVwfbPzL07UYXYWPRTN-s1EU_bOj0wPN6Gwr22_aUQILLieRhWW3EjccrjG7MeDEus6DHn_GS3iYwhsvXkijRAL9-553dM
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEekewpeUSV-2BHTWqalLiNxg5wysdusVW9yqwSdAQGegOOjpzCRqpmOZxaHreO1rXUFTL-kxXxe6G72YTIniHETN0f4jPszvc5lzRB7F83PHOTzm5V7PCZtVYMlu4qanmnqzsGCrU4Mg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFH4xjOj1wrCCvumtfXlXlgTOh8pryD9AE43UN1DF6jVPgi-ApnjTekoDOG8ezN2CRiIEZHW6cNFXPtYXLHg0AUOS5aWLuzaxF0LbC-X2Dm_gqFuAHIPe32D1iOX_2rqGhSJyXa3T8-8PVPs1pWAvGXles=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFRFePF8MJ4llq0q9Glst2b7THiLVogz7_1pmsfrjazFrSflsDgQSmfxezA7d7W2sq6cuAQ6DhYl0eud99W3bru3eQ9eGMtX0aD_d2Fc056jXcvwa-JSa9eJaMF-LyUvILaRrE4CN-YARpFoIdUfdKzkuPOCDaVrQxzZ44tekxxZFxjh7ciCoogt327TtqZsHTK_nwv5rUD81esu9E=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEMZhnxv3k0rVBE8xBiPNATU5atMu6-ko3pFSa9nJENos6pOePURrN1X3Fw4fMdOuFXxv0mwd2fxMRUKQQ3l7ztCjjVUTtWKumsHoULldKnmubI-Vu7
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQELY8Gs8yHclGzRLp3fz-J4w2DmP86j_2FZ3Bs5Y6k000Go5NQ2g4fWJW40jqWVqdFQJcWatuluzDwx-qWsSa155eDqWSADuHuBRP_2zbsbV9gWWcc1k8ChbvFbOOBNpPJpBHlAUsnxktf-ijcZpN75VGorwstaznQjZi8E_w==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFnIjRARrRpWy2NtZ8TypfpOzlqtToitJO7jDPRkjmN2gsqrsDdju-osrA44DC9MYEbETpnSNyA0pNO_oCE5OenDAOUmKjfdtQUonIA_DtY769koa-LN6y6inUqomreorj58yyLlV_s_NA0ONLzwymrWCjAjb3UHA_yJxpHR5-Yx_z9PoM_KmRHJmGsdBfNV582yGdf