Repository: sanic-org/sanic

Issue:
Exception when stopping the server.
**Describe the bug**
After using the browser like Chrome to access the API, and then stop the server, it will block and wait for a moment, and an exception will occur. 100% reproduction.
```shell
[2021-10-25 15:21:56 +0800] [1879] [INFO] Goin' Fast @ http://127.0.0.1:8000
[2021-10-25 15:21:56 +0800] [1879] [INFO] Starting worker [1879]
[2021-10-25 15:22:00 +0800] - (sanic.access)[INFO][127.0.0.1:51319]: GET http://127.0.0.1:8000/  200 13
[2021-10-25 15:22:00 +0800] - (sanic.access)[INFO][127.0.0.1:51319]: GET http://127.0.0.1:8000/favicon.ico  404 673
^C[2021-10-25 15:22:03 +0800] [1879] [INFO] Stopping worker [1879]
[2021-10-25 15:22:19 +0800] [1879] [ERROR] protocol.connection_task uncaught
Traceback (most recent call last):
  File "/Users/zeb/miniconda3/lib/python3.9/site-packages/sanic/http.py", line 138, in http1
    await self.http1_request_header()
  File "http1_request_header", line 18, in http1_request_header
    ServerError,
  File "/Users/zeb/miniconda3/lib/python3.9/site-packages/sanic/server/protocols/base_protocol.py", line 82, in receive_more
    await self._data_received.wait()
  File "/Users/zeb/miniconda3/lib/python3.9/asyncio/locks.py", line 226, in wait
    await fut
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "connection_task", line 15, in connection_task
    from sanic.http import Http, Stage
  File "/Users/zeb/miniconda3/lib/python3.9/site-packages/sanic/http.py", line 153, in http1
    f"Request: {self.request.method} {self.request.url} "
AttributeError: 'NoneType' object has no attribute 'method'
[2021-10-25 15:22:19 +0800] [1879] [INFO] Server Stopped
```

**Code snippet**
```python
from sanic import Sanic
from sanic.response import text

app = Sanic("MyHelloWorldApp")

@app.get("/")
async def hello_world(request):
	return text("Hello, world.")

app.run()
```

**Expected behavior**
The server should exit gracefully.


**Environment (please complete the following information):**
 - OS: MacOS 10.15.7
 - Version: sanic-21.9.1





Discussion:

Resolving patch:
diff --git a/sanic/http.py b/sanic/http.py
--- a/sanic/http.py
+++ b/sanic/http.py
@@ -105,7 +105,6 @@ def __init__(self, protocol):
         self.keep_alive = True
         self.stage: Stage = Stage.IDLE
         self.dispatch = self.protocol.app.dispatch
-        self.init_for_request()
 
     def init_for_request(self):
         """Init/reset all per-request variables."""
@@ -129,14 +128,20 @@ async def http1(self):
         """
         HTTP 1.1 connection handler
         """
-        while True:  # As long as connection stays keep-alive
+        # Handle requests while the connection stays reusable
+        while self.keep_alive and self.stage is Stage.IDLE:
+            self.init_for_request()
+            # Wait for incoming bytes (in IDLE stage)
+            if not self.recv_buffer:
+                await self._receive_more()
+            self.stage = Stage.REQUEST
             try:
                 # Receive and handle a request
-                self.stage = Stage.REQUEST
                 self.response_func = self.http1_response_header
 
                 await self.http1_request_header()
 
+                self.stage = Stage.HANDLER
                 self.request.conn_info = self.protocol.conn_info
                 await self.protocol.request_handler(self.request)
 
@@ -187,16 +192,6 @@ async def http1(self):
                 if self.response:
                     self.response.stream = None
 
-            # Exit and disconnect if no more requests can be taken
-            if self.stage is not Stage.IDLE or not self.keep_alive:
-                break
-
-            self.init_for_request()
-
-            # Wait for the next request
-            if not self.recv_buffer:
-                await self._receive_more()
-
     async def http1_request_header(self):  # no cov
         """
         Receive and parse request header into self.request.
@@ -299,7 +294,6 @@ async def http1_request_header(self):  # no cov
 
         # Remove header and its trailing CRLF
         del buf[: pos + 4]
-        self.stage = Stage.HANDLER
         self.request, request.stream = request, self
         self.protocol.state["requests_count"] += 1
 


Test patch:
diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py
deleted file mode 100644
--- a/tests/test_request_timeout.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import asyncio
-
-import httpcore
-import httpx
-import pytest
-
-from sanic_testing.testing import SanicTestClient
-
-from sanic import Sanic
-from sanic.response import text
-
-
-class DelayableHTTPConnection(httpcore._async.connection.AsyncHTTPConnection):
-    async def arequest(self, *args, **kwargs):
-        await asyncio.sleep(2)
-        return await super().arequest(*args, **kwargs)
-
-    async def _open_socket(self, *args, **kwargs):
-        retval = await super()._open_socket(*args, **kwargs)
-        if self._request_delay:
-            await asyncio.sleep(self._request_delay)
-        return retval
-
-
-class DelayableSanicConnectionPool(httpcore.AsyncConnectionPool):
-    def __init__(self, request_delay=None, *args, **kwargs):
-        self._request_delay = request_delay
-        super().__init__(*args, **kwargs)
-
-    async def _add_to_pool(self, connection, timeout):
-        connection.__class__ = DelayableHTTPConnection
-        connection._request_delay = self._request_delay
-        await super()._add_to_pool(connection, timeout)
-
-
-class DelayableSanicSession(httpx.AsyncClient):
-    def __init__(self, request_delay=None, *args, **kwargs) -> None:
-        transport = DelayableSanicConnectionPool(request_delay=request_delay)
-        super().__init__(transport=transport, *args, **kwargs)
-
-
-class DelayableSanicTestClient(SanicTestClient):
-    def __init__(self, app, request_delay=None):
-        super().__init__(app)
-        self._request_delay = request_delay
-        self._loop = None
-
-    def get_new_session(self):
-        return DelayableSanicSession(request_delay=self._request_delay)
-
-
-@pytest.fixture
-def request_no_timeout_app():
-    app = Sanic("test_request_no_timeout")
-    app.config.REQUEST_TIMEOUT = 0.6
-
-    @app.route("/1")
-    async def handler2(request):
-        return text("OK")
-
-    return app
-
-
-@pytest.fixture
-def request_timeout_default_app():
-    app = Sanic("test_request_timeout_default")
-    app.config.REQUEST_TIMEOUT = 0.6
-
-    @app.route("/1")
-    async def handler1(request):
-        return text("OK")
-
-    @app.websocket("/ws1")
-    async def ws_handler1(request, ws):
-        await ws.send("OK")
-
-    return app
-
-
-def test_default_server_error_request_timeout(request_timeout_default_app):
-    client = DelayableSanicTestClient(request_timeout_default_app, 2)
-    _, response = client.get("/1")
-    assert response.status == 408
-    assert "Request Timeout" in response.text
-
-
-def test_default_server_error_request_dont_timeout(request_no_timeout_app):
-    client = DelayableSanicTestClient(request_no_timeout_app, 0.2)
-    _, response = client.get("/1")
-    assert response.status == 200
-    assert response.text == "OK"
-
-
-def test_default_server_error_websocket_request_timeout(
-    request_timeout_default_app,
-):
-
-    headers = {
-        "Upgrade": "websocket",
-        "Connection": "upgrade",
-        "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
-        "Sec-WebSocket-Version": "13",
-    }
-
-    client = DelayableSanicTestClient(request_timeout_default_app, 2)
-    _, response = client.get("/ws1", headers=headers)
-
-    assert response.status == 408
-    assert "Request Timeout" in response.text
diff --git a/tests/test_timeout_logic.py b/tests/test_timeout_logic.py
--- a/tests/test_timeout_logic.py
+++ b/tests/test_timeout_logic.py
@@ -26,6 +26,7 @@ def protocol(app, mock_transport):
     protocol = HttpProtocol(loop=loop, app=app)
     protocol.connection_made(mock_transport)
     protocol._setup_connection()
+    protocol._http.init_for_request()
     protocol._task = Mock(spec=asyncio.Task)
     protocol._task.cancel = Mock()
     return protocol