Implemented in BASIL, with ordinary suspending functions and owned TCP connections. The protocol follows RFC 9110 and RFC 9112 .
A small server
router=HTTPRouter()
RouteGet(BYREF router,"/hello/{name}",Hello)
MountDirectory(BYREF router,"/static/","/var/web")
listener=ListenTCP("127.0.0.1",8080)
WHILE TRUE
connection=Accept(listener)
SPAWN ServeHTTP(TAKE connection,router)
WEND
FUNCTION Hello(request AS HTTPRequest) AS HTTPResponse
RETURN TextResponse("Hello "+Get(request.Parameters,"name",""))
END FUNCTIONEach child owns its connection and an independent router snapshot. Scope exit cancels
and joins children and closes connections. No handshake or status channel is needed.
ServeHTTP(listener,router) also supplies the accept/spawn loop. Serving errors are
translated to HTTP responses when possible; disconnected peers simply end that
connection. ServeHTTPChecked(connection,router,options) exposes operational/protocol
and original application errors to a supervising function. Mutable router slots
explicitly admit Error, so a checked server caller must handle or declare that
open family. Convenience servers recover handler failures and report concrete
error names. Ordinary handler signatures and ownership remain checked statically.
Route accepts an explicit method; RouteGet and RoutePost are conveniences.
Literal segments beat {parameter} segments, which beat final {*remainder}.
Duplicate method/pattern shapes are rejected. Unknown paths return 404; wrong methods
return 405 with Allow. HEAD falls back to GET and suppresses body bytes. OPTIONS is
supported. Paths are percent-decoded once; malformed encodings, dot segments, NUL,
backslash and encoded slashes are rejected. Query strings remain separate;
HTTPQueryValues decodes form-style query parameters.
Buffered handlers receive request.Body as LIST OF BYTE and request.Trailers.
TextResponse, BytesResponse and RedirectResponse construct independent values.
Streaming handlers are SUBs taking (request AS HTTPRequest, connection AS TCPConnection, BYREF exchange AS HTTPExchange) and declaring
FAILS IoError,HTTPError,IndexError,StringError. Use ReadHTTPBody,
StartHTTPResponse, WriteHTTPBody, FinishHTTPResponse; alternatively use
WriteHTTPResponse. Read chunks until empty before reusing a connection.
ReadHTTPBodyAll is a bounded convenience. Chunked output is selected when no
Content-Length is supplied. Explicit lengths must match actual bytes. Trailers are exposed separately from headers; application-defined trailer fields must permit trailer use. The library rejects core framing/routing/authentication fields rather than merging them into initial headers.
UseHTTP registers streaming middleware; writing a response ends dispatch.
exchange.Context carries string context to subsequent middleware/handlers through
request.Context. Buffered request bodies are read before middleware runs.
HTTPFallback installs a streaming fallback. These are checked noncapturing function
values, not escaping closures. Route configuration may carry independent string values.
HTTPS and clients
ServeHTTPS(connection,router,tlsOptions [,httpOptions]) upgrades the same owned
connection using TLSServerOptions. A listener overload supplies the accept loop.
Certificates and keys use the TLS interfaces documented in networking
; https-server.bas
is a complete example.
ALPN is restricted to http/1.1. Verification remains enabled for HTTPS clients.
response=HTTPGet("https://example.com/")
PRINT response.Status
PRINT DecodeUtf8(response.Body)HTTPPost accepts STRING or LIST OF BYTE. RequestHTTP accepts arbitrary methods.
HTTPClientOptions combines HTTPOptions and TLSClientOptions. Redirects are bounded;
cross-origin redirects strip authorization/cookies, and HTTPS downgrade is forbidden
by default. A redirect limit of zero disables following. HTTPGetPlain and
RequestPlainHTTP reject HTTPS and allow the static linker to omit TLS entirely.
Generic scheme-selecting clients necessarily retain both transports.
For explicit streaming/reuse: ParseHTTPURL, ConnectHTTP, HTTPExchange(options),
StartHTTPRequest, WriteHTTPBody, FinishHTTPRequest, ReadHTTPResponse, then
ReadHTTPBody until empty. Finish/drain one exchange before starting another. Errors
require abandoning that connection; do not reuse damaged framing state. The buffered
client does this automatically. There is no automatic connection pool, proxy, cookie
jar, decompression, HTTP/2, WebSocket upgrade or CONNECT tunnel in this slice.
Directory serving and file workers
listener=ListenTCP("127.0.0.1",8080)
WHILE TRUE
connection=Accept(listener)
SPAWN ServeDirectory(TAKE connection,"/var/web")
WENDThe same handler backs MountDirectory. GET/HEAD, index.html, MIME types, weak ETag,
If-None-Match, Last-Modified and single byte ranges are supported. Multi-range requests
are ignored; If-Range falls back to a full response. Listings are opt-in with
HTTPOptions().DirectoryListings configured through a local options value; HTML and
URL components are escaped. Hidden files are disabled by default. Linux openat2
containment rejects child symlinks and traversal, including symlinks within the root.
Unsupported kernels fail explicitly; there is no unsafe path-prefix fallback.
Files stream in 32 KiB chunks. Listings are bounded snapshots, not unbounded traversal.
Ordinary open/read/stat/directory/seek APIs now park their coroutine while four bounded filesystem workers execute the operation. Cancellation removes queued work immediately; running kernel operations must acknowledge completion before resources/buffers are released. A stuck kernel operation can therefore delay cancellation. File writes, flush, atomic save and other mutation APIs have not all moved to workers yet.
Limits and protocol policy
HTTPOptions defaults: idle 30 s, headers 10 s, body 30 s, writes 30 s; start line 8192 bytes, headers 32768 bytes/100 fields, body 16 MiB, chunks 65536, requests per connection 1000, redirects 5. Each phase uses its deadline; arbitrary handler CPU work is not preempted. Streaming still enforces body limits. Header values must be UTF-8. Strict CRLF and framing checks reject duplicate Content-Length, TE+CL, unsupported transfer coding, malformed chunks/trailers and premature EOF. Request header folding is rejected; response folding is normalized. Expect: 100-continue is supported. HEAD/204/304 suppress bodies. A partial response is never followed by a second status. Allocation failure remains fatal, as elsewhere in the language.
Typed JSON helpers
Typed URLs and shared parsing
URL values/builders provide ParseURL, component access, JoinURLPath(base,path,Query=parameters), repeated query values and explicit RFC reference resolution. HTTPGet/HTTPPost and request helpers accept typed URL values. ParseHTTPURL and Host-header adaptation use that parser with the existing stricter HTTP authority/port/transport policy. Redirects now use the shared RFC resolver; encoded delimiters retain their meaning and fragment data is not sent as a target.