blobstore.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Copyright 2011 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. // Package blobstore provides a client for App Engine's persistent blob
  5. // storage service.
  6. package blobstore // import "google.golang.org/appengine/blobstore"
  7. import (
  8. "bufio"
  9. "encoding/base64"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "mime"
  14. "mime/multipart"
  15. "net/http"
  16. "net/textproto"
  17. "net/url"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "github.com/golang/protobuf/proto"
  22. "golang.org/x/net/context"
  23. "google.golang.org/appengine"
  24. "google.golang.org/appengine/datastore"
  25. "google.golang.org/appengine/internal"
  26. basepb "google.golang.org/appengine/internal/base"
  27. blobpb "google.golang.org/appengine/internal/blobstore"
  28. )
  29. const (
  30. blobInfoKind = "__BlobInfo__"
  31. blobFileIndexKind = "__BlobFileIndex__"
  32. zeroKey = appengine.BlobKey("")
  33. )
  34. // BlobInfo is the blob metadata that is stored in the datastore.
  35. // Filename may be empty.
  36. type BlobInfo struct {
  37. BlobKey appengine.BlobKey
  38. ContentType string `datastore:"content_type"`
  39. CreationTime time.Time `datastore:"creation"`
  40. Filename string `datastore:"filename"`
  41. Size int64 `datastore:"size"`
  42. MD5 string `datastore:"md5_hash"`
  43. // ObjectName is the Google Cloud Storage name for this blob.
  44. ObjectName string `datastore:"gs_object_name"`
  45. }
  46. // isErrFieldMismatch returns whether err is a datastore.ErrFieldMismatch.
  47. //
  48. // The blobstore stores blob metadata in the datastore. When loading that
  49. // metadata, it may contain fields that we don't care about. datastore.Get will
  50. // return datastore.ErrFieldMismatch in that case, so we ignore that specific
  51. // error.
  52. func isErrFieldMismatch(err error) bool {
  53. _, ok := err.(*datastore.ErrFieldMismatch)
  54. return ok
  55. }
  56. // Stat returns the BlobInfo for a provided blobKey. If no blob was found for
  57. // that key, Stat returns datastore.ErrNoSuchEntity.
  58. func Stat(c context.Context, blobKey appengine.BlobKey) (*BlobInfo, error) {
  59. c, _ = appengine.Namespace(c, "") // Blobstore is always in the empty string namespace
  60. dskey := datastore.NewKey(c, blobInfoKind, string(blobKey), 0, nil)
  61. bi := &BlobInfo{
  62. BlobKey: blobKey,
  63. }
  64. if err := datastore.Get(c, dskey, bi); err != nil && !isErrFieldMismatch(err) {
  65. return nil, err
  66. }
  67. return bi, nil
  68. }
  69. // Send sets the headers on response to instruct App Engine to send a blob as
  70. // the response body. This is more efficient than reading and writing it out
  71. // manually and isn't subject to normal response size limits.
  72. func Send(response http.ResponseWriter, blobKey appengine.BlobKey) {
  73. hdr := response.Header()
  74. hdr.Set("X-AppEngine-BlobKey", string(blobKey))
  75. if hdr.Get("Content-Type") == "" {
  76. // This value is known to dev_appserver to mean automatic.
  77. // In production this is remapped to the empty value which
  78. // means automatic.
  79. hdr.Set("Content-Type", "application/vnd.google.appengine.auto")
  80. }
  81. }
  82. // UploadURL creates an upload URL for the form that the user will
  83. // fill out, passing the application path to load when the POST of the
  84. // form is completed. These URLs expire and should not be reused. The
  85. // opts parameter may be nil.
  86. func UploadURL(c context.Context, successPath string, opts *UploadURLOptions) (*url.URL, error) {
  87. req := &blobpb.CreateUploadURLRequest{
  88. SuccessPath: proto.String(successPath),
  89. }
  90. if opts != nil {
  91. if n := opts.MaxUploadBytes; n != 0 {
  92. req.MaxUploadSizeBytes = &n
  93. }
  94. if n := opts.MaxUploadBytesPerBlob; n != 0 {
  95. req.MaxUploadSizePerBlobBytes = &n
  96. }
  97. if s := opts.StorageBucket; s != "" {
  98. req.GsBucketName = &s
  99. }
  100. }
  101. res := &blobpb.CreateUploadURLResponse{}
  102. if err := internal.Call(c, "blobstore", "CreateUploadURL", req, res); err != nil {
  103. return nil, err
  104. }
  105. return url.Parse(*res.Url)
  106. }
  107. // UploadURLOptions are the options to create an upload URL.
  108. type UploadURLOptions struct {
  109. MaxUploadBytes int64 // optional
  110. MaxUploadBytesPerBlob int64 // optional
  111. // StorageBucket specifies the Google Cloud Storage bucket in which
  112. // to store the blob.
  113. // This is required if you use Cloud Storage instead of Blobstore.
  114. // Your application must have permission to write to the bucket.
  115. // You may optionally specify a bucket name and path in the format
  116. // "bucket_name/path", in which case the included path will be the
  117. // prefix of the uploaded object's name.
  118. StorageBucket string
  119. }
  120. // Delete deletes a blob.
  121. func Delete(c context.Context, blobKey appengine.BlobKey) error {
  122. return DeleteMulti(c, []appengine.BlobKey{blobKey})
  123. }
  124. // DeleteMulti deletes multiple blobs.
  125. func DeleteMulti(c context.Context, blobKey []appengine.BlobKey) error {
  126. s := make([]string, len(blobKey))
  127. for i, b := range blobKey {
  128. s[i] = string(b)
  129. }
  130. req := &blobpb.DeleteBlobRequest{
  131. BlobKey: s,
  132. }
  133. res := &basepb.VoidProto{}
  134. if err := internal.Call(c, "blobstore", "DeleteBlob", req, res); err != nil {
  135. return err
  136. }
  137. return nil
  138. }
  139. func errorf(format string, args ...interface{}) error {
  140. return fmt.Errorf("blobstore: "+format, args...)
  141. }
  142. // ParseUpload parses the synthetic POST request that your app gets from
  143. // App Engine after a user's successful upload of blobs. Given the request,
  144. // ParseUpload returns a map of the blobs received (keyed by HTML form
  145. // element name) and other non-blob POST parameters.
  146. func ParseUpload(req *http.Request) (blobs map[string][]*BlobInfo, other url.Values, err error) {
  147. _, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
  148. if err != nil {
  149. return nil, nil, err
  150. }
  151. boundary := params["boundary"]
  152. if boundary == "" {
  153. return nil, nil, errorf("did not find MIME multipart boundary")
  154. }
  155. blobs = make(map[string][]*BlobInfo)
  156. other = make(url.Values)
  157. mreader := multipart.NewReader(io.MultiReader(req.Body, strings.NewReader("\r\n\r\n")), boundary)
  158. for {
  159. part, perr := mreader.NextPart()
  160. if perr == io.EOF {
  161. break
  162. }
  163. if perr != nil {
  164. return nil, nil, errorf("error reading next mime part with boundary %q (len=%d): %v",
  165. boundary, len(boundary), perr)
  166. }
  167. bi := &BlobInfo{}
  168. ctype, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition"))
  169. if err != nil {
  170. return nil, nil, err
  171. }
  172. bi.Filename = params["filename"]
  173. formKey := params["name"]
  174. ctype, params, err = mime.ParseMediaType(part.Header.Get("Content-Type"))
  175. if err != nil {
  176. return nil, nil, err
  177. }
  178. bi.BlobKey = appengine.BlobKey(params["blob-key"])
  179. if ctype != "message/external-body" || bi.BlobKey == "" {
  180. if formKey != "" {
  181. slurp, serr := ioutil.ReadAll(part)
  182. if serr != nil {
  183. return nil, nil, errorf("error reading %q MIME part", formKey)
  184. }
  185. other[formKey] = append(other[formKey], string(slurp))
  186. }
  187. continue
  188. }
  189. // App Engine sends a MIME header as the body of each MIME part.
  190. tp := textproto.NewReader(bufio.NewReader(part))
  191. header, mimeerr := tp.ReadMIMEHeader()
  192. if mimeerr != nil {
  193. return nil, nil, mimeerr
  194. }
  195. bi.Size, err = strconv.ParseInt(header.Get("Content-Length"), 10, 64)
  196. if err != nil {
  197. return nil, nil, err
  198. }
  199. bi.ContentType = header.Get("Content-Type")
  200. // Parse the time from the MIME header like:
  201. // X-AppEngine-Upload-Creation: 2011-03-15 21:38:34.712136
  202. createDate := header.Get("X-AppEngine-Upload-Creation")
  203. if createDate == "" {
  204. return nil, nil, errorf("expected to find an X-AppEngine-Upload-Creation header")
  205. }
  206. bi.CreationTime, err = time.Parse("2006-01-02 15:04:05.000000", createDate)
  207. if err != nil {
  208. return nil, nil, errorf("error parsing X-AppEngine-Upload-Creation: %s", err)
  209. }
  210. if hdr := header.Get("Content-MD5"); hdr != "" {
  211. md5, err := base64.URLEncoding.DecodeString(hdr)
  212. if err != nil {
  213. return nil, nil, errorf("bad Content-MD5 %q: %v", hdr, err)
  214. }
  215. bi.MD5 = string(md5)
  216. }
  217. // If the GCS object name was provided, record it.
  218. bi.ObjectName = header.Get("X-AppEngine-Cloud-Storage-Object")
  219. blobs[formKey] = append(blobs[formKey], bi)
  220. }
  221. return
  222. }
  223. // Reader is a blob reader.
  224. type Reader interface {
  225. io.Reader
  226. io.ReaderAt
  227. io.Seeker
  228. }
  229. // NewReader returns a reader for a blob. It always succeeds; if the blob does
  230. // not exist then an error will be reported upon first read.
  231. func NewReader(c context.Context, blobKey appengine.BlobKey) Reader {
  232. return openBlob(c, blobKey)
  233. }
  234. // BlobKeyForFile returns a BlobKey for a Google Storage file.
  235. // The filename should be of the form "/gs/bucket_name/object_name".
  236. func BlobKeyForFile(c context.Context, filename string) (appengine.BlobKey, error) {
  237. req := &blobpb.CreateEncodedGoogleStorageKeyRequest{
  238. Filename: &filename,
  239. }
  240. res := &blobpb.CreateEncodedGoogleStorageKeyResponse{}
  241. if err := internal.Call(c, "blobstore", "CreateEncodedGoogleStorageKey", req, res); err != nil {
  242. return "", err
  243. }
  244. return appengine.BlobKey(*res.BlobKey), nil
  245. }