HTTP/1.1 client using non-blocking sockets and Futures. Sends Connection: close, reads the full response, and resolves a Future.
HTTPClient is the main user-facing module of the m2http library. It issues HTTP GET and HEAD requests over non-blocking TCP sockets, integrated with the m2evloop event loop. Response data is delivered through a Future that resolves with a heap-allocated Response record containing status code, headers, and body.
- Event-loop integrated: Uses
EventLoop.WatchFdfor non-blocking connect/send/recv. - Futures-based: Results delivered via Promise/Future — composable with other async operations.
- No hidden globals: All state is in the per-connection
ConnRec. - Robust parsing: Handles Content-Length, chunked transfer encoding, and connection-close body termination.
- Minimal surface: Two request procedures (
Get,Head) plus two response helpers (FindHeader,FreeResponse).
┌──────────────────────────────────────────────────────────┐
│ Application │
│ Get(loop, sched, uri, future) → Run(loop) → check │
├──────────────────────────────────────────────────────────┤
│ HTTPClient │
│ │
│ DoRequest: │
│ 1. DNS.ResolveA → AddrRec │
│ 2. ALLOCATE ConnRec │
│ 3. PromiseCreate → promise + future │
│ 4. ALLOCATE Response │
│ 5. Buffers.Create → recvBuf │
│ 6. BuildRequest → "GET /path HTTP/1.1\r\n..." │
│ 7. SocketCreate + SetNonBlocking │
│ 8. m2_connect_ipv4 (non-blocking) │
│ 9. EventLoop.WatchFd → OnSocketEvent │
│ │
│ OnSocketEvent (state machine): │
│ StConnecting → StSending → StRecvStatus → │
│ StRecvHeaders → StRecvBody → SucceedConn │
├──────────────────────────────────────────────────────────┤
│ EventLoop Buffers DNS Sockets │
│ (fd watch) (I/O buf) (resolve) (TCP) │
└──────────────────────────────────────────────────────────┘
TYPE ConnRec = RECORD
state : INTEGER; (* StConnecting..StError *)
sock : Socket; (* TCP socket fd *)
promise : Promise; (* for resolving the Future *)
loop : EventLoop.Loop; (* event loop handle *)
sched : Scheduler; (* scheduler handle *)
recvBuf : Buffers.Buffer; (* incoming data buffer *)
resp : ResponsePtr; (* response being built *)
request : ARRAY [0..4095] OF CHAR; (* outgoing request *)
reqLen : INTEGER; (* request byte count *)
reqSent : INTEGER; (* bytes sent so far *)
contentLen : INTEGER; (* from Content-Length, or -1 *)
bodyRead : INTEGER; (* body bytes received *)
chunked : BOOLEAN; (* chunked transfer? *)
headOnly : BOOLEAN; (* HEAD request? *)
chunkState : INTEGER; (* ChSize/ChData/ChTrailer/ChDone *)
chunkRem : INTEGER; (* bytes remaining in chunk *)
END;
| State | Value | Description |
|---|---|---|
StConnecting |
0 | Non-blocking connect in progress. |
StSending |
1 | Sending HTTP request. |
StRecvStatus |
2 | Receiving status line. |
StRecvHeaders |
3 | Receiving headers. |
StRecvBody |
4 | Receiving body (Content-Length/chunked). |
StDone |
5 | Complete. Future resolved. |
StError |
6 | Failed. Future rejected. |
| State | Value | Description |
|---|---|---|
ChSize |
0 | Reading chunk size line. |
ChData |
1 | Reading chunk data bytes. |
ChTrailer |
2 | Consuming CRLF after chunk data. |
ChDone |
3 | Final zero-length chunk received. |
TYPE
Header = RECORD
name : ARRAY [0..63] OF CHAR;
value : ARRAY [0..1023] OF CHAR;
nameLen : INTEGER;
valueLen : INTEGER;
END;
Response = RECORD
statusCode : INTEGER;
headers : ARRAY [0..31] OF Header;
headerCount : INTEGER;
body : Buffer;
contentLength : INTEGER; (* -1 if not specified *)
END;
| Resource | Allocation | Freed by |
|---|---|---|
| ConnRec | DoRequest | SucceedConn/FailConn |
| Response | DoRequest | FreeResponse (caller) |
| recvBuf | DoRequest | SucceedConn/FailConn |
| body Buffer | OnSocketEvent | FreeResponse (caller) |
| AddrRec (DNS) | DNS.ResolveA | DoRequest |
On success, the body buffer is transferred from recvBuf to Response.body. The caller is responsible for calling FreeResponse to free both the Response and its body buffer.
On failure, all resources are cleaned up by FailConn and the Future is rejected.
| Status | Meaning |
|---|---|
OK |
Request initiated. Future will settle later. |
Invalid |
NIL loop or scheduler. |
ConnectFailed |
Socket creation or connect failed. |
DNSFailed |
Hostname resolution failed. |
OutOfMemory |
Heap or Promise pool exhausted. |
ParseError |
(not returned by Get/Head, used internally) |
Timeout |
(reserved for future use) |
TLSFailed |
TLS context or session creation failed. |
| Code | Meaning |
|---|---|
| 1 | Connect failed or socket error. |
| 2 | Send failed. |
| 3 | Recv failed or connection closed. |
| 5 | HTTP parse error. |
| 6 | TLS error (handshake, verify). |
- Non-blocking I/O: Connect, send, and recv are all non-blocking. The event loop polls for readiness.
- Incremental parsing: Status line and headers are parsed incrementally as data arrives.
- Single allocation per request: One
ConnRec+ oneResponse+ twoBuffers (~130 KB total). - No connection pooling: Each request creates and closes a TCP connection.
- HTTPS: Supported via m2tls. Peer verification ON by default.
- No HTTP/2: HTTP/1.1 only.
- No keep-alive: Sends
Connection: close. No connection pooling. - No request body: GET and HEAD only. No POST/PUT support.
- 64 KB body limit: Body size limited by
Buffers.MaxCap. - 32 headers max:
MaxHeaders = 32. - 4 KB request limit:
MaxReqSize = 4096for the outgoing request. - Blocking DNS: DNS resolution blocks the event loop (see DNS module).
- No redirects: Does not follow 3xx redirects automatically.
- No timeouts: No per-request timeout (future extension).
TLS support: Done. HTTPS via m2tls (OpenSSL/LibreSSL).- Connection pooling: Reuse connections with keep-alive.
- POST/PUT: Add request body support with
DoRequestaccepting a body buffer. - Automatic redirects: Follow 3xx responses up to a configurable limit.
- Request timeouts: Timer-based timeout that rejects the Future.
- Streaming: Callback-based body reception for large responses.
- HTTP/2: Frame-based protocol implementation.
| Constant | Value | Description |
|---|---|---|
MaxHeaders |
32 | Max response headers. |
MaxHeaderName |
64 | Max header name length. |
MaxHeaderVal |
1024 | Max header value length. |
Header — Response header with name/value arrays and lengths.
Response — Complete HTTP response: status code, headers, body buffer, content length.
ResponsePtr — POINTER TO Response.
Status — (OK, Invalid, ConnectFailed, DNSFailed, OutOfMemory, ParseError, Timeout, TLSFailed).
PROCEDURE Get(lp: Loop; sched: Scheduler;
VAR uri: URIRec;
VAR outFuture: Future): Status;
Issue an HTTP GET request. On OK, the event loop will drive the request to completion and settle outFuture. The Future resolves with Value.ptr pointing to a heap-allocated Response. Caller must call FreeResponse when done.
PROCEDURE Head(lp: Loop; sched: Scheduler;
VAR uri: URIRec;
VAR outFuture: Future): Status;
Issue an HTTP HEAD request. Same as Get but the server returns no body.
PROCEDURE FindHeader(resp: ResponsePtr;
VAR name: ARRAY OF CHAR;
VAR out: ARRAY OF CHAR): BOOLEAN;
Case-insensitive header lookup. Returns TRUE if found, copies value into out.
PROCEDURE FreeResponse(VAR resp: ResponsePtr);
Free a Response and its body buffer. Sets resp to NIL.
FROM EventLoop IMPORT Loop, Create, GetScheduler, Run;
FROM URI IMPORT URIRec, Parse;
FROM HTTPClient IMPORT Get, FreeResponse, FindHeader;
FROM Promise IMPORT Future, GetResultIfSettled;
VAR loop: Loop; uri: URIRec; future: Future; resp: ResponsePtr;
Parse("http://example.com/api", uri);
Create(loop);
Get(loop, GetScheduler(loop), uri, future);
Run(loop);
(* ... check future, use resp, FreeResponse(resp) ... *)
- Buffers — Buffer used for recv and response body
- URI — URI parsing for request targeting
- DNS — Hostname resolution
- Net-Architecture — Overall networking stack design
- ../m2evloop/EventLoop — Event loop integration
- ../m2futures/Promise — Future/Promise types
- ../m2tls/TLS — TLS transport layer (HTTPS support)