func (b *bucket) StatObject( ctx context.Context, req *StatObjectRequest) (o *Object, err error) { // Construct an appropriate URL (cf. http://goo.gl/MoITmB). opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o/%s", httputil.EncodePathSegment(b.Name()), httputil.EncodePathSegment(req.Name)) query := make(url.Values) query.Set("projection", "full") url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Create an HTTP request. httpReq, err := httputil.NewRequest(ctx, "GET", url, nil, 0, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Execute the HTTP request. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { // Special case: handle not found errors. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusNotFound { err = &NotFoundError{Err: typed} } } return } // Parse the response. var rawObject *storagev1.Object if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil { return } // Convert the response. if o, err = toObject(rawObject); err != nil { err = fmt.Errorf("toObject: %v", err) return } return }
func (b *bucket) DeleteObject( ctx context.Context, req *DeleteObjectRequest) (err error) { // Construct an appropriate URL (cf. http://goo.gl/TRQJjZ). opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o/%s", httputil.EncodePathSegment(b.Name()), httputil.EncodePathSegment(req.Name)) query := make(url.Values) if req.Generation != 0 { query.Set("generation", fmt.Sprintf("%d", req.Generation)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Create an HTTP request. httpReq, err := httputil.NewRequest("DELETE", url, nil, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Execute the HTTP request. httpRes, err := httputil.Do(ctx, b.client, httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. err = googleapi.CheckResponse(httpRes) // Special case: we want deletes to be idempotent. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusNotFound { err = nil } } // Propagate other errors. if err != nil { return } return }
func (b *bucket) ComposeObjects( ctx context.Context, req *ComposeObjectsRequest) (o *Object, err error) { // We encode using json.NewEncoder, which is documented to silently transform // invalid UTF-8 (cf. http://goo.gl/3gIUQB). So we can't rely on the server // to detect this for us. if !utf8.ValidString(req.DstName) { err = errors.New("Invalid object name: not valid UTF-8") return } // Construct an appropriate URL. bucketSegment := httputil.EncodePathSegment(b.Name()) objectSegment := httputil.EncodePathSegment(req.DstName) opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o/%s/compose", bucketSegment, objectSegment) query := make(url.Values) if req.DstGenerationPrecondition != nil { query.Set("ifGenerationMatch", fmt.Sprint(*req.DstGenerationPrecondition)) } if req.DstMetaGenerationPrecondition != nil { query.Set( "ifMetagenerationMatch", fmt.Sprint(*req.DstMetaGenerationPrecondition)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Set up the request body. body, err := b.makeComposeObjectsBody(req) if err != nil { err = fmt.Errorf("makeComposeObjectsBody: %v", err) return } // Create the HTTP request. httpReq, err := httputil.NewRequest( ctx, "POST", url, body, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Set up HTTP request headers. httpReq.Header.Set("Content-Type", "application/json") // Execute the HTTP request. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { // Special case: handle not found and precondition errors. if typed, ok := err.(*googleapi.Error); ok { switch typed.Code { case http.StatusNotFound: err = &NotFoundError{Err: typed} case http.StatusPreconditionFailed: err = &PreconditionError{Err: typed} } } return } // Parse the response. var rawObject *storagev1.Object if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil { return } // Convert the response. if o, err = toObject(rawObject); err != nil { err = fmt.Errorf("toObject: %v", err) return } return }
func (b *bucket) UpdateObject( ctx context.Context, req *UpdateObjectRequest) (o *Object, err error) { // Construct an appropriate URL (cf. http://goo.gl/B46IDy). opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o/%s", httputil.EncodePathSegment(b.Name()), httputil.EncodePathSegment(req.Name)) query := make(url.Values) query.Set("projection", "full") if req.Generation != 0 { query.Set("generation", fmt.Sprintf("%d", req.Generation)) } if req.MetaGenerationPrecondition != nil { query.Set( "ifMetagenerationMatch", fmt.Sprintf("%d", *req.MetaGenerationPrecondition)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Set up the request body. body, err := b.makeUpdateObjectBody(req) if err != nil { err = fmt.Errorf("makeUpdateObjectBody: %v", err) return } // Create an HTTP request. httpReq, err := httputil.NewRequest(ctx, "PATCH", url, body, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } httpReq.Header.Set("Content-Type", "application/json") // Execute the HTTP request. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { // Special case: handle not found errors. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusNotFound { err = &NotFoundError{Err: typed} } } // Special case: handle precondition errors. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusPreconditionFailed { err = &PreconditionError{Err: typed} } } return } // Parse the response. var rawObject *storagev1.Object if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil { return } // Convert the response. if o, err = toObject(rawObject); err != nil { err = fmt.Errorf("toObject: %v", err) return } return }
func (b *bucket) CopyObject( ctx context.Context, req *CopyObjectRequest) (o *Object, err error) { // We encode using json.NewEncoder, which is documented to silently transform // invalid UTF-8 (cf. http://goo.gl/3gIUQB). So we can't rely on the server // to detect this for us. if !utf8.ValidString(req.DstName) { err = errors.New("Invalid object name: not valid UTF-8") return } // Construct an appropriate URL (cf. https://goo.gl/A41CyJ). opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o/%s/copyTo/b/%s/o/%s", httputil.EncodePathSegment(b.Name()), httputil.EncodePathSegment(req.SrcName), httputil.EncodePathSegment(b.Name()), httputil.EncodePathSegment(req.DstName)) query := make(url.Values) query.Set("projection", "full") if req.SrcGeneration != 0 { query.Set("sourceGeneration", fmt.Sprintf("%d", req.SrcGeneration)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // We don't want to update any metadata. body := ioutil.NopCloser(strings.NewReader("")) // Create an HTTP request. httpReq, err := httputil.NewRequest("POST", url, body, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } httpReq.Header.Set("Content-Type", "application/json") // Execute the HTTP request. httpRes, err := httputil.Do(ctx, b.client, httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { // Special case: handle not found errors. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusNotFound { err = &NotFoundError{Err: typed} } } return } // Parse the response. var rawObject *storagev1.Object if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil { return } // Convert the response. if o, err = toObject(rawObject); err != nil { err = fmt.Errorf("toObject: %v", err) return } return }
func (b *bucket) startResumableUpload( ctx context.Context, req *CreateObjectRequest) (uploadURL *url.URL, err error) { // Construct an appropriate URL. // // The documentation (http://goo.gl/IJSlVK) is extremely vague about how this // is supposed to work. As of 2015-03-26, it simply gives an example: // // POST https://www.googleapis.com/upload/storage/v1/b/<bucket>/o // // In Google-internal bug 19718068, it was clarified that the intent is that // the bucket name be encoded into a single path segment, as defined by RFC // 3986. bucketSegment := httputil.EncodePathSegment(b.Name()) opaque := fmt.Sprintf( "//www.googleapis.com/upload/storage/v1/b/%s/o", bucketSegment) query := make(url.Values) query.Set("projection", "full") query.Set("uploadType", "resumable") if req.GenerationPrecondition != nil { query.Set("ifGenerationMatch", fmt.Sprint(*req.GenerationPrecondition)) } if req.MetaGenerationPrecondition != nil { query.Set( "ifMetagenerationMatch", fmt.Sprint(*req.MetaGenerationPrecondition)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Set up the request body. body, err := b.makeCreateObjectBody(req) if err != nil { err = fmt.Errorf("makeCreateObjectBody: %v", err) return } // Create the HTTP request. httpReq, err := httputil.NewRequest( ctx, "POST", url, body, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Set up HTTP request headers. httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("X-Upload-Content-Type", req.ContentType) // Execute the HTTP request. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { return } // Extract the Location header. str := httpRes.Header.Get("Location") if str == "" { err = fmt.Errorf("Expected a Location header.") return } // Parse it. uploadURL, err = url.Parse(str) if err != nil { err = fmt.Errorf("url.Parse: %v", err) return } return }
func (b *bucket) CreateObject( ctx context.Context, req *CreateObjectRequest) (o *Object, err error) { // We encode using json.NewEncoder, which is documented to silently transform // invalid UTF-8 (cf. http://goo.gl/3gIUQB). So we can't rely on the server // to detect this for us. if !utf8.ValidString(req.Name) { err = errors.New("Invalid object name: not valid UTF-8") return } // Start a resumable upload, obtaining an upload URL. uploadURL, err := b.startResumableUpload(ctx, req) if err != nil { return } // Set up a follow-up request to the upload URL. httpReq, err := httputil.NewRequest( ctx, "PUT", uploadURL, ioutil.NopCloser(req.Contents), b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } httpReq.Header.Set("Content-Type", req.ContentType) // Execute the request. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { // Special case: handle precondition errors. if typed, ok := err.(*googleapi.Error); ok { if typed.Code == http.StatusPreconditionFailed { err = &PreconditionError{Err: typed} } } return } // Parse the response. var rawObject *storagev1.Object if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil { return } // Convert the response. if o, err = toObject(rawObject); err != nil { err = fmt.Errorf("toObject: %v", err) return } return }
func (b *bucket) NewReader( ctx context.Context, req *ReadObjectRequest) (rc io.ReadCloser, err error) { // Construct an appropriate URL. // // The documentation (https://goo.gl/9zeA98) is vague about how this is // supposed to work. As of 2015-05-14, it has no prose but gives the example: // // www.googleapis.com/download/storage/v1/b/<bucket>/o/<object>?alt=media // // In Google-internal bug 19718068, it was clarified that the intent is that // each of the bucket and object names are encoded into a single path // segment, as defined by RFC 3986. bucketSegment := httputil.EncodePathSegment(b.name) objectSegment := httputil.EncodePathSegment(req.Name) opaque := fmt.Sprintf( "//www.googleapis.com/download/storage/v1/b/%s/o/%s", bucketSegment, objectSegment) query := make(url.Values) query.Set("alt", "media") if req.Generation != 0 { query.Set("generation", fmt.Sprintf("%d", req.Generation)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Create an HTTP request. httpReq, err := httputil.NewRequest("GET", url, nil, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Set a Range header, if appropriate. var bodyLimit int64 if req.Range != nil { var v string v, bodyLimit = makeRangeHeaderValue(*req.Range) httpReq.Header.Set("Range", v) } // Call the server. httpRes, err := httputil.Do(ctx, b.client, httpReq) if err != nil { return } // Close the body if we're returning in error. defer func() { if err != nil { googleapi.CloseBody(httpRes) } }() // Check for HTTP error statuses. if err = googleapi.CheckResponse(httpRes); err != nil { if typed, ok := err.(*googleapi.Error); ok { // Special case: handle not found errors. if typed.Code == http.StatusNotFound { err = &NotFoundError{Err: typed} } // Special case: if the user requested a range and we received HTTP 416 // from the server, treat this as an empty body. See makeRangeHeaderValue // for more details. if req.Range != nil && typed.Code == http.StatusRequestedRangeNotSatisfiable { err = nil googleapi.CloseBody(httpRes) rc = ioutil.NopCloser(strings.NewReader("")) } } return } // The body contains the object data. rc = httpRes.Body // If the user requested a range and we didn't see HTTP 416 above, we require // an HTTP 206 response and must truncate the body. See the notes on // makeRangeHeaderValue. if req.Range != nil { if httpRes.StatusCode != http.StatusPartialContent { err = fmt.Errorf( "Received unexpected status code %d instead of HTTP 206", httpRes.StatusCode) return } rc = newLimitReadCloser(rc, bodyLimit) } return }
func (b *bucket) ListObjects( ctx context.Context, req *ListObjectsRequest) (listing *Listing, err error) { // Construct an appropriate URL (cf. http://goo.gl/aVSAhT). opaque := fmt.Sprintf( "//www.googleapis.com/storage/v1/b/%s/o", httputil.EncodePathSegment(b.Name())) query := make(url.Values) query.Set("projection", "full") if req.Prefix != "" { query.Set("prefix", req.Prefix) } if req.Delimiter != "" { query.Set("delimiter", req.Delimiter) } if req.ContinuationToken != "" { query.Set("pageToken", req.ContinuationToken) } if req.MaxResults != 0 { query.Set("maxResults", fmt.Sprintf("%v", req.MaxResults)) } url := &url.URL{ Scheme: "https", Host: "www.googleapis.com", Opaque: opaque, RawQuery: query.Encode(), } // Create an HTTP request. httpReq, err := httputil.NewRequest(ctx, "GET", url, nil, 0, b.userAgent) if err != nil { err = fmt.Errorf("httputil.NewRequest: %v", err) return } // Call the server. httpRes, err := b.client.Do(httpReq) if err != nil { return } defer googleapi.CloseBody(httpRes) // Check for HTTP-level errors. if err = googleapi.CheckResponse(httpRes); err != nil { return } // Parse the response. var rawListing *storagev1.Objects if err = json.NewDecoder(httpRes.Body).Decode(&rawListing); err != nil { return } // Convert the response. if listing, err = toListing(rawListing); err != nil { return } return }