Skip to content

Commit c93b5cc

Browse files
authored
Merge pull request #2051 from aws/add100ContinueCustomization
Port v1 SDK customization for s3 HTTP PUT request
2 parents 3497eac + c01aac6 commit c93b5cc

File tree

10 files changed

+311
-0
lines changed

10 files changed

+311
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "bbab7da0-e250-4beb-b9d9-99dacb2de133",
3+
"type": "feature",
4+
"description": "port v1 sdk 100-continue http header customization for s3 PutObject/UploadPart request and enable user config",
5+
"modules": [
6+
"service/internal/s3shared",
7+
"service/s3"
8+
]
9+
}

aws/signer/internal/v4/headers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var IgnoredHeaders = Rules{
77
"Authorization": struct{}{},
88
"User-Agent": struct{}{},
99
"X-Amzn-Trace-Id": struct{}{},
10+
"Expect": struct{}{},
1011
},
1112
},
1213
}

aws/signer/internal/v4/headers_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,31 @@ func TestAllowedQueryHoisting(t *testing.T) {
3333
})
3434
}
3535
}
36+
37+
func TestIgnoredHeaders(t *testing.T) {
38+
cases := map[string]struct {
39+
Header string
40+
ExpectIgnored bool
41+
}{
42+
"expect": {
43+
Header: "Expect",
44+
ExpectIgnored: true,
45+
},
46+
"authorization": {
47+
Header: "Authorization",
48+
ExpectIgnored: true,
49+
},
50+
"X-AMZ header": {
51+
Header: "X-Amz-Content-Sha256",
52+
ExpectIgnored: false,
53+
},
54+
}
55+
56+
for name, c := range cases {
57+
t.Run(name, func(t *testing.T) {
58+
if e, a := c.ExpectIgnored, IgnoredHeaders.IsValid(c.Header); e == a {
59+
t.Errorf("expect ignored %v, was %v", e, a)
60+
}
61+
})
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package software.amazon.smithy.aws.go.codegen.customization;
2+
3+
import software.amazon.smithy.codegen.core.SymbolProvider;
4+
import software.amazon.smithy.go.codegen.GoDelegator;
5+
import software.amazon.smithy.go.codegen.GoSettings;
6+
import software.amazon.smithy.go.codegen.GoWriter;
7+
import software.amazon.smithy.go.codegen.SymbolUtils;
8+
import software.amazon.smithy.go.codegen.integration.ConfigField;
9+
import software.amazon.smithy.go.codegen.integration.GoIntegration;
10+
import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar;
11+
import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin;
12+
import software.amazon.smithy.model.Model;
13+
import software.amazon.smithy.model.shapes.ServiceShape;
14+
import software.amazon.smithy.model.traits.HttpTrait;
15+
import software.amazon.smithy.utils.ListUtils;
16+
17+
import java.util.Arrays;
18+
import java.util.HashSet;
19+
import java.util.List;
20+
import java.util.Set;
21+
22+
/**
23+
* Add middleware, which adds {Expect: 100-continue} header for s3 client HTTP PUT request larger than 2MB
24+
* or with unknown size streaming bodies, during operation builder step
25+
*/
26+
public class S3100Continue implements GoIntegration {
27+
private static final String ADD_100Continue_Header = "add100Continue";
28+
private static final String ADD_100Continue_Header_INTERNAL = "Add100Continue";
29+
private static final String Continue_Client_Option = "ContinueHeaderThresholdBytes";
30+
private static final Set<String> Put_Op_ShapeId_Set = new HashSet<>(Arrays.asList("com.amazonaws.s3#PutObject", "com.amazonaws.s3#UploadPart"));
31+
32+
/**
33+
* Return true if service is Amazon S3.
34+
*
35+
* @param model is the generation model.
36+
* @param service is the service shape being audited.
37+
*/
38+
private static boolean isS3Service(Model model, ServiceShape service) {
39+
return S3ModelUtils.isServiceS3(model, service);
40+
}
41+
42+
/**
43+
* Gets the sort order of the customization from -128 to 127, with lowest
44+
* executed first.
45+
*
46+
* @return Returns the sort order, defaults to -40.
47+
*/
48+
@Override
49+
public byte getOrder() {
50+
return 126;
51+
}
52+
53+
@Override
54+
public void writeAdditionalFiles(
55+
GoSettings settings,
56+
Model model,
57+
SymbolProvider symbolProvider,
58+
GoDelegator goDelegator
59+
) {
60+
ServiceShape service = settings.getService(model);
61+
if (!isS3Service(model, service)) {
62+
return;
63+
}
64+
65+
goDelegator.useShapeWriter(service, this::writeMiddlewareHelper);
66+
}
67+
68+
private void writeMiddlewareHelper(GoWriter writer) {
69+
writer.openBlock("func $L(stack *middleware.Stack, options Options) error {", "}", ADD_100Continue_Header, () -> {
70+
writer.write("return $T(stack, options.ContinueHeaderThresholdBytes)",
71+
SymbolUtils.createValueSymbolBuilder(ADD_100Continue_Header_INTERNAL,
72+
AwsCustomGoDependency.S3_SHARED_CUSTOMIZATION).build()
73+
);
74+
});
75+
writer.insertTrailingNewline();
76+
}
77+
78+
@Override
79+
public List<RuntimeClientPlugin> getClientPlugins() {
80+
return ListUtils.of(
81+
RuntimeClientPlugin.builder()
82+
.operationPredicate((model, service, operation) ->
83+
isS3Service(model, service) && Put_Op_ShapeId_Set.contains(operation.getId().toString())
84+
)
85+
.registerMiddleware(MiddlewareRegistrar.builder()
86+
.resolvedFunction(SymbolUtils.createValueSymbolBuilder(ADD_100Continue_Header).build())
87+
.useClientOptions()
88+
.build()
89+
)
90+
.build(),
91+
RuntimeClientPlugin.builder()
92+
.servicePredicate(S3100Continue::isS3Service)
93+
.configFields(ListUtils.of(
94+
ConfigField.builder()
95+
.name(Continue_Client_Option)
96+
.type(SymbolUtils.createValueSymbolBuilder("int64")
97+
.putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true)
98+
.build())
99+
.documentation("The threshold ContentLength in bytes for HTTP PUT request to receive {Expect: 100-continue} header. " +
100+
"Setting to -1 will disable adding the Expect header to requests; setting to 0 will set the threshold " +
101+
"to default 2MB")
102+
.build()
103+
))
104+
.build()
105+
);
106+
}
107+
}

codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ software.amazon.smithy.aws.go.codegen.customization.BackfillEc2UnboxedToBoxedSha
4747
software.amazon.smithy.aws.go.codegen.customization.AdjustAwsRestJsonContentType
4848
software.amazon.smithy.aws.go.codegen.customization.SQSValidateMessageChecksum
4949
software.amazon.smithy.aws.go.codegen.EndpointDiscoveryGenerator
50+
software.amazon.smithy.aws.go.codegen.customization.S3100Continue
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package s3shared
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/aws/smithy-go/middleware"
7+
smithyhttp "github.com/aws/smithy-go/transport/http"
8+
)
9+
10+
const s3100ContinueID = "S3100Continue"
11+
const default100ContinueThresholdBytes int64 = 1024 * 1024 * 2
12+
13+
// Add100Continue add middleware, which adds {Expect: 100-continue} header for s3 client HTTP PUT request larger than 2MB
14+
// or with unknown size streaming bodies, during operation builder step
15+
func Add100Continue(stack *middleware.Stack, continueHeaderThresholdBytes int64) error {
16+
return stack.Build.Add(&s3100Continue{
17+
continueHeaderThresholdBytes: continueHeaderThresholdBytes,
18+
}, middleware.After)
19+
}
20+
21+
type s3100Continue struct {
22+
continueHeaderThresholdBytes int64
23+
}
24+
25+
// ID returns the middleware identifier
26+
func (m *s3100Continue) ID() string {
27+
return s3100ContinueID
28+
}
29+
30+
func (m *s3100Continue) HandleBuild(
31+
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
32+
) (
33+
out middleware.BuildOutput, metadata middleware.Metadata, err error,
34+
) {
35+
sizeLimit := default100ContinueThresholdBytes
36+
switch {
37+
case m.continueHeaderThresholdBytes == -1:
38+
return next.HandleBuild(ctx, in)
39+
case m.continueHeaderThresholdBytes > 0:
40+
sizeLimit = m.continueHeaderThresholdBytes
41+
default:
42+
}
43+
44+
req, ok := in.Request.(*smithyhttp.Request)
45+
if !ok {
46+
return out, metadata, fmt.Errorf("unknown request type %T", req)
47+
}
48+
49+
if req.ContentLength == -1 || (req.ContentLength == 0 && req.Body != nil) || req.ContentLength >= sizeLimit {
50+
req.Header.Set("Expect", "100-continue")
51+
}
52+
53+
return next.HandleBuild(ctx, in)
54+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package s3shared
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go-v2/internal/awstesting"
6+
"github.com/aws/smithy-go/middleware"
7+
smithyhttp "github.com/aws/smithy-go/transport/http"
8+
"testing"
9+
)
10+
11+
// unit test for service/internal/s3shared/s3100continue.go
12+
func TestAdd100ContinueHttpHeader(t *testing.T) {
13+
const HeaderKey = "Expect"
14+
HeaderValue := "100-continue"
15+
16+
cases := map[string]struct {
17+
ContentLength int64
18+
Body *awstesting.ReadCloser
19+
ExpectValueFound string
20+
ContinueHeaderThresholdBytes int64
21+
}{
22+
"http request smaller than default 2MB": {
23+
ContentLength: 1,
24+
Body: &awstesting.ReadCloser{Size: 1},
25+
ExpectValueFound: "",
26+
},
27+
"http request smaller than configured threshold": {
28+
ContentLength: 1024 * 1024 * 2,
29+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 2},
30+
ExpectValueFound: "",
31+
ContinueHeaderThresholdBytes: 1024 * 1024 * 3,
32+
},
33+
"http request larger than default 2MB": {
34+
ContentLength: 1024 * 1024 * 3,
35+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 3},
36+
ExpectValueFound: HeaderValue,
37+
},
38+
"http request larger than configured threshold": {
39+
ContentLength: 1024 * 1024 * 4,
40+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 4},
41+
ExpectValueFound: HeaderValue,
42+
ContinueHeaderThresholdBytes: 1024 * 1024 * 3,
43+
},
44+
"http put request with unknown -1 ContentLength": {
45+
ContentLength: -1,
46+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 10},
47+
ExpectValueFound: HeaderValue,
48+
},
49+
"http put request with 0 ContentLength but unknown non-nil body": {
50+
ContentLength: 0,
51+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 3},
52+
ExpectValueFound: HeaderValue,
53+
},
54+
"http put request with unknown -1 ContentLength and configured threshold": {
55+
ContentLength: -1,
56+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 3},
57+
ExpectValueFound: HeaderValue,
58+
ContinueHeaderThresholdBytes: 1024 * 1024 * 10,
59+
},
60+
"http put request with continue header disabled": {
61+
ContentLength: 1024 * 1024 * 3,
62+
Body: &awstesting.ReadCloser{Size: 1024 * 1024 * 3},
63+
ExpectValueFound: "",
64+
ContinueHeaderThresholdBytes: -1,
65+
},
66+
}
67+
68+
for name, c := range cases {
69+
t.Run(name, func(t *testing.T) {
70+
var err error
71+
req := smithyhttp.NewStackRequest().(*smithyhttp.Request)
72+
73+
req.ContentLength = c.ContentLength
74+
req.Body = c.Body
75+
var updatedRequest *smithyhttp.Request
76+
m := s3100Continue{
77+
continueHeaderThresholdBytes: c.ContinueHeaderThresholdBytes,
78+
}
79+
_, _, err = m.HandleBuild(context.Background(),
80+
middleware.BuildInput{Request: req},
81+
middleware.BuildHandlerFunc(func(ctx context.Context, input middleware.BuildInput) (
82+
out middleware.BuildOutput, metadata middleware.Metadata, err error) {
83+
updatedRequest = input.Request.(*smithyhttp.Request)
84+
return out, metadata, nil
85+
}),
86+
)
87+
if err != nil {
88+
t.Fatalf("expect no error, got %v", err)
89+
}
90+
91+
if e, a := c.ExpectValueFound, updatedRequest.Header.Get(HeaderKey); e != a {
92+
t.Errorf("expect header value %v found, got %v", e, a)
93+
}
94+
})
95+
}
96+
}

service/s3/api_client.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/s3/api_op_PutObject.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/s3/api_op_UploadPart.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)