※ 当たり前のことでドキュメント通読すればいい話ですが、ちょっとハマったので備忘録として書きました。
Google Cloud Storage(GCS)に保存してあるデータを取得するという話です。
GCSから取得する時
storageパッケージを使います。
データを取得するだけであれば以下のコードを書いて終了です。
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
が出てしまっていたわけです。
まとめ
なんかソースコード読めばすぐにわかったことで半日くらい悩んでしまってました。。。