5
5
6
6
package software .amazon .nio .spi .s3 ;
7
7
8
- import static java .util .concurrent .TimeUnit .MINUTES ;
9
- import static software .amazon .nio .spi .s3 .util .TimeOutUtils .logAndGenerateExceptionOnTimeOut ;
10
-
11
8
import com .github .benmanes .caffeine .cache .Cache ;
12
9
import com .github .benmanes .caffeine .cache .Caffeine ;
13
- import java .net .URI ;
14
10
import java .time .Duration ;
15
- import java .util .Optional ;
16
- import java .util .concurrent .ExecutionException ;
17
- import java .util .concurrent .TimeoutException ;
18
- import org .slf4j .Logger ;
19
- import org .slf4j .LoggerFactory ;
20
- import software .amazon .awssdk .regions .Region ;
21
11
import software .amazon .awssdk .services .s3 .S3AsyncClient ;
22
12
import software .amazon .awssdk .services .s3 .S3CrtAsyncClientBuilder ;
23
- import software .amazon .awssdk .services .s3 .model .HeadBucketResponse ;
24
- import software .amazon .awssdk .services .s3 .model .S3Exception ;
25
13
import software .amazon .nio .spi .s3 .config .S3NioSpiConfiguration ;
26
14
27
15
/**
28
- * Factory/builder class that creates async S3 clients. It also provides
29
- * default clients that can be used for basic operations (e.g. bucket discovery).
16
+ * Creates async S3 clients used by this library.
30
17
*/
31
18
public class S3ClientProvider {
32
19
33
- private static final Logger logger = LoggerFactory .getLogger (S3ClientProvider .class );
34
-
35
20
/**
36
21
* Default asynchronous client using the "<a href="https://s3.us-east-1.amazonaws.com">...</a>" endpoint
37
22
*/
23
+ @ Deprecated
38
24
protected S3AsyncClient universalClient ;
39
25
40
26
/**
@@ -49,10 +35,6 @@ public class S3ClientProvider {
49
35
S3AsyncClient .crtBuilder ()
50
36
.crossRegionAccessEnabled (true );
51
37
52
- private final Cache <String , String > bucketRegionCache = Caffeine .newBuilder ()
53
- .maximumSize (16 )
54
- .expireAfterWrite (Duration .ofMinutes (30 ))
55
- .build ();
56
38
57
39
private final Cache <String , CacheableS3Client > bucketClientCache = Caffeine .newBuilder ()
58
40
.maximumSize (4 )
@@ -61,35 +43,12 @@ public class S3ClientProvider {
61
43
62
44
public S3ClientProvider (S3NioSpiConfiguration c ) {
63
45
this .configuration = (c == null ) ? new S3NioSpiConfiguration () : c ;
64
- this .universalClient = S3AsyncClient .builder ()
65
- .endpointOverride (URI .create ("https://s3.us-east-1.amazonaws.com" ))
66
- .crossRegionAccessEnabled (true )
67
- .region (Region .US_EAST_1 )
68
- .build ();
69
46
}
70
47
71
48
public void asyncClientBuilder (final S3CrtAsyncClientBuilder builder ) {
72
49
asyncClientBuilder = builder ;
73
50
}
74
51
75
- /**
76
- * This method returns a universal client bound to the us-east-1 region
77
- * that can be used by certain S3 operations for discovery such as getBucketLocation.
78
- *
79
- * @return an S3AsyncClient bound to us-east-1
80
- */
81
- S3AsyncClient universalClient () {
82
- return universalClient ;
83
- }
84
-
85
- /**
86
- * Sets the fallback client used to make {@code S3AsyncClient#getBucketLocation()} calls. Typically, this would
87
- * only be set for testing purposes to use a {@code Mock} or {@code Spy} class.
88
- * @param client the client to be used for getBucketLocation calls.
89
- */
90
- void universalClient (S3AsyncClient client ) {
91
- this .universalClient = client ;
92
- }
93
52
94
53
/**
95
54
* Generates a sync client for the named bucket using a client configured by the default region configuration chain.
@@ -98,108 +57,15 @@ void universalClient(S3AsyncClient client) {
98
57
* @return an S3 client appropriate for the region of the named bucket
99
58
*/
100
59
protected S3AsyncClient generateClient (String bucket ) {
101
- try {
102
- return generateClient (bucket , S3AsyncClient .create ());
103
- } catch (ExecutionException e ) {
104
- throw new RuntimeException (e );
105
- } catch (InterruptedException e ) {
106
- Thread .currentThread ().interrupt ();
107
- throw new RuntimeException (e );
108
- }
109
- }
110
-
111
- /**
112
- * Generate an async client for the named bucket using a provided client to
113
- * determine the location of the named client
114
- *
115
- * @param bucketName the name of the bucket to make the client for
116
- * @param locationClient the client used to determine the location of the
117
- * named bucket, recommend using {@code S3ClientProvider#UNIVERSAL_CLIENT}
118
- * @return an S3 client appropriate for the region of the named bucket
119
- */
120
- S3AsyncClient generateClient (String bucketName , S3AsyncClient locationClient )
121
- throws ExecutionException , InterruptedException {
122
- logger .debug ("generating client for bucket: '{}'" , bucketName );
123
-
124
- String bucketLocation = null ;
125
- if (configuration .endpointUri () == null ) {
126
- // we try to locate a bucket only if no endpoint is provided, which means we are dealing with AWS S3 buckets
127
- bucketLocation = getBucketLocation (bucketName , locationClient );
128
-
129
- if (bucketLocation == null ) {
130
- // if here, no S3 nor other client has been created yet, and we do not
131
- // have a location; we'll let it figure out from the profile region
132
- logger .warn ("Unable to determine the region of bucket: '{}'. Generating a client for the profile region." ,
133
- bucketName );
134
- }
135
- }
136
-
137
- var client = bucketClientCache .getIfPresent (bucketName );
60
+ var client = bucketClientCache .getIfPresent (bucket );
138
61
if (client != null && !client .isClosed ()) {
139
62
return client ;
140
63
} else {
141
64
if (client != null && client .isClosed ()) {
142
- bucketClientCache .invalidate (bucketName ); // remove the closed client from the cache
143
- }
144
- String r = Optional .ofNullable (bucketLocation ).orElse (configuration .getRegion ());
145
- return bucketClientCache .get (bucketName , b -> new CacheableS3Client (configureCrtClientForRegion (r )));
146
- }
147
- }
148
-
149
- private String getBucketLocation (String bucketName , S3AsyncClient locationClient )
150
- throws ExecutionException , InterruptedException {
151
-
152
- if (bucketRegionCache .getIfPresent (bucketName ) != null ) {
153
- return bucketRegionCache .getIfPresent (bucketName );
154
- }
155
-
156
- logger .debug ("checking if the bucket is in the same region as the providedClient using HeadBucket" );
157
- try (var client = locationClient ) {
158
- final HeadBucketResponse response = client
159
- .headBucket (builder -> builder .bucket (bucketName ))
160
- .get (configuration .getTimeoutLow (), MINUTES );
161
- bucketRegionCache .put (bucketName , response .bucketRegion ());
162
- return response .bucketRegion ();
163
-
164
- } catch (TimeoutException e ) {
165
- throw logAndGenerateExceptionOnTimeOut (
166
- logger ,
167
- "generateClient" ,
168
- configuration .getTimeoutLow (),
169
- MINUTES );
170
- } catch (Throwable t ) {
171
-
172
- if (t instanceof ExecutionException &&
173
- t .getCause () instanceof S3Exception &&
174
- ((S3Exception ) t .getCause ()).statusCode () == 301 ) { // you got a redirect, the region should be in the header
175
- logger .debug ("HeadBucket was unsuccessful, redirect received, attempting to extract x-amz-bucket-region header" );
176
- S3Exception s3e = (S3Exception ) t .getCause ();
177
- final var matchingHeaders = s3e .awsErrorDetails ().sdkHttpResponse ().matchingHeaders ("x-amz-bucket-region" );
178
- if (matchingHeaders != null && !matchingHeaders .isEmpty ()) {
179
- bucketRegionCache .put (bucketName , matchingHeaders .get (0 ));
180
- return matchingHeaders .get (0 );
181
- }
182
- } else if (t instanceof ExecutionException &&
183
- t .getCause () instanceof S3Exception &&
184
- ((S3Exception ) t .getCause ()).statusCode () == 403 ) { // HeadBucket was forbidden
185
- logger .debug ("HeadBucket forbidden. Attempting a call to GetBucketLocation using the UNIVERSAL_CLIENT" );
186
- try {
187
- String location = universalClient .getBucketLocation (builder -> builder .bucket (bucketName ))
188
- .get (configuration .getTimeoutLow (), MINUTES ).locationConstraintAsString ();
189
- bucketRegionCache .put (bucketName , location );
190
- } catch (TimeoutException e ) {
191
- throw logAndGenerateExceptionOnTimeOut (
192
- logger ,
193
- "generateClient" ,
194
- configuration .getTimeoutLow (),
195
- MINUTES );
196
- }
197
- } else {
198
- // didn't handle the exception - rethrow it
199
- throw t ;
65
+ bucketClientCache .invalidate (bucket ); // remove the closed client from the cache
200
66
}
67
+ return bucketClientCache .get (bucket , b -> new CacheableS3Client (configureCrtClient ().build ()));
201
68
}
202
- return "" ;
203
69
}
204
70
205
71
S3CrtAsyncClientBuilder configureCrtClient () {
@@ -216,14 +82,4 @@ S3CrtAsyncClientBuilder configureCrtClient() {
216
82
return asyncClientBuilder .forcePathStyle (configuration .getForcePathStyle ());
217
83
}
218
84
219
- private S3AsyncClient configureCrtClientForRegion (String regionName ) {
220
- var region = getRegionFromRegionName (regionName );
221
- logger .debug ("bucket region is: '{}'" , region );
222
- return configureCrtClient ().region (region ).build ();
223
- }
224
-
225
- private static Region getRegionFromRegionName (String regionName ) {
226
- return (regionName == null || regionName .isBlank ()) ? null : Region .of (regionName );
227
- }
228
-
229
85
}
0 commit comments