Lesson 2 - Step 3 of 5

Handling POST Requests

Learn to read request bodies from POST requests.

Reading the Request Body

POST requests carry data in the body. To read it, you need to:

  1. Check the Content-Length header to know how many bytes to read
  2. Read that many bytes from self.rfile
python
from http.server import HTTPServer, BaseHTTPRequestHandler


class PostHandler(BaseHTTPRequestHandler):

    def do_POST(self):
        # Get the content length from headers
        content_length = int(self.headers.get("Content-Length", 0))

        # Read the body
        body = self.rfile.read(content_length)

        # Decode bytes to string
        body_str = body.decode("utf-8")

        print(f"Received: {body_str}")

        # Send response
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(f"Received {len(body)} bytes".encode())

Test with curl

bash
# Send a POST request with data
curl -X POST http://localhost:8080 -d "Hello, Server!"

# Output: Received 14 bytes

Handling Missing Content-Length

Always handle the case where Content-Length might be missing:

python
def do_POST(self):
    # Safely get content length
    content_length = self.headers.get("Content-Length")

    if content_length is None:
        self.send_response(400)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(b"Content-Length header required")
        return

    content_length = int(content_length)

    if content_length == 0:
        self.send_response(400)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(b"Empty body not allowed")
        return

    # Read the body
    body = self.rfile.read(content_length)
    # ... process body

Reading Headers

Access any request header using self.headers:

python
def do_POST(self):
    # Get specific headers
    content_type = self.headers.get("Content-Type")
    authorization = self.headers.get("Authorization")
    custom_header = self.headers.get("X-Custom-Header")

    # Check content type
    if content_type != "application/json":
        self.send_response(415)  # Unsupported Media Type
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(b"Content-Type must be application/json")
        return

    # ... continue processing
Header Names Are Case-Insensitive

HTTP headers are case-insensitive, so Content-Type, content-type, and CONTENT-TYPE are all equivalent. Python's self.headers.get() handles this for you.

A Complete POST Handler

python
def do_POST(self):
    """Handle POST request with validation."""
    # Check content type
    content_type = self.headers.get("Content-Type", "")
    if "application/json" not in content_type:
        self.send_error(415, "Content-Type must be application/json")
        return

    # Get content length
    try:
        content_length = int(self.headers.get("Content-Length", 0))
    except ValueError:
        self.send_error(400, "Invalid Content-Length")
        return

    if content_length == 0:
        self.send_error(400, "Empty body")
        return

    # Read body
    body = self.rfile.read(content_length)

    # Process and respond
    self.send_response(200)
    self.send_header("Content-Type", "application/json")
    self.end_headers()
    self.wfile.write(b'{"status": "ok"}')