Repository: localstack/localstack

Issue:
s3.upload returns `Location: http://localhost:4566`
# Bug report

# Detailed description

The `AWS.s3.upload()` (official SDK - https://github.com/aws/aws-sdk-js) returns an object with the `Location` key that points to 4566 instead of 4572 (LocalStack S3 port).

## Expected behavior

The `Location` should point to the file on S3.

Example:

```
Location: http://localhost:4572/path/to/bucket.txt
```

## Actual behavior

The `Location` points to the LocalStack entrypoint.

Example:

```
Location: http://localhost:4566/path/to/bucket.txt
```

# Steps to reproduce

- Upload a file to S3 using the official AWS SDK (https://github.com/aws/aws-sdk-js).
- Check out the `Location` property.

## Client code

```javascript
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
  region: 'us-west-1',
  endpoint: 'http://localhost:4566',
  apiVersion: '2006-03-01',
  s3ForcePathStyle: true,
});

(async () => {
  await s3
    .createBucket({ Bucket: 'my-bucket', ACL: 'private' })
    .promise();

  const { Location } = await s3
    .upload({ Key: 'file.txt', Body: 'test', Bucket: 'my-bucket' })
    .promise();

  console.assert(Location === 'http://localhost:4572/my-bucket/file.txt');
})();
```


Discussion:
Thanks for reporting @BR0kEN- . This is the expected behavior. Please note that starting with version 0.11.0 we've added an _edge service_ on port 4566 which acts as a proxy and central API entry point that redirects requests to backend APIs (e.g., S3). In the future, all APIs will be exposed via the edge URL only, and service-specific ports will get deprecated.

My assumption would be that you can access the file under the given URL (http://localhost:4566/path/to/bucket.txt) . Please report here if that's not the case and you cannot access that URL. Thanks

Please note that we also have a pending PR that prints the updated/deprecated port numbers - will be merged soon: https://github.com/localstack/localstack/pull/2318
That's exactly the case @whummer. I'm still able to access the file via `http://localhost:4572/path/to/bucket.txt` while `http://localhost:4566/path/to/bucket.txt` returns `{"status": "running"}`.

Resolving patch:
diff --git a/localstack/services/edge.py b/localstack/services/edge.py
--- a/localstack/services/edge.py
+++ b/localstack/services/edge.py
@@ -38,6 +38,10 @@ def forward_request(self, method, path, data, headers):
             # detect S3 presigned URLs
             if 'AWSAccessKeyId=' in path or 'Signature=' in path:
                 port = config.PORT_S3
+            # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`
+            # TODO: move S3 public URLs to a separate port/endpoint, OR check ACLs here first
+            if method == 'GET' and '/' in path.strip('/'):
+                port = config.PORT_S3
 
         if not port:
             LOG.info('Unable to find forwarding rule for host "%s", path "%s", target header "%s", auth header "%s"' %


Test patch:
diff --git a/tests/integration/test_lambda.py b/tests/integration/test_lambda.py
--- a/tests/integration/test_lambda.py
+++ b/tests/integration/test_lambda.py
@@ -487,9 +487,7 @@ def test_lambda_subscribe_sns_topic(self):
         testutil.create_lambda_function(handler_file=TEST_LAMBDA_ECHO_FILE,
                                         func_name=function_name, runtime=LAMBDA_RUNTIME_PYTHON36)
 
-        topic = self.sns_client.create_topic(
-            Name=TEST_SNS_TOPIC_NAME
-        )
+        topic = self.sns_client.create_topic(Name=TEST_SNS_TOPIC_NAME)
         topic_arn = topic['TopicArn']
 
         self.sns_client.subscribe(
diff --git a/tests/unit/test_sns.py b/tests/unit/test_sns.py
--- a/tests/unit/test_sns.py
+++ b/tests/unit/test_sns.py
@@ -23,15 +23,15 @@ def test_unsubscribe_without_arn_should_error(self):
         self.assertEqual(error.status_code, 400)
 
     def test_unsubscribe_should_remove_listener(self):
-        sub_arn = 'arn:aws:sns:us-east-1:123456789012:test-topic:45e61c7f-dca5-4fcd-be2b-4e1b0d6eef72'
-        topic_arn = 'arn:aws:sns:us-east-1:123456789012:test-topic'
+        sub_arn = 'arn:aws:sns:us-east-1:000000000000:test-topic:45e61c7f-dca5-4fcd-be2b-4e1b0d6eef72'
+        topic_arn = 'arn:aws:sns:us-east-1:000000000000:test-topic'
 
         self.assertFalse(sns_listener.get_topic_by_arn(topic_arn))
         sns_listener.do_create_topic(topic_arn)
         self.assertTrue(sns_listener.get_topic_by_arn(topic_arn) is not None)
         sns_listener.do_subscribe(
             topic_arn,
-            'arn:aws:sqs:us-east-1:123456789012:test-queue',
+            'arn:aws:sqs:us-east-1:000000000000:test-queue',
             'sqs',
             sub_arn,
             {}
@@ -205,8 +205,8 @@ def test_create_sns_message_timestamp_millis(self):
         self.assertTrue(match is not None)
 
     def test_only_one_subscription_per_topic_per_endpoint(self):
-        sub_arn = 'arn:aws:sns:us-east-1:123456789012:test-topic:45e61c7f-dca5-4fcd-be2b-4e1b0d6eef72'
-        topic_arn = 'arn:aws:sns:us-east-1:123456789012:test-topic'
+        sub_arn = 'arn:aws:sns:us-east-1:000000000000:test-topic:45e61c7f-dca5-4fcd-be2b-4e1b0d6eef72'
+        topic_arn = 'arn:aws:sns:us-east-1:000000000000:test-topic'
 
         self.assertFalse(sns_listener.get_topic_by_arn(topic_arn))
         sns_listener.do_create_topic(topic_arn)
@@ -214,7 +214,7 @@ def test_only_one_subscription_per_topic_per_endpoint(self):
         for i in [1, 2]:
             sns_listener.do_subscribe(
                 topic_arn,
-                'arn:aws:sqs:us-east-1:123456789012:test-queue-1',
+                'arn:aws:sqs:us-east-1:000000000000:test-queue-1',
                 'sqs',
                 sub_arn,
                 {}