Networking — the http unit

PXX ships its own native HTTP/1.1 client (not a wrapper around an external library). It does URL parsing, request building, response framing (Content-Length and chunked Transfer-Encoding), redirects, keep-alive, connection pooling, and — with a TLS backend registered — https://.

program get_example;
uses http;
var r: THttpResponse;
begin
  r := HttpGet('http://example.com/');
  if r.Ok then
  begin
    writeln('status ', r.Status, ' ', r.Reason);
    writeln('content-type: ', HttpResponseHeader(r, 'Content-Type'));
    writeln(Length(r.Body), ' bytes');
  end
  else
    writeln('request failed');
end.

The response record

type
  THttpResponse = record
    Ok:      Boolean;      // transport + parse succeeded
    Status:  Integer;      // e.g. 200; 0 if unparsed
    Reason:  AnsiString;   // e.g. 'OK'
    Headers: AnsiString;   // raw header block (no status line)
    Body:    AnsiString;
  end;

Read a single header without parsing the whole block, or get them structured:

ct   := HttpResponseHeader(r, 'Content-Type');   // case-insensitive, '' if absent
hdrs := HttpResponseHeaders(r);                  // THttpHeaders: name/value pairs

Methods and helpers

Call Does
HttpGet(url) GET
HttpPost(url, contentType, body) POST with a body
HttpHead / HttpPut / HttpDelete the matching method
HttpExec(method, url, extraHeaders, body) any method + custom headers
HttpGetFollow(url, maxRedirects) follow up to N 3xx Location hops
HttpUrlEncode / HttpUrlDecode / HttpQueryAdd query / form encoding

Each call returns a THttpResponse. extraHeaders, when non-empty, is CRLF-terminated lines; a Content-Length is added automatically when a body is present.

Async (reactor) variants

Every call has an …Async form (HttpGetAsync, HttpExecAsync, …) that runs on the coroutine reactor (scheduler): call it from inside a coroutine and it yields instead of blocking, so one thread can drive many requests (and servers) concurrently. Keep-alive (THttpConnection) and a connection pool (HttpGetPooledAsync) reuse sockets across requests.

HTTPS

https:// URLs are routed through a pluggable TLS seam (tls unit). The http unit itself contains no crypto; it asks whichever TLS backend is registered to handshake and to encrypt/decrypt the bytes. If no backend is registered, an https:// request fails cleanly (Ok is False) — it never crashes.

OpenSSL backend

The tls_openssl unit provides a backend that loads the system libssl at runtime (via dlopen). Because loading a shared library pulls in libc, it is opt-in: build with -dPXX_DYNLIB_LIBC (the default build stays libc-free and has no dynamic loader). Register it once at startup, then use the normal http calls. OpenSslTlsRegister is secure by default — it verifies the peer certificate against the system trust store and checks that the certificate matches the hostname; an untrusted or mismatched certificate fails the request (Ok is False).

program https_example;
uses http, tls_openssl;
var r: THttpResponse;
begin
  if not OpenSslTlsRegister then   // dlopen libssl + register as the TLS backend
  begin
    writeln('no TLS backend (build with -dPXX_DYNLIB_LIBC, and libssl present)');
    Halt(1);
  end;
  r := HttpGet('https://example.com/');
  writeln('status ', r.Status);
end.

Build and run:

pxx -dPXX_DYNLIB_LIBC -Fulib/rtl/platform/posix https_example.pas https_example
./https_example

Both the blocking (HttpGet/HttpExec) and async (HttpGetAsync, …) families work over HTTPS: the async handshake yields on the reactor while OpenSSL waits for the socket, so TLS requests compose with everything else on the coroutine loop.

Trust store and private CAs

To trust a private or self-signed CA (e.g. an internal service, or a test server), register with OpenSslTlsRegisterEx(verifyPeer, caFile):

OpenSslTlsRegisterEx(True, '/path/to/ca.pem');   // system store + this CA, verified

caFile is added on top of the system trust store. Passing verifyPeer = False turns verification off entirely — only for development against throwaway endpoints; never in production. After a refused handshake, OpenSslTlsLastVerifyResult returns the OpenSSL X509_V_* code explaining why.

Server-side TLS

The backend can also play the server role. OpenSslTlsServerInit(certFile, keyFile) builds a server context from a PEM certificate + key; an accepting socket then handshakes with TlsHandshake(fd, tlsServer, '', conn) (driving TlsHandshakeResume on want-read/write, the same loop the client uses) and moves bytes with TlsRead / TlsWrite. Client and server share the one backend, so a single process can both serve TLS and make TLS requests. (There is no high-level HTTPS server object yet — you wire accept + the seam yourself; the http unit itself is a client.)

Current limits of the OpenSSL backend: x86-64 only (where the dynamic loader is verified).

A from-scratch native TLS stack is planned as a second, interchangeable backend behind the same seam.