Repository: encode/uvicorn

Issue:
Disable Server Header
Hi, 

I'm looking for a  way to prevent the `server:uvicorn` header from being returned in every response. 
I found https://github.com/encode/uvicorn/issues/243 which is similar but the solution doesn't address this particular problem. 

@davidism [asked this question](https://github.com/encode/uvicorn/pull/321#issuecomment-471183072) but @Jitsusama's suggestion of using "middleware to remove it at the last possible second" doesn't work because the default header is added after all the middlewares & routers have run. 

@antirais's [suggestion](https://github.com/encode/uvicorn/issues/243#issuecomment-470603894) of monkey-patching does work but it's very hacky and I'm looking for a cleaner solution.

Thank you


Discussion:
Hi there, thanks for getting involved. 😃

I'm closing this off for now, since we're moving to a "maintainer driven" policy on our issue tracker & pull request spaces. This will gradually help us ensure that these spaces do a good job of properly reflecting the ongoing project priorities.

The best places to start with this are instead [the chat room](https://gitter.im/encode/community), or [the discussion group](https://discuss.encode.io/).

Many thanks. 💛✨

[Read more about our contribution guidelines.](https://github.com/encode/.github/blob/master/CONTRIBUTING.md)

Resolving patch:
diff --git a/uvicorn/config.py b/uvicorn/config.py
--- a/uvicorn/config.py
+++ b/uvicorn/config.py
@@ -145,6 +145,8 @@ def __init__(
         reload_delay=None,
         workers=None,
         proxy_headers=True,
+        server_header=True,
+        date_header=True,
         forwarded_allow_ips=None,
         root_path="",
         limit_concurrency=None,
@@ -183,6 +185,8 @@ def __init__(
         self.reload_delay = reload_delay or 0.25
         self.workers = workers or 1
         self.proxy_headers = proxy_headers
+        self.server_header = server_header
+        self.date_header = date_header
         self.root_path = root_path
         self.limit_concurrency = limit_concurrency
         self.limit_max_requests = limit_max_requests
@@ -297,9 +301,9 @@ def load(self):
             for key, value in self.headers
         ]
         self.encoded_headers = (
-            encoded_headers
-            if b"server" in dict(encoded_headers)
-            else [(b"server", b"uvicorn")] + encoded_headers
+            [(b"server", b"uvicorn")] + encoded_headers
+            if b"server" not in dict(encoded_headers) and self.server_header
+            else encoded_headers
         )  # type: List[Tuple[bytes, bytes]]
 
         if isinstance(self.http, str):
diff --git a/uvicorn/main.py b/uvicorn/main.py
--- a/uvicorn/main.py
+++ b/uvicorn/main.py
@@ -175,6 +175,18 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
     help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to "
     "populate remote address info.",
 )
+@click.option(
+    "--server-header/--no-server-header",
+    is_flag=True,
+    default=True,
+    help="Enable/Disable default Server header.",
+)
+@click.option(
+    "--date-header/--no-date-header",
+    is_flag=True,
+    default=True,
+    help="Enable/Disable default Date header.",
+)
 @click.option(
     "--forwarded-allow-ips",
     type=str,
@@ -310,6 +322,8 @@ def main(
     log_level: str,
     access_log: bool,
     proxy_headers: bool,
+    server_header: bool,
+    date_header: bool,
     forwarded_allow_ips: str,
     root_path: str,
     limit_concurrency: int,
@@ -351,6 +365,8 @@ def main(
         "reload_delay": reload_delay,
         "workers": workers,
         "proxy_headers": proxy_headers,
+        "server_header": server_header,
+        "date_header": date_header,
         "forwarded_allow_ips": forwarded_allow_ips,
         "root_path": root_path,
         "limit_concurrency": limit_concurrency,
diff --git a/uvicorn/server.py b/uvicorn/server.py
--- a/uvicorn/server.py
+++ b/uvicorn/server.py
@@ -221,9 +221,15 @@ async def on_tick(self, counter) -> bool:
         if counter % 10 == 0:
             current_time = time.time()
             current_date = formatdate(current_time, usegmt=True).encode()
-            self.server_state.default_headers = [
-                (b"date", current_date)
-            ] + self.config.encoded_headers
+
+            if self.config.date_header:
+                date_header = [(b"date", current_date)]
+            else:
+                date_header = []
+
+            self.server_state.default_headers = (
+                date_header + self.config.encoded_headers
+            )
 
             # Callback to `callback_notify` once every `timeout_notify` seconds.
             if self.config.callback_notify is not None:


Test patch:
diff --git a/tests/test_default_headers.py b/tests/test_default_headers.py
--- a/tests/test_default_headers.py
+++ b/tests/test_default_headers.py
@@ -36,6 +36,20 @@ async def test_override_server_header():
             )
 
 
+@pytest.mark.asyncio
+async def test_disable_default_server_header():
+    config = Config(
+        app=app,
+        loop="asyncio",
+        limit_max_requests=1,
+        server_header=False,
+    )
+    async with run_server(config):
+        async with httpx.AsyncClient() as client:
+            response = await client.get("http://127.0.0.1:8000")
+            assert "server" not in response.headers
+
+
 @pytest.mark.asyncio
 async def test_override_server_header_multiple_times():
     config = Config(
@@ -69,3 +83,17 @@ async def test_add_additional_header():
                 and response.headers["server"] == "uvicorn"
                 and response.headers["date"]
             )
+
+
+@pytest.mark.asyncio
+async def test_disable_default_date_header():
+    config = Config(
+        app=app,
+        loop="asyncio",
+        limit_max_requests=1,
+        date_header=False,
+    )
+    async with run_server(config):
+        async with httpx.AsyncClient() as client:
+            response = await client.get("http://127.0.0.1:8000")
+            assert "date" not in response.headers