<p dir="auto">Using <code class="notranslate">Flask 2.2.2</code>, it seems that serving static resources leads to a duplicate <code class="notranslate">Date</code> HTTP Header field.</p>
<p dir="auto">Here is a simple snippet easy to understand and to show how to reproduce:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from flask import Flask

app = Flask(__name__, static_url_path='/static')

@app.route('/', methods=['GET'])
def index():
    return &quot;Hello World&quot;

app.run(port=30887)"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">flask</span> <span class="pl-k">import</span> <span class="pl-v">Flask</span>

<span class="pl-s1">app</span> <span class="pl-c1">=</span> <span class="pl-v">Flask</span>(<span class="pl-s1">__name__</span>, <span class="pl-s1">static_url_path</span><span class="pl-c1">=</span><span class="pl-s">'/static'</span>)

<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">route</span>(<span class="pl-s">'/'</span>, <span class="pl-s1">methods</span><span class="pl-c1">=</span>[<span class="pl-s">'GET'</span>])</span>
<span class="pl-k">def</span> <span class="pl-en">index</span>():
    <span class="pl-k">return</span> <span class="pl-s">"Hello World"</span>

<span class="pl-s1">app</span>.<span class="pl-en">run</span>(<span class="pl-s1">port</span><span class="pl-c1">=</span><span class="pl-c1">30887</span>)</pre></div>
<p dir="auto">Let's create a simple file inside <code class="notranslate">static</code> directory:</p>
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="mkdir static
echo Hello &gt; style.css"><pre class="notranslate">mkdir static
<span class="pl-c1">echo</span> Hello <span class="pl-k">&gt;</span> style.css</pre></div>
<p dir="auto">Now let's check HTTP headers:</p>
<div class="highlight highlight-source-httpspec notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="maxux@workx0 ~ $ curl -I http://127.0.0.1:30887
HTTP/1.1 200 OK
Server: Werkzeug/2.2.2 Python/3.10.8
Date: Thu, 05 Jan 2023 21:53:31 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 11
Connection: close"><pre class="notranslate"><span class="pl-ii">maxux@workx0 ~ $ curl -I http://127.0.0.1:30887</span>
<span class="pl-c1">HTTP/1.1 200 OK</span>
<span class="pl-s"><span class="pl-v">Server:</span> Werkzeug/2.2.2 Python/3.10.8</span>
<span class="pl-s"><span class="pl-v">Date:</span> Thu, 05 Jan 2023 21:53:31 GMT</span>
<span class="pl-s"><span class="pl-v">Content-Type:</span> text/html; charset=utf-8</span>
<span class="pl-s"><span class="pl-v">Content-Length:</span> 11</span>
<span class="pl-s"><span class="pl-v">Connection:</span> close</span></pre></div>
<p dir="auto">All good here.</p>
<div class="highlight highlight-source-httpspec notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="maxux@workx0 ~ $ curl -I http://127.0.0.1:30887/static/style.css
HTTP/1.1 200 OK
Server: Werkzeug/2.2.2 Python/3.10.8
Date: Thu, 05 Jan 2023 21:53:53 GMT                &lt;&lt;&lt;&lt;
Content-Disposition: inline; filename=style.css
Content-Type: text/css; charset=utf-8
Content-Length: 6
Last-Modified: Thu, 05 Jan 2023 21:50:59 GMT
Cache-Control: no-cache
ETag: &quot;1672955459.103843-6-1459357711&quot;
Date: Thu, 05 Jan 2023 21:53:53 GMT                &lt;&lt;&lt;&lt;
Connection: close"><pre class="notranslate"><span class="pl-ii">maxux@workx0 ~ $ curl -I http://127.0.0.1:30887/static/style.css</span>
<span class="pl-c1">HTTP/1.1 200 OK</span>
<span class="pl-s"><span class="pl-v">Server:</span> Werkzeug/2.2.2 Python/3.10.8</span>
<span class="pl-s"><span class="pl-v">Date:</span> Thu, 05 Jan 2023 21:53:53 GMT                &lt;&lt;&lt;&lt;</span>
<span class="pl-s"><span class="pl-v">Content-Disposition:</span> inline; filename=style.css</span>
<span class="pl-s"><span class="pl-v">Content-Type:</span> text/css; charset=utf-8</span>
<span class="pl-s"><span class="pl-v">Content-Length:</span> 6</span>
<span class="pl-s"><span class="pl-v">Last-Modified:</span> Thu, 05 Jan 2023 21:50:59 GMT</span>
<span class="pl-s"><span class="pl-v">Cache-Control:</span> no-cache</span>
<span class="pl-s"><span class="pl-v">ETag:</span> "1672955459.103843-6-1459357711"</span>
<span class="pl-s"><span class="pl-v">Date:</span> Thu, 05 Jan 2023 21:53:53 GMT                &lt;&lt;&lt;&lt;</span>
<span class="pl-s"><span class="pl-v">Connection:</span> close</span></pre></div>
<p dir="auto">You can see <code class="notranslate">Date</code> header twice, for a static content response.</p>
<p dir="auto">In addition, you can see python and Flask running version in the response as well to confirm running version.</p>
<p dir="auto">This was spotted using nginx on front which complains (by a warning) that the header is duplicated.</p>
<p dir="auto">I ran into some warnings from nginx that my Flask server was generating duplicate Date headers, so I investigated.</p>
<p dir="auto">Here's how to reproduce the situation:</p>
<p dir="auto">app.py</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from flask import Flask

app = Flask(__name__)

@app.route(&quot;/hello&quot;)
def hello_world():
    return &quot;Hello from a flask route!&quot;"><pre class="notranslate"><code class="notranslate">from flask import Flask

app = Flask(__name__)

@app.route("/hello")
def hello_world():
    return "Hello from a flask route!"
</code></pre></div>
<p dir="auto">static/hello</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Hello from static world."><pre class="notranslate"><code class="notranslate">Hello from static world.
</code></pre></div>
<p dir="auto"><code class="notranslate">$flask run</code></p>
<p dir="auto">From another terminal run:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="[root@vis cli]$ curl -v 'http://127.0.0.1:5000/hello'
*   Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
&gt; GET /hello HTTP/1.1
&gt; Host: 127.0.0.1:5000
&gt; User-Agent: curl/7.81.0
&gt; Accept: */*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Server: Werkzeug/2.2.2 Python/3.10.4
&lt; Date: Wed, 10 Aug 2022 08:05:18 GMT
&lt; Content-Type: text/html; charset=utf-8
&lt; Content-Length: 25
&lt; Connection: close
&lt; 
* Closing connection 0
Hello from a flask route!
[root@vis cli]$ curl -v 'http://127.0.0.1:5000/static/hello'
*   Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
&gt; GET /static/hello HTTP/1.1
&gt; Host: 127.0.0.1:5000
&gt; User-Agent: curl/7.81.0
&gt; Accept: */*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Server: Werkzeug/2.2.2 Python/3.10.4
&lt; Date: Wed, 10 Aug 2022 08:05:22 GMT
&lt; Content-Disposition: inline; filename=hello
&lt; Content-Type: application/octet-stream
&lt; Content-Length: 25
&lt; Last-Modified: Wed, 10 Aug 2022 08:00:56 GMT
&lt; Cache-Control: no-cache
&lt; Date: Wed, 10 Aug 2022 08:05:22 GMT
&lt; Connection: close
&lt; 
Hello from static world.
* Closing connection 0"><pre class="notranslate"><code class="notranslate">[root@vis cli]$ curl -v 'http://127.0.0.1:5000/hello'
*   Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
&gt; GET /hello HTTP/1.1
&gt; Host: 127.0.0.1:5000
&gt; User-Agent: curl/7.81.0
&gt; Accept: */*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Server: Werkzeug/2.2.2 Python/3.10.4
&lt; Date: Wed, 10 Aug 2022 08:05:18 GMT
&lt; Content-Type: text/html; charset=utf-8
&lt; Content-Length: 25
&lt; Connection: close
&lt; 
* Closing connection 0
Hello from a flask route!
[root@vis cli]$ curl -v 'http://127.0.0.1:5000/static/hello'
*   Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
&gt; GET /static/hello HTTP/1.1
&gt; Host: 127.0.0.1:5000
&gt; User-Agent: curl/7.81.0
&gt; Accept: */*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Server: Werkzeug/2.2.2 Python/3.10.4
&lt; Date: Wed, 10 Aug 2022 08:05:22 GMT
&lt; Content-Disposition: inline; filename=hello
&lt; Content-Type: application/octet-stream
&lt; Content-Length: 25
&lt; Last-Modified: Wed, 10 Aug 2022 08:00:56 GMT
&lt; Cache-Control: no-cache
&lt; Date: Wed, 10 Aug 2022 08:05:22 GMT
&lt; Connection: close
&lt; 
Hello from static world.
* Closing connection 0
</code></pre></div>
<p dir="auto">The -v turns on curl's verbose mode so that the request and response headers are printed.</p>
<p dir="auto">Note how the headers of the static route have two Date fields, whereas the headers of the dynamic route only have one.</p>
<p dir="auto">I dove into this some more and it seems that here's what is happening:</p>
<p dir="auto">The http package always adds the date header, regardless of if it's a dynamic or static route.<br>
/usr/lib/python3.10/http/server.py:493<br>
<code class="notranslate">        self.send_header('Date', self.date_time_string())</code></p>
<p dir="auto">The werkzeug package adds it again for a static request:<br>
/usr/local/lib/python3.10/dist-packages/werkzeug/wrappers/response.py:802<br>
<code class="notranslate">                self.headers["Date"] = http_date()</code></p>
<p dir="auto">Obviously we should be doubling up on headers, right?  What do you believe is the best fix for this?</p>