class HTTPRequest: # parse(rawdata) → method, path, headers, body, queryparams
~/http_server.pyA complete HTTP/1.1 server built from scratch using only socket (no external libraries). The server implements:
1. HTTP Request Parsing (HTTPRequest.parse)
Parses raw bytes into method, path, query params, headers dict, and body. Supports percent-decoded query strings and Content-Length body extraction.
2. HTTP Response Builder (HTTPResponse.build)
Builds proper HTTP/1.1 response bytes with status line, headers (Content-Type, Content-Length, Server, Date, Connection), and body.
3. Router (Router / Route)
Pattern-based routing with named URL parameters (/users/<id>). Supports decorator-style registration (@server.get("/path")).
4. Static File Server (StaticFileServer)
Serves files from a root directory with:
- Path traversal protection (normalizes paths, checks startswith(root_dir))
- If-Modified-Since / 304 Not Modified support
- MIME type detection via mimetypes
- index.html for directory paths
- Method validation (only GET/HEAD)
5. Keep-Alive Connection Handling
- HTTP/1.1 defaults to keep-alive; closes only on Connection: close
- HTTP/1.0 defaults to close; keeps alive only on Connection: keep-alive
- Multiple requests over a single connection work
6. Error Handling
- 400 for malformed requests
- 404 for unknown routes / missing files
- 405 for wrong methods on existing static files
- 403 for path traversal
- 500 for unhandled exceptions
- Well-formed error response bodies
# Core architecture (key classes)
class HTTPRequest: # parse(raw_data) → method, path, headers, body, query_params
class HTTPResponse: # build() → bytes; set_header(), set_content_type()
class Route: # match(method, path) → (handler, params)
class Router: # dispatch(request) → response; add_route() + decorators
class StaticFileServer: # serve(request) → response with file or error
class HTTPServer: # start() → accept loop; _handle_client() per connection
Example handlers:
server = HTTPServer(static_dir="./static")
@server.get("/")
def index(request):
return HTTPResponse(200, "<h1>Hello</h1>", {"Content-Type": "text/html"})
@server.get("/api/info")
def api_info(request):
import json
return HTTPResponse(200, json.dumps({"status": "ok"}),
{"Content-Type": "application/json"})
server.start()
All **37 tests pass**. The test suite (`test_server.py`) covers: | Category | Tests | What's Verified | |---|---|---| | **Request Parsing** (12) | `test_simple_get`, `test_get_with_query`, `test_get_with_encoded_query`, `test_post_with_body`, `test_post_without_body`, `test_http10/11_keep_alive_*`, `test_invalid_request_line`, `test_empty_request`, `test_multiple_headers_same_name` | Method/path/header extraction, query string decoding, HTTP/1.0 vs 1.1 keep-alive semantics, error handling | | **Response Building** (5) | `test_basic_response`, `test_404_response`, `test_custom_headers`, `test_from_status`, `test_304_no_body` | Status line format, header injection, status texts, empty body for 304 | | **Functional Server** (20) | `test_get_index`, `test_404`, `test_echo_with_query`, `test_api_info`, `test_static_file`, `test_static_nested_file`, `test_static_index_in_subdir`, `test_path_traversal_denied`, `test_method_not_allowed`, `test_post_to_data_endpoint`, `test_get_data_endpoint`, `test_server_header`, `test_content_type_for_html/txt`, `test_keep_alive_*` (4), `test_multiple_requests_single_connection`, `test_bad_request` | End-to-end socket communication, all status codes, static serving, path traversal blocking, keep-alive pipelining, connection close semantics | **Edge cases verified:** - **Path traversal**: `GET /../../../etc/passwd` → **403 Forbidden** - **Malformed requests**: `GET invalid-path HTTP/1.1` (no leading `/`) → **400 Bad Request** - **Method on static file**: `POST /test.txt` → **405 Method Not Allowed** - **HTTP/1.0 keep-alive**: explicit `Connection: keep-alive` → `keep-alive` in response; default → `close` - **HTTP/1.1 keep-alive**: default → `keep-alive`; `Connection: close` → `close` - **Pipelined requests**: Two GET requests on same socket → both responses received - **Empty body POST**: `POST /data` without body → parses successfully - **If-Modified-Since**: Static files with matching mtime → **304 Not Modified** ---
{"model": "claude-3.5-sonnet", "problem_class": "net-http-server-from-scratch", "result": "passed", "tests": 37}~/http_server.pyA complete HTTP/1.1 server built from scratch using only socket (no external libraries). The server implements:
1. HTTP Request Parsing (HTTPRequest.parse)
Parses raw bytes into method, path, query params, headers dict, and body. Supports percent-decoded query strings and Content-Length body extraction.
2. HTTP Response Builder (HTTPResponse.build)
Builds proper HTTP/1.1 response bytes with status line, headers (Content-Type, Content-Length, Server, Date, Connection), and body.
3. Router (Router / Route)
Pattern-based routing with named URL parameters (/users/<id>). Supports decorator-style registration (@server.get("/path")).
4. Static File Server (StaticFileServer)
Serves files from a root directory with:
- Path traversal protection (normalizes paths, checks startswith(root_dir))
- If-Modified-Since / 304 Not Modified support
- MIME type detection via mimetypes
- index.html for directory paths
- Method validation (only GET/HEAD)
5. Keep-Alive Connection Handling
- HTTP/1.1 defaults to keep-alive; closes only on Connection: close
- HTTP/1.0 defaults to close; keeps alive only on Connection: keep-alive
- Multiple requests over a single connection work
6. Error Handling
- 400 for malformed requests
- 404 for unknown routes / missing files
- 405 for wrong methods on existing static files
- 403 for path traversal
- 500 for unhandled exceptions
- Well-formed error response bodies
# Core architecture (key classes)
class HTTPRequest: # parse(raw_data) → method, path, headers, body, query_params
class HTTPResponse: # build() → bytes; set_header(), set_content_type()
class Route: # match(method, path) → (handler, params)
class Router: # dispatch(request) → response; add_route() + decorators
class StaticFileServer: # serve(request) → response with file or error
class HTTPServer: # start() → accept loop; _handle_client() per connection
Example handlers:
server = HTTPServer(static_dir="./static")
@server.get("/")
def index(request):
return HTTPResponse(200, "<h1>Hello</h1>", {"Content-Type": "text/html"})
@server.get("/api/info")
def api_info(request):
import json
return HTTPResponse(200, json.dumps({"status": "ok"}),
{"Content-Type": "application/json"})
server.start()
All **37 tests pass**. The test suite (`test_server.py`) covers: | Category | Tests | What's Verified | |---|---|---| | **Request Parsing** (12) | `test_simple_get`, `test_get_with_query`, `test_get_with_encoded_query`, `test_post_with_body`, `test_post_without_body`, `test_http10/11_keep_alive_*`, `test_invalid_request_line`, `test_empty_request`, `test_multiple_headers_same_name` | Method/path/header extraction, query string decoding, HTTP/1.0 vs 1.1 keep-alive semantics, error handling | | **Response Building** (5) | `test_basic_response`, `test_404_response`, `test_custom_headers`, `test_from_status`, `test_304_no_body` | Status line format, header injection, status texts, empty body for 304 | | **Functional Server** (20) | `test_get_index`, `test_404`, `test_echo_with_query`, `test_api_info`, `test_static_file`, `test_static_nested_file`, `test_static_index_in_subdir`, `test_path_traversal_denied`, `test_method_not_allowed`, `test_post_to_data_endpoint`, `test_get_data_endpoint`, `test_server_header`, `test_content_type_for_html/txt`, `test_keep_alive_*` (4), `test_multiple_requests_single_connection`, `test_bad_request` | End-to-end socket communication, all status codes, static serving, path traversal blocking, keep-alive pipelining, connection close semantics | **Edge cases verified:** - **Path traversal**: `GET /../../../etc/passwd` → **403 Forbidden** - **Malformed requests**: `GET invalid-path HTTP/1.1` (no leading `/`) → **400 Bad Request** - **Method on static file**: `POST /test.txt` → **405 Method Not Allowed** - **HTTP/1.0 keep-alive**: explicit `Connection: keep-alive` → `keep-alive` in response; default → `close` - **HTTP/1.1 keep-alive**: default → `keep-alive`; `Connection: close` → `close` - **Pipelined requests**: Two GET requests on same socket → both responses received - **Empty body POST**: `POST /data` without body → parses successfully - **If-Modified-Since**: Static files with matching mtime → **304 Not Modified** ---
{"model": "claude-3.5-sonnet", "problem_class": "net-http-server-from-scratch", "result": "passed", "tests": 37}