Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws/request: Fix for Go 1.8 request incorrectly sent with body #991

Merged
merged 11 commits into from
Dec 13, 2016

Conversation

jasdel
Copy link
Contributor

@jasdel jasdel commented Dec 9, 2016

Go 1.8 tightened and clarified the rules code needs to use when building
requests with the http package. Go 1.8 removed the automatic detection
of if the Request.Body was empty, or actually had bytes in it. The SDK
always sets the Request.Body even if it is empty and should not actually
be sent. This is incorrect.

Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
client that the request really should be sent without a body. The
Request.Body cannot be set to nil, which is preferable, because the
field is exported and could introduce nil pointer dereferences for users
of the SDK if they used that field.

This change also deprecates the aws.ReaderSeekerCloser type. This
type has a bug in its design that hides the fact the underlying reader
is not also a seeker. This obfication, leads to unexpected bugs. If using
this type for operations such as S3.PutObject it is suggested to use
s3manager.Upload instead. The s3manager.Upload takes an io.Reader
to make streaming to S3 easier.

Related golang/go#18257

Fix #984

Go 1.8 tightened and clarified the rules code needs to use when building
requests with the http package. Go 1.8 removed the automatic detection
of if the Request.Body was empty, or actually had bytes in it. The SDK
always sets the Request.Body even if it is empty and should not actually
be sent. This is incorrect.

Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
client that the request really should be sent without a body. The
Request.Body cannot be set to nil, which is preferable, because the
field is exported and could introduce nil pointer dereferences for users
of the SDK if they used that field.

Related golang/go#18257

Fix aws#984
@jasdel jasdel requested a review from xibz December 10, 2016 00:00
r.Error = awserr.New("SerializationError", "failed to seek request body", err)
return
}
_, err = r.Body.Seek(r.BodyStart, io.SeekStart)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove copy/paste error handling?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking adding a util function that does all this and returns the size of the body.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah included this in the refactor

r.ResetBody()

if v, ok := r.HTTPRequest.Body.(*offsetReader); !ok || v == nil {
t.Errorf("expected request body to be set to reader, got %#v",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update error messages to be more obvious what failed. This and other tests added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Copy link
Contributor

@xibz xibz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments

func resetBody(r *Request) {
r.safeBody = newOffsetReader(r.Body, r.BodyStart)

seekable := false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seekable := true

seekable = v.IsSeeker()
case *aws.ReaderSeekerCloser:
seekable = v.IsSeeker()
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can get rid of this

r.Error = awserr.New("SerializationError", "failed to seek request body", err)
return
}
_, err = r.Body.Seek(r.BodyStart, io.SeekStart)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking adding a util function that does all this and returns the size of the body.

// of the SDK if they used that field.
//
// Related golang/go#18257
func resetBody(r *Request) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we feel about returning an error? I feel like when we get an error in here, it'll at least clean it up some, rather than mutating the request's error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up refactoring this logic simplifying this

"testing"
)

func TestResetBody_WithBodyContents(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests to test the non-seekable case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add

@pin
Copy link

pin commented Dec 13, 2016

We have run a bunch of S3-related tests on 0a88972 with go1.8beta1 and it seems that it works correctly.

In current version we see issues you mentioned in #984 (comment): "0" symbol in the body where body is supposed to be empty (e.g. HEAD request) appears because net/http applies chunked encoding on []byte(""): zero is just a length of the first chunk.

@jasdel
Copy link
Contributor Author

jasdel commented Dec 13, 2016

@pin thanks for testing against this change. I think you're correct. The SDK pre-sets the request to a io.ReadSeeker, and later populates this with connects. The SDK was relying on a undocumented feature in Go where Go's http client would figure out if the request should be sent with a request body or not based on the length of the body.

In the case of the SDK, AWS services do not seem to like GET, HEAD, or DELETE HTTP requests with chunked encoding bodies, even if those body chunks are 0 length. The request will fails with a timeout. I'm working on reaching out internally to see if that issue can be addressed in a broader way server side to ignore the body instead of timing out the socket. But, for now this change should fix the SDK's behavior.

Copy link
Contributor

@xibz xibz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the one comment

return
}

if l == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this looks a lot better

@@ -321,7 +331,7 @@ func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, regi
// If the request is not presigned the body should be attached to it. This
// prevents the confusion of wanting to send a signed request without
// the body the request was signed for attached.
if !ctx.isPresign {
if !v4.DisableRequestBodyOverwrite && !ctx.isPresign {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!(v4.DisableRequestBodyOverwrite || ctx.isPresign)

Don't know if the Go compiler does De Morgan's laws under the hood.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated thanks

@jasdel jasdel merged commit db7b699 into aws:master Dec 13, 2016
@jasdel jasdel deleted the fix/requestBody branch December 13, 2016 23:40
@awstools awstools mentioned this pull request Dec 15, 2016
skotambkar added a commit to skotambkar/aws-sdk-go that referenced this pull request May 20, 2021
Adds resolver function for loading S3 client specific config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants