-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
service/s3: Fixes parsing S3 error messages in HTTP 200 responses
If a S3 Copy operation fails a HTTP 200 response could still be returned with an error message body. This change adds special handling to S3 unmarshalling to look for error responses for these operations. http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html
- Loading branch information
Showing
4 changed files
with
168 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -215,4 +215,4 @@ func elemOf(value reflect.Value) reflect.Value { | |
value = value.Elem() | ||
} | ||
return value | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package s3 | ||
|
||
import ( | ||
"bytes" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/aws/aws-sdk-go/aws/request" | ||
) | ||
|
||
func copyMultipartStatusOKUnmarhsalError(r *request.Request) { | ||
b, err := ioutil.ReadAll(r.HTTPResponse.Body) | ||
if err != nil { | ||
r.Error = awserr.New("SerializationError", "unable to read response body", err) | ||
return | ||
} | ||
body := bytes.NewReader(b) | ||
r.HTTPResponse.Body = aws.ReadSeekCloser(body) | ||
defer r.HTTPResponse.Body.(aws.ReaderSeekerCloser).Seek(0, 0) | ||
|
||
if body.Len() == 0 { | ||
// If there is no body don't attempt to parse the body. | ||
return | ||
} | ||
|
||
unmarshalError(r) | ||
if err, ok := r.Error.(awserr.Error); ok && err != nil { | ||
if err.Code() == "SerializationError" { | ||
r.Error = nil | ||
return | ||
} | ||
r.HTTPResponse.StatusCode = http.StatusServiceUnavailable | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package s3_test | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"fmt" | ||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/aws/aws-sdk-go/service/s3" | ||
"time" | ||
) | ||
|
||
const errMsg = `<Error><Code>ErrorCode</Code><Message>message body</Message><RequestId>requestID</RequestId><HostId>hostID=</HostId></Error>` | ||
|
||
var lastModifiedTime = time.Date(2009, 11, 23, 0, 0, 0, 0, time.UTC) | ||
|
||
func TestCopyObjectNoError(t *testing.T) { | ||
const successMsg = ` | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<CopyObjectResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>"1da64c7f13d1e8dbeaea40b905fd586c"</ETag></CopyObjectResult>` | ||
|
||
res, err := newCopyTestSvc(successMsg).CopyObject(&s3.CopyObjectInput{ | ||
Bucket: aws.String("bucketname"), | ||
CopySource: aws.String("bucketname/exists.txt"), | ||
Key: aws.String("destination.txt"), | ||
}) | ||
|
||
require.NoError(t, err) | ||
|
||
assert.Equal(t, fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyObjectResult.ETag) | ||
assert.Equal(t, lastModifiedTime, *res.CopyObjectResult.LastModified) | ||
} | ||
|
||
func TestCopyObjectError(t *testing.T) { | ||
_, err := newCopyTestSvc(errMsg).CopyObject(&s3.CopyObjectInput{ | ||
Bucket: aws.String("bucketname"), | ||
CopySource: aws.String("bucketname/doesnotexist.txt"), | ||
Key: aws.String("destination.txt"), | ||
}) | ||
|
||
require.Error(t, err) | ||
e := err.(awserr.Error) | ||
|
||
assert.Equal(t, "ErrorCode", e.Code()) | ||
assert.Equal(t, "message body", e.Message()) | ||
} | ||
|
||
func TestUploadPartCopySuccess(t *testing.T) { | ||
const successMsg = ` | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<UploadPartCopyResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>"1da64c7f13d1e8dbeaea40b905fd586c"</ETag></CopyObjectResult>` | ||
|
||
res, err := newCopyTestSvc(successMsg).UploadPartCopy(&s3.UploadPartCopyInput{ | ||
Bucket: aws.String("bucketname"), | ||
CopySource: aws.String("bucketname/doesnotexist.txt"), | ||
Key: aws.String("destination.txt"), | ||
PartNumber: aws.Int64(0), | ||
UploadId: aws.String("uploadID"), | ||
}) | ||
|
||
require.NoError(t, err) | ||
|
||
assert.Equal(t, fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyPartResult.ETag) | ||
assert.Equal(t, lastModifiedTime, *res.CopyPartResult.LastModified) | ||
} | ||
|
||
func TestUploadPartCopyError(t *testing.T) { | ||
_, err := newCopyTestSvc(errMsg).UploadPartCopy(&s3.UploadPartCopyInput{ | ||
Bucket: aws.String("bucketname"), | ||
CopySource: aws.String("bucketname/doesnotexist.txt"), | ||
Key: aws.String("destination.txt"), | ||
PartNumber: aws.Int64(0), | ||
UploadId: aws.String("uploadID"), | ||
}) | ||
|
||
require.Error(t, err) | ||
e := err.(awserr.Error) | ||
|
||
assert.Equal(t, "ErrorCode", e.Code()) | ||
assert.Equal(t, "message body", e.Message()) | ||
} | ||
|
||
func TestCompleteMultipartUploadSuccess(t *testing.T) { | ||
const successMsg = ` | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>locationName</Location><Bucket>bucketName</Bucket><Key>keyName</Key><ETag>"etagVal"</ETag></CompleteMultipartUploadResult>` | ||
res, err := newCopyTestSvc(successMsg).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{ | ||
Bucket: aws.String("bucketname"), | ||
Key: aws.String("key"), | ||
UploadId: aws.String("uploadID"), | ||
}) | ||
|
||
require.NoError(t, err) | ||
|
||
assert.Equal(t, `"etagVal"`, *res.ETag) | ||
assert.Equal(t, "bucketName", *res.Bucket) | ||
assert.Equal(t, "keyName", *res.Key) | ||
assert.Equal(t, "locationName", *res.Location) | ||
} | ||
|
||
func TestCompleteMultipartUploadError(t *testing.T) { | ||
_, err := newCopyTestSvc(errMsg).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{ | ||
Bucket: aws.String("bucketname"), | ||
Key: aws.String("key"), | ||
UploadId: aws.String("uploadID"), | ||
}) | ||
|
||
require.Error(t, err) | ||
e := err.(awserr.Error) | ||
|
||
assert.Equal(t, "ErrorCode", e.Code()) | ||
assert.Equal(t, "message body", e.Message()) | ||
} | ||
|
||
func newCopyTestSvc(errMsg string) *s3.S3 { | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
http.Error(w, errMsg, http.StatusOK) | ||
})) | ||
return s3.New(aws.NewConfig(). | ||
WithEndpoint(server.URL). | ||
WithDisableSSL(true). | ||
WithMaxRetries(0). | ||
WithS3ForcePathStyle(true)) | ||
} |