前々回のエントリでは、GAE/GoがWebサーバとしてちゃんと動くことが確認できたし、前回のエントリでは、GAE/Goからメールを送ることが確認できたので、今回はGAEからGoogleCloudStorage上にファイルをアップしたりそれを読んだりしてみたい。
参考
- Storing Data in Go - Go — Google Cloud Platform
- Using Google Cloud Storage - Go — Google Cloud Platform
- Go Example - Cloud Storage — Google Cloud Platform
- storage - GoDoc
- gcloud-golang/app.go at master · GoogleCloudPlatform/gcloud-golang · GitHub
- The appengine package - Go — Google Cloud Platform
サンプル読んだ感じざっくりとした流れ
- *http.Requestから、AppEngineのContextを取得する *1
- GAEのapp_idとか、regionとかそういうのが入ってんだろうな
- appengine.NewContext
- context.Context
- Contextから、DefaultBucketNameを取得する *2
- AppEngineへのリクエストなので、app_idに紐付いたbucket名を取得するんではないかとおもわれる
- GetDefaultBucketName
- そうっぽい
- Contextから、GCSのクライアントを初期化する *3
- まあお作法とかAPIのフォーマットとかが詰まってんだろうな
defer client.Close()
注意
- いろいろできる模様 *4
- ファイルの作成 *5
- クライアントにバケット名を食わせて、*BucketHandleというのを取得し
- BucketHandleにオブジェクト名を食わせて、*ObjectHandleというのを取得し
- ObjectHandleにContextを食わせると、io.WriteCloserなものが得られるっぽい
- この時点でいわゆるローカルで
os.File
を扱うのとなんら変わらない感じにできたので、あとはWriteすればいいと思う
- この時点でいわゆるローカルで
- os.Create的なことはしなくていいのかな。ObjectHandleをつくった段階でObjectがあるていでやっていいっぽいな
- ファイルの読み込み *6
- 同様に
client.Bucket(name).Object(objName).NewReader(ctx)
ってして - io.ReadCloserなものが得られるっぽいので、あとはos.Fileと同様
- 同様に
- ファイルの作成 *5
書いてみる
サンプル
まずは、プロジェクトのダッシュボードから、CloudStorageを追加。デフォルトでBucketが2つできるはず。
サンプルは、これAppEngine GoでHello,Worldやってみたログ - DRYな備忘録をベースに、分かりやすいようにだらだらと書いた
package cloudstoragex import ( "io/ioutil" "net/http" "google.golang.org/appengine" "google.golang.org/appengine/file" "google.golang.org/cloud/storage" "github.com/otiai10/marmoset" ) const objname = "foo/bar/baz.txt" func init() { marmoset.LoadViews("./views") router := marmoset.NewRouter() // READするほう router.GET("/", func(w http.ResponseWriter, r *http.Request) { // Requestから、Contextを取得 ctx := appengine.NewContext(r) // Contextから、default bucket nameを取得 bucketname, err := file.DefaultBucketName(ctx) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to get bucket name: " + err.Error()}) return } // Contextから、Clientの取得 client, err := storage.NewClient(ctx) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to get client: " + err.Error()}) return } // クライアントにバケット名を食わせてBucketHandleを取得し、 // それにオブジェクト名を食わせてObjectHandleを取得し、 // そこにContextを食わせてObjectへのReaderを取得 reader, err := client.Bucket(bucketname).Object(objname).NewReader(ctx) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to get reader: " + err.Error()}) return } defer reader.Close() // CloudStorage上のObjectの、コンテンツの読み込み body, err := ioutil.ReadAll(reader) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": err.Error()}) return } marmoset.Render(w).HTML("index", map[string]interface{}{ "objname": objname, "content": string(body), }) }) // WRITEするほう router.POST("/", func(w http.ResponseWriter, r *http.Request) { // まずはリクエストパラメータのmultipartのパースとファイルオブジェクトの取得 f, h, err := r.FormFile("foo") if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to extract file : " + err.Error()}) return } ctx := appengine.NewContext(r) // {{{ このへんはGETと同様なのでコメント割愛 bucketname, err := file.DefaultBucketName(ctx) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to get bucket name: " + err.Error()}) return } client, err := storage.NewClient(ctx) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to get client: " + err.Error()}) return } // }}} // アップロードされたファイルのコンテンツを読む body, err := ioutil.ReadAll(f) if err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to read uploaded file: " + err.Error()}) return } // Writer取得 writer := client.Bucket(bucketname).Object(objname).NewWriter(ctx) writer.ContentType = "text/plain" defer writer.Close() // コンテンツを書き込む if _, err := writer.Write(body); err != nil { marmoset.Render(w).HTML("index", map[string]interface{}{"error": "Failed to write to object: " + err.Error()}) return } marmoset.Render(w).HTML("index", map[string]interface{}{ "success": "Successfully uploaded file: " + h.Filename, "objname": h.Filename, "content": string(body), }) }) http.Handle("/", router) }
ローカルで動かすとReadで401とか返ってくるのはさておき、とりあえずデプロイしたら動いた。
雑感
- 開発段階で、ローカルからもGCSにRead/Writeしたいんだけど、どうすんだろ
staging.{app_id}
っていうbucketも同時につくられる- これを指定するのは手動で
staging.
プレフィックスをつければいいんだろうか - GetDefaultBucketNameがあるんだから、GetStagingBucketNameみたいなのあるのかな?
- これを指定するのは手動で
DRYな備忘録として
*1:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L68
*2:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L71
*3:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L77
*4:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L96-L112
*5:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L126
*6:https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/examples/storage/appengine/app.go#L152