emahiro/b.log

Drastically Repeat Yourself !!!!

GCSに保存してあるデータを取得する

※ 当たり前のことでドキュメント通読すればいい話ですが、ちょっとハマったので備忘録として書きました。
Google Cloud Storage(GCS)に保存してあるデータを取得するという話です。

GCSから取得する時

storageパッケージを使います。

godoc.org

データを取得するだけであれば以下のコードを書いて終了です。

func GetDataFromGCS(w http.ResponseWriter, r *http.Request) http.Handler{
  
  ctx := appengine.NewContext(r) // Contextの作成
  clinet, err := storage.NewClient(ctx)
  if err != nil {
    panic(err)
  }
  
  reader, err := client.Bucket("BUCKET_NAME").Object("OBJECT_PATH").NewReader(ctx)
  if err := nil {
    panic(err)
  }
  
  b, err := ioutil.ReadAll(reader)
  if err != nil {
    panic(err)
  }
  
  // これでGCSから取得したデータをbinaryに変換したので、いかようにも料理可能になります。
}

僕がハマったのは以下の部分です。

reader, err := client.Bucket("BUCKET_NAME").Object("OBJECT_PATH").NewReader(ctx)

ここでBucket内の任意のpathにあるデータをstreamで取得することが出来るわけですが、このpathの指定の仕方でハマりました。

例えば https://bucketName/pathToImage/test.png という画像を取得する場合に最初

reader, err := client.Bucket("bucketName").Object("/pathToImage/test.png").NewReader(ctx)

と書いていたのですが、この Object(path string) に渡すpathの書き方が間違っていて正しくは / を付けずに

reader, err := client.Bucket("bucketName").Object("pathToImage/test.png").NewReader(ctx)

のように指定しなければ行けません。
こんなことでメッチャハマってしまったわけです...

Objectを取得するときの流れ

ただ、ハマってしまっても仕方ないので、Object(path string) を取得するまでの流れを追っかけにライブラリの中まで見てみると cloud.google.com/go/storage/bucket.go にObjectメソッドの実装があります。

// Object returns an ObjectHandle, which provides operations on the named object.
// This call does not perform any network operations.
//
// name must consist entirely of valid UTF-8-encoded runes. The full specification
// for valid object names can be found at:
//   https://cloud.google.com/storage/docs/bucket-naming
func (b *BucketHandle) Object(name string) *ObjectHandle {
    return &ObjectHandle{
        c:      b.c,
        bucket: b.name,
        object: name,
        acl: ACLHandle{
            c:           b.c,
            bucket:      b.name,
            object:      name,
            userProject: b.userProject,
        },
        gen:         -1,
        userProject: b.userProject,
    }
}

この中の ObjectHandle を更に追っかけてみると clodud.google.com/go/storage/storage.go

// NewReader creates a new Reader to read the contents of the
// object.
// ErrObjectNotExist will be returned if the object is not found.
//
// The caller must call Close on the returned Reader when done reading.
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
    return o.NewRangeReader(ctx, 0, -1)
}

というどうやらreadをしている場所があって、この (o *ObjectHandler) NewReader (ctx context.Context) の中の NewRangeReader を見てみると

// NewRangeReader reads part of an object, reading at most length bytes
// starting at the given offset. If length is negative, the object is read
// until the end.
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (*Reader, error) {
        // 略
    u := &url.URL{
        Scheme:   "https",
        Host:     "storage.googleapis.com",
        Path:     fmt.Sprintf("/%s/%s", o.bucket, o.object),
        RawQuery: conditionsQuery(o.gen, o.conds),
    }

}

読み込む対象のpathを

Path:     fmt.Sprintf("/%s/%s", o.bucket, o.object)

という箇所がありました。ここがどうやらbucketのpathをしている箇所みたいです。

当初ハマっていたときのような /pathToImage/test.png のようなpathを取ってしまうと

fmt.Sprintf("/%s/%s", o.bucket, o.object) -> // bucketName//pathToImage/test.png

のようなpathになってしまうわけで、この結果指定されたpathでは object doesn't exist が出てしまっていたわけです。

まとめ

なんかソースコード読めばすぐにわかったことで半日くらい悩んでしまってました。。。