How to Mock HTTP 304 Responses with If-None-Match in Python
Spin up a local HTTP server that returns a 304 Not Modified when a request carries a matching ETag, useful for testing cache behavior.
Python code
47 linesfrom http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
import urllib.request
ETAG = '"abc123"'
BODY = b'{"status": "ok"}'
class MockServer(BaseHTTPRequestHandler):
def do_GET(self):
if self.headers.get('If-None-Match') == ETAG:
self.send_response(304)
self.end_headers()
else:
self.send_response(200)
self.send_header('ETag', ETAG)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(BODY)))
self.end_headers()
self.wfile.write(BODY)
def log_message(self, format, *args):
pass
def run_mock_server():
server = HTTPServer(('127.0.0.1', 8080), MockServer)
server_thread = Thread(target=server.serve_forever, daemon=True)
server_thread.start()
return server
if __name__ == "__main__":
server = run_mock_server()
# First request: no If-None-Match header
req = urllib.request.Request('http://127.0.0.1:8080/resource')
with urllib.request.urlopen(req) as resp:
print(f"First status: {resp.status}, body: {resp.read().decode()}")
etag = resp.headers.get('ETag')
# Second request: send If-None-Match
req2 = urllib.request.Request('http://127.0.0.1:8080/resource', headers={'If-None-Match': etag})
try:
with urllib.request.urlopen(req2) as resp:
print(f"Second status: {resp.status}")
except urllib.error.HTTPError as e:
print(f"Second status: {e.code}")
server.shutdown()
Output
First status: 200, body: {"status": "ok"}
Second status: 304
How it works
This script uses http.server to create a lightweight mock server that implements conditional GET requests. The server checks the If-None-Match header sent by the client and returns a 304 status with no body when it matches the stored ETag. Otherwise, it returns a 200 with the JSON payload and an ETag header. The threading is used to run the server in the background while the main thread makes the requests. This pattern is useful for testing clients that rely on HTTP cache validators.
Common mistakes
- Forgetting that `urllib.error.HTTPError` is raised for 304 responses, so you must catch it to read the status.
- Not including the `Content-Length` header, which can cause the client to hang waiting for more data.
- Sending the `If-None-Match` header on every request, which would incorrectly trigger 304 responses.
- Assuming the server handles HEAD requests when only implementing do_GET.
Variations
- Use `socketserver.ThreadingMixIn` to handle concurrent requests.
- Add `If-Modified-Since` support with `Last-Modified` headers.
Real-world use cases
- Testing client cache behavior against a known ETag when building an API consumer.
- Simulating a CDN or proxy that returns 304 responses during automated integration tests.
- Verifying that a download or sync tool correctly handles cache validation without re-downloading data.
Sponsored
More from API design & gRPC
- Build a Bulk Array POST Mock Server in Python medium
- Build a Mock REST API with PUT and GET in Python medium
- Convert Protobuf to JSON and Dict in Python easy
- Create a Data Helper in Python for gRPC-style APIs easy
- Format data in Python using dataclasses like gRPC messages easy
- Generate an OpenAPI Spec from Mock Routes in Python easy
Keep learning
Related tutorials and quizzes for this topic.