中継サーバーでローカルからCloudflare R2を操作できるようにする
Cloudflareが提供しているRemixのテンプレ構成が気に入っているが、ローカル起動だとR2やD1はリモートにアクセスするのではなく、ローカルでエミュレートされてしまう。リモートにアクセスしてリソースを引っ張ってくるようにしたい。
下記記事のposgreSQL(Supabase)にアクセスするみたく、R2やD1もリモートにアクセスさせたい。
ローカルでの起動は二つ方法がある。
-
remix vite:dev
- viteでサーバー起動
-
wrangler pages dev ./build/client
- wranglerでサーバー起動
個人的に、viteでサーバー起動させて開発したい。
R2を利用したい場合、wrangler.toml
で定義する。
context.cloudflare.env.<bindingの名前>
でcontext
からR2にアクセスできるようになる。
...
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "your-bucket-name"
...
ここで、問題が....
remix vite:dev
でサーバー起動し、R2から画像一覧や画像ファイルを取得しようとすると、空で何も返ってこない。
普通に、リモートサーバーから画像一覧や画像ファイルを所得できるようにしてほしい...
中継サーバーをホスティングして、それ経由でR2を操作する
手順
- Remixに中継サーバー用のエンドポイントを生やす。
- R2をラップして操作する。
- 環境変数(
.dev.vars
)で中継サーバーを利用するか設定できるようにする。
Remixに中継サーバー用のエンドポイントを生やす。
※これだとセキュリティーガバガバなので、APIキーとかで制御するように改造したりしてもいい。
app/routes/r2.relay.tsx
import { ActionFunction, json } from "@remix-run/cloudflare";
type R2Event = "list" | "get";
export const action: ActionFunction = async ({ request, context }) => {
if (request.method !== "POST") {
throw new Error("Method not allowed");
}
const { event, ...rest } = await request.json<{
event?: R2Event;
[key: string]: unknown;
}>();
if (!event) {
throw new Error("not found event");
}
const R2 = context.cloudflare.env.MY_BUCKET;
switch (event) {
case "list": {
const { options } = rest;
const res = await R2.list(options as R2ListOptions);
return json(res);
}
case "get": {
const { key, options } = rest;
const res = await R2.get(key as string, options as R2GetOptions);
return json(res);
}
default: {
throw new Error(`not supported event ${event}`);
}
}
};
R2をラップして操作する。
R2Relay
を作成する。
export class R2Relay {
r2: R2Bucket;
RELAY_SERVER_URL?: string;
constructor(r2: R2Bucket, opts?: { RELAY_SERVER_URL?: string }) {
const { RELAY_SERVER_URL } = opts || {};
this.r2 = r2;
this.RELAY_SERVER_URL = RELAY_SERVER_URL!;
}
async list(options?: R2ListOptions): Promise<R2Objects> {
if (this.RELAY_SERVER_URL) {
this.log(`Relaying list operation to ${this.RELAY_SERVER_URL}`);
const res = await fetch(this.RELAY_SERVER_URL, {
method: "POST",
body: JSON.stringify({ options }),
});
return res.json();
}
this.log(`Relaying list operation to R2`);
const res = await this.r2.list(options);
return res;
}
async get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null> {
if (this.RELAY_SERVER_URL) {
this.log(`Relaying get operation to ${this.RELAY_SERVER_URL}`);
const res = await fetch(this.RELAY_SERVER_URL, {
method: "POST",
body: JSON.stringify({ key, options }),
});
return res.json();
}
this.log(`Relaying get operation to R2`);
const res = await this.r2.get(key, options);
return res;
}
log(message: string) {
console.log(`[R2Relay]`, message);
}
}
load-context.ts
import { type PlatformProxy } from "wrangler";
import { type AppLoadContext } from "@remix-run/cloudflare";
import { R2Relay } from "./relay-server";
type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
declare module "@remix-run/cloudflare" {
interface AppLoadContext {
cloudflare: Cloudflare;
R2: R2Relay;
}
}
type GetLoadContext = (args: {
request: Request;
context: {
cloudflare: Cloudflare;
};
}) => Promise<AppLoadContext>;
export const getLoadContext: GetLoadContext = async ({ context }) => {
const r2Relay = new R2Relay(context.cloudflare.env.MY_BUCKET, {
RELAY_SERVER_URL: context.cloudflare.env.R2_RELAY_SERVER_URL,
});
return {
cloudflare: context.cloudflare,
R2: r2Relay,
};
};
環境変数(.dev.vars)で中継サーバーを利用するか設定できるようにする。
Remixのアプリをホスティング(中継サーバーをホスティング)してから、エンドポイントを.dev.vars
に設定して、ローカル起動するとR2にアクセスできるようになる。
.dev.vars
R2_RELAY_SERVER_URL="https://<remix-app-domain>/r2/relay"
問題点
この方法だと、R2にアクセスできるようになるが、返り値のメソッド(R2ObjectBody.blob()
など)は使えないので、不完全....
結局、AWSのS3のSDK使うのが安牌かも。