The built-in http.server
Python includes a simple HTTP server in the standard library.
Great for:
- local demos
- serving static files
Not ideal for production.
Serve a folder
If you run:
python -m http.server 8000Then open:
http://localhost:8000
Custom handler
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"Hello from Python HTTP server")
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 8000), Handler)
print("Serving on http://127.0.0.1:8000")
server.serve_forever()What’s happening?
do_GEThandles HTTP GET requestssend_responsesets status code- headers must be sent before the body
flowchart TD
A["GET /hello.txt"] --> B{"does it exist under the CWD?"}
B -->|yes| C["200 + the file, Content-Type guessed from the suffix"]
B -->|no| D["404 File not found"]
E["GET /"] --> F["200 + a GENERATED directory listing"]
G["POST anything"] --> H["501 Unsupported method"]
C --> I["the document root is os.getcwd()"]
F --> I
I --> J["so it must never be exposed beyond localhost"]
Requests and responses
HTTP is a conversation made of text. The client sends a request: a method (GET, POST), a path (/hello), headers and sometimes a body. The server sends back a response: a status code (200 for success, 404 for not found), headers, and a body. BaseHTTPRequestHandler parses the incoming request for you and calls a method named after the HTTP verb: do_GET for GET, do_POST for POST, and so on. Your job is to write the response in the right order: status line, headers, blank line, body.
Reading the custom handler
In the example, send_response(200) writes the status line. send_header("Content-Type", "text/plain") tells the browser how to read the body. end_headers() writes the blank line that separates headers from the body, and self.wfile.write(...) sends the body. The body must be bytes, which is why the string is written as b"...". If you skip end_headers() the browser sees a broken response.
HTTPServer(("127.0.0.1", 8000), Handler) binds to the local machine only, which is what you want for experiments. serve_forever() then loops, handling one request at a time. Stop it with Ctrl+C.
Reacting to the path
Inside do_GET, self.path holds what the client asked for. This example returns JSON for /api and a plain message otherwise:
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api":
body = json.dumps({"status": "ok"}).encode("utf-8")
content_type = "application/json"
code = 200
else:
body = b"Not found"
content_type = "text/plain"
code = 404
self.send_response(code)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()Visit http://127.0.0.1:8000/api to see the JSON and any other path to see the 404. Sending Content-Length lets the client know when the body ends.
Common mistakes
- Writing a
strtowfile. It needs bytes; use.encode("utf-8"). - Sending the body before the headers. Headers always come first and end with
end_headers(). - Serving the wrong folder.
python -m http.servershares the current directory, including files you did not mean to expose. - Binding to
0.0.0.0on a shared network and exposing it to others. - Forgetting other methods. A handler with only
do_GETanswers a POST with an error.
When to use it
Use http.server for local previews of static sites, quick demos, and learning how HTTP works. For real applications, use a framework such as Flask or FastAPI behind a production server.
Check yourself
`python -m http.server` serves which directory?
Whatever folder you started it in. Run it in the wrong place and it will serve your source, your `.env` and anything else that is there.
Show answer
B — The current working directory — Whatever folder you started it in. Run it in the wrong place and it will serve your source, your `.env` and anything else that is there.
What does a POST to the built-in handler return?
Verified. `SimpleHTTPRequestHandler` implements only GET and HEAD, so it is a file viewer rather than a framework — to handle POST you subclass it and write `do_POST`.
Show answer
D — 501 Unsupported method — Verified. `SimpleHTTPRequestHandler` implements only GET and HEAD, so it is a file viewer rather than a framework — to handle POST you subclass it and write `do_POST`.
You request a path that is a directory. What comes back?
Measured: 200 with a 299-byte generated listing. If an `index.html` exists it is served instead, which is what makes this useful for previewing a static site.
Show answer
C — 200 with a generated listing (or index.html if present) — Measured: 200 with a 299-byte generated listing. If an `index.html` exists it is served instead, which is what makes this useful for previewing a static site.
Why should this server never be exposed beyond localhost?
It also binds all interfaces by default, so on a shared network the whole directory is readable. Bind it to 127.0.0.1 and treat it as a local preview tool.
Show answer
B — No authentication, no TLS, and its root is whatever directory you happened to be in — It also binds all interfaces by default, so on a shared network the whole directory is readable. Bind it to 127.0.0.1 and treat it as a local preview tool.
🧪 Try It Yourself
Exercise 1 – HTTP GET with urllib
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Socket Connection
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Resolve a Hostname
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.