Repository: getmoto/moto

Issue:
mock_s3 gives "ConnectionError: Connection refused..." error when Key used in put_object call contains leading forward slash
This is very specific situation, however the behavior of moto in this situation doesn't match what happens when you use the actual boto3 connecting to AWS.

The situation is if you have a Key for a S3 object that you are putting into a S3 bucket that has a leading forward slash ("/").

So if your Key is "/abc" and you try putting an object into a S3 bucket with the key as follows:

```
def do_something():
  my_bucket = boto3.resource('s3', region_name='us-west-2').Bucket('my_bucket')
  my_bucket.put_object(Key='/abc', Body='ABCD')
```

If you run this snippet of code (along with required imports and so on), then an object with the key "/abc" will be placed in the bucket with the content specified.

When you view this on the AWS Console, it will seem a bit strange because at the "root" level of the S3 bucket, you'll find a "folder" with no name. Upon clicking this no-named folder, you'll see an object named "abc" with the content specified. So this is the expected behavior when boto3 is being used without moto.

When moto is used in a similar situation, lets say as follows:

```
@moto_s3
def do_something_moto()
  conn = botot3.resource('s3', region_name='us-west-2')
  conn.create_bucket(Bucket='my_bucket')
  bucket.put_object(Key='/abc', Body='ABCD')
```

If you run this code, you will get an error with a traceback ending with:

`ConnectionError: Connection refused: PUT https://foobar.s3.us-west-2.amazonaws.com//abc`

If you remove the leading forward slash from the Key in the put_object call:

`bucket.put_object(Key='abc', Body='ABCD')`

Then this code will run without an error. As pointed out above when using the boto3 without moto, a Key with a leading forward slash is a valid key and should not cause an error. This is really an edge case, but I think moto should be made to handle this situation correctly.

As for versions of boto3 and moto being used:
boto3: 1.7.19
moto: 1.3.3


Discussion:
Thanks for opening. Agree this is a bug and going to set it as such.
Debugging this -- I'm thinking the issue is related to the S3 regex.
@spulec : going to need your help on this.

The regex URL logic is extremely hard to follow and debug. There is definitely something up in the `url_paths`, but I don't really see how to step into the logic (and can't actually find where `url_paths` is even being referenced.

```
url_paths = {
    # subdomain bucket
    '{0}/$': S3ResponseInstance.bucket_response,

    # subdomain key of path-based bucket
    '{0}/(?P<key_or_bucket_name>[^/]+)/?$': S3ResponseInstance.ambiguous_response,
    # path-based bucket + key
    '{0}/(?P<bucket_name_path>[^/]+)/(?P<key_name>.+)': S3ResponseInstance.key_response,
}
```
^^ This is the current code.

I've tried doing a bunch of things to no avail. I've tried:
```
-    '{0}/(?P<bucket_name_path>[^/]+)//(?P<key_name>.+)': S3ResponseInstance.key_response,
-    '{0}/(?P<bucket_name_path>[^/]+)/(/?P<key_name>.+)': S3ResponseInstance.key_response,
-    '{0}/(?P<bucket_name_path>[^/]+)\/(?P<key_name>.+)': S3ResponseInstance.key_response,
-    '{0}/(?P<bucket_name_path>[^/]+)/(\/?P<key_name>.+)': S3ResponseInstance.key_response,
-    '{0}/(?P<key_or_bucket_name>[^/]+)//?$': S3ResponseInstance.ambiguous_response,
-    '{0}//(?P<key_or_bucket_name>[^/]+)/?$': S3ResponseInstance.ambiguous_response,
```

At this point, I'm just throwing crap at the wall to see what sticks. In the meantime, I'm going to fix my tests to not make use of the leading slash.

I was recently burned by this. I couldn't modify my tests to not use the leading slash. Our leading slash was caused by a pre-fix var being set to an empty string, I did the reasonable thing, and wrapped setting of the key in a conditional to drop the prefix if its not set. 
I believe there are two problems that need to be solved here.

One is lack of the proper regexp for paths with leading two slashes in `url_paths`. Fixing this makes the code from the original report work, but not in the server mode.

Unfortunately there's also a bug in Werkzeug, which is used by moto for server mode, that prevents proper handling of paths with multiple leading slashes, see [here](https://github.com/pallets/werkzeug/pull/1286#issuecomment-383943350).

I think I have the fix for the first problem, but the tests verifying it need to be skipped in server mode as they are obviously still failing there. Should I create the PR for this or wait till Werkzeug is fixed first?

A PR for the first problem would be great until Werkzeug solves the second problem.

Resolving patch:
diff --git a/moto/s3/urls.py b/moto/s3/urls.py
--- a/moto/s3/urls.py
+++ b/moto/s3/urls.py
@@ -15,4 +15,6 @@
     '{0}/(?P<key_or_bucket_name>[^/]+)/?$': S3ResponseInstance.ambiguous_response,
     # path-based bucket + key
     '{0}/(?P<bucket_name_path>[^/]+)/(?P<key_name>.+)': S3ResponseInstance.key_response,
+    # subdomain bucket + key with empty first part of path
+    '{0}//(?P<key_name>.*)$': S3ResponseInstance.key_response,
 }
diff --git a/moto/server.py b/moto/server.py
--- a/moto/server.py
+++ b/moto/server.py
@@ -174,10 +174,11 @@ def create_backend_app(service):
     backend_app.url_map.converters['regex'] = RegexConverter
     backend = list(BACKENDS[service].values())[0]
     for url_path, handler in backend.flask_paths.items():
+        view_func = convert_flask_to_httpretty_response(handler)
         if handler.__name__ == 'dispatch':
             endpoint = '{0}.dispatch'.format(handler.__self__.__name__)
         else:
-            endpoint = None
+            endpoint = view_func.__name__
 
         original_endpoint = endpoint
         index = 2
@@ -191,7 +192,7 @@ def create_backend_app(service):
             url_path,
             endpoint=endpoint,
             methods=HTTP_METHODS,
-            view_func=convert_flask_to_httpretty_response(handler),
+            view_func=view_func,
             strict_slashes=False,
         )
 


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -2,6 +2,7 @@
 from __future__ import unicode_literals
 
 import datetime
+import os
 from six.moves.urllib.request import urlopen
 from six.moves.urllib.error import HTTPError
 from functools import wraps
@@ -23,6 +24,7 @@
 import six
 import requests
 import tests.backport_assert_raises  # noqa
+from nose import SkipTest
 from nose.tools import assert_raises
 
 import sure  # noqa
@@ -2991,3 +2993,28 @@ def test_accelerate_configuration_is_not_supported_when_bucket_name_has_dots():
             AccelerateConfiguration={'Status': 'Enabled'},
         )
     exc.exception.response['Error']['Code'].should.equal('InvalidRequest')
+
+def store_and_read_back_a_key(key):
+    s3 = boto3.client('s3', region_name='us-east-1')
+    bucket_name = 'mybucket'
+    body = b'Some body'
+
+    s3.create_bucket(Bucket=bucket_name)
+    s3.put_object(
+        Bucket=bucket_name,
+        Key=key,
+        Body=body
+    )
+
+    response = s3.get_object(Bucket=bucket_name, Key=key)
+    response['Body'].read().should.equal(body)
+
+@mock_s3
+def test_paths_with_leading_slashes_work():
+    store_and_read_back_a_key('/a-key')
+
+@mock_s3
+def test_root_dir_with_empty_name_works():
+    if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'true':
+        raise SkipTest('Does not work in server mode due to error in Workzeug')
+    store_and_read_back_a_key('/')