Firebaseでは、Firebase Hostingの構成をカスタマイズすることで、特定のURLパターンをCloud FunctionsやCloud Runに処理させることが出来ます。
{
"hosting": {
"public": "packages/site/dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/fun",
"function": "http"
},
{
"source": "/run",
"run": {
"serviceId": "container",
"region": "asia-northeast1"
}
}
]
}
}
※上の例では、/fun
から始まるURLは全てCloud Functionsに、/run
から始まるURLは全てCloud Runに処理させています
去年もこんなことをしてたし、今年はこんな発表もしてたりで、相変わらずFirebaseを使っています。
社内でも相変わらずプレビュー環境は運用中です。
いろいろなタイミングで使って見るたびに「ヘッダーで何取れたっけ?」と考えたりしているので、一度どんなリクエストが流れてくるのか整理してみることにしました。
準備する
準備するとは言っても、今回用意するのは次の3項目だけです。
- ヘッダーをダンプするためのサーバーアプリケーション
- Cloud Functionsのエンドポイント定義
- Cloud Runのコンテナ動作
Firebase上での実装となるため、Expressを使います。
ソース全体はGitHubリポジトリにて。
本体: app.ts
とりあえずの情報確認なので、シンプルにリクエスト時のパスとヘッダーのみをJSONにしてエコーするだけのシンプルな実装です。
import express from 'express';
export const app = express();
const router = express.Router();
app.use(router);
type Content = {
path: string;
headers: { [key: string]: string; };
}
router.get('*', (req, res) => {
let content = {
path: req.path,
headers: req.headers,
};
res.send(JSON.stringify(content));
});
デプロイする
作業の流れとしては、こうなります。
- Firebaseプロジェクトを作成して、Blazeプランにする
- そのプロジェクトのGCPコンソールに移動して、Cloud Runを有効にする
- ローカルでDockerイメージを作成してプッシュする
- Cloud Runで検証用のサービス
run
を作成する - Firebaseのデプロイを一式行う
確認する
Cloud Functions/Cloud RUN直接の経路も合わせて確認してみましょう。
というわけで、確認してみるURLは以下のとおりとなります。
- Cloud Functions
- 直接:
https://us-central1-PROJECT_ID.cloudfunctions.net/fun
- 中継:
https://PROJECT_ID.web.app/fun
- 直接:
- Cloud Run
- 直接:
https://run-XXXXX.a.run.app/run
- 中継:
https://PROJECT_ID.web.app/run
- 直接:
※PROJECT_ID
は自身のFirebaseプロジェクトID、XXXXX
はGCPが付与したランダムな文字列、がそれぞれ入ります
これらのURLに対して、ローカルマシン上のHTTPieでアクセスしてレスポンスの中身を見てみることにします。
幕間:HTTPie
Python製のCLIT HTTPクライアントです。curl
やwget
のような使い方を目的としていますが、JSONだったら適切に加工表示してくれたりするので、単純な確認などに重宝します。
(こんな感じに表示してくれます)
» http https://run-7zw4c6v9nq-uc.a.run.app/run
HTTP/1.1 200 OK
Alt-Svc: h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Content-Length: 356
Date: Wed, 09 Dec 2020 11:48:43 GMT
Server: Google Frontend
X-Cloud-Trace-Context: f8fefa1ccbb8ec95e3381779c285a7cf;o=1
content-type: text/html; charset=utf-8
etag: W/"164-Y0NZ+ieZbD6GyZVOnkGU3NtR1cI"
x-powered-by: Express
{
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate",
"content-length": "0",
"forwarded": "for=\"127.0.0.1\";proto=https",
"host": "run-7zw4c6v9nq-uc.a.run.app",
"user-agent": "HTTPie/2.0.0",
"x-cloud-trace-context": "f8fefa1ccbb8ec95e3381779c285a7cf/11614369908781814861;o=1",
"x-forwarded-for": "127.0.0.1",
"x-forwarded-proto": "https"
},
"path": "/run"
}
確認してみたサマリ
※補足:HTTPie経由での結果を元に表を出しているため、例えばブラウザなどでは別のヘッダーが出てくることもあります 1
/fun 直接 |
/fun 中継 |
/run 直接 |
/run 中継 |
|
---|---|---|---|---|
パス | / |
/fun |
/run |
/run |
accept | o | o | o | o |
accept-encoding | o | o | o | o |
accept-language | - | o | o | o |
cache-control | - | o | - | o |
cdn-loop | - | o | - | o |
connection | o | o | - | - |
content-length | - | - | o | o |
fastly-client | - | o | - | o |
fastly-client-ip | - | o | - | o |
fastly-ff | - | o | - | o |
fastly-orig-accept-encoding | - | o | - | o |
fastly-ssl | - | o | - | o |
fastly-temp-xff | - | o | - | o |
forwarded | o | o | o | o |
function-execution-id | o | o | - | - |
host | o | o | o | o |
pragma | - | o | - | o |
transparent | o | o | - | - |
user-agent | o | o | o | o |
via | - | o | - | o |
x-appengine-city | o | - | - | - |
x-appenginecitylatlong | o | - | - | - |
x-appengine-country | o | o | - | - |
x-appengine-default-version-hostname | o | o | - | - |
x-appengine-https | o | o | - | - |
x-appengine-region | o | - | - | - |
x-appengine-request-log-id | o | o | - | - |
x-appengine-timeout-ms | o | o | - | - |
x-appengine-user-ip | o | o | - | - |
x-cloud-trace-context | o | o | o | o |
x-country-code | - | o | - | o |
x-firebase-channel | - | o | - | o |
x-firebase-hosting-channel | - | o | - | o |
x-forwarded-for | o | o | o | o |
x-forwarded-host | - | o | - | o |
x-forwarded-proto | o | o | o | o |
x-forwarded-server | - | o | - | o |
x-forwarded-url | - | o | - | o |
x-timer | - | o | - | o |
x-varnish | - | o | - | o |
ちょっと考察してみる
軽く眺めつつ、グルーピングしたりして考察してみましょう。
fastly-
系
こちらにもあるとおり、Firebase HostingではエッジとしてFastlyが使われています。
そのため、fastly-
から始まるヘッダーが追加付与されています。
普段の自分的にあまり有効利用できないのですが、fastly-client-ip
には確実にクライアントIPが載ってくるっぽいので、扱うならその辺りでしょうか。
x-appengine-
系
こちらは、Cloud Functions系のリクエスト時にアプリケーションに流れてくるヘッダー群です。
目に付くのは、*「多くの位置情報系のヘッダーが消滅する」「x-appengine-country
がZZ
になる」*でしょうか。
これは、クライアント->Functions
という通信経路がクライアント->Firebase->Functions
になったことで、
Cloud Functionsの位置情報判定を取らなくなったのかなと思います。
もし、位置情報をここで取るようなプロジェクトだと、注意が必要かもしれませんね。
※一応x-country-code
で国レベルの場所だけは取れるみたいです。
x-firebase-
系
x-firebase-channel
x-firebase-hosting-channel
の2個があるんですが、
何度か試した感じではいずれも live
という値のみが入ってたので、ちょっと使いどころは不明ですね...
x-forwarded-
系
よくあるヘッダーです。x-forwarded-for
,x-forwarded-proto
辺りは途中で必ず用意されるようにはなってます。
Firebase Hosting経由でアクセスした場合では、x-forwarded-host
が付与されるようになるため、
サービスFQDNをアプリケーションで知りたい場合はこのヘッダーを見てあげましょう。(自分がよく向き合っている項目)
なお、host
ヘッダはーそれぞれのサービスのFQDNがそのまま載ってきます。
Cloud Functionsにおけるパス情報
Cloud Functionsでは、直接リクエストとFirebase Hosting経由で違いがあります。
Cloud Functionsでは同一プロジェクト/リージョンにある複数の関数でFQDNを使いまわしている関係で、
アプリケーション自身が認識しているルートパスが1階層下になる感じですね。
Firebase Hosting経由を前提に開発しているなら気にしなくて良いのですが、
逆にCloud Functions直接展開を想定している時には、ローカル開発で気をつける必要がありそうです。
まとめ
**とはいえなんですが、**Firebaseを雰囲気で使っているので、実はガッツリ「このヘッダーを積極活用する!」というのがあまりなかったりします。
- Webアプリケーションとして考えると
X-Forwarded-
系が適切に提供されているので、基本的にこれを活用すれば良い - 位置情報系も、基本的に位置情報ライブラリを使って取ったほうが早い
今回自分の場合は、「Cloud Runの内部でFirebase側のFQDNをきちんと知る必要がある」という理由がきっかけで調べてみました。
普段ふわっと使っている領域を半歩深堀りするのは意義があるし、楽しいですね。
-
例として、クライアントからのリクエスト時点で
accept-language
ヘッダーがある場合は、Firebase Hosting 経由ではx-orig-accept-language
が付与されるらしいです ↩