CloudFrontとS3を使ったWebサイトでWebPの画像を配信してみた

CloudFrontとS3を使ったWebサイトでWebPの画像を配信してみた

CloudFrontとS3でWebサイトを構成している場合、Acceptヘッダーに応じて動的にWebPを配信するのはひと工夫が必要
Clock Icon2025.01.24

WebPに対応している場合はWebPを配信したい

こんにちは、のんピ(@non____97)です。

皆さんはCloudFrontとS3を使ったWebサイトで、クライアントがWebPに対応している場合はWebPを配信したいなと思ったことはありますか? 私はあります。

JPEGやPNGではなく、WebPを使用した方が配信速度の向上が見られるなどメリットを享受することが可能です。

ただし、IEや古いSafari、Firefoxなどごくごく稀にWebPに対応していないことがあります。

https://caniuse.com/webp

そのような場合はWebPではなく、JPEGを配信したいところです。

WebPをサポートしているかはHTTPリクエストヘッダーのAcceptimage/webpを含んでいるかで判断できます。2025/1/24時点ではS3単体でリクエストヘッダーをベースにレスポンスするオブジェクトを切り替えることはできません。

ということで、CloudFront Functions、もしくはLambda@EdgeでAcceptヘッダーをベースに動的にレスポンスする画像ファイルを切り替えてみました。

いきなりまとめ

  • 動的にWebPをレスポンスするか判定するためにはAcceptヘッダーおよび CloudFront Functions or Lambda@Edgeを使おう
    • CloudFront Functionsの場合は、全ての画像ファイルがWebP形式に対応していることが前提
      • 対応していない場合は404が返る
      • Lambda@Edgeの場合は、オブジェクトが存在するか判定する時間的余裕があるため、.webpが存在しなければクライアントのオリジナルのURIのファイルを返す

やってみた

検証環境

検証環境は以下のとおりです。

CloudFrontとS3を使ったWebサイトでWebPの画像を配信してみた検証環境構成図.png

※ 本当はAthenaのクエリ出力先S3バケットやGlueテーブルなどもありますが、割愛

検証環境は全てAWS CDKでデプロイしました。使用したコードは以下GitHubリポジトリに保存しています。

https://github.com/non-97/cloudfront-s3-website/tree/v3.0.0

こちらのベースとなったコードの詳細な説明は以下記事をご覧ください。

https://dev.classmethod.jp/articles/aws-cdk-cloudfront-s3-website/

https://dev.classmethod.jp/articles/aws-cdk-cloudfront-s3-website-log-analytics/

以降、これらの記事で紹介しているコードからの主な変更点を紹介します。

コンテンツのデプロイ

まずはコンテンツのデプロイです。

コンテンツのデプロイはWebPかそれ以外かの2回に分けて行っています。

./lib/construct/contents-delivery-construct.ts
    // Deploy contents
    if (!props.contentsPath) {
      return;
    }
    const asset = cdk.aws_s3_deployment.Source.asset(props.contentsPath, {
      exclude: [".DS_Store"],
    });

    new cdk.aws_s3_deployment.BucketDeployment(this, "DeployContents", {
      sources: [asset],
      destinationBucket: props.websiteBucketConstruct.bucket,
      exclude: ["*.webp"],
      distribution,
      distributionPaths: ["/*"],
    });

    new cdk.aws_s3_deployment.BucketDeployment(this, "DeployContentsWebP", {
      sources: [asset],
      destinationBucket: props.websiteBucketConstruct.bucket,
      exclude: ["*"],
      include: ["*.webp"],
      distribution,
      distributionPaths: ["/*"],
      contentType: "image/webp",
    });

というのもaws_s3_deployment.BucketDeploymentで、まとめて全てデプロイすると、WebPのオブジェクトのcontentTypebinary/octet-streamとなってしまいます。

binary/octet-streamの場合、ブラウザ上に画面は表示されず、画像ファイルをダウンロードする形になってしまいます。

そのため、対応として拡張子がwebpのファイルについては明示的にcontentTypeimage/webpを指定しています。

ちなみに今回WebPファイルの生成にはcwebpを使用しました。

> cwebp non__97.png -o non__97.png.webp
Saving file 'non__97.png.webp'
File:      non__97.png
Dimension: 421 x 440
Output:    5464 bytes Y-U-V-All-PSNR 44.77 49.16 50.29   45.88 dB
           (0.24 bpp)
block count:  intra4:        166  (21.96%)
              intra16:       590  (78.04%)
              skipped:       560  (74.07%)
bytes used:  header:             96  (1.8%)
             mode-partition:    905  (16.6%)
 Residuals bytes  |segment 1|segment 2|segment 3|segment 4|  total
    macroblocks:  |       1%|       7%|      15%|      78%|     756
      quantizer:  |      36 |      36 |      31 |      24 |
   filter level:  |      11 |       8 |       5 |       4 |

> ls -lh | grep -e png -e jpeg
-rw-r--r--@ 1 <ユーザー名>  <グループ名>    34K 10 11  2016 non__97.jpeg
-rw-r--r--@ 1 <ユーザー名>  <グループ名>    75K  7  2  2024 non__97.png
-rw-r--r--@ 1 <ユーザー名>  <グループ名>   5.3K  1 24 10:52 non__97.png.webp
-rw-r--r--@ 1 <ユーザー名>  <グループ名>   125K  1 24 10:49 non__97_2.png

キャッシュポリシー

Acceptヘッダーごとにキャッシュするようにキャッシュポリシーを設定しました。

./lib/construct/contents-delivery-construct.ts
    const acceptCachePolicy = new cdk.aws_cloudfront.CachePolicy(
      this,
      "AcceptCachePolicy",
      {
        defaultTtl: cdk.Duration.days(1),
        minTtl: cdk.Duration.seconds(1),
        maxTtl: cdk.Duration.days(7),
        cookieBehavior: cdk.aws_cloudfront.CacheCookieBehavior.none(),
        headerBehavior:
          cdk.aws_cloudfront.CacheHeaderBehavior.allowList("Accept"),
        queryStringBehavior: cdk.aws_cloudfront.CacheQueryStringBehavior.none(),
        enableAcceptEncodingGzip: true,
        enableAcceptEncodingBrotli: true,
      }
    );

Acceptヘッダーごとにキャッシュしなければ、WebPをサポートしていないクライアントがアクセスしてきた場合にもWebPで返してしまいます。

また、用意したキャッシュポリシーを適応するのはJPEGやPNGのみにしています。

./lib/construct/contents-delivery-construct.ts
    const addBehaviorOptions: cdk.aws_cloudfront.AddBehaviorOptions = {
      allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD,
      cachedMethods: cdk.aws_cloudfront.CachedMethods.CACHE_GET_HEAD,
      cachePolicy: acceptCachePolicy,
      originRequestPolicy:
        cdk.aws_cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
      viewerProtocolPolicy:
        cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      responseHeadersPolicy:
        cdk.aws_cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS,
      functionAssociations: rewriteToWebpCF2
        ? [
            {
              function: rewriteToWebpCF2,
              eventType: cdk.aws_cloudfront.FunctionEventType.VIEWER_REQUEST,
            },
          ]
        : undefined,
      edgeLambdas: rewriteToWebpLambdaEdge
        ? [
            {
              functionVersion: rewriteToWebpLambdaEdge.currentVersion,
              eventType: cdk.aws_cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
            },
          ]
        : undefined,
    };

    distribution.addBehavior(
      "/*.jpe?g",
      cdk.aws_cloudfront_origins.S3BucketOrigin.withOriginAccessControl(
        props.websiteBucketConstruct.bucket
      ),
      addBehaviorOptions
    );
    distribution.addBehavior(
      "/*.png",
      cdk.aws_cloudfront_origins.S3BucketOrigin.withOriginAccessControl(
        props.websiteBucketConstruct.bucket
      ),
      addBehaviorOptions
    );

注意点として、Acceptヘッダーはブラウザやアクセスの仕方によって大きく異なるため、キャッシュヒット率が低下します。

例えば、私のChromeの場合はimage/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7で、Safariはimage/webp,image/avif,image/jxl,image/heic,image/heic-sequence,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5です。

もし、キャッシュヒット率を下げたくないのであれば、Viewer RequestでCloudFront Functionsを動かし、Acceptヘッダー内にimage/webpを含んでいる場合はx-available-webpなどとカスタムヘッダーを付与した上で、URIの末尾に.webpを付与してリクエストするといった対応が必要になります。

CloudFront Functionsで判定する場合

CloudFront Functionsで判定する場合について紹介します。

行っていることは以下のとおりです。

  • Acceptヘッダー内にimage/webpを含む かつ 拡張子がjpeg or jpg or pngの場合、URIの末尾に.webpを付与してリクエストする
  • Acceptヘッダー内にimage/webpを含まない かつ .webpにアクセスしようとしている場合は末尾の.webpを削除してリクエストする
./lib/src/cf2/rewrite-to-webp/index.js
async function handler(event) {
  const request = event.request;
  const uri = request.uri;

  // Check WebP support in Accept header
  const headers = request.headers;
  const acceptHeader = headers.accept ? headers.accept.value : '';
  const supportsWebP = acceptHeader.includes('image/webp');

  // Regular expression for image extensions
  const imageExtRegex = /\.(jpe?g|png)$/i;

  if (supportsWebP && imageExtRegex.test(uri)) {
    // Add custom header to track WebP conversion attempt
    request.headers['x-original-uri'] = {
      value: uri
    };
    // Append .webp to the URI for supported browsers
    request.uri = `${uri}.webp`;
  }
  // Handle .webp requests for non-supporting browsers
  else if (uri.endsWith('.webp') && !supportsWebP) {
    // Remove .webp extension to get original image
    request.uri = uri.slice(0, -5);
  }

  return request;
}

実際に挙動を確認してみましょう。

まずはAcceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97.png
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 05:22:37 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 23bc6d6a912d17773e1bf97197cbfc1e.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: ddWdCadlo-Lj8rrv32SNK6u6RPoUgnRTbN9MAjHm9rptyna6JVyunA==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97.png
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 05:22:37 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 9b8a6e30994167e8de984036681d4ff6.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: UsFAHVde0acvEnQVSK9yEi_7aj5nUHEJBR_cIoPeAh3bUU2gAckBbg==
age: 4
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

ステータスコードが200でcontent-typeimage/pngと、正常にPNGファイルにアクセスできていることが分かります。また、2回目のアクセスではキャッシュヒットしていることが分かります。

続いて、Acceptヘッダーにimage/webpを指定してnon__97.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定してにnon__97.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Fri, 24 Jan 2025 05:23:18 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 d8a0cc77a7428fd572abace71c0eeda2.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: DDhAyxXtrdajsW6tzfUWybww3leTKQ0ohF6QBvvQt1ZMwuj1-H6APA==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにimage/webpを指定してnon__97.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/webp
content-length: 5464
date: Fri, 24 Jan 2025 05:23:18 GMT
last-modified: Fri, 24 Jan 2025 03:43:34 GMT
etag: "54b0857ccfbab67746434fb9042aacff"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 269160a4d1e0a4937fee2132fea7cb32.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: Ftx5QMM7YcnwcY3ULkEf314YhfhvgJFS-nEpiFSRYfyUThoyCRVRRg==
age: 3
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

ステータスコードが200でcontent-typeimage/webpと、正常にWebPファイルにアクセスできていることが分かります。また、2回目のアクセスではキャッシュヒットしていることが分かります。

続いて、続いて、Acceptヘッダーにimage/webpを指定してnon__97_2.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定してにnon__97_2.pngにリクエストをした場合
> curl -I https://www.non-97.net/non__97_2.png -H "Accept:image/webp"
HTTP/2 404
content-type: text/html
content-length: 12
date: Fri, 24 Jan 2025 06:56:18 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "347dfa37997b9353b3da6992f8753439"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Error from cloudfront
via: 1.1 b4fcd16c2d55faa87f8fa28379c19ab0.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: qgYXjKNuYUtHe8dTr9CwZoWNHl_wp17iOZSPoTFZpIchYo9f9783Dw==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

はい、404エラーになりました。なぜならnon__97_2.png.webpというオブジェクトがS3バケット上に存在しないためです。

CloudFront Functionsの最大実行時間は1msです。

https://aws.amazon.com/jp/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/

そのため、オブジェクトにHEADでアクセスして、実際に存在するのか判定する時間的余裕はありません。

ということで、CloudFront FunctionsでWebPの判定をする場合は、JPEGやPNGに対応してWebPファイルが必ず存在している必要があります。

Lambda@Edgeで判定する場合

Lambda@Edge版も紹介します。

Lambda@Edgeでは以下のような処理を行っています。

  • Acceptヘッダー内にimage/webpを含む かつ 拡張子がjpeg or jpg or pngの場合
    • URIの末尾に.webpを付与した上で、HEADメソッドでリクエストを行い、S3バケット上に.webpのオブジェクトが存在するか判定する
      • 存在する場合はオリジンにリクエストする
      • 存在しない場合は元のURIでリクエストする

Lambda@EdgeはOrigin Requestの場合タイムアウトは最大30秒です。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge

そのため、オリジンに実際にリクエストを投げる前にHEADをして、オブジェクトが存在するかどうかの判定を行うことが可能です。

また、Lambda@EdgeはOrigin Requestでも動作させることが可能なので、キャッシュが効いている場合は実行されないのも嬉しいポイントです。

実際のコードは以下のとおりです。

./lib/src/lambda/rewrite-to-webp/index.ts
import { CloudFrontRequestEvent, CloudFrontRequest } from "aws-lambda";
import * as https from "https";

// Constants
const TIMEOUT_MS = 2000;
const IMAGE_EXTENSION_PATTERN = /\.(jpe?g|png)$/i;

// Types
type WebPCheckResult = {
  availableWebP: boolean;
  error?: Error;
};

/**
 * Lambda@Edge handler for WebP image conversion
 */
export const handler = async (event: CloudFrontRequestEvent) => {
  const request = event.Records[0].cf.request;
  const uri = request.uri;

  // Check WebP support using the same logic as CloudFront Functions
  const acceptHeader = request.headers.accept?.[0]?.value ?? "";
  const supportsWebP = acceptHeader.includes("image/webp");

  // Process if the request is for an image and browser supports WebP
  if (supportsWebP && IMAGE_EXTENSION_PATTERN.test(uri)) {
    const webpCheckResult = await checkWebpAvailability(request);

    if (webpCheckResult.availableWebP) {
      // Store original URI in header
      request.headers["x-original-uri"] = [
        {
          key: "x-original-uri",
          value: uri,
        },
      ];
      request.uri = `${uri}.webp`;
    }
  }

  return request;
};

/**
 * Check if WebP version of the image is available
 */
async function checkWebpAvailability(
  request: CloudFrontRequest
): Promise<WebPCheckResult> {
  if (!request.origin?.s3?.domainName) {
    return { availableWebP: false };
  }

  try {
    const exists = await objectExists(
      request.origin.s3.domainName,
      `${request.uri}.webp`
    );

    return { availableWebP: exists };
  } catch (error) {
    console.error("Error checking WebP existence:", error);
    return {
      availableWebP: false,
      error: error instanceof Error ? error : new Error("Unknown error"),
    };
  }
}

/**
 * Check if object exists in S3 bucket
 */
function objectExists(domainName: string, path: string): Promise<boolean> {
  return new Promise((resolve, reject) => {
    const request = https.request(
      {
        hostname: domainName,
        path: path,
        method: "HEAD",
        timeout: TIMEOUT_MS,
      },
      (response) => {
        if (response.statusCode === undefined) {
          reject(new Error("Status code is undefined"));
          return;
        }
        resolve(response.statusCode >= 200 && response.statusCode < 300);
      }
    );

    request.on("error", reject);
    request.on("timeout", () => {
      request.destroy();
      reject(new Error(`Request timeout after ${TIMEOUT_MS}ms`));
    });

    request.end();
  });
}

挙動の確認をしましょう。

まずはAcceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97.png
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 07:43:00 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 f22f45735eceb3450fbe806ce121aab8.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: s2slRpGdQGob3x0Xz83e9B6Bi_E51mhD352c2hmEaN5z3q-LSKgZFg==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにimage/webpを指定せずにnon__97.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97.png
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 07:43:00 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 f0499023f5cce9a24cc0ed91910c47ee.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: hJjV-sHPgxsltc8B8oD7SLtCk8qK9BQcQpKbVve3Qgb65SNDiXr6sQ==
age: 7
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

ステータスコードが200でcontent-typeimage/pngと、正常にPNGファイルにアクセスできていることが分かります。また、2回目のアクセスではキャッシュヒットしていることが分かります。

続いて、Acceptヘッダーにimage/webpを指定してnon__97.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定してにnon__97.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 07:43:51 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 03e670dad9bf75ede7f4618a9edd6fde.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: ns8zyzyJctO3GUycsi92WRiAnEuQ0v6GfBKoDSj0P-0niB8fu6u-FA==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにimage/webpを指定してnon__97.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/png
content-length: 76942
date: Fri, 24 Jan 2025 07:43:51 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "cac2eacf135495f8eb947890b6c84526"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 d6b84a5611c3f3ea786cd180e1d7ebee.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: 7xHsyUJ4h8_yOa4RDobHpy2zF17lrrc2ciSpIaT4Ot1PL4SynhgCzg==
age: 5
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

ステータスコードが200でcontent-typeimage/webpと、正常にWebPファイルにアクセスできていることが分かります。また、2回目のアクセスではキャッシュヒットしていることが分かります。

続いて、続いて、Acceptヘッダーにimage/webpを指定してnon__97_2.pngにリクエストをした場合です。

Acceptヘッダーにimage/webpを指定してにnon__97_2.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97_2.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/png
content-length: 128244
date: Fri, 24 Jan 2025 07:45:34 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "5b0828f49a563829dce7f1eac62ae5b5"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 67c8b7e623dc98088ceb29dc1e64b5ea.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: es7owe0qum0lq2q6HaOEpVYF9sIV8i947nSOzWX7_kgRkhz1W_Rq0A==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにimage/webpを指定してにnon__97_2.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97_2.png -H "Accept:image/webp"
HTTP/2 200
content-type: image/png
content-length: 128244
date: Fri, 24 Jan 2025 07:45:34 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "5b0828f49a563829dce7f1eac62ae5b5"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 f790dd98745df719189c547ecb87d18e.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: S-DelpS_4zo6HBpMOH8zpDlyjNRKmnPrD8SrlXlEDh0-HejfJ8GkfQ==
age: 2
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

Accept:image/webpとヘッダーを指定しましたが、non__97_2.png.webpというオブジェクトは存在しないため、non__97_2.pngにアクセスしcontent-type: image/pngが返ってきました。2回目のアクセス数ではキャッシュが効いていることも確認できます。

おまけで、Acceptヘッダーにtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7を指定してnon__97_2.pngにリクエストをした場合です。

Acceptヘッダーにtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7を指定してにnon__97_2.pngにリクエストをした場合 (初回アクセス)
> curl -I https://www.non-97.net/non__97_2.png -H "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
HTTP/2 200
content-type: image/png
content-length: 128244
date: Fri, 24 Jan 2025 07:49:59 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "5b0828f49a563829dce7f1eac62ae5b5"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 9edec502e732ce2bc0b08066a0b40af4.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: ux7xVQZ6oG6e3iWElYszd_MImSmIxfWVa4hKyhQsi6lUnN3lb_SORg==
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000
Acceptヘッダーにtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7を指定してにnon__97_2.pngにリクエストをした場合 (2回目のアクセス)
> curl -I https://www.non-97.net/non__97_2.png -H "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
HTTP/2 200
content-type: image/png
content-length: 128244
date: Fri, 24 Jan 2025 07:49:59 GMT
last-modified: Fri, 24 Jan 2025 03:43:32 GMT
etag: "5b0828f49a563829dce7f1eac62ae5b5"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 8d094829a2df82945a7c7fbea18cea10.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-P1
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: 0s1zcQM-Mhyrnr9zr54v6pU4QE1fXEn6QFH0WdBAno24g0xkPXuThw==
age: 24
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

はい、問題なく動作しています。

CloudFrontとS3でWebサイトを構成している場合、Acceptヘッダーに応じて動的にWebPを配信するのはひと工夫が必要

CloudFrontとS3を使ったWebサイトでWebPの画像を配信してみました。

CloudFrontとS3でWebサイトを構成している場合、Acceptヘッダーに応じて動的にWebPを配信するのはひと工夫が必要です。

個人的にはLambda@Edgeで対応するのが良いのかなと考えています。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.