This document walks through main.pb — the server's entry point — as a complete, annotated tutorial. By the end you will understand exactly how every library in src/ fits together and how to assemble your own server using the same building blocks.
PureSimpleHTTPServer is a set of independent library modules (*.pbi) that each solve one well-defined problem:
TCP sockets → TcpServer.pbi
HTTP parsing → HttpParser.pbi
HTTP responses → HttpResponse.pbi
Middleware chain → Middleware.pbi ← central architecture (v2.0.0+)
File serving → FileServer.pbi (utility functions for middleware)
Directory listing → DirectoryListing.pbi
Range requests → RangeParser.pbi
MIME types → MimeTypes.pbi
URL rewriting → RewriteEngine.pbi
Logging → Logger.pbi
Embedded assets → EmbeddedAssets.pbi
Configuration → Config.pbi
Auto-TLS → AutoTLS.pbi (v2.2.0+)
Signal handling → SignalHandler.pbi
Windows Service → WindowsService.pbi (v1.6.0+)
Date formatting → DateHelper.pbi
URL decoding → UrlHelper.pbi
Shared constants → Global.pbi
Shared types → Types.pbi
main.pb is the glue. It includes all modules, sets up the middleware chain, configures TLS, and starts the server.
In v2.x, all request handling flows through a middleware chain. There is no single HandleRequest procedure; instead, RunRequest() in Middleware.pbi orchestrates 11 middleware in a fixed order:
Client → TCP → RunRequest() → [chain] → send → free → log
Chain: Rewrite → IndexFile → CleanUrls → SpaFallback → HiddenPath
→ ETag304 → GzipSidecar → GzipCompress → EmbeddedAssets
→ FileServer → DirectoryListing
Each middleware can:
- Pre-process: modify
req\Pathand callCallNext() - Short-circuit: fill
respand return#True - Post-process: call
CallNext()first, then modify the response (e.g., GzipCompress)
RunRequest is the single point of network I/O and memory cleanup. Middleware never call SendNetwork* directly.
LoadDefaults(@g_Config)
ParseCLI(@g_Config)
; Windows: handle --install, --uninstall, --start, --stop, --service
InitRewriteEngine()
LoadGlobalRules(g_Config\RewriteFile)
; Configure logger globals, open log files, write PID file
; Start daily rotation thread, install SIGHUP handler
If g_Config\AutoTlsDomain <> ""
; Start HTTP redirect on port 80, issue certificate, start renewal thread
; Load cert into g_TlsKey/g_TlsCert, set g_TlsEnabled = #True
ElseIf g_Config\TlsCert <> "" And g_Config\TlsKey <> ""
; Read PEM files into g_TlsKey/g_TlsCert, set g_TlsEnabled = #True
EndIf
BuildChain() ; register 11 middleware in order
g_Handler = @RunRequestWrapper() ; bridge to RunRequest with g_Config
StartServer(g_Config\Port) ; blocks until StopServer()
StopCertRenewal() / StopHttpRedirect() ; if auto-tls
RemoveSignalHandlers()
StopDailyRotation()
CloseLogFile() / CloseErrorLog()
DeleteFile(PidFile)
CleanupRewriteEngine()
CloseEmbeddedPack()
RunRequest(connection, raw, *cfg) is the core of the v2.x architecture:
- Parse —
ParseHttpRequest(raw, req). Bad request → 400. - Method check — Only GET proceeds. Others → 400.
- Init — Empty
ResponseBufferandMiddlewareContext. - Run chain —
CallNext(@req, @resp, @mCtx)starts the middleware chain. - Send — If
resp\Handled, send headers viaBuildResponseHeadersand body viaPlainWriter. - Fallback — If no middleware handled, send 404.
- Free —
FreeMemory(resp\Body)if allocated. - Log —
LogAccess(...)unconditionally.
Main thread Worker threads (one per request)
───────────────────────────────── ──────────────────────────────────
CreateServerWithTLS() (optional TLS)
Repeat
drain g_CloseList ← CloseNetworkConnection here only
event = NetworkServerEvent()
Case Data:
accum(key) += received bytes
if \r\n\r\n found:
*td = AllocateStructure(ThreadData)
CreateThread(@ConnectionThread(), *td)
ConnectionThread:
g_Handler(client, raw)
→ RunRequestWrapper → RunRequest
→ middleware chain
push client to g_CloseList
Until g_Running = #False
Key constraint: CloseNetworkConnection() must only be called from the main thread.
The simplest server using the middleware chain:
; minimal_middleware_server.pb
EnableExplicit
XIncludeFile "src/Global.pbi"
XIncludeFile "src/Types.pbi"
XIncludeFile "src/DateHelper.pbi"
XIncludeFile "src/UrlHelper.pbi"
XIncludeFile "src/HttpParser.pbi"
XIncludeFile "src/HttpResponse.pbi"
XIncludeFile "src/TcpServer.pbi"
XIncludeFile "src/MimeTypes.pbi"
XIncludeFile "src/Logger.pbi"
XIncludeFile "src/FileServer.pbi"
XIncludeFile "src/DirectoryListing.pbi"
XIncludeFile "src/RangeParser.pbi"
XIncludeFile "src/EmbeddedAssets.pbi"
XIncludeFile "src/Config.pbi"
XIncludeFile "src/RewriteEngine.pbi"
XIncludeFile "src/Middleware.pbi"
Global g_Config.ServerConfig
Procedure.i RunRequestWrapper(connection.i, raw.s)
ProcedureReturn RunRequest(connection, raw, @g_Config)
EndProcedure
LoadDefaults(@g_Config)
g_Config\Port = 8080
g_Config\RootDirectory = "/srv/www"
InitRewriteEngine()
OpenEmbeddedPack()
BuildChain()
g_Handler = @RunRequestWrapper()
PrintN("Listening on http://localhost:8080")
StartServer(8080)
CleanupRewriteEngine()
CloseEmbeddedPack()
This gives you the full 11-middleware chain with file serving, ETag, directory listing, gzip, etc.
| Module | What it owns |
|---|---|
Global.pbi |
HTTP status constants, buffer sizes |
Types.pbi |
HttpRequest, ResponseBuffer, MiddlewareContext, ResponseWriter, ServerConfig |
TcpServer.pbi |
TCP socket, event loop, thread dispatch, TLS |
HttpParser.pbi |
Raw string → HttpRequest |
HttpResponse.pbi |
Header assembly, SendTextResponse, FillTextResponse |
Middleware.pbi |
Chain infra, all 11 middleware, RunRequest, gzip, PlainWriter |
FileServer.pbi |
ResolveIndexFile, BuildETag, IsHiddenPath |
DirectoryListing.pbi |
HTML directory listing |
RangeParser.pbi |
Range header parsing, 206 sending |
MimeTypes.pbi |
Extension → Content-Type |
Logger.pbi |
Access + error log, rotation, SIGHUP reopen |
EmbeddedAssets.pbi |
In-memory ZIP serving |
Config.pbi |
Defaults + CLI parsing + ReadPEMFile |
RewriteEngine.pbi |
Rule loading, matching, ApplyRewrites |
AutoTLS.pbi |
acme.sh integration, cert renewal, port 80 redirect |
SignalHandler.pbi |
SIGHUP registration |
WindowsService.pbi |
Windows Service API wrapper |
DateHelper.pbi |
RFC 7231 date formatting |
UrlHelper.pbi |
URL decoding, path normalization |
Each module is independently testable. The 124 unit tests in tests/ verify them without running a real TCP server.