Repository: encode/uvicorn

Issue:
"Date" header not changing between requests
### Discussed in https://github.com/encode/uvicorn/discussions/1610

<div type='discussions-op-text'>

<sup>Originally posted by **arjwilliams** August 22, 2022</sup>
I am testing out some cache-control headers in my web app, but the auto-generated "Date" header seems to only be changing when a new connection is established, rather than with each new request, which is preventing the browser from caching new responses, as it sees the response as being older than it actually is.

I would expect that the "Date" header to change between requests, even when using an existing connection.

I am testing on Python 3.10.5, with Uvicorn 0.18.2.

Possible test case, which is currently failing for me:
```python
import asyncio

from email.utils import parsedate_to_datetime
from contextlib import asynccontextmanager

import httpx

from uvicorn import Config, Server

async def test_default_date_header_different():
    config = Config(app=app, loop="asyncio", limit_max_requests=2)
    async with run_server(config):
        async with httpx.AsyncClient() as client:
            response1 = await client.get("http://127.0.0.1:8000")
            await asyncio.sleep(2)
            response2 = await client.get("http://127.0.0.1:8000")

            response1_date = parsedate_to_datetime(response1.headers["date"])
            response2_date = parsedate_to_datetime(response2.headers["date"])
            assert(response2_date > response1_date)

async def app(scope, receive, send):
    assert scope["type"] == "http"
    await send({"type": "http.response.start", "status": 200, "headers": []})
    await send({"type": "http.response.body", "body": b"", "more_body": False})

@asynccontextmanager
async def run_server(config: Config):
    server = Server(config=config)
    cancel_handle = asyncio.ensure_future(server.serve())
    await asyncio.sleep(0.1)
    try:
        yield server
    finally:
        await server.shutdown()
        cancel_handle.cancel()

asyncio.run(test_date_header_different())
```</div>


Discussion:
not sure about that, isn't that the keep-alive of 5s is for, the tests pass fine if for instance we set 
```
    config = Config(app=app, timeout_keep_alive=1.1)
    server = Server(config=config)
```
@euri10 that would mean the connection expires on TimeOut right?

The discussion here was mainly focusing on repeated request using same connection.

The discussion has a rough comparison with other servers too. 
> not sure about that, isn't that the keep-alive of 5s is for, the tests pass fine if for instance we set
> 
> ```
>     config = Config(app=app, timeout_keep_alive=1.1)
>     server = Server(config=config)
> ```

Correct. This is not a bug.
After reading around a bit, I think this is a bug. 😅

If the same connection is kept alive for a year, it will still show the same date. I don't think that's expected...

EDIT: I also don't think that a connection would be alive for a year, fwiw. 👀
One year!?😅

For a possible fix, we may take reference from Hypercorn and set default headers on every response. If we do this, we might not need the on_tick function to update default header on every second.

Resolving patch:
diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py
--- a/uvicorn/protocols/http/h11_impl.py
+++ b/uvicorn/protocols/http/h11_impl.py
@@ -92,7 +92,6 @@ def __init__(
         self.server_state = server_state
         self.connections = server_state.connections
         self.tasks = server_state.tasks
-        self.default_headers = server_state.default_headers
 
         # Per-connection state
         self.transport: asyncio.Transport = None  # type: ignore[assignment]
@@ -230,7 +229,7 @@ def handle_events(self) -> None:
                     logger=self.logger,
                     access_logger=self.access_logger,
                     access_log=self.access_log,
-                    default_headers=self.default_headers,
+                    default_headers=self.server_state.default_headers,
                     message_event=asyncio.Event(),
                     on_response=self.on_response_complete,
                 )
diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py
--- a/uvicorn/protocols/http/httptools_impl.py
+++ b/uvicorn/protocols/http/httptools_impl.py
@@ -90,7 +90,6 @@ def __init__(
         self.server_state = server_state
         self.connections = server_state.connections
         self.tasks = server_state.tasks
-        self.default_headers = server_state.default_headers
 
         # Per-connection state
         self.transport: asyncio.Transport = None  # type: ignore[assignment]
@@ -199,7 +198,7 @@ def handle_upgrade(self) -> None:
     def send_400_response(self, msg: str) -> None:
 
         content = [STATUS_LINE[400]]
-        for name, value in self.default_headers:
+        for name, value in self.server_state.default_headers:
             content.extend([name, b": ", value, b"\r\n"])
         content.extend(
             [
@@ -274,7 +273,7 @@ def on_headers_complete(self) -> None:
             logger=self.logger,
             access_logger=self.access_logger,
             access_log=self.access_log,
-            default_headers=self.default_headers,
+            default_headers=self.server_state.default_headers,
             message_event=asyncio.Event(),
             expect_100_continue=self.expect_100_continue,
             keep_alive=http_version != "1.0",


Test patch: