Reputation: 2056
I am trying to upload a file to S3 using this example:
But I have this error:
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint. (Service: S3, Status Code: 301, Request ID: 15F45F10B11A3267, Extended Request ID: 9TdN5/GMl9h6VGPq6ee4MttjlLFm+V5GjIaXIJLAO1YasdZCls0M4udxJso8n1P+qMr4iigP5g=)
Upvotes: 1
Views: 790
Reputation: 32315
What annoyed me is that if you create the S3 client once during initialization and re-use it (which is just the thing that makes sense), then you have to know ahead of time all the regions where you need to access buckets, and pre-init multiple S3 clients - there's no way that I can find (at least for the Java SDK) to "just send one request using a different endpoint".
Because pre-initing all the clients for all the regions that are possible (and they keep adding more all the time) is just insane, I wrote up this wrapper (it is using the async clients and exposes a Vert.x promises API, but it should be easy to adapt to anything else):
/**
* S3 client factory that helps resolve region-specific clients
* Licensed for any use under CC0
*/
public class S3AsyncClientFactory {
private ConcurrentHashMap<String, S3AsyncClient> cache =
new ConcurrentHashMap<>();
private S3AsyncClient global;
private S3AsyncClientFactory() {
// I call it "global" but it actually uses your "default region".
// There is a "global" region, but I couldn't be bothered to
// figure out how to create such a client.
global = S3AsyncClient.create();
}
public Future<Region> getBucketLocation(String bucket) {
return Future.fromCompletionStage(
global.getBucketLocation(b -> b.bucket(bucket)))
.map(loc -> loc.locationConstraintAsString())
// annoyingly, get location returns an empty string for
// the "standard region" and Region.of() can't handle that
.map(loc -> loc == null || loc.isEmpty() ?
Region.US_EAST_1 : Region.of(loc));
}
public Future<S3AsyncClient> forBucket(String bucket) {
return getBucketLocation(bucket).map(this::forRegion);
}
public S3AsyncClient global() {
return global;
}
public S3AsyncClient forRegion(Region region) {
return cache.computeIfAbsent(region.toString(),
// you may want to replace this part if you have custom
// init code or you need non-default credentials chain
r -> S3AsyncClient.builder().region(region).build());
}
public S3AsyncClient forRegion(String region) {
return forRegion(Region.of(region));
}
}
With that piece of code you just create and cache per-region S3 clients, as needed:
factory.forBucket(bucket).compose(c -> Future.fromCompletionStage(
c.getObject(b -> b.bucket(bucket).key(key), responseTransformer)))
.map(res -> ...
(please don't mind the ugly "different libraries have difference concurrency implementations" code 🤷)
You may also want to have a bucket-keyed client implementation cache (I have it in another layer where it makes more sense for my use case).
Upvotes: 0
Reputation: 1401
It seems likely that this bucket was created in a different region. Please send your request to your region endpoint. The default region is as follow's
US Standard is us-east-1
The upper link have region 'Region.US_WEST_2'. First confirm your region then send your request accordingly.
Upvotes: 2