mdx Iのオブジェクトストレージに対するproxyサーバの構築
概要
mdx Iのオブジェクトストレージに対するproxyサーバの構築に関する備忘録です。
背景
mdx Iの利用手引きにおいて、以下のように説明されています。
mdxが提供するS3データサービス(DDN EXAScaler S3 Data Service)で取り扱うAPI仕様を記載したDataDirect Networks社のマニュアルです。
Tipsのオブジェクトストレージの利用方法例 と合わせてご確認ください。
こちらをGoogle NotebookLMに読み込ませ、corsの実装状況について確認しました。以下が回答です。
回答
資料 でBucket CORS Featureが未実装であることが明記されているため、EXAScaler Access S3 APIに対して、ウェブブラウザ上で動作するJavaScriptなどから直接、異なるオリジン(例えば、APIのエンドポイントとは異なるドメインで提供されているウェブサイトのJavaScript)を通してリクエストを行うと、CORS(Cross-Origin Resource Sharing)の問題が発生し、通常はリクエストがブロックされます。
これは、ウェブブラウザのSame-Origin Policyというセキュリティ上の仕組みによるものです。異なるオリジンからのリソースへのアクセスは、サーバー側が明示的に許可しない限り制限されます。CORSは、この許可の仕組みを提供するものですが、EXAScaler Access S3においては、この機能がまだ実装されていないため、サーバー側からのCORSヘッダーなどのレスポンスがなく、ブラウザはクロスオリジンリクエストを拒否すると考えられます。
したがって、プロキシサーバーなどを利用して、クライアントサイドのリクエストを一旦プロキシサーバーに送り、そこからEXAScaler Access S3 APIにリクエストを行うことで、オリジンが一致するように見せかけるなどの回避策が必要になる場合があります。プロキシサーバーは、APIからのレスポンスに必要なヘッダーを付与する役割を担うこともできます。
ただし、これはCORSの問題を回避するための一つの一般的な方法であり、他にもサーバーサイドでAPIを公開する際にオリジンを一致させるなどの方法も考えられます。重要なのは、EXAScaler Access S3 API自体がCORSの仕組みをサポートしていないため、クライアントサイドから直接クロスオリジンリクエストを行う際には、何らかの対策が必要になるということです。
対策
理解が間違っている点もあるかもしれませんが、上記の回答から、mdx Iのオブジェクトストレージに対するproxyサーバの構築を行うこととしました。
構築したproxyサーバのリポジトリは以下です。
例えば、以下で指定したバケット上のファイルにアクセスできます。
https://s3-proxy.vercel.app/public/CETEIcean.css
一方、以下が直接アクセスした例です。
https://s3ds.mdx.jp/satoru196/public/CETEIcean.css
前者では、以下のようなレスポンスヘッダーが確認でき、CORSの設定がなされていることが確認できます。
access-control-allow-origin: *
age: 0
cache-control: public, max-age=0, must-revalidate
content-encoding: br
content-type: text/css
date: Sun, 23 Mar 2025 03:11:27 GMT
etag: W/“3d5c-psb8jsgRM3DKYLgMVwi7Ns/AIhg”
server: Vercel
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-powered-by: Express
x-vercel-cache: MISS
x-vercel-id: hnd1::iad1::zbdgm-1742699486592-2b286aacd062
実装
Expressを用いて、以下のように実装しました。aws-sdk
については、AWS SDK for JavaScript v3
に移行する必要があるようなので、この点はご注意ください。
import express, { Request, Response } from "express";
import cors from "cors";
import AWS from "aws-sdk";
// 開発環境では.envを使用 (本番環境のVercelでは不要)
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const app = express();
app.use(cors());
const PORT = process.env.PORT || 4000;
// 固定のバケット名を環境変数から取得
const BUCKET_NAME = process.env.S3_BUCKET_NAME || 'default-bucket-name';
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
endpoint: process.env.AWS_ENDPOINT,
region: process.env.AWS_REGION,
});
// パスパラメータとしてファイルパスを受け取る
app.get("/*", async (req: Request, res: Response) => {
// リクエストURLから '/file/' を除いた部分をキーとして使用
const key = req.path.substring(1);
if (!key) {
res.status(400).send("ファイルパスが指定されていません");
}
s3.getObject({
Bucket: BUCKET_NAME,
Key: key,
}).promise()
.then(data => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Content-Type", data.ContentType || 'application/octet-stream');
res.send(data.Body);
})
.catch(error => {
const errorMessage = error instanceof Error ? error.message : '不明なエラーが発生しました';
res.status(500).json({ error: errorMessage });
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}: ${BUCKET_NAME}`);
});
まとめ
間違った理解をしている点もあるかもしれませんが、参考になりましたら幸いです。
Discussion