Skip to main content

Lib/http/ (part 2)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/http/server.py

This annotation covers the server-side HTTP request handler. See lib_http_detail for http.client.HTTPConnection, HTTPResponse, and http.cookies.

Map

LinesSymbolRole
1-120HTTPServerTCPServer subclass that binds a socket and serves requests
121-350BaseHTTPRequestHandlerParse request line and headers; dispatch to do_* methods
351-560SimpleHTTPRequestHandlerServe files from the local filesystem
561-700CGIHTTPRequestHandlerExecute CGI scripts via subprocess
701-1000send_response / send_header / end_headersWrite HTTP response headers

Reading

BaseHTTPRequestHandler

# CPython: Lib/http/server.py:380 BaseHTTPRequestHandler
class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
"""HTTP request handler.

Subclass and override do_GET, do_POST, etc. to handle specific methods.
"""
def handle_one_request(self):
"""Handle a single HTTP request."""
try:
self.raw_requestline = self.rfile.readline(65537)
except TimeoutError:
self.close_connection = True
return
if not self.parse_request():
return
mname = 'do_' + self.command # e.g. 'do_GET', 'do_POST'
if not hasattr(self, mname):
self.send_error(
HTTPStatus.NOT_IMPLEMENTED,
f"Unsupported method ({self.command!r})")
return
method = getattr(self, mname)
method()
self.wfile.flush()

parse_request splits the request line into self.command, self.path, and self.request_version. Headers are parsed into self.headers (a http.client.HTTPMessage object).

send_response

# CPython: Lib/http/server.py:480 send_response
def send_response(self, code, message=None):
"""Add the response header to the headers buffer and log the response code."""
if self.request_version != 'HTTP/0.9':
if message is None:
if code in self.responses:
message = self.responses[code][0]
else:
message = ''
self._headers_buffer.append(("%s %d %s\r\n" %
(self.protocol_version, code, message)).encode("latin-1"))
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())

def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
self._headers_buffer.append(b"\r\n")
self.flush_headers()

send_response + zero or more send_header calls + end_headers writes the HTTP response head. The body is then written directly to self.wfile.

SimpleHTTPRequestHandler

# CPython: Lib/http/server.py:670 SimpleHTTPRequestHandler
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
"""Serve files from the current directory and below."""

def do_GET(self):
f = self.send_head()
if f:
try:
self.copyfile(f, self.wfile)
finally:
f.close()

def send_head(self):
path = self.translate_path(self.path)
if os.path.isdir(path):
return self.list_directory(path)
ctype = self.guess_type(path)
f = open(path, 'rb')
self.send_response(HTTPStatus.OK)
self.send_header("Content-type", ctype)
self.send_header("Content-Length", str(os.fstat(f.fileno()).st_size))
self.end_headers()
return f

SimpleHTTPRequestHandler is the backend for python -m http.server. translate_path maps URL paths to filesystem paths, stripping .. components to prevent directory traversal attacks.

Range requests in SimpleHTTPRequestHandler

# CPython: Lib/http/server.py:740 send_head (range)
# If Range header is present, serve partial content (HTTP 206)
if 'Range' in self.headers:
ranges = self.headers['Range'] # e.g. 'bytes=0-1023'
...
self.send_response(HTTPStatus.PARTIAL_CONTENT)
self.send_header('Content-Range', ...)

HTTP Range requests allow resumable downloads. SimpleHTTPRequestHandler supports single-range requests from Python 3.11+.

gopy notes

http.server is pure Python over socketserver.TCPServer. HTTPServer.socket is objects.Socket. BaseHTTPRequestHandler.rfile/wfile are io.BufferedRWPair wrappers. SimpleHTTPRequestHandler.copyfile uses shutil.copyfileobj which calls io.RawIOBase.read.