From 8079b244c7e1142a3b77ec6c0606684f48b46157 Mon Sep 17 00:00:00 2001 From: Rohan Verma Date: Wed, 26 Dec 2018 13:36:35 +0530 Subject: [PATCH] feat: read response from 201 on file upload --- README.md | 6 +++++- avatar.png | Bin 0 -> 6294 bytes simples3.go | 48 +++++++++++++++++++++++++++++++++++------------ simples3_test.go | 31 +++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 avatar.png diff --git a/README.md b/README.md index 0422cf7..96a4f53 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,13 @@ s3 := simples3.New(Region, AWSAccessKey, AWSSecretKey) // obtain credentials from IAM attached to the instance. s3 := simples3.NewUsingIAM(Region) +// Note: Consider adding a testTxt.Seek(0, 0) +// in case you have read +// the body, as the pointer is shared by the library. + // File Upload is as simple as providing the following // details. -err := s3.FileUpload(simples3.UploadInput{ +resp, err := s3.FileUpload(simples3.UploadInput{ Bucket: AWSBucket, ObjectKey: "test.txt", ContentType: "text/plain", diff --git a/avatar.png b/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..a71cae1e42783e787090a459184354015b99926c GIT binary patch literal 6294 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 XBwS}qzcxjfHN`FRq5~(u_jCUNduB)E literal 0 HcmV?d00001 diff --git a/simples3.go b/simples3.go index c090632..40cc6a1 100644 --- a/simples3.go +++ b/simples3.go @@ -8,6 +8,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/json" + "encoding/xml" "errors" "fmt" "io" @@ -40,6 +41,22 @@ type UploadInput struct { Body multipart.File } +// UploadResponse receives the following XML +// in case of success, since we set a 201 response from S3. +// Sample response: +// +// https://s3.amazonaws.com/link-to-the-file +// s3-bucket +// development/8614bd40-691b-4668-9241-3b342c6cf429/image.jpg +// "32-bit-tag" +// +type UploadResponse struct { + Location string `xml:"Location"` + Bucket string `xml:"Bucket"` + Key string `xml:"Key"` + ETag string `xml:"ETag"` +} + // DeleteInput is passed to FileDelete as a parameter. type DeleteInput struct { Bucket string @@ -133,7 +150,7 @@ func detectFileSize(body multipart.File) int64 { // FileUpload makes a POST call with the file written as multipart // and on successful upload, checks for 200 OK. -func (s3 *S3) FileUpload(u UploadInput) error { +func (s3 *S3) FileUpload(u UploadInput) (UploadResponse, error) { policies, err := s3.CreateUploadPolicies(UploadConfig{ UploadURL: fmt.Sprintf(s3.URIFormat, s3.Region, u.Bucket), BucketName: u.Bucket, @@ -141,12 +158,12 @@ func (s3 *S3) FileUpload(u UploadInput) error { ContentType: u.ContentType, FileSize: detectFileSize(u.Body), MetaData: map[string]string{ - "success_action_status": "200", + "success_action_status": "201", // returns XML doc on success }, }) if err != nil { - return err + return UploadResponse{}, err } var b bytes.Buffer @@ -154,28 +171,28 @@ func (s3 *S3) FileUpload(u UploadInput) error { for k, v := range policies.Form { if err := w.WriteField(k, v); err != nil { - return err + return UploadResponse{}, err } } fw, err := w.CreateFormFile("file", u.FileName) if err != nil { - return err + return UploadResponse{}, err } if _, err = io.Copy(fw, u.Body); err != nil { - return err + return UploadResponse{}, err } // Don't forget to close the multipart writer. // If you don't close it, your request will be missing the terminating boundary. if err := w.Close(); err != nil { - return err + return UploadResponse{}, err } // Now that you have a form, you can submit it to your handler. req, err := http.NewRequest("POST", policies.URL, &b) if err != nil { - return err + return UploadResponse{}, err } // Don't forget to set the content type, this will contain the boundary. req.Header.Set("Content-Type", w.FormDataContentType()) @@ -184,18 +201,25 @@ func (s3 *S3) FileUpload(u UploadInput) error { client := &http.Client{} res, err := client.Do(req) if err != nil { - return err + return UploadResponse{}, err } + defer res.Body.Close() // Check the response - if res.Status != "200 OK" { + if res.Status != "201 Created" { err = fmt.Errorf("status code: %s", res.Status) data, _ := ioutil.ReadAll(res.Body) log.Printf("response:%s\n", string(data)) - return err + return UploadResponse{}, err } - return nil + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return UploadResponse{}, err + } + var ur UploadResponse + xml.Unmarshal(data, &ur) + return ur, nil } // FileDelete makes a DELETE call with the file written as multipart diff --git a/simples3_test.go b/simples3_test.go index 8cbcc38..8103d9e 100644 --- a/simples3_test.go +++ b/simples3_test.go @@ -11,6 +11,11 @@ func TestS3_FileUpload(t *testing.T) { return } defer testTxt.Close() + testPng, err := os.Open("avatar.png") + if err != nil { + return + } + defer testPng.Close() type fields struct { AccessKey string @@ -44,13 +49,37 @@ func TestS3_FileUpload(t *testing.T) { }, wantErr: false, }, + { + name: "Upload avatar.png", + fields: fields{ + AccessKey: os.Getenv("AWS_S3_ACCESS_KEY"), + SecretKey: os.Getenv("AWS_S3_SECRET_KEY"), + Region: os.Getenv("AWS_S3_REGION"), + }, + args: args{ + UploadInput{ + Bucket: os.Getenv("AWS_S3_BUCKET"), + Bucket: "zerodha-testbucket", + ObjectKey: "xyz/image.png", + ContentType: "image/png", + FileName: "avatar.png", + Body: testPng, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s3 := New(tt.fields.Region, tt.fields.AccessKey, tt.fields.SecretKey) - if err := s3.FileUpload(tt.args.u); (err != nil) != tt.wantErr { + resp, err := s3.FileUpload(tt.args.u) + if (err != nil) != tt.wantErr { t.Errorf("S3.FileUpload() error = %v, wantErr %v", err, tt.wantErr) } + // check for empty response + if (resp == UploadResponse{}) { + t.Errorf("S3.FileUpload() returned empty response, %v", resp) + } }) } }