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.
Python code
29 linesfrom 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
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
- Use `Access-Control-Allow-Origin` = request origin from the `Origin` header for a dynamic allowlist
- 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
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.