Closed9

HonoXで画像配信パターンの検証

TSTS

はじまりはここから

ゆーすけベーさんのCloudflare画像配信パターンについての記事。
https://zenn.dev/yusukebe/articles/7cad4c909f1a60

HonoでやってるcloudflareのR2、Workers配信の配信パターンを
HonoXでやるならどうなるんだろうと思って、HonoXをまだ使いこなせていない分際ですが
やってみたくなった。

一旦導入

とりあえずはプロジェクトを作成していく。
https://github.com/honojs/honox

npm create hono@latest
TSTS

R2のバケットを作成する

何やら開発環境ではふたつのバケットが必要なようなので以下を2回実行。
片方にpreview的なサフィックスをつけた。

npx wrangler r2 bucket create <YOUR_BUCKET_NAME>

紐付ける

不要なコメントアウトを削除しつつ、R2の作成したバケットとの紐付けを行なっていく。

wrangler.json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "<YOUR_PROJECT_NAME>",
  "compatibility_date": "2025-02-08",
  "pages_build_output_dir": "./dist",
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "r2_buckets": [
    {
      "binding": "<YOUR_BINDING_NAME>",
      "bucket_name": "<YOUR_BUCKET_NAME>",
      "preview_bucket_name": "<YOUR_PREVIEW_BUCKET_NAME>"
    }
  ]
}
TSTS

とりあえず実現したいこと

画像へのGETリクエストがあった時にR2から配信したい。

app/routes/index.tsx
import { css } from 'hono/css'
import { createRoute } from 'honox/factory'
import Counter from '../islands/counter'

const className = css`
  font-family: sans-serif;
`

export default createRoute((c) => {
  const name = c.req.query('name') ?? 'Hono'
  return c.render(
    <div class={className}>
      <h1>Hello, {name}!</h1>
      <Counter />

      {/* ↓追加↓ */}
      <img src="/images/xxx.jpeg" alt="" />
    </div>,
    { title: name }
  )
})
TSTS

見つけた方法

APIのエンドポイントを作成できるらしいので一旦作ってみる
https://github.com/honojs/honox?tab=readme-ov-file#2-using-a-hono-instance

一旦作成

GETとPUTの処理は一旦元記事から拝借して作成。

app/routes/images/index.tsx
// app/routes/about/index.ts
import { Hono } from 'hono'

// TypeScriptの型を指定
type Bindings = {
    <YOUR_BINDING_NAME>: R2Bucket
  }
  
const app = new Hono<{
    Bindings: Bindings
}>()

app.get('/:filename', async(c) => {
  const object = await c.env.<YOUR_BINDING_NAME>.get(c.req.param('filename'))
  if (!object) {
    return c.notFound()
  }
  const body = await object.arrayBuffer()
  return c.body(body, 200, {
    'Content-Type': object.httpMetadata?.contentType ?? 'image/jpeg'
  })
})

app.put('/upload', async(c)=>{
    console.log(await c.env.<YOUR_BINDING_NAME>.list())
  // フォームリクエストから画像と名前を取得
  const { file, name } = await c.req.parseBody<{ file: File; name: string }>()
  
  // R2バケットに置く
  const result = await c.env.<YOUR_BINDING_NAME>.put(name, file, {
    httpMetadata: {
      // メタデータでContent Typeを指定
      contentType: file.type
    }
  })
  return c.json(result)
})

export default app

POSTMANを使用してPUTリクエストを送信する

VSCodeのPOSTMAN拡張機能を使用して適当な画像ファイルを送信してみる。

配信できたっぽい

ひとまず配信はできたっぽい。
次はキャッシュの設定をしていく。

TSTS

キャッシュを効かせる

https://zenn.dev/yusukebe/articles/7cad4c909f1a60#cache-api

Workers内ではサービスワーカーにあるCache相当のAPIが実装されており、それを使うことでレスポンスにキャッシュを効かせることができます。

ということで元記事から拝借したものを入れてみる。

app/routes/images/index.tsx
// workers-typeに存在していたcachesをひとまずインポート
import { caches } from '@cloudflare/workers-types/experimental'

// 中略

app.get('*', async (c, next) => {
  // URLをキーにする
  const cacheKey = c.req.url
  // キャッシュオブジェクト
  const cache = caches.default
  // キャッシュされたレスポンスを取得
  const cachedResponse = await cache.match(cacheKey)
  // もしキャッシュが存在したら返却
  if (cachedResponse) {
    return cachedResponse
  }

  await next()

  if (!c.res.ok) {
    return
  }

  // Cache-Controlヘッダの値によってキャッシュする時間を設定
  c.header('Cache-Control', 's-maxage=60')
  // waitUntilを使って非同期でキャッシュをセット
  const res = c.res.clone()
  c.executionCtx.waitUntil(cache.put(cacheKey, res))
})

// 中略

エラーとなる

そううまくはいかないですよね。
ここで一旦お手洗いに行きたくなったので終了。

Cannot read properties of undefined (reading 'default')

experimentalだからローカル環境ではまだ使用できない説もある。

TSTS

ちょっと理解

workers.devとlocalhostでは動作しなくて、
カスタムドメインをR2に割り当てる必要があるのか

Custom domains
When a custom domain is connected to your bucket, the contents of your bucket will be made publicly accessible through that domain. Websites connected can also benefit from Cloudflare features such as bot management, Access, and Cache.

TSTS

そもそもPagesにデプロイされるけど・・・

Workers的な処理ができるのかというところを疑問に思ったけど。
HonoXのビルドのエントリーポイントは_workers.jsになるらしいから
おそらくAdvanced modeなるものでPages Functionが動作するらしいから大丈夫でしょう

Cloudflare PagesでR2を使用する

Pages FunctionでR2を使用している例もあるから大丈夫だろうと思う。
https://developers.cloudflare.com/pages/tutorials/use-r2-as-static-asset-storage-for-pages/

あとはカスタムドメインでキャッシュが使えるか

TSTS

期間が少し空いたけど続けてはいます

再度確認してみる

experimentalでインポートは有効じゃないけど、デプロイはできるのかなと検証してみた。
入れたのは以下のコード

app.get('*', async (c, next) => {
  // URLをキーにする
  const cacheKey = c.req.url
  // キャッシュオブジェクト
  const cache = caches.default
  // キャッシュされたレスポンスを取得
  const cachedResponse = await cache.match(cacheKey)
  // もしキャッシュが存在したら返却
  if (cachedResponse) {
    return cachedResponse
  }

  await next()

  if (!c.res.ok) {
    return
  }

  // Cache-Controlヘッダの値によってキャッシュする時間を設定
  c.header('Cache-Control', 's-maxage=3600')
  // waitUntilを使って非同期でキャッシュをセット
  const res = c.res.clone()
  c.executionCtx.waitUntil(cache.put(cacheKey, res))
})
TSTS

ページ確認

設定したCache-Controlもついているし、
Cf-Cache-StatusもHITになっているから大丈夫そう
ヘッダー

このスクラップは2025/02/22にクローズされました