Skip to content

Commit

Permalink
Support disabling default Server and Date headers (encode#818)
Browse files Browse the repository at this point in the history
* Support disabling default Server header (encode#688)

Section 7.4.2 of RFC 7231 states that "an origin server MAY generate
a Server field in its responses." The "MAY" means that sending this
header is entirely optional.

While the implementation of encode#321 allowed applications to override
the Server header, there was no way to disable the Server header
altogether.

By default, uvicorn adds a Server header to each response that doesn't
have one through other means. This change adds the server_header flag
to Config, as well as the commandline option --no-server-header,
through which this behaviour can be disabled.

* Support disabling default Date header

When an origin server does not have a sufficiently reliable clock,
section 7.1.1.2 of RFC 7231 stipulates that it "MUST NOT" send
a Date header field.

By default, uvicorn adds a Date header to each response. This change
adds the date_header flag to Config, as well as the commandline
option --no-date-header, through which this behaviour can be disabled.
  • Loading branch information
stiiin authored and Kludex committed Oct 29, 2022
1 parent 2452d93 commit 30d01d1
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 6 deletions.
4 changes: 4 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ Options:
Enable/Disable X-Forwarded-Proto,
X-Forwarded-For, X-Forwarded-Port to
populate remote address info.
--server-header / --no-server-header
Enable/Disable default Server header.
--date-header / --no-date-header
Enable/Disable default Date header.
--forwarded-allow-ips TEXT Comma seperated list of IPs to trust with
proxy headers. Defaults to the
$FORWARDED_ALLOW_IPS environment variable if
Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ Options:
Enable/Disable X-Forwarded-Proto,
X-Forwarded-For, X-Forwarded-Port to
populate remote address info.
--server-header / --no-server-header
Enable/Disable default Server header.
--date-header / --no-date-header
Enable/Disable default Date header.
--forwarded-allow-ips TEXT Comma seperated list of IPs to trust with
proxy headers. Defaults to the
$FORWARDED_ALLOW_IPS environment variable if
Expand Down
28 changes: 28 additions & 0 deletions tests/test_default_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
10 changes: 7 additions & 3 deletions uvicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,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,
Expand Down Expand Up @@ -187,6 +189,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
Expand Down Expand Up @@ -301,9 +305,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):
Expand Down
16 changes: 16 additions & 0 deletions uvicorn/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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,
Expand Down Expand Up @@ -326,6 +338,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,
Expand Down Expand Up @@ -369,6 +383,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,
Expand Down
12 changes: 9 additions & 3 deletions uvicorn/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 30d01d1

Please sign in to comment.