How to handle CORS preflight OPTIONS requests in Python

Create a mock HTTP server with a CORS preflight OPTIONS handler that returns the correct headers for browser-based API requests.

Easy Python 3.9+ Aug 9, 2026 API design & gRPC 13 views 0 copies

Python code

29 lines
Python 3.9+
from http.server import BaseHTTPRequestHandler, HTTPServer

class CORSRequestHandler(BaseHTTPRequestHandler):
    def _send_cors_headers(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")

    def do_OPTIONS(self):
        self.send_response(204)
        self._send_cors_headers()
        self.send_header("Content-Length", "0")
        self.end_headers()
        print(f"OPTIONS request received: {self.path}")

    def do_GET(self):
        self.send_response(200)
        self._send_cors_headers()
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"status": "ok"}')

if __name__ == "__main__":
    server = HTTPServer(("localhost", 8080), CORSRequestHandler)
    print("Mock CORS server running on http://localhost:8080")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nServer stopped.")

Output

stdout
Mock CORS server running on http://localhost:8080
OPTIONS request received: /api/data

How it works

The do_OPTIONS method is called by BaseHTTPRequestHandler when it receives an HTTP OPTIONS request. You must call send_response(204) first to set the status code and start the response headers. Then _send_cors_headers writes the required Access-Control-Allow-* headers so browsers know which origins, methods, and headers are permitted. The Content-Length: 0 header prevents the server from trying to send a body for a 204 response. end_headers finalizes the header block, and the server then logs the request path to the console for debugging.

Common mistakes

  • Forgetting to call `end_headers()` after sending CORS headers, which leaves the response incomplete
  • Sending a body with a 204 response — browsers reject bodies on 204 and the connection may hang
  • Not including `OPTIONS` in the `Access-Control-Allow-Methods` header, which breaks preflight for PUT/DELETE
  • Hard-coding the `Access-Control-Allow-Origin` to a specific domain when you need to support multiple origins

Variations

  1. Use `Access-Control-Allow-Origin` = request origin from the `Origin` header for a dynamic allowlist
  2. Use a framework like Flask with the `flask-cors` extension for production-ready CORS handling

Real-world use cases

  • Mocking a backend API locally to test frontend JavaScript fetch calls with CORS enabled.
  • Building a lightweight internal tool that serves JSON data to a browser-based dashboard with cross-origin requests.
  • Stubbing a third-party API endpoint during integration tests to verify CORS preflight behavior in a CI pipeline.

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.