😺

node の S3Client から S3, GCS, R2 を操作する

2022/07/08に公開約3,200字

S3、というより S3 API は、AWS というよりオブジェクトストレージ界の標準になりつつあります。S3 互換を謳うオブジェクトストレージはそれこそ毎月一個増えてるんじゃないでしょうか。

複数の CDN 構成をとるとき、クラウドごとのSDK のコードを書くのではなく、一つの実装(自分の場合 node の @aws-sdk/client-s3)で、全部のバケット処理を統一できないか考えて実験してみました。

APIごとに有効な機能、無効な機能で細かい差がありますが、この記事ではそこまで踏み込みません。

記事中の <key><secret> は自分で取得してください。

AWS S3

まずは普通に AWS に繋ぐコードです。 accessKeyId や secrentAccessKey の取得方法は省略します。

s3-r2.ts
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";

// R2
const client = new S3Client({
  region: 'ap-northeast-1',
  credentials: {
    accessKeyId: `<key>`,
    secretAccessKey: '<secret>',
  },
});

const res = await client.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
}));
console.log(res.Contents!);

my-bucket の ファイル一覧が取れます。

当たり前ですが、API KEY に対して Bucket 操作の権限が足りてないと動きません。これは他のエンドポイントも同様の概念が発生しています。

GCS with S3Client

GCS にも s3 互換エンドポイントがあります。 https://storage.googleapis.com
認証には、通常の認証方式ではなく、プリンシパル等に対して HMAC キーを発行する必要があります。

https://cloud.google.com/storage/docs/authentication/hmackeys?hl=ja

発行した key/secret を credentials に使います。

s3-gcs.ts
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({
  region: 'auto',
  endpoint: "https://storage.googleapis.com",
  credentials: {
    accessKeyId: '<key>',
    secretAccessKey: `<secret>`,
  }
});

const res = await client.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
}));
console.log(res.Contents!);

余談: この記事では省略しますが、 ListObjectsV2Command では、 一回あたりのリクエスト上限が違う、という差がありました。実際500件を超えるとページネーションして取り直す処理が必要なのですが、S3 API経由だとGCS本来の1000件から500件まで減りました。

Cloudflare R2 on s3client

Cloudflare R2 も S3 互換です。https://www.cloudflare.com/ja-jp/products/r2/

こちらも GCS と同じく R2 用の API_TOKEN を発行してから利用します。

発行方法: https://developers.cloudflare.com/r2/platform/s3-compatibility/tokens/

endpoint に https://<client_id>.r2.cloudflarestorage.com を指定します。これは自分の client_id を入力してください。

s3-r2.ts
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";

// R2
const client = new S3Client({
  endpoint: `https://<client_id>.r2.cloudflarestorage.com`,
  region: 'auto',
  credentials: {
    // NOTE: 1年ごとにトークンの更新が必要
    accessKeyId: `<id>`,
    secretAccessKey: '<secret>',
    // @ts-ignore
    signatureVersion: 'v4', // いらないかも?
  },
});

const res = await client.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
}));
console.log(res.Contents);

参考: https://developers.cloudflare.com/r2/examples/aws-sdk-js/

Minio with S3Client

書こうと思ったけど略。docker でテスト用途に動かす分には便利そう。

https://dev.classmethod.jp/articles/s3-compatible-storage-minio/

おまけ: S3 Client を Mock する

aws-sdk-client-mock を使うと、この s3 client の返り値をモックしてテストを返すことができます

https://www.npmjs.com/package/aws-sdk-client-mock
import { mockClient } from 'aws-sdk-client-mock';
import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3';

const mockS3Client = mockClient(S3Client);

mockS3Client.on(ListObjectsV2Command).resolves({
  $metadata: {
    httpStatusCode: 200,
  },
  Contents: []
});

// mock されている
const client = new S3Client({ /* 略 */ });
const res = await client.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
}));
console.log(res.Contents);

自分で型を調べて書かないといけないのがちょっと面倒でした。テストコード書く範囲では、必要な部分だけ書くと割り切ってます。

Discussion

ログインするとコメントできます