whakerpy.httpd module
HTTPD package
Scope
The httpd package implements the HTTP layer of WhakerPy.
It provides:
- a local HTTP server (based on
http.server) - a WSGI-compatible execution path
- a unified policy mechanism for request filtering and response post-processing
This package does not implement application logic. It implements transport, routing, and security policies.
Design principles
1. Strict separation of responsibilities
| Concern | Location |
|---|---|
| HTTP protocol handling | HTTPDHandler, BaseHTTPDServer |
| Web application data | WebSiteData (outside this package) |
| HTML generation | BaseResponseRecipe (outside this package) |
| Security / routing policies | HTTPDPolicy, BlackListAgent, BlackListPath, BanList, SignedURL |
| Static file access control | FileAccessChecker, UnixPermissions |
| Utilities / helpers | HTTPDHandlerUtils, HTTPDStatus |
No class mixes data, business logic, and transport policy.
2. One decision point
All request filtering and security decisions are centralized in one class:
HTTPD Policy. Neither handlers nor responses implement:
- blacklist checks
- signed URL verification
- security decisions
They delegate once to the policy.
3. Identical behavior in local HTTPD and WSGI
The same policy object is used by:
HTTPDHandler(local server)WSGIApplication(uWSGI / production)
This guarantees:
- same access rules
- same error responses
- same security semantics
Main classes and relationships
BaseHTTPDServer
Role:
- orchestrates request handling
- stores baked pages
- owns the policy instance
Responsibilities:
- page creation (
_create_pages) - page baking (
page_bakery) - policy configuration (
configure) - delegation to
HTTPDPolicy
It does not:
- inspect requests
- apply security rules
- know blacklist or signed URL details
HTTPDHandler
Role:
- receive HTTP requests
- normalize paths and queries
- delegate decisions to the server policy
Flow (GET / POST):
- extract the query string, then normalize the path
- delegate the request to the server policy (
policy_check) - serve a static file when it exists, else bake the dynamic page
- send the response
A GET carries its data in the query string exactly the way a POST
carries it in its body: both are parsed into the same events dictionary
(see parse_query_string). Because a GET is a navigation and not a form
submission, query parameters the recipe does not handle (status 205) do
not prevent the page from being served: the baked page is returned with a
200.
No security logic exists here.
HTTPDPolicy
Role:
- central decision engine
- transport-level security and routing policy
Current policies implemented:
- temporary ban of the client addresses that met a forbidden URL path
- blacklist of user agents
- blacklist of URL paths
- signed URLs (time-limited access)
Responsibilities:
- accept or reject a request
- generate standard rejection responses
- post-process outgoing HTML (
finalize_html)
This is the only place where:
- blacklist logic exists
- signed URL verification exists
- HTML rewriting for signed URLs exists
Blacklist, BlackListAgent, BlackListPath
Role:
- store and match forbidden entries
Blacklist stores the entries and reads them from a dict or from a file.
It is the base class of the two blacklists of the package. Each subclass
tells what an entry is tested against, and with which rule:
| Class | Tested against | Rule |
|---|---|---|
BlackListAgent |
the User-Agent header |
the entry is a substring of the header |
BlackListPath |
the URL path | the path starts with the entry, or ends with it |
The two lists are configured by two distinct keys of the "WhakerPy"
section: "blacklist" holds the user agents, "blacklist_path" holds the
URL paths. The "blacklist" key is tested against the User-Agent only:
a path fragment written there has no effect any more, and has to be moved
to "blacklist_path".
An entry of "blacklist_path" is written the way it appears in the path:
/wp-login.php, /.git/, .php. The middle of a path is never tested, so
an entry rejects what it names and nothing else.
Characteristics:
- configuration-based
- supports file-based or dict-based configuration
- pure matching logic
It does not:
- decide responses
- know HTTP semantics
BanList
Role:
- reject, during a short duration, the addresses that met a forbidden path
Characteristics:
- an address is recorded by
HTTPDPolicywhenBlackListPathmatched - the comparison is exact: an address is never a part of another one
- the entries are stored in a file, shared by the processes of a server
- an entry older than the duration is ignored, and dropped when the file is written
The "banlist" key of the "WhakerPy" section holds the path of that file
and the duration in seconds:
"banlist": {"file": "banned.txt", "duration": 7200}
The path is used as it is written, so a relative one depends on the working directory of the server. This list is the only state the package keeps while a server runs, and a site has two conditions to check before it enables the key:
REMOTE_ADDRmust carry the address of the visitor and not the one of a relay of the hosting, otherwise a single ban reaches every reader- the duration stays short, because a campus or a mobile operator shares one address between many readers
It does not:
- decide responses
- know HTTP semantics
SignedURL
Role:
- stateless signed URL mechanism
Characteristics:
- HMAC-based
- time-limited (TTL)
- no cookies
- no server-side state
- standard library only
It does not:
- know request context
- know HTML
- apply policy decisions
HTTPDHandlerUtils
Role:
- shared helper utilities
Includes:
- response helpers
- HTML error generation
- content baking helpers
- MIME utilities
- query string parsing (
parse_query_string, shared by GET and POST)
No policy logic here.
FileAccessChecker / UnixPermissions
Role:
- static file access control, before any file is served
Characteristics:
UnixPermissionsreads the owner / group / others read bits of a fileFileAccessCheckeranswersread_allowed(who)for a given file- used by
HTTPDHandlerUtils.static_contentto refuse unreadable files
It does not:
- know HTTP semantics
- apply routing or blacklist rules
HTTPDStatus
Role:
- HTTP status abstraction
- generation of consistent HTML error pages
Request lifecycle (simplified)
HTTP request ↓ HTTPDHandler / WSGIApplication ↓ HTTPDPolicy.check(path, query, headers) ↓ ├─ rejected → standard HTML error response └─ allowed → content resolution ├─ static file └─ dynamic page (bakery) ↓ HTTPDPolicy.finalize_html (HTML only) ↓ response sent
Why policies are not in Responses
Responses:
- generate HTML content
- express application UI logic
They must not:
- know routing rules
- know security constraints
- modify URLs for transport reasons
Signed URLs, blacklist, and access rules are transport policies, not UI logic.
Extensibility
New policies can be added by extending HTTPDPolicy, for example:
- rate limiting
- authentication
- CSP headers
- referer rules
Without modifying:
- handlers
- responses
- applications
Summary
httpdimplements the HTTP engine- policies are centralized, explicit, and testable
- no hidden coupling between UI and security
- same behavior in local and production environments
- no cookies, no sessions, no external dependencies
- one state only, on disk: the temporary list of banned addresses
This package is the boundary between applications and the web.
