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.

Easy Python 3.6+ Aug 9, 2026 API design & gRPC 11 views 0 copies

Python code

47 lines
Python 3.6+
from 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

stdout
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

  1. Use `socketserver.ThreadingMixIn` to handle concurrent requests.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from API design & gRPC

Related tutorials and quizzes for this topic.