はじめに
バックエンドサーバーとして公開状態でないオブジェクトストレージを利用するケースで、
オリジンサーバーとして Google Cloud Storage のプライベートオブジェクトにアクセスできることを確認します。
Google Cloud Storage で HMAC アクセスキーを発行
HMAC アクセスキーの発行は、こちらを参考にしました。
Workers コード
上記のコードを使います。ポイントは以下の点です
-
aws4fetch
を使います。R2 向けのサンプルコードが参考になります。 - HMAC 認証付きオリジンのようなケースでは
Authorization
ヘッダが付くため、通常のfetch
だとキャッシュ対象外となります。 - そのため、Cache API によるキャッシュをおこなう必要があります。こちらのサンプルコードが参考になります。
import { AwsClient } from 'aws4fetch'
const hostname = `${GCS_BUCKET}.storage.googleapis.com`
const aws = new AwsClient({
accessKeyId: GCS_ACCESS_KEY_ID,
secretAccessKey: GCS_SECRET_ACCESS_KEY,
service: "s3",
});
addEventListener('fetch', function (event) {
event.respondWith(handleRequest(event))
});
async function handleRequest(event) {
const request = event.request
const cacheUrl = new URL(request.url);
const cacheKey = new Request(cacheUrl.toString(), request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
console.log(
`Response for request url: ${request.url} not present in cache. Fetching and caching request.`
);
const pathname = cacheUrl.pathname;
//console.log(pathname);
const gcsurl = `https://${hostname}${pathname}`
const signedRequest = await aws.sign(gcsurl);
response = await fetch(signedRequest, {
/* This does not work because authorization header result cf-cache-status: BYPASS
"cf": {
// resolveOverride,
cacheEverything: true,
cacheTtl: 3,
}*/
})
response = new Response(response.body, response);
response.headers.set("Cache-Control", "s-maxage=30");
//response.headers.append("Cloudflare-CDN-Cache-Control", "max-age=30")
console.log(JSON.stringify([...request.headers]));
console.log(JSON.stringify([...response.headers]));
event.waitUntil(cache.put(cacheKey, response.clone()));
console.log(`Response from Origin`);
} else {
console.log(`Cache hit for: ${request.url}.`);
}
return response;
}
Workers デプロイ
GitHub からクローンしたコードと、シークレット変数設定をおこないます。
git clone https://github.com/kyouheicf/private-access-to-gcs.git && cd $(basename $_ .git)
npm install aws4fetch
wrangler secret put GCS_BUCKET
wrangler secret put GCS_ACCESS_KEY_ID
wrangler secret put GCS_SECRET_ACCESS_KEY
また、以下のようにルート設定を追加します。
vi wrangler.toml
routes = [
{ pattern = "private-access-to-gcs.example.com/*", zone_name = "example.com" }
]
以下のコマンドでデプロイします。
wrangler publish src/cache-api-worker.js
Cache API での確認
以下のコマンドで確認できます。
初回は cf-cache-status: MISS
ですが、
% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control"
< cf-cache-status: MISS
< cache-control: s-maxage=30
< expires: Sun, 04 Jun 2023 19:11:34 GMT
次回以降は cf-cache-status: HIT
となります。
% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control"
< cf-cache-status: HIT
< age: 5
< cache-control: public, max-age=14400
< expires: Sun, 04 Jun 2023 19:11:51 GMT
Image Resizing リクエスト
Image Resizing Worker では "origin-auth": "share-publicly"
のオプションが用意されているため、こちらを使います。
Google Cloud Storage の場合、Authorization
、x-amz-content-sha256
、x-amz-date
を使うため、問題ありません。
aws4fetch
を使います。R2 向けのサンプルコードが参考になります。
以下のコマンドでデプロイします。
wrangler publish src/image-resizing-private-gcs.js
import { AwsClient } from 'aws4fetch'
const hostname = `${GCS_BUCKET}.storage.googleapis.com`
const aws = new AwsClient({
accessKeyId: GCS_ACCESS_KEY_ID,
secretAccessKey: GCS_SECRET_ACCESS_KEY,
service: "s3",
});
addEventListener('fetch', function (event) {
event.respondWith(handleRequest(event.request))
});
async function handleRequest(request) {
const url = new URL(request.url);
const pathname = url.pathname;
const gcsurl = `https://${hostname}${pathname}`
const signedRequest = await aws.sign(gcsurl);
console.log(JSON.stringify([...signedRequest.headers]));
return await fetch(signedRequest, {
"cf": {
// resolveOverride,
cacheEverything: true,
cacheTtl: 30,
image: {
//anim: true,
//background: "#RRGGBB",
//blur: 50,
//border: {color: "#FFFFFF", width: 10},
//brightness: 0.5,
//compression: "fast",
//contrast: 0.5,
//dpr: 1,
//fit: "scale-down",
//format: "webp",
//gamma: 0.5,
//gravity: "auto",
//height: 250,
//metadata: "keep",
//onerror: "redirect",
//quality: 50,
rotate: 90,
"origin-auth": "share-publicly"
//sharpen: 2,
//trim: {"top": 12, "right": 78, "bottom": 34, "left": 56,},
//width: 250,
}
}
})
}
Image Resizing でのキャッシュ確認
以下のコマンドで確認できます。
初回は cf-cache-status: MISS
ですが、
% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control|cf-resized:"
< cf-cache-status: MISS
< cache-control: private, max-age=14400
< cf-resized: internal=ok/b q=0 n=878+0 c=20+111 v=2023.5.0 l=90529
< warning: cf-images 299 "cache-control is too restrictive"
次回以降は cf-cache-status: HIT
となります。
% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control|cf-resized:"
< cf-cache-status: HIT
< cache-control: private, max-age=14400
< cf-resized: internal=ok/b q=0 n=878+0 c=20+111 v=2023.5.0 l=90529
< warning: cf-images 299 "cache-control is too restrictive"
この warning
ヘッダの意味ですが、オリジンが cache-control: private
を返しているため、オリジナルソース画像がキャッシュされない旨の内容です(リサイズ済み画像はキャッシュされる)。
cf-resized: internal=ok/b
の b
は、オリジナルソース画像のキャッシュステータスを表し、b
は BYPASS
の意味です。
(cf-resized
は一度 cf-cache-status: HIT
したら更新されず、リサイズパイプラインがトリガーされたときにのみ、cf-resizedヘッダが設定されます)
Source image is cached using regular caching rules.
BYPASS: The origin server instructed Cloudflare to bypass cache via a Cache-Control header set to no-cache,private, or max-age=0 even though Cloudflare originally preferred to cache the asset. BYPASS is returned when enabling Origin Cache-Control. Cloudflare also sets BYPASS when your origin web server sends cookies in the response header. If the Request to your origin includes an
Authorization
header, its response will be also BYPASS.
また、オリジンの Google Cloud Storage では、パブリック公開オブジェクト以外で Cache-Control
をカスタム適用することができないため、今回のような認証付きプライベートオリジンに対しては、オリジナルソース画像へのリクエストが新規リサイズの必要に応じて都度発生する形になります。
Cache-Control only applies when accessing objects that:
Are publicly accessible.
そのため、Image Resizing を使う場合には、Cache-Control
を制御可能な形のオリジンを利用できることが推奨です。
まとめ
バックエンドサーバーとして公開状態でないオブジェクトストレージを利用するケースで、オリジンサーバーとして Google Cloud Storage のプライベートオブジェクトにアクセスできることが無事確認できましたが、Image Resizing の際は留意する点があることが確認でき、よかったです。