Last-Modified
and If-Modified-Since
Headersby Christoph Schiessl on Python and FastAPI
In one of my previous articles, I explained the basics of ETag
-based caching. Today, I want to introduce you to timestamp-based caching as an alternative, which relies on the Last-Modified
response header and the If-Modified-Since
request header. Therefore, similar to HTTP caching controlled by the ETag
and If-None-Match
headers, both client and server-side support are required.
Last-Modified
response headerImagine having a _site
directory with a single index.html
file and a simple Python application that uses FastAPI's StaticFiles
feature to serve this directory.
import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/", StaticFiles(directory="_site", html=True), name="_site")
uvicorn.run(app=app, port=3000)
$ tree --noreport .
.
├── app.py
└── _site
└── index.html
If you start your FastAPI app with python app.py
and request the index.html
file, you'll see the Last-Modified
header in the HTTP response:
$ http GET http://localhost:3000/index.html
HTTP/1.1 200 OK
content-length: 62
content-type: text/html; charset=utf-8
date: Sun, 21 Apr 2024 16:22:10 GMT
etag: "d5eabc651bf2490653431623615b1546"
last-modified: Sun, 21 Apr 2024 14:15:02 GMT
server: uvicorn
<!doctype html><meta charset=utf-8><title>/index.html</title>
In the case of FastAPI's StaticFiles
, the timestamp comes from the modification timestamp of the file on disk. So, if you touch index.html
or modify the file in some other way, the modification timestamp and the value of the header change. However, the timestamp could also come from a different source, such as a database. Please note the timestamp's format: It must be formatted strictly according to the time format defined in RFC 5322. If you ever have to work with RFC 5332-formatted dates, consider using the email.utils
module, which is the library that FastAPI uses internally.
If-Modified-Since
request headerThe opposite side of the Last-Modified
header is the If-Modified-Since
request header. This header is set by the HTTP client (e.g., your browser) and must adhere to the same formatting. If the request header is present and the HTTP server supports timestamp-based caching, then the following process is followed for incoming requests:
If-Modified-Since
header into a datetime
object that supports comparisons with other datetime
objects.index.html
), determine the datetime
for the Last-Modified
response header.Last-Modified
is less than or equal to If-Modified-Since
, the server responds with status 304 Not Modified
.200 OK
.The whole point of all of this is to save bandwidth by not transferring payloads that the client has already cached. The trick is that 304 Not Modified
responses never contain a body, but 200 OK
responses do.
$ # 200 OK if 'Last-Modified' > 'If-Modified-Since' ...
$ http GET http://localhost:3000/index.html 'If-Modified-Since: "Sun, 21 Apr 2024 14:15:01 GMT"'
HTTP/1.1 200 OK
content-length: 62
content-type: text/html; charset=utf-8
date: Sun, 21 Apr 2024 16:24:23 GMT
etag: "d5eabc651bf2490653431623615b1546"
last-modified: Sun, 21 Apr 2024 14:15:02 GMT
server: uvicorn
<!doctype html><meta charset=utf-8><title>/index.html</title>
$ # 304 Not Modified if 'Last-Modified' == 'If-Modified-Since' ...
$ http GET http://localhost:3000/index.html 'If-Modified-Since: "Sun, 21 Apr 2024 14:15:02 GMT"'
HTTP/1.1 304 Not Modified
date: Sun, 21 Apr 2024 16:24:08 GMT
etag: "d5eabc651bf2490653431623615b1546"
server: uvicorn
$ # 304 Not Modified if 'Last-Modified' < 'If-Modified-Since' ...
$ http GET http://localhost:3000/index.html 'If-Modified-Since: "Sun, 21 Apr 2024 14:15:03 GMT"'
HTTP/1.1 304 Not Modified
date: Sun, 21 Apr 2024 16:24:08 GMT
etag: "d5eabc651bf2490653431623615b1546"
server: uvicorn
This is everything for today. Thank you for reading, and see you next time!
I send two weekly emails on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL. No spam. Unscubscribe at any time.
ETag
and If-None-Match
Headers
Learn how to use ETag
and If-None-Match
headers to limit your web application's resource consumption by preventing data retransfers.
By Christoph Schiessl on Python and FastAPI
undefined
Isn’t What You Think It Is
In this informative article, you learn that undefined
is not a keyword in JavaScript, and it's up to you to ensure it refers to the value its name suggests.
By Christoph Schiessl on JavaScript
StaticFiles
Learn how to serve a static site using FastAPI. Perfect for locally testing statically generated websites, for instance, with httpie
.
By Christoph Schiessl on Python and FastAPI