Skip to content

Commit 6a1b839

Browse files
authored
Request.SetBody() allows overwriting with an empty body (#19754)
1 parent 5210814 commit 6a1b839

File tree

3 files changed

+53
-18
lines changed

3 files changed

+53
-18
lines changed

sdk/azcore/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
### Other Changes
1515
* Updated `internal` module to latest version.
16+
* `policy/Request.SetBody()` allows replacing a request's body with an empty one
1617

1718
## 1.2.0 (2022-11-04)
1819

sdk/azcore/internal/exported/request.go

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,46 @@ func (req *Request) OperationValue(value interface{}) bool {
100100
return req.values.get(value)
101101
}
102102

103-
// SetBody sets the specified ReadSeekCloser as the HTTP request body.
103+
// SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length
104+
// accordingly. If the ReadSeekCloser is nil or empty, Content-Length won't be set. If contentType is "",
105+
// Content-Type won't be set.
104106
func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error {
105-
// Set the body and content length.
106-
size, err := body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
107-
if err != nil {
108-
return err
107+
var err error
108+
var size int64
109+
if body != nil {
110+
size, err = body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
111+
if err != nil {
112+
return err
113+
}
109114
}
110115
if size == 0 {
111-
body.Close()
112-
return nil
113-
}
114-
_, err = body.Seek(0, io.SeekStart)
115-
if err != nil {
116-
return err
116+
// treat an empty stream the same as a nil one: assign req a nil body
117+
body = nil
118+
// RFC 9110 specifies a client shouldn't set Content-Length on a request containing no content
119+
// (Del is a no-op when the header has no value)
120+
req.req.Header.Del(shared.HeaderContentLength)
121+
} else {
122+
_, err = body.Seek(0, io.SeekStart)
123+
if err != nil {
124+
return err
125+
}
126+
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
127+
req.Raw().GetBody = func() (io.ReadCloser, error) {
128+
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
129+
return body, err
130+
}
117131
}
118-
req.Raw().GetBody = func() (io.ReadCloser, error) {
119-
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
120-
return body, err
121-
}
122-
// keep a copy of the original body. this is to handle cases
132+
// keep a copy of the body argument. this is to handle cases
123133
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
124134
req.body = body
125135
req.req.Body = body
126136
req.req.ContentLength = size
127-
req.req.Header.Set(shared.HeaderContentType, contentType)
128-
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
137+
if contentType == "" {
138+
// Del is a no-op when the header has no value
139+
req.req.Header.Del(shared.HeaderContentType)
140+
} else {
141+
req.req.Header.Set(shared.HeaderContentType, contentType)
142+
}
129143
return nil
130144
}
131145

sdk/azcore/internal/exported/request_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414
"testing"
1515

16+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
1617
"github.com/stretchr/testify/require"
1718
)
1819

@@ -123,6 +124,25 @@ func TestRequestEmptyBody(t *testing.T) {
123124
require.NoError(t, err)
124125
require.NoError(t, req.SetBody(NopCloser(strings.NewReader("")), "application/text"))
125126
require.Nil(t, req.Body())
127+
require.NotContains(t, req.Raw().Header, shared.HeaderContentLength)
128+
require.Equal(t, []string{"application/text"}, req.Raw().Header[shared.HeaderContentType])
129+
130+
// SetBody should treat a nil ReadSeekCloser the same as one having no content
131+
req, err = NewRequest(context.Background(), http.MethodPost, testURL)
132+
require.NoError(t, err)
133+
require.NoError(t, req.SetBody(nil, ""))
134+
require.Nil(t, req.Body())
135+
require.NotContains(t, req.Raw().Header, shared.HeaderContentLength)
136+
require.NotContains(t, req.Raw().Header, shared.HeaderContentType)
137+
138+
// SetBody should allow replacing a previously set body with an empty one
139+
req, err = NewRequest(context.Background(), http.MethodPost, testURL)
140+
require.NoError(t, err)
141+
require.NoError(t, req.SetBody(NopCloser(strings.NewReader("content")), "application/text"))
142+
require.NoError(t, req.SetBody(nil, "application/json"))
143+
require.Nil(t, req.Body())
144+
require.NotContains(t, req.Raw().Header, shared.HeaderContentLength)
145+
require.Equal(t, []string{"application/json"}, req.Raw().Header[shared.HeaderContentType])
126146
}
127147

128148
func TestRequestClone(t *testing.T) {

0 commit comments

Comments
 (0)