We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
S3: POST/PUT to bucket URLs don't route correctly on port 4566
<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->

# Type of request: This is a ...

[x] bug report
[ ] feature request

# Detailed description

We noticed this while converting a service to use Localstack for tests. The service generates S3 presigned post URLs. We're able to create and use presigned S3 URLs on port 4572 (deprecated S3 port), but not 4566, the new shared one. The same issue happens with PUT requests, which is the simplest to repro.

While just using 4572 works, this does force us to use the deprecated port and I figured it was worth opening an issue because of the discrepancy.

## Expected behavior

POST http://localhost:4566/hello (with appropriate form params) should return a 204, in the same way that POST http://localhost:4572/hello does.

PUT http://localhost:4566/hello should create a bucket and return a 200, in the same way that PUT http://localhost:4572/hello does.

## Actual behavior

Both PUT and POST http://localhost:4566/hello return a 404.

In the localstack logs:
2020-05-20T13:37:41:INFO:localstack.services.edge: Unable to find forwarding rule for host "localhost:4566", path "/hello", target header "", auth header ""

# Steps to reproduce

```bash
$ curl -i -XPUT http://localhost:4572/hello
HTTP/1.1 200 OK
Server: BaseHTTP/0.6 Python/3.8.2
Date: Wed, 20 May 2020 13:43:17 GMT
Content-Type: application/xml; charset=utf-8
content-length: 159
Access-Control-Allow-Origin: *
Last-Modified: Wed, 20 May 2020 13:43:17 GMT
x-amz-request-id: 0ABD347D7A4E0697
x-amz-id-2: MzRISOwyjmnup0ABD347D7A4E06977/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,content-type,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging
Access-Control-Expose-Headers: x-amz-version-id

<CreateBucketResponse xmlns="http://s3.amazonaws.com/doc/2006-03-01"><CreateBucketResponse><Bucket>hello</Bucket></CreateBucketResponse></CreateBucketResponse>%                                                                                                                

$ curl -i -XPUT http://localhost:4566/hello
HTTP/1.1 404 Not Found
Server: BaseHTTP/0.6 Python/3.8.2
Date: Wed, 20 May 2020 13:43:22 GMT
Content-Length: 21
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,content-type,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging
Access-Control-Expose-Headers: x-amz-version-id

{"status": "running"}%
```

## Command used to start LocalStack

`localstack start`

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `localstack/services/edge.py`
Content:
```
1 import re
2 import os
3 import sys
4 import json
5 import logging
6 from requests.models import Response
7 from localstack import config
8 from localstack.constants import HEADER_LOCALSTACK_TARGET, HEADER_LOCALSTACK_EDGE_URL, LOCALSTACK_ROOT_FOLDER
9 from localstack.utils.common import run, is_root, TMP_THREADS
10 from localstack.utils.common import safe_requests as requests
11 from localstack.services.generic_proxy import ProxyListener, GenericProxy
12 
13 LOG = logging.getLogger(__name__)
14 
15 # Header to indicate that the process should kill itself. This is required because if
16 # this process is started as root, then we cannot kill it from a non-root process
17 HEADER_KILL_SIGNAL = 'x-localstack-kill'
18 
19 
20 class ProxyListenerEdge(ProxyListener):
21 
22     def forward_request(self, method, path, data, headers):
23         if method == 'OPTIONS':
24             return 200
25 
26         # kill the process if we receive this header
27         headers.get(HEADER_KILL_SIGNAL) and os._exit(0)
28 
29         target = headers.get('x-amz-target', '')
30         auth_header = headers.get('authorization', '')
31         host = headers.get('host', '')
32         headers[HEADER_LOCALSTACK_EDGE_URL] = 'https://%s' % host
33 
34         # extract API details
35         api, port, path, host = get_api_from_headers(headers, path)
36 
37         if port and int(port) < 0:
38             return 404
39 
40         if not port:
41             # detect S3 presigned URLs
42             if 'AWSAccessKeyId=' in path or 'Signature=' in path:
43                 port = config.PORT_S3
44             # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`
45             # TODO: move S3 public URLs to a separate port/endpoint, OR check ACLs here first
46             if method == 'GET' and '/' in path.strip('/'):
47                 port = config.PORT_S3
48 
49         if not port:
50             if api in ['', None, '_unknown_']:
51                 LOG.info(('Unable to find forwarding rule for host "%s", path "%s", '
52                     'target header "%s", auth header "%s"') % (host, path, target, auth_header))
53             else:
54                 LOG.info(('Unable to determine forwarding port for API "%s" - please '
55                     'make sure this API is enabled via the SERVICES configuration') % api)
56             response = Response()
57             response.status_code = 404
58             response._content = '{"status": "running"}'
59             return response
60 
61         use_ssl = config.USE_SSL
62 
63         connect_host = '%s:%s' % (config.HOSTNAME, port)
64         url = 'http%s://%s%s' % ('s' if use_ssl else '', connect_host, path)
65         headers['Host'] = host
66         function = getattr(requests, method.lower())
67         if isinstance(data, dict):
68             data = json.dumps(data)
69 
70         response = function(url, data=data, headers=headers, verify=False)
71         return response
72 
73 
74 def get_api_from_headers(headers, path=None):
75     target = headers.get('x-amz-target', '')
76     host = headers.get('host', '')
77     auth_header = headers.get('authorization', '')
78     ls_target = headers.get(HEADER_LOCALSTACK_TARGET, '')
79     path = path or '/'
80 
81     # initialize result
82     result = '_unknown_', 0
83 
84     # https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
85     try:
86         credential_scope = auth_header.split(',')[0].split()[1]
87         _, _, _, service, _ = credential_scope.split('/')
88         result = service, get_service_port_for_account(service, headers)
89     except Exception:
90         pass
91 
92     result_before = result
93 
94     # Fallback rules and route customizations applied below
95 
96     if host.endswith('cloudfront.net'):
97         path = path or '/'
98         result = 'cloudfront', config.PORT_CLOUDFRONT
99     elif target.startswith('AWSCognitoIdentityProviderService') or 'cognito-idp.' in host:
100         result = 'cognito-idp', config.PORT_COGNITO_IDP
101     elif target.startswith('AWSCognitoIdentityService') or 'cognito-identity.' in host:
102         result = 'cognito-identity', config.PORT_COGNITO_IDENTITY
103     elif result[0] == 's3' or re.match(r'.*s3(\-website)?\.([^\.]+\.)?amazonaws.com', host):
104         host = re.sub(r's3-website\..*\.amazonaws', 's3.amazonaws', host)
105         result = 's3', config.PORT_S3
106     elif result[0] == 'states' in auth_header or host.startswith('states.'):
107         result = 'stepfunctions', config.PORT_STEPFUNCTIONS
108     elif '.execute-api.' in host:
109         result = 'apigateway', config.PORT_APIGATEWAY
110     elif target.startswith('DynamoDBStreams') or host.startswith('streams.dynamodb.'):
111         result = 'dynamodbstreams', config.PORT_DYNAMODBSTREAMS
112     elif ls_target == 'web' or path == '/graph':
113         result = 'web', config.PORT_WEB_UI
114 
115     return result[0], result_before[1] or result[1], path, host
116 
117 
118 def get_service_port_for_account(service, headers):
119     # assume we're only using a single account, hence return the static port mapping from config.py
120     return config.service_port(service)
121 
122 
123 def do_start_edge(port, use_ssl, asynchronous=False):
124     try:
125         # start local DNS server, if present
126         from localstack_ext.services import dns_server
127         dns_server.start_servers()
128     except Exception:
129         pass
130 
131     # get port and start Edge
132     print('Starting edge router (http%s port %s)...' % ('s' if use_ssl else '', port))
133     # use use=True here because our proxy allows both, HTTP and HTTPS traffic
134     proxy = GenericProxy(port, ssl=True, update_listener=ProxyListenerEdge())
135     proxy.start()
136     if not asynchronous:
137         proxy.join()
138     return proxy
139 
140 
141 def can_use_sudo():
142     try:
143         run('echo | sudo -S echo', print_error=False)
144         return True
145     except Exception:
146         return False
147 
148 
149 def ensure_can_use_sudo():
150     if not is_root() and not can_use_sudo():
151         print('Please enter your sudo password (required to configure local network):')
152         run('sudo echo', stdin=True)
153 
154 
155 def start_edge(port=None, use_ssl=True, asynchronous=False):
156     if not port:
157         port = config.EDGE_PORT
158     if config.EDGE_PORT_HTTP:
159         do_start_edge(config.EDGE_PORT_HTTP, use_ssl=False, asynchronous=True)
160     if port > 1024 or is_root():
161         return do_start_edge(port, use_ssl, asynchronous=asynchronous)
162 
163     # process requires priviledged port but we're not root -> try running as sudo
164 
165     class Terminator(object):
166 
167         def stop(self, quiet=True):
168             try:
169                 url = 'http%s://localhost:%s' % ('s' if use_ssl else '', port)
170                 requests.verify_ssl = False
171                 requests.post(url, headers={HEADER_KILL_SIGNAL: 'kill'})
172             except Exception:
173                 pass
174 
175     # make sure we can run sudo commands
176     ensure_can_use_sudo()
177 
178     # register a signal handler to terminate the sudo process later on
179     TMP_THREADS.append(Terminator())
180 
181     # start the process as sudo
182     sudo_cmd = 'sudo '
183     python_cmd = sys.executable
184     cmd = '%sPYTHONPATH=.:%s %s %s %s' % (sudo_cmd, LOCALSTACK_ROOT_FOLDER, python_cmd, __file__, port)
185     process = run(cmd, asynchronous=asynchronous)
186     return process
187 
188 
189 if __name__ == '__main__':
190     logging.basicConfig()
191     start_edge(int(sys.argv[1]))
192
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/localstack/services/edge.py b/localstack/services/edge.py
--- a/localstack/services/edge.py
+++ b/localstack/services/edge.py
@@ -6,7 +6,7 @@
 from requests.models import Response
 from localstack import config
 from localstack.constants import HEADER_LOCALSTACK_TARGET, HEADER_LOCALSTACK_EDGE_URL, LOCALSTACK_ROOT_FOLDER
-from localstack.utils.common import run, is_root, TMP_THREADS
+from localstack.utils.common import run, is_root, TMP_THREADS, to_bytes
 from localstack.utils.common import safe_requests as requests
 from localstack.services.generic_proxy import ProxyListener, GenericProxy
 
@@ -41,10 +41,18 @@
             # 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('/'):
+            stripped = path.strip('/')
+            if method == 'GET' and '/' in stripped:
+                # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`
                 port = config.PORT_S3
+            if stripped and '/' not in stripped:
+                if method == 'PUT':
+                    # assume that this is an S3 PUT bucket request with URL path `/<bucket>`
+                    port = config.PORT_S3
+                elif method == 'POST' and to_bytes('key=') in to_bytes(data or ''):
+                    # assume that this is an S3 POST request with form parameters in the body
+                    port = config.PORT_S3
 
         if not port:
             if api in ['', None, '_unknown_']:

{"golden_diff": "diff --git a/localstack/services/edge.py b/localstack/services/edge.py\n--- a/localstack/services/edge.py\n+++ b/localstack/services/edge.py\n@@ -6,7 +6,7 @@\n from requests.models import Response\n from localstack import config\n from localstack.constants import HEADER_LOCALSTACK_TARGET, HEADER_LOCALSTACK_EDGE_URL, LOCALSTACK_ROOT_FOLDER\n-from localstack.utils.common import run, is_root, TMP_THREADS\n+from localstack.utils.common import run, is_root, TMP_THREADS, to_bytes\n from localstack.utils.common import safe_requests as requests\n from localstack.services.generic_proxy import ProxyListener, GenericProxy\n \n@@ -41,10 +41,18 @@\n             # detect S3 presigned URLs\n             if 'AWSAccessKeyId=' in path or 'Signature=' in path:\n                 port = config.PORT_S3\n-            # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`\n             # TODO: move S3 public URLs to a separate port/endpoint, OR check ACLs here first\n-            if method == 'GET' and '/' in path.strip('/'):\n+            stripped = path.strip('/')\n+            if method == 'GET' and '/' in stripped:\n+                # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`\n                 port = config.PORT_S3\n+            if stripped and '/' not in stripped:\n+                if method == 'PUT':\n+                    # assume that this is an S3 PUT bucket request with URL path `/<bucket>`\n+                    port = config.PORT_S3\n+                elif method == 'POST' and to_bytes('key=') in to_bytes(data or ''):\n+                    # assume that this is an S3 POST request with form parameters in the body\n+                    port = config.PORT_S3\n \n         if not port:\n             if api in ['', None, '_unknown_']:\n", "issue": "S3: POST/PUT to bucket URLs don't route correctly on port 4566\n<!-- Love localstack? Please consider supporting our collective:\r\n\ud83d\udc49  https://opencollective.com/localstack/donate -->\r\n\r\n# Type of request: This is a ...\r\n\r\n[x] bug report\r\n[ ] feature request\r\n\r\n# Detailed description\r\n\r\nWe noticed this while converting a service to use Localstack for tests. The service generates S3 presigned post URLs. We're able to create and use presigned S3 URLs on port 4572 (deprecated S3 port), but not 4566, the new shared one. The same issue happens with PUT requests, which is the simplest to repro.\r\n\r\nWhile just using 4572 works, this does force us to use the deprecated port and I figured it was worth opening an issue because of the discrepancy.\r\n\r\n## Expected behavior\r\n\r\nPOST http://localhost:4566/hello (with appropriate form params) should return a 204, in the same way that POST http://localhost:4572/hello does.\r\n\r\nPUT http://localhost:4566/hello should create a bucket and return a 200, in the same way that PUT http://localhost:4572/hello does.\r\n\r\n## Actual behavior\r\n\r\nBoth PUT and POST http://localhost:4566/hello return a 404.\r\n\r\nIn the localstack logs:\r\n2020-05-20T13:37:41:INFO:localstack.services.edge: Unable to find forwarding rule for host \"localhost:4566\", path \"/hello\", target header \"\", auth header \"\"\r\n\r\n# Steps to reproduce\r\n\r\n```bash\r\n$ curl -i -XPUT http://localhost:4572/hello\r\nHTTP/1.1 200 OK\r\nServer: BaseHTTP/0.6 Python/3.8.2\r\nDate: Wed, 20 May 2020 13:43:17 GMT\r\nContent-Type: application/xml; charset=utf-8\r\ncontent-length: 159\r\nAccess-Control-Allow-Origin: *\r\nLast-Modified: Wed, 20 May 2020 13:43:17 GMT\r\nx-amz-request-id: 0ABD347D7A4E0697\r\nx-amz-id-2: MzRISOwyjmnup0ABD347D7A4E06977/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp\r\nAccess-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH\r\nAccess-Control-Allow-Headers: authorization,content-type,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging\r\nAccess-Control-Expose-Headers: x-amz-version-id\r\n\r\n<CreateBucketResponse xmlns=\"http://s3.amazonaws.com/doc/2006-03-01\"><CreateBucketResponse><Bucket>hello</Bucket></CreateBucketResponse></CreateBucketResponse>%                                                                                                                \r\n\r\n$ curl -i -XPUT http://localhost:4566/hello\r\nHTTP/1.1 404 Not Found\r\nServer: BaseHTTP/0.6 Python/3.8.2\r\nDate: Wed, 20 May 2020 13:43:22 GMT\r\nContent-Length: 21\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH\r\nAccess-Control-Allow-Headers: authorization,content-type,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging\r\nAccess-Control-Expose-Headers: x-amz-version-id\r\n\r\n{\"status\": \"running\"}%\r\n```\r\n\r\n## Command used to start LocalStack\r\n\r\n`localstack start`\n", "before_files": [{"content": "import re\nimport os\nimport sys\nimport json\nimport logging\nfrom requests.models import Response\nfrom localstack import config\nfrom localstack.constants import HEADER_LOCALSTACK_TARGET, HEADER_LOCALSTACK_EDGE_URL, LOCALSTACK_ROOT_FOLDER\nfrom localstack.utils.common import run, is_root, TMP_THREADS\nfrom localstack.utils.common import safe_requests as requests\nfrom localstack.services.generic_proxy import ProxyListener, GenericProxy\n\nLOG = logging.getLogger(__name__)\n\n# Header to indicate that the process should kill itself. This is required because if\n# this process is started as root, then we cannot kill it from a non-root process\nHEADER_KILL_SIGNAL = 'x-localstack-kill'\n\n\nclass ProxyListenerEdge(ProxyListener):\n\n    def forward_request(self, method, path, data, headers):\n        if method == 'OPTIONS':\n            return 200\n\n        # kill the process if we receive this header\n        headers.get(HEADER_KILL_SIGNAL) and os._exit(0)\n\n        target = headers.get('x-amz-target', '')\n        auth_header = headers.get('authorization', '')\n        host = headers.get('host', '')\n        headers[HEADER_LOCALSTACK_EDGE_URL] = 'https://%s' % host\n\n        # extract API details\n        api, port, path, host = get_api_from_headers(headers, path)\n\n        if port and int(port) < 0:\n            return 404\n\n        if not port:\n            # detect S3 presigned URLs\n            if 'AWSAccessKeyId=' in path or 'Signature=' in path:\n                port = config.PORT_S3\n            # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`\n            # TODO: move S3 public URLs to a separate port/endpoint, OR check ACLs here first\n            if method == 'GET' and '/' in path.strip('/'):\n                port = config.PORT_S3\n\n        if not port:\n            if api in ['', None, '_unknown_']:\n                LOG.info(('Unable to find forwarding rule for host \"%s\", path \"%s\", '\n                    'target header \"%s\", auth header \"%s\"') % (host, path, target, auth_header))\n            else:\n                LOG.info(('Unable to determine forwarding port for API \"%s\" - please '\n                    'make sure this API is enabled via the SERVICES configuration') % api)\n            response = Response()\n            response.status_code = 404\n            response._content = '{\"status\": \"running\"}'\n            return response\n\n        use_ssl = config.USE_SSL\n\n        connect_host = '%s:%s' % (config.HOSTNAME, port)\n        url = 'http%s://%s%s' % ('s' if use_ssl else '', connect_host, path)\n        headers['Host'] = host\n        function = getattr(requests, method.lower())\n        if isinstance(data, dict):\n            data = json.dumps(data)\n\n        response = function(url, data=data, headers=headers, verify=False)\n        return response\n\n\ndef get_api_from_headers(headers, path=None):\n    target = headers.get('x-amz-target', '')\n    host = headers.get('host', '')\n    auth_header = headers.get('authorization', '')\n    ls_target = headers.get(HEADER_LOCALSTACK_TARGET, '')\n    path = path or '/'\n\n    # initialize result\n    result = '_unknown_', 0\n\n    # https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html\n    try:\n        credential_scope = auth_header.split(',')[0].split()[1]\n        _, _, _, service, _ = credential_scope.split('/')\n        result = service, get_service_port_for_account(service, headers)\n    except Exception:\n        pass\n\n    result_before = result\n\n    # Fallback rules and route customizations applied below\n\n    if host.endswith('cloudfront.net'):\n        path = path or '/'\n        result = 'cloudfront', config.PORT_CLOUDFRONT\n    elif target.startswith('AWSCognitoIdentityProviderService') or 'cognito-idp.' in host:\n        result = 'cognito-idp', config.PORT_COGNITO_IDP\n    elif target.startswith('AWSCognitoIdentityService') or 'cognito-identity.' in host:\n        result = 'cognito-identity', config.PORT_COGNITO_IDENTITY\n    elif result[0] == 's3' or re.match(r'.*s3(\\-website)?\\.([^\\.]+\\.)?amazonaws.com', host):\n        host = re.sub(r's3-website\\..*\\.amazonaws', 's3.amazonaws', host)\n        result = 's3', config.PORT_S3\n    elif result[0] == 'states' in auth_header or host.startswith('states.'):\n        result = 'stepfunctions', config.PORT_STEPFUNCTIONS\n    elif '.execute-api.' in host:\n        result = 'apigateway', config.PORT_APIGATEWAY\n    elif target.startswith('DynamoDBStreams') or host.startswith('streams.dynamodb.'):\n        result = 'dynamodbstreams', config.PORT_DYNAMODBSTREAMS\n    elif ls_target == 'web' or path == '/graph':\n        result = 'web', config.PORT_WEB_UI\n\n    return result[0], result_before[1] or result[1], path, host\n\n\ndef get_service_port_for_account(service, headers):\n    # assume we're only using a single account, hence return the static port mapping from config.py\n    return config.service_port(service)\n\n\ndef do_start_edge(port, use_ssl, asynchronous=False):\n    try:\n        # start local DNS server, if present\n        from localstack_ext.services import dns_server\n        dns_server.start_servers()\n    except Exception:\n        pass\n\n    # get port and start Edge\n    print('Starting edge router (http%s port %s)...' % ('s' if use_ssl else '', port))\n    # use use=True here because our proxy allows both, HTTP and HTTPS traffic\n    proxy = GenericProxy(port, ssl=True, update_listener=ProxyListenerEdge())\n    proxy.start()\n    if not asynchronous:\n        proxy.join()\n    return proxy\n\n\ndef can_use_sudo():\n    try:\n        run('echo | sudo -S echo', print_error=False)\n        return True\n    except Exception:\n        return False\n\n\ndef ensure_can_use_sudo():\n    if not is_root() and not can_use_sudo():\n        print('Please enter your sudo password (required to configure local network):')\n        run('sudo echo', stdin=True)\n\n\ndef start_edge(port=None, use_ssl=True, asynchronous=False):\n    if not port:\n        port = config.EDGE_PORT\n    if config.EDGE_PORT_HTTP:\n        do_start_edge(config.EDGE_PORT_HTTP, use_ssl=False, asynchronous=True)\n    if port > 1024 or is_root():\n        return do_start_edge(port, use_ssl, asynchronous=asynchronous)\n\n    # process requires priviledged port but we're not root -> try running as sudo\n\n    class Terminator(object):\n\n        def stop(self, quiet=True):\n            try:\n                url = 'http%s://localhost:%s' % ('s' if use_ssl else '', port)\n                requests.verify_ssl = False\n                requests.post(url, headers={HEADER_KILL_SIGNAL: 'kill'})\n            except Exception:\n                pass\n\n    # make sure we can run sudo commands\n    ensure_can_use_sudo()\n\n    # register a signal handler to terminate the sudo process later on\n    TMP_THREADS.append(Terminator())\n\n    # start the process as sudo\n    sudo_cmd = 'sudo '\n    python_cmd = sys.executable\n    cmd = '%sPYTHONPATH=.:%s %s %s %s' % (sudo_cmd, LOCALSTACK_ROOT_FOLDER, python_cmd, __file__, port)\n    process = run(cmd, asynchronous=asynchronous)\n    return process\n\n\nif __name__ == '__main__':\n    logging.basicConfig()\n    start_edge(int(sys.argv[1]))\n", "path": "localstack/services/edge.py"}], "after_files": [{"content": "import re\nimport os\nimport sys\nimport json\nimport logging\nfrom requests.models import Response\nfrom localstack import config\nfrom localstack.constants import HEADER_LOCALSTACK_TARGET, HEADER_LOCALSTACK_EDGE_URL, LOCALSTACK_ROOT_FOLDER\nfrom localstack.utils.common import run, is_root, TMP_THREADS, to_bytes\nfrom localstack.utils.common import safe_requests as requests\nfrom localstack.services.generic_proxy import ProxyListener, GenericProxy\n\nLOG = logging.getLogger(__name__)\n\n# Header to indicate that the process should kill itself. This is required because if\n# this process is started as root, then we cannot kill it from a non-root process\nHEADER_KILL_SIGNAL = 'x-localstack-kill'\n\n\nclass ProxyListenerEdge(ProxyListener):\n\n    def forward_request(self, method, path, data, headers):\n        if method == 'OPTIONS':\n            return 200\n\n        # kill the process if we receive this header\n        headers.get(HEADER_KILL_SIGNAL) and os._exit(0)\n\n        target = headers.get('x-amz-target', '')\n        auth_header = headers.get('authorization', '')\n        host = headers.get('host', '')\n        headers[HEADER_LOCALSTACK_EDGE_URL] = 'https://%s' % host\n\n        # extract API details\n        api, port, path, host = get_api_from_headers(headers, path)\n\n        if port and int(port) < 0:\n            return 404\n\n        if not port:\n            # detect S3 presigned URLs\n            if 'AWSAccessKeyId=' in path or 'Signature=' in path:\n                port = config.PORT_S3\n            # TODO: move S3 public URLs to a separate port/endpoint, OR check ACLs here first\n            stripped = path.strip('/')\n            if method == 'GET' and '/' in stripped:\n                # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`\n                port = config.PORT_S3\n            if stripped and '/' not in stripped:\n                if method == 'PUT':\n                    # assume that this is an S3 PUT bucket request with URL path `/<bucket>`\n                    port = config.PORT_S3\n                elif method == 'POST' and to_bytes('key=') in to_bytes(data or ''):\n                    # assume that this is an S3 POST request with form parameters in the body\n                    port = config.PORT_S3\n\n        if not port:\n            if api in ['', None, '_unknown_']:\n                LOG.info(('Unable to find forwarding rule for host \"%s\", path \"%s\", '\n                    'target header \"%s\", auth header \"%s\"') % (host, path, target, auth_header))\n            else:\n                LOG.info(('Unable to determine forwarding port for API \"%s\" - please '\n                    'make sure this API is enabled via the SERVICES configuration') % api)\n            response = Response()\n            response.status_code = 404\n            response._content = '{\"status\": \"running\"}'\n            return response\n\n        use_ssl = config.USE_SSL\n\n        connect_host = '%s:%s' % (config.HOSTNAME, port)\n        url = 'http%s://%s%s' % ('s' if use_ssl else '', connect_host, path)\n        headers['Host'] = host\n        function = getattr(requests, method.lower())\n        if isinstance(data, dict):\n            data = json.dumps(data)\n\n        response = function(url, data=data, headers=headers, verify=False)\n        return response\n\n\ndef get_api_from_headers(headers, path=None):\n    target = headers.get('x-amz-target', '')\n    host = headers.get('host', '')\n    auth_header = headers.get('authorization', '')\n    ls_target = headers.get(HEADER_LOCALSTACK_TARGET, '')\n    path = path or '/'\n\n    # initialize result\n    result = '_unknown_', 0\n\n    # https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html\n    try:\n        credential_scope = auth_header.split(',')[0].split()[1]\n        _, _, _, service, _ = credential_scope.split('/')\n        result = service, get_service_port_for_account(service, headers)\n    except Exception:\n        pass\n\n    result_before = result\n\n    # Fallback rules and route customizations applied below\n\n    if host.endswith('cloudfront.net'):\n        path = path or '/'\n        result = 'cloudfront', config.PORT_CLOUDFRONT\n    elif target.startswith('AWSCognitoIdentityProviderService') or 'cognito-idp.' in host:\n        result = 'cognito-idp', config.PORT_COGNITO_IDP\n    elif target.startswith('AWSCognitoIdentityService') or 'cognito-identity.' in host:\n        result = 'cognito-identity', config.PORT_COGNITO_IDENTITY\n    elif result[0] == 's3' or re.match(r'.*s3(\\-website)?\\.([^\\.]+\\.)?amazonaws.com', host):\n        host = re.sub(r's3-website\\..*\\.amazonaws', 's3.amazonaws', host)\n        result = 's3', config.PORT_S3\n    elif result[0] == 'states' in auth_header or host.startswith('states.'):\n        result = 'stepfunctions', config.PORT_STEPFUNCTIONS\n    elif '.execute-api.' in host:\n        result = 'apigateway', config.PORT_APIGATEWAY\n    elif target.startswith('DynamoDBStreams') or host.startswith('streams.dynamodb.'):\n        result = 'dynamodbstreams', config.PORT_DYNAMODBSTREAMS\n    elif ls_target == 'web' or path == '/graph':\n        result = 'web', config.PORT_WEB_UI\n\n    return result[0], result_before[1] or result[1], path, host\n\n\ndef get_service_port_for_account(service, headers):\n    # assume we're only using a single account, hence return the static port mapping from config.py\n    return config.service_port(service)\n\n\ndef do_start_edge(port, use_ssl, asynchronous=False):\n    try:\n        # start local DNS server, if present\n        from localstack_ext.services import dns_server\n        dns_server.start_servers()\n    except Exception:\n        pass\n\n    # get port and start Edge\n    print('Starting edge router (http%s port %s)...' % ('s' if use_ssl else '', port))\n    # use use=True here because our proxy allows both, HTTP and HTTPS traffic\n    proxy = GenericProxy(port, ssl=True, update_listener=ProxyListenerEdge())\n    proxy.start()\n    if not asynchronous:\n        proxy.join()\n    return proxy\n\n\ndef can_use_sudo():\n    try:\n        run('echo | sudo -S echo', print_error=False)\n        return True\n    except Exception:\n        return False\n\n\ndef ensure_can_use_sudo():\n    if not is_root() and not can_use_sudo():\n        print('Please enter your sudo password (required to configure local network):')\n        run('sudo echo', stdin=True)\n\n\ndef start_edge(port=None, use_ssl=True, asynchronous=False):\n    if not port:\n        port = config.EDGE_PORT\n    if config.EDGE_PORT_HTTP:\n        do_start_edge(config.EDGE_PORT_HTTP, use_ssl=False, asynchronous=True)\n    if port > 1024 or is_root():\n        return do_start_edge(port, use_ssl, asynchronous=asynchronous)\n\n    # process requires priviledged port but we're not root -> try running as sudo\n\n    class Terminator(object):\n\n        def stop(self, quiet=True):\n            try:\n                url = 'http%s://localhost:%s' % ('s' if use_ssl else '', port)\n                requests.verify_ssl = False\n                requests.post(url, headers={HEADER_KILL_SIGNAL: 'kill'})\n            except Exception:\n                pass\n\n    # make sure we can run sudo commands\n    ensure_can_use_sudo()\n\n    # register a signal handler to terminate the sudo process later on\n    TMP_THREADS.append(Terminator())\n\n    # start the process as sudo\n    sudo_cmd = 'sudo '\n    python_cmd = sys.executable\n    cmd = '%sPYTHONPATH=.:%s %s %s %s' % (sudo_cmd, LOCALSTACK_ROOT_FOLDER, python_cmd, __file__, port)\n    process = run(cmd, asynchronous=asynchronous)\n    return process\n\n\nif __name__ == '__main__':\n    logging.basicConfig()\n    start_edge(int(sys.argv[1]))\n", "path": "localstack/services/edge.py"}]}