AWS SDK for Go v2を使用したCloudflare R2へのファイルのアップロード方法
はじめに
Cloudflare R2はCloudflare社が提供するS3互換のオブジェクトストレージサービスです。
この記事では、aws-sdk-go-v2
を使用してCloudflare R2にファイルをアップロードするAPIを作成する手順をステップバイステップで解説します。Webフレームワークはechoを採用しました。
前提条件
- Goがインスール済み (バージョン1.21以上)
- Cloudflare Accountを作成済み
- GitHubのアカウントが作成済み
Cloudfare R2のセットアップ
以下の手順でR2のバケットを作成し、必要な情報を取得します。
1. R2バケットの作成
Cloudflareダッシュボードの左側にあるサイドバーのR2タブをクリックしてページ遷移します。
その後「R2サブスクリプションを開始する」ボタンをクリックするとことでR2バケットを作成できるようになります。
サブスリプション処理が正常に完了すると以下のようなページに遷移し、バケットに対する操作をダッシュボードから行えるようになります。
次にバケット名を指定してバケットを作成します。ここではr2-golang-demo
というバケット名にしました。
バケットの作成に成功すると以下のようなバケットのダッシュボードに遷移します。
2. API Tokenの作成
Cloudflare R2を外部から操作するためにはAPI tokenを作成する必要があります。
R2のホーム画面の右側、アカウント詳細の下側に「R2 APIトークンの管理」というボタンがあるのでクリックしてAPIトークン管理画面に遷移します。
つぎにAPIトークンを作成するボタンをクリックします。
APIトークンを作成する画面では以下の内容を指定します。
- トークン名: トークンの名前。ここでは
R2 Token for r2-golang-demo
としました。 - 権限: トークンの権限タイプを指定します。今回は書き込み権限があれば良いので
オブジェクトの読み取りと書き込み
を選択しました。 - バケットの指定: 今回は
r2-golang-demo
バケットのみを操作できれば十分なため、r2-golang-demo
のみを指定しました。 - TTL: トークンのアクティブ期間を指定します。今回は無期限にしました。
- クライアントIPアドレスフィルタリング: APIトークンを使用できるクライアントをIPアドレスで制限する場合はIPアドレスを入力します。今回は空白としました。
必要な情報の入力が終わったら「APIトークンを作成する」ボタンをクリックしてAPIトークンを作成します。
トークンが正常に作成されると以下の情報が表示されます。これらの情報は一度しか表示されないためどこかに保存しておきます。
ここでは1Passwordに保存しました。
- トークンの値
- アクセスキーID
- シークレットアクセスキー
- S3クライントのエンドポイント
Gitリポジトリのセットアップ
1. リモートリポジトリの作成
以下のドキュメントを参考にしてGitHubのWebから新しいリポジトリを作成します。
今回はリポジトリ名はr2-aws-go-v2-demo
としました。
2. ローカル環境にクローン
git clone git@github.com:kira924age/r2-aws-go-v2-demo.git
Goプロジェクトのセットアップ
以下の手順でGoでAPIを開発する上で必要な準備を行います。
1. ディレクトリの移動
クローンして作成されたプロジェクトのディレクトリに移動します。
cd r2-aws-go-v2-demo
2. go moduleの初期化
go mod init commandを使用してmoduleを初期化します。
go mod init github.com/kira924age/r2-aws-go-v2-demo
3. AWS SDK for Go v2のインストール
go get github.com/aws/aws-sdk-go-v2/aws
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3
4. echoのインストール
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4/middleware
APIの開発
1. 仕様の決定
今回はmultipart/form-data
形式のデータをリクエストボディを経由してファイルを受け取るようにしました。
Key | Value | description |
---|---|---|
file | File | R2にアップロードするファイル |
APIのエンドポイントは以下の通りです。
POST /file
2. S3クライアントのセットアップ
aws-sdk-go-v2/config
のLoadDefaultConfigメソッドを使用して認証情報を読み込ませます。
読み込み方法は環境変数、AWS shared configuration file (~/.aws/config
)、AWS shared credentials file (~/.aws/credentials
)の3つの方法があります。
今回は環境変数を利用して認証情報を渡すようにします。
このとき必要な環境変数は以下の通りです。
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_DEFAULT_REGION
またR2のエンドポイントとバケット名の情報も必要なため、これらも環境変数経由で渡すようにします。
この情報の環境変数名は何でも良いです。ここではそれぞれAWS_S3_ENDPOINT、AWS_S3_BUCKET_NAMEとしました。
ローカルで開発する場合、環境変数をセットするスクリプトを用意しておくと便利です。
ここでは以下のシェルスクリプトを作成し、set_env.shというファイル名で保存しました。
#!/bin/bash
export AWS_ACCESS_KEY_ID="XXX"
export AWS_SECRET_ACCESS_KEY="XXX"
export AWS_DEFAULT_REGION="auto"
export AWS_S3_ENDPOINT="XXX"
export AWS_S3_BUCKET_NAME="r2-golang-demo"
AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_ENDPOINTはそれぞれAPI Token作成時に表示された値を使用します。AWS_DEFAULT_REGIONはautoを指定します。
また、AWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEYは秘匿すべき情報であるため.gitignoreにset_env.sh
を追加しました。
config.LoadDefaultConfig()
により環境変数から認証情報を読み込み、その情報をもとにs3クライントを作成するコードは例えば以下のように記述できます。
var client *s3.Client
func createS3Client() error {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return err
}
var s3endpoint string = os.Getenv("AWS_S3_ENDPOINT")
client = s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String(s3endpoint)
})
return nil
}
3. ファイルアップロード関数の実装
aws-sdk-go-v2を利用してS3互換オブジェクトストレージにファイルをアップロードするにはPutObject
メソッドを使用します。
PutObjectのパラメータにはBucket名、Key、Body、ContentTypeを渡す必要があります。
ここではBucket名は環境変数から取得した値を固定値として利用し、ContentTypeはObjectKeyの拡張子をもとに決定するようにしています。
func uploadFile(objectKey string, r io.Reader) error {
var bucketName string = os.Getenv("AWS_S3_BUCKET_NAME")
var objectKeyParts []string = strings.Split(objectKey, ".")
var ext string = "." + objectKeyParts[len(objectKeyParts)-1]
var contentType string = mime.TypeByExtension(ext)
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
Body: r,
ContentType: aws.String(contentType),
})
return err
}
4. APIの実装
echoのFormFileを利用すると簡単にmultipart/form-data
を利用したファイルのやり取りを記述できます。
func postFileHandler(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
err = uploadFile(file.Filename, src)
if err != nil {
return err
}
return c.String(http.StatusOK, "File uploaded")
}
ここまでのコードを全て一つのmain.goで記述すると以下のようになります。
package main
import (
"context"
"io"
"mime"
"net/http"
"os"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
var client *s3.Client
func createS3Client() error {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return err
}
var s3endpoint string = os.Getenv("AWS_S3_ENDPOINT")
client = s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String(s3endpoint)
})
return nil
}
func uploadFile(objectKey string, r io.Reader) error {
var bucketName string = os.Getenv("AWS_S3_BUCKET_NAME")
var objectKeyParts []string = strings.Split(objectKey, ".")
var ext string = "." + objectKeyParts[len(objectKeyParts)-1]
var contentType string = mime.TypeByExtension(ext)
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
Body: r,
ContentType: aws.String(contentType),
})
return err
}
func main() {
err := createS3Client()
if err != nil {
panic(err)
}
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.POST("/file", postFileHandler)
e.Logger.Fatal(e.Start(":1323"))
}
func postFileHandler(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
err = uploadFile(file.Filename, src)
if err != nil {
return err
}
return c.String(http.StatusOK, "File uploaded")
}
5. 動作確認
ローカル環境で実行し、きちんとファイルがアップロードできているかどうかの動作確認を行います。
環境変数設定スクリプトを実行し、開発サーバーをgo runコマンドで起動します。
$ source set_env.sh && go run main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.12.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:1323
エンドポイントはPOST /file
。リクエスト形式はmultipart/form-data
。Keyはfile
としてAPIを実装したので、例えばPostmanを利用すると以下のようにしてAPIを呼び出すことができます。
おわりに
この記事では、Cloudflare R2を利用しaws-sdk-go-v2とEchoフレームワークを用いてGoでファイルアップロードAPIを構築する方法を解説しました。
なお、今回作成したコードは以下のGitHubリポジトリより閲覧可能です。
Discussion