Firebase StorageをGolangから利用する

FirebaseのStorage機能を使うと、無料でGoogle Cloud Storageを5GB分利用できる。Golangからファイルをアップロード/ダウンロードする方法を以下に記す。

# 事前準備
## Firebase Storageの設定
<img width="248" alt="Screen Shot 2019-07-21 at 22.11.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/86ae4200-8e2b-57a6-307a-4be45d53db95.png">
メニューから「Storage」を選ぶ
<img width="961" alt="Screen Shot 2019-07-21 at 22.12.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/ee0edc5c-d9ab-2c1a-cf1b-fbf9a6bf6357.png">
「スタートガイド」をクリック
<img width="654" alt="Screen Shot 2019-07-21 at 22.13.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/753b557c-331e-e645-8ad7-8593f1660c76.png">
セキュリティ保護ルールの説明を読んだら「次へ」
<img width="655" alt="Screen Shot 2019-07-21 at 22.14.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/2b708103-d303-c45a-ce89-83639c0ff6ea.png">
必要に応じてロケーションを変更して、設定を完了する。ロケーションは後から変更できないので注意。
<img width="969" alt="Screen Shot 2019-07-21 at 22.16.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/70300fbb-101f-cac7-8c1d-aa9cfeeb4e53.png">
設定が完了。ここのStorageのBucketのURLは後で参照するのでメモっておく。
## アクセス用Credentialの取得
すでにサービスアカウントの秘密鍵を生成している場合はそちらを使用してください。

秘密鍵を生成していない場合は新しく作成します。

<img width="411" alt="Screen Shot 2019-07-21 at 22.17.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/f7c69492-8ae4-7e2d-1d51-80db0ff2fbea.png">
プロジェクトの設定を開く
<img width="1051" alt="Screen Shot 2019-07-21 at 22.18.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/27a669b0-a2df-7783-e163-c43346b029bd.png">
「サービスアカウント」から「新しい秘密鍵の生成」を行う。
<img width="449" alt="Screen Shot 2019-07-21 at 22.21.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/f96914a8-3024-6506-53e1-9735dc90b3ab.png">
「キーを生成」すると、生成された秘密鍵のJSONファイルがダウンロードされるので、適当なファイル名(ここでは`storage-exp-key.json`)に変更して適当な場所に置く。

# Admin SDKのインストール
`go get`でFirebase Admin SDKをインストールする。

```
go get firebase.google.com/go
```

https://firebase.google.com/docs/admin/setup

# Firebase Admin SDKを初期化する
```go
config := &firebase.Config{
	StorageBucket: "storage-exp.appspot.com",
}
opt := option.WithCredentialsFile("storage-exp-key.json")
app, err := firebase.NewApp(context.Background(), config, opt)
if err != nil {
	log.Fatalln(err)
}
```

先ほど作ったサービスアカウントのキーを使って、Firebase SDKの初期化を行う。その際、StorageのBucketのURLを指定する。StorageのBucketのURLは、Storage画面のここで確認できる。
<img width="969" alt="Screen Shot 2019-07-21 at 22.16.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/70300fbb-101f-cac7-8c1d-aa9cfeeb4e53.png">

# StorageのBucketを取得する
```go
client, err := app.Storage(context.Background())
if err != nil {
	log.Fatalln(err)
}

bucket, err := client.DefaultBucket()
if err != nil {
	log.Fatalln(err)
}
```

`app.Storage()`でStorage操作用のクライアントを取得し、さらにそのクライアントの`DefaultBucket()`を呼ぶことで、上で設定したBucketを取得できる。Blazeプラン以上の場合はBucketを複数追加して、Default Bucket以外のCustom Bucketを使うこともできるが、無料プランの場合はDefault Bucket以外は利用できない。

https://firebase.google.com/docs/storage/admin/start

ここで返される`bucket`はGoogle Cloud StorageのBucketHandleなので、ここから先はGoogle Cloud StorageのAPIを利用していくことになる。Firebaseのサービスキーを使ってすでに認証も完了した状態になっているのでそのままAPIを叩ける。

# ファイルをアップロードする
Cloud Storage APIを使ってファイルをアップロードしてみる

https://cloud.google.com/go/getting-started/using-cloud-storage?hl=ja

```go
localFilename := "sample.txt"  // ローカルのファイル名
remoteFilename := "sample.txt" // Bucketに保存されるファイル名
contentType := "text/plain"
ctx := context.Background()

writer := bucket.Object(remoteFilename).NewWriter(ctx)
writer.ObjectAttrs.ContentType = contentType
writer.ObjectAttrs.CacheControl = "no-cache"
writer.ObjectAttrs.ACL = []storage.ACLRule{
	{
		Entity: storage.AllUsers,
		Role:   storage.RoleReader,
	},
}

f, err := os.Open(localFilename)
if _, err = io.Copy(writer, f); err != nil {
	log.Fatalln(err)
}
defer f.Close()

if err := writer.Close(); err != nil {
	log.Fatalln(err)
}
```

ローカルにある`sample.txt`の内容を読み込んで、Bucket上に同じく`sample.txt`という名前のファイルで保存する。`contentType`はテキストなので`text/plain`を、動作確認で混乱がないようにキャッシュは`no-cache`にした。アクセスは全員が`Read`することができるように指定している。この辺りの設定は実際の使用用途に合わせて適宜書き換えて使う。

<img width="954" alt="Screen Shot 2019-07-21 at 22.48.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/e0551e4c-4c06-df05-3b18-6b08469ef5f5.png">

プログラムを実行すると、ファイルがアップロードされる。アップロードされたファイルはFirebase Consoleの`Storage`画面で確認できる。

# ファイルをダウンロードする
Bucketにアップロードしたファイルを今度はダウンロードしてみる。

https://cloud.google.com/storage/docs/downloading-objects#storage-download-object-go

```go
rc, err := bucket.Object(remoteFilename).NewReader(ctx)
if err != nil {
	log.Fatalln(err)
}
defer rc.Close()

data, err := ioutil.ReadAll(rc)
if err != nil {
	log.Fatalln(err)
}

log.Printf("Downloaded contents: %v\n", string(data))
```

ファイル名指定でBucketからコンテンツをダウンロードして表示してみる。やり方は通常のCloud StorageのAPIを利用する時と変わらない。

# ファイルをブラウザからダウンロードする
## ルールの設定
<img width="1116" alt="Screen Shot 2019-07-21 at 23.47.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14097/27124f21-c513-bb50-76ca-4a77f10d4959.png">

デフォルトの設定ではStorageにアップロードされたファイルは認証済みのユーザしかReadもWriteもできないようになっている。今回は認証していないユーザでもダウンロード(read)できるように変更する。(writeは引き続き、認証済みのユーザのみ)

```
allow read;
allow write: if request.auth != null;
```

## Javascriptでダウンロード用のURLを発行する
```html
<script src="https://www.gstatic.com/firebasejs/6.3.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.3.1/firebase-storage.js"></script>
```
```javascript
// Your web app's Firebase configuration
var firebaseConfig = {
  // Consoleで表示されているConfigをそのままコピペする
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

var storage = firebase.storage();
storage.ref('sample.txt').getDownloadURL().then(function (url) {
  console.log(url);
})
```
StorageのAPIを利用するには、`firebase-storage.js`の読み込みが必要なので忘れないように注意。`getDownloadURL()`でダウンロード用のURLを取得できる。

```
curl "https://firebasestorage.googleapis.com/v0/b/storage-exp.appspot.com/o/sample.txt?alt=media&token=..."
```