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

Changed files:
moto/s3/urls.py, tests/test_s3/test_s3.py, moto/server.py

diff --git a/moto/s3/urls.py b/moto/s3/urls.py
index fa81568a4..1388c81e5 100644
--- a/moto/s3/urls.py
+++ b/moto/s3/urls.py
@@ -15,4 +15,6 @@ url_paths = {
     '{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
index 89be47093..b245f6e6f 100644
--- 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,
         )
 
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index cd57fc92b..0c0721f01 100644
--- 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 @@ from freezegun import freeze_time
 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('/')