Repository: sanic-org/sanic

Issue:
In ASGI mod, the response turns into 500 server error.
In ASGI mod, if request middleware raises an exception and meanwhile there is an error in fetching handler from router part as well, server response turns into 500 server error. 

**app.py**
```python
from sanic import Request, Sanic
from sanic.exceptions import Forbidden
from sanic.response import text

app = Sanic("My Hello, world app")


@app.get("/")
async def hello_world(request):
    return text("Hello, world.")


@app.middleware
async def request_middleware(request: Request) -> None:
    if "Authorization" not in request.headers:
        raise Forbidden(message="Authorization header not found")
```
```bash
>>> uvicorn app:app
INFO:     Started server process [227205]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic_routing/router.py", line 79, in resolve
    route, param_basket = self.find_route(
  File "", line 9, in find_route
sanic_routing.exceptions.NotFound: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/router.py", line 33, in _get
    return self.resolve(
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic_routing/router.py", line 96, in resolve
    raise self.exception(str(e), path=path)
sanic_routing.exceptions.NotFound: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/app.py", line 723, in handle_request
    route, handler, kwargs = self.router.get(
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/router.py", line 61, in get
    return self._get(path, method, host)
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/router.py", line 39, in _get
    raise NotFound("Requested URL {} not found".format(e.path))
sanic.exceptions.NotFound: Requested URL /wrong not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 371, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 59, in __call__
    return await self.app(scope, receive, send)
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/app.py", line 1276, in __call__
    await asgi_app()
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/asgi.py", line 210, in __call__
    await self.sanic_app.handle_request(self.request)
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/app.py", line 791, in handle_request
    await self.handle_exception(request, e)
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/app.py", line 667, in handle_exception
    response = await self._run_request_middleware(
  File "/home/cansarigol/Documents/sanic-demo/venv/lib/python3.8/site-packages/sanic/app.py", line 1116, in _run_request_middleware
    response = await response
  File "/home/cansarigol/Documents/sanic-demo/./app.py", line 16, in request_middleware
    raise Forbidden(message="Authorization header not found")
sanic.exceptions.Forbidden: Authorization header not found
INFO:     127.0.0.1:52662 - "GET /wrong HTTP/1.1" 500 Internal Server Error
```

**Expected behavior**
it should have been 403 (the same approach as WSGI).

**Environment (please complete the following information):**
 - OS: ubuntu 20.04
 - Version
        python = "3.8"
        sanic = "21.6.1"
        uvicorn = "0.14.0"



Discussion:
:thinking: Am I must be missing something? How did you get a 500?

```
$ uvicorn p:app               
INFO:     Started server process [306986]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:60770 - "GET / HTTP/1.1" 403 Forbidden
```

```
$ curl localhost:8000 -i
HTTP/1.1 403 Forbidden
date: Mon, 02 Aug 2021 12:24:43 GMT
server: uvicorn
content-type: text/html; charset=utf-8
transfer-encoding: chunked

<!DOCTYPE html><html lang=en><meta charset=UTF-8><title>⚠️ 403 — Forbidden</title>
<style>
        html { font-family: sans-serif }
        h2 { color: #888; }
        .tb-wrapper p { margin: 0 }
        .frame-border { margin: 1rem }
        .frame-line > * { padding: 0.3rem 0.6rem }
        .frame-line { margin-bottom: 0.3rem }
        .frame-code { font-size: 16px; padding-left: 4ch }
        .tb-wrapper { border: 1px solid #eee }
        .tb-header { background: #eee; padding: 0.3rem; font-weight: bold }
        .frame-descriptor { background: #e2eafb; font-size: 14px }
    </style>
<h1>⚠️ 403 — Forbidden</h1><p>Authorization header not found

```
Never mind... I was able to reproduce:

```
$ curl localhost:8000/111 -i
HTTP/1.1 500 Internal Server Error
date: Mon, 02 Aug 2021 12:25:49 GMT
server: uvicorn
content-type: text/plain; charset=utf-8
connection: close
transfer-encoding: chunked

Internal Server Error
```

@ahopkins When I put a try-expect in call dunder of the asgi app class, the problem is solved.

```git
diff --git a/sanic/asgi.py b/sanic/asgi.py
index 5765a5c..330ced5 100644
--- a/sanic/asgi.py
+++ b/sanic/asgi.py
@@ -207,4 +207,7 @@ class ASGIApp:
         """
         Handle the incoming request.
         """
-        await self.sanic_app.handle_request(self.request)
+        try:
+            await self.sanic_app.handle_request(self.request)
+        except Exception as e:
+            await self.sanic_app.handle_exception(self.request, e)
``` 
Nice. Can you make a PR? In your PR, you can also change the value of `./sanic/__version__.py` to 21.6.2 and I can get it ready to send out a patch. We probably should add a unit test for this. I am really surprised there is not one already.

Resolving patch:
diff --git a/sanic/__version__.py b/sanic/__version__.py
--- a/sanic/__version__.py
+++ b/sanic/__version__.py
@@ -1 +1 @@
-__version__ = "21.6.1"
+__version__ = "21.6.2"
diff --git a/sanic/asgi.py b/sanic/asgi.py
--- a/sanic/asgi.py
+++ b/sanic/asgi.py
@@ -207,4 +207,7 @@ async def __call__(self) -> None:
         """
         Handle the incoming request.
         """
-        await self.sanic_app.handle_request(self.request)
+        try:
+            await self.sanic_app.handle_request(self.request)
+        except Exception as e:
+            await self.sanic_app.handle_exception(self.request, e)


Test patch:
diff --git a/tests/test_asgi.py b/tests/test_asgi.py
--- a/tests/test_asgi.py
+++ b/tests/test_asgi.py
@@ -7,7 +7,7 @@
 
 from sanic import Sanic
 from sanic.asgi import MockTransport
-from sanic.exceptions import InvalidUsage
+from sanic.exceptions import Forbidden, InvalidUsage, ServiceUnavailable
 from sanic.request import Request
 from sanic.response import json, text
 from sanic.websocket import WebSocketConnection
@@ -346,3 +346,32 @@ def send_custom(request):
 
     _, response = await app.asgi_client.get("/custom")
     assert response.headers.get("content-type") == "somethingelse"
+
+
+@pytest.mark.asyncio
+async def test_request_handle_exception(app):
+    @app.get("/error-prone")
+    def _request(request):
+        raise ServiceUnavailable(message="Service unavailable")
+
+    _, response = await app.asgi_client.get("/wrong-path")
+    assert response.status_code == 404
+
+    _, response = await app.asgi_client.get("/error-prone")
+    assert response.status_code == 503
+
+@pytest.mark.asyncio
+async def test_request_exception_suppressed_by_middleware(app):
+    @app.get("/error-prone")
+    def _request(request):
+        raise ServiceUnavailable(message="Service unavailable")
+
+    @app.on_request
+    def forbidden(request):
+        raise Forbidden(message="forbidden")
+
+    _, response = await app.asgi_client.get("/wrong-path")
+    assert response.status_code == 403
+
+    _, response = await app.asgi_client.get("/error-prone")
+    assert response.status_code == 403
\ No newline at end of file