ブラウザから直接Geminiを叩かせない。このサイトの裏側を説明する記事がずっと欲しかったのに、作品紹介ばかりを書いて後回しにしていました。ある夜、エラーメッセージを取得する関数が静かに "SSL connect error" と呟いた瞬間、背筋を冷たい汗が走りました。冷や汗。そこでようやく、仕組みそのものを題材にする決心がつきました。
題材となるプロジェクトはありません。けれど .htaccess、config.php、gemini-api.php に散らばった工夫には物語があります。この記事では、その舞台裏を制作エッセイとして紹介します。公開してはいけない値はすべて *** に伏せていますので安心してください。
対象読者
- Gemini API をブラウザから使いたいが、キー流出が怖すぎるフロントエンド開発者
.htaccess で静的/動的を切り替える運用をしており、Rewriteの裏話を覗きたい人
- 429/403 地獄を味わい、APIキーのローテーション戦略を模索している人
記事に書いてあること
- 設定ファイルを金庫にして APIキーを複数管理し、Webサーバーの設定ファイルで外部から完全に遮断する方法
- APIプロキシが POSTオンリーのCORSゲートになり、ラウンドロビン+フォールバックを回している理由
- 90秒タイムアウトを受け入れてまで Search Grounding を支えた待機設計と、監視ログを感情で読む習慣
- 公開してはいけない情報を
*** で伏せながら読者に伝えるライティング上の工夫
作ったもの
仕組みを作品として語るなら、これは「Gemini専用の暗室」だと感じています。外から覗かれても光が漏れず、中では静かにフィルムを現像しているイメージです。
レッドアクト状態を全画面で確認する
最初に書いたのは「キーを何枚持ち、どう伏せるか」というメモだけでした。けれど実際にデモ化してみると、編集者モードと公開モードの切替だけで安心感が段違いです。
config.php を金庫に変える儀式
設定ファイルはGitには入れず、Webサーバーの設定ファイルで外部からのアクセスを拒否する設定を書いて鍵をかけています。ファイル内には以下のように複数キーを配列で保持し、記事では *** で伏せています。
⚠️
WARNING
3つのキーを持っているのに、1つ目だけを定数として再定義しているのはレガシー対応の名残りです。消しても動くのに、手が震えて消せませんでした。静かに震えた。
カウンターファイルの動きを体験する
一時ディレクトリに置いたカウンターファイルを読み書きするだけの仕組みですが、実際にボタンを押してみると気持ちが落ち着きます。いつ誰がどのキーを使ったか、感覚で理解できるからです。
.htaccess で扉を二重化する
Webサーバーの設定ファイルでは、gzip周りのエンコードを指定しつつ、node_modules や AI_CONVERSION_GUIDE.md もURL書き換えで閉じています。特に new_toppage/article.html を内部で article.php に書き換える一行は、クロール対策も兼ねた重要な扉でした。
このテーブルを触っていると、アクセスを許す/拒む判断を人力で下している気分になります。設定ファイルへの直叩きは即403。assets 配下は普通に静的配信。URL書き換えの矢印は内部だけを静かに通り抜けます。
設定ファイルへの直叩きは即403。config.php、.htaccess など、機密情報を含むファイルは外部から完全に遮断します。
assets 配下は普通に静的配信。CSS、JavaScript、画像など、公開して問題ないファイルは通常通り配信します。
URL書き換えの矢印は内部だけを静かに通り抜けます。article.html → article.php のような内部リライトは、ユーザーには見えませんが、サーバー内部では動的に処理されます。
Geminiプロキシの呼吸を整える
APIプロキシは POSTオンリーのCORSゲートです。OPTIONS リクエストなら200で終わり、GET なら405。ヘッダーに Access-Control-Allow-Origin: * を出しながらも、実際に通るのは JSON を抱えたPOSTだけ。
✅
SUCCESS
この4行を入れた瞬間、ブラウザのネットワークタブから余計なログが消えました。「特定の誰かに閉じる」のではなく「メソッド単位で閉じる」。そのほうが気持ちが穏やかです。
429/403 の階段を降りた日
Search Grounding を有効にしている midori301(観光情報を最新化した記事)から大量リクエストが来た夜、3回連続で429を踏みました。フォールバックのラダーが自動で降りていくのを見ながら、心臓はもっと激しく上下していました。救われた。
試行上限を3回に決めているので、キーが5枚あっても3枚で止まります。走らせすぎて逆にロックされるほうが怖いからです。
最初は、すべてのキーを試すようにしていました。でも、キーが5枚ある場合、5回すべて試すと、API側でロックされる可能性がありました。次に、試行上限を2回にしましたが、今度は有効なキーがあるのに諦めてしまうことがありました。最終的に、試行上限を3回にすることで、ロックを避けつつ、有効なキーを見つけられるようになりました。
走らせすぎて逆にロックされるほうが怖いからです。API側のレート制限を考慮し、3回の試行で止めることで、過度なリクエストを防ぎます。これにより、キー全体がロックされるリスクを最小化します。
待機72秒という覚悟
Grounding付きのリクエストは平均72秒。短い。と感じた人は demo6 を覗いてください。
タイムアウトを90秒に延ばす前は45秒で切っていました。結果、Search Groundingのレスポンスを待たずにタイムアウトを返してしまい、ユーザーの文章も途中で宙に浮きました。諦めるには早すぎたと痛感しました。
最初は、タイムアウトを30秒に設定していました。でも、通常のリクエストでも30秒を超えることがあり、タイムアウトが頻発しました。次に、45秒に延ばしましたが、Search Grounding付きのリクエストでは、まだ足りませんでした。最終的に、90秒に延ばすことで、Search Grounding付きのリクエストでも、ほとんどの場合にレスポンスが返ってくるようになりました。ただし、90秒を超えるリクエストも稀にあるため、完全ではありませんが、実用的なバランスになりました。
90秒という値は、Search Grounding付きのリクエストの平均処理時間(72秒)を考慮して設定しました。これにより、ほとんどのリクエストがタイムアウトせずに完了できるようになりました。ただし、90秒を超えるリクエストも稀にあるため、完全ではありませんが、実用的なバランスです。
エラーメッセージを感情で読む
エラーメッセージを取得する関数が空文字列ならOK、何か入ったらすべて赤文字でログに残す。たったそれだけなのに、波形で見ると鼓動が聞こえてきます。
429で波形が跳ね、SSLエラーで平坦になる様子は、midori227(MorphFoundryで辞書を抱えた記事)の解析ログを見返すような懐かしさがあります。ログを読むのではなく、感じる。そう意識すると1本1本のトレースに温度が宿ります。
最初は、エラーログを単純にテキストで出力していました。でも、エラーが発生した時に、すぐに気づけないことがありました。次に、エラーメッセージを赤文字で出力するようにしましたが、大量のログの中では見落とすことがありました。最終的に、波形で可視化することで、エラーのパターンが一目で分かるようになりました。
波形で可視化することで、エラーのパターンが一目で分かるようになりました。429エラーでは波形が跳ね、SSLエラーでは平坦になるなど、エラーの種類によって波形の特徴が異なります。これにより、ログを読むのではなく、感じることで、エラーの状況を直感的に把握できるようになりました。
セキュリティフローを俯瞰する
最後に、Webサーバーの設定ファイル → 設定ファイル → APIプロキシ → Gemini本体という流れを俯瞰できるマップを残しました。
ノードをクリックするたびに、当時の決断と失敗が蘇ります。URL書き換えで守れた夜。設定ファイルをうっかりブラウザに晒してしまった朝。短文。もう二度と同じミスはしたくない。
実際に試した例
- Search Grounding × 旅行記事生成
APIプロキシに 2,400文字の観光ガイドをPOST。72秒待機してもレスポンスが返ってくるように、事前にタイムアウトを90秒に設定していることを確認し、429時の体験もデモで可視化した。
midori304 のアラート履歴を JSON で丸ごと POST。ラウンドロビンでキーを分散しつつ、12秒で返る通常モードの安定感を再確認した。
急ぎの翻訳をブラウザから叩いた際、GETでアクセスして405を受け取る失敗を経験。CORSゲートの意味を改めて体で理解した。
使ってみて
使用時の注意点
1. APIプロキシに対して、POSTメソッドでJSONデータを送信する形で必ずPOSTにする
2. 429/403が連続したら、数秒 sleep を挟んで呼び直す。ラダーが3段で止まる仕様を思い出す
3. 公開記事にキーを書きたくなったら、必ず * に置き換えてから commit する
デモ6を全画面で開き、待機時間を確認する
CORSゲートのシミュレーションを試す
まとめ
- Webサーバーの設定ファイルでファイルアクセスとURL書き換えを分け、暗室の入り口を二重化した
- 設定ファイルの複数キーをラウンドロビンし、429/403が来ても3回だけ挑戦するようにした
- タイムアウトを90秒に設定するという覚悟を持ち、Search Groundingの72秒待機を受け入れた
- エラーメッセージを中心にした監視をデモ化し、感情でログを読む仕組みを用意した
- 3つのAPIキーをラウンドロビンで分散
- 試行上限は3回に設定(ロック回避のため)
- タイムアウトは90秒(Search Grounding対応)
- エラーログは波形で可視化
さらに深く学ぶには
最後まで読んでくださり、ありがとうございました。記事に出てくる *** の奥には、夜中に震えながら守った値があります。どうかあなたのAPIキーも温かい暗室にしまってください。いつかまた、暗室談義で会いましょう。