Repository: sanic-org/sanic

Issue:
Changes to Blueprints application breaks multiply applied routes!!!
Consider the following pattern:

```
@app.get("/url1")
@app.get("/url2")
async def handle_multiple(request):
     return text("some response")
```

The recent change in #1690 breaks that pattern.

It's a common pattern to use multiple `@blueprint.get(...)` and `@app.get(...)` to handle routes that have common behavior with slight variations, like a login process.

The only alternative I can think of is splitting out each of these routes into distinct rump functions that literally forward onto a common function, which inflates from one line declarations to three lines plus the constant common route handler.



Discussion:
@benjolitz

The original change in #1690 was done in order to account for an old standing open issue that actually needed fixing. I missed this case when implementing the change that seems to have broken the case of multiple routes being bound to the same handler method. 

Let me see what can be done to address this while still being able to handle the original request from #37 
@harshanarayana okay. wrong username for me btw. ;)
@autumnjolitz oh. Sorry. That explains why the auto complete wasn't working last night. 😂 sorry about the mistake 
Hi @autumnjolitz 
Until we get a proper fix for this, can you try this workaround?

```
async def handle_multiple(request):
     return text("some response")
names1, handler1 = app.get("/url1")(handle_multiple)
names2, handler2 = app.get("/url2")(handle_multiple)
```

Resolving patch:
diff --git a/sanic/app.py b/sanic/app.py
--- a/sanic/app.py
+++ b/sanic/app.py
@@ -194,6 +194,12 @@ def route(
             strict_slashes = self.strict_slashes
 
         def response(handler):
+            if isinstance(handler, tuple):
+                # if a handler fn is already wrapped in a route, the handler
+                # variable will be a tuple of (existing routes, handler fn)
+                routes, handler = handler
+            else:
+                routes = []
             args = list(signature(handler).parameters.keys())
 
             if not args:
@@ -205,14 +211,16 @@ def response(handler):
             if stream:
                 handler.is_stream = stream
 
-            routes = self.router.add(
-                uri=uri,
-                methods=methods,
-                handler=handler,
-                host=host,
-                strict_slashes=strict_slashes,
-                version=version,
-                name=name,
+            routes.extend(
+                self.router.add(
+                    uri=uri,
+                    methods=methods,
+                    handler=handler,
+                    host=host,
+                    strict_slashes=strict_slashes,
+                    version=version,
+                    name=name,
+                )
             )
             return routes, handler
 
@@ -476,6 +484,13 @@ def websocket(
             strict_slashes = self.strict_slashes
 
         def response(handler):
+            if isinstance(handler, tuple):
+                # if a handler fn is already wrapped in a route, the handler
+                # variable will be a tuple of (existing routes, handler fn)
+                routes, handler = handler
+            else:
+                routes = []
+
             async def websocket_handler(request, *args, **kwargs):
                 request.app = self
                 if not getattr(handler, "__blueprintname__", False):
@@ -516,13 +531,15 @@ async def websocket_handler(request, *args, **kwargs):
                     self.websocket_tasks.remove(fut)
                 await ws.close()
 
-            routes = self.router.add(
-                uri=uri,
-                handler=websocket_handler,
-                methods=frozenset({"GET"}),
-                host=host,
-                strict_slashes=strict_slashes,
-                name=name,
+            routes.extend(
+                self.router.add(
+                    uri=uri,
+                    handler=websocket_handler,
+                    methods=frozenset({"GET"}),
+                    host=host,
+                    strict_slashes=strict_slashes,
+                    name=name,
+                )
             )
             return routes, handler
 


Test patch:
diff --git a/tests/test_routes.py b/tests/test_routes.py
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -551,6 +551,35 @@ async def handler4(request, dynamic):
             pass
 
 
+def test_double_stack_route(app):
+    @app.route("/test/1")
+    @app.route("/test/2")
+    async def handler1(request):
+        return text("OK")
+
+    request, response = app.test_client.get("/test/1")
+    assert response.status == 200
+    request, response = app.test_client.get("/test/2")
+    assert response.status == 200
+
+
+@pytest.mark.asyncio
+async def test_websocket_route_asgi(app):
+    ev = asyncio.Event()
+
+    @app.websocket("/test/1")
+    @app.websocket("/test/2")
+    async def handler(request, ws):
+        ev.set()
+
+    request, response = await app.asgi_client.websocket("/test/1")
+    first_set = ev.is_set()
+    ev.clear()
+    request, response = await app.asgi_client.websocket("/test/1")
+    second_set = ev.is_set()
+    assert(first_set and second_set)
+
+
 def test_method_not_allowed(app):
     @app.route("/test", methods=["GET"])
     async def handler(request):