(sigil http client)
(sigil http client) - HTTP Client Implementation
Provides HTTP/1.1 client functionality for making requests to HTTP and HTTPS servers.
Example: (import (sigil http client)) (let ((response (http-get "https://example.com/"))) (display (http-response-body response)))
Exports
urlprocedureConstruct a url struct.
url?procedureTest if a value is a url struct.
url-schemeprocedureGet the scheme field of a url struct.
url-hostprocedureGet the host field of a url struct.
url-portprocedureGet the port field of a url struct.
url-pathprocedureGet the path field of a url struct.
url-queryprocedureGet the query field of a url struct.
parse-urlprocedureParse a URL string into a url record.
Supports http://host:port/path?query and https://.... Use url-scheme, url-host, url-port, url-path, url-query to access the components.
(let ((u (parse-url "https://example.com:8080/api?key=val")))
(url-host u)) ; => "example.com"connect-to-serverprocedureConnect to a server, using TLS if scheme is https. Returns connection object or #f on failure.
connect-timeout (seconds, or #f) bounds the TLS connect phase so a blackholed address can't hang on the OS SYN timeout. It is only honored for HTTPS (the TLS connect path); plain HTTP uses the socket layer's blocking connect (no timeout knob there yet).
conn-writeprocedureWrite data to connection (socket or TLS)
conn-readprocedureRead data from connection (socket or TLS)
conn-read-bytesprocedureRead binary data from connection as bytevector (socket or TLS)
conn-closeprocedureClose connection
conn-set-non-blocking!procedurePut a connection (socket or TLS) into non-blocking mode. Used by the timeout path so reads return immediately when no data is available, letting the read loop enforce a deadline.
timeout->deadlineprocedureCompute an absolute deadline in jiffies from a timeout in seconds, or #f when no (positive) timeout was requested.
deadline-expired?procedureHas the (jiffy) deadline passed?
http-ack-unconfirmed?procedureIs exn an "ack unconfirmed" error (request sent, response read failed)? Distinguishes a delivered-but-unconfirmed send from a genuine never-sent failure (the latter does not carry this mark).
raise-http-ack-unconfirmedprocedureRaise an "ack unconfirmed" error: the request was sent but the response could not be read (read deadline, empty/closed read, or unparseable response). Carries ack-unconfirmed-irritant.
raise-http-timeoutprocedureRaise a clean timeout error (read deadline). Marked ack-unconfirmed because the deadline only fires after the request was written.
build-request-stringprocedureBuild HTTP request string
build-header-linesprocedureBuild header lines from headers (dict or alist)
parse-status-lineprocedureParse HTTP status line Returns (version status-code reason) or #f
parse-response-headersprocedureParse response headers from data Returns dict with keyword keys
find-header-end-bytesprocedureFind the end of the header block (rnrn) in a bytevector. Returns the index of the first r, or #f if not yet present.
no-body-expected?procedureDoes this method/status combination forbid a response body? HEAD never has a body; 1xx/204/304 never have a body (RFC 9112).
detect-framingprocedureInspect the accumulated response bytes and determine how the body is framed. Returns #f while the header block is still incomplete, otherwise a descriptor: (no-body) — no body permitted; complete at headers (length <body-start> <n>) — fixed Content-Length body (chunked <body-start>) — chunked transfer-encoding (until-close) — unframed; read until the peer closes This lets the reader stop as soon as the full body has arrived instead of waiting for the connection to close (EOF), which a keep-alive/half-open peer may never do.
chunked-body-complete?procedureIs the chunked body starting at start fully present in bv? Walks chunk-size lines until the terminating 0-size chunk and its closing CRLF (no trailers). Returns #f if more data is needed.
framing-complete?procedureIs the response complete per its framing descriptor? combined is the accumulated bytevector (only needed for chunked).
read-http-responseprocedureRead HTTP response from connection Returns http-response or #f on error. method is the request method (HEAD responses carry no body). deadline is a jiffy deadline (or #f). When set, the read loop enforces a timeout instead of blocking on a stalled read.
read-all-dataprocedureRead a full HTTP response from the connection. Reads as bytevectors and converts to string once at the end to avoid splitting multi-byte UTF-8 characters across chunks.
Termination is HTTP-framing-aware: once the header block has arrived, the response is considered complete as soon as the body is fully received per its framing (Content-Length, the chunked 0-terminator, or a body-less status/method). The reader does NOT wait for the connection to close — a keep-alive or half-open peer may hold it open indefinitely even after sending a complete response, which previously hung the read (or, with a deadline, tripped a spurious timeout on an already-delivered request). Only an unframed response (no Content-Length, not chunked) falls back to reading until EOF.
When deadline is #f, reads block. When set, the connection is non-blocking: an empty read means "no data yet", so we sleep briefly and retry until the response completes, the peer closes, or the deadline passes (which raises a timeout error).
parse-http-responseprocedureParse HTTP response from string
decode-chunked-bodyprocedureDecode chunked transfer encoding using byte-level operations. Chunk sizes in HTTP are byte counts, so we must work with bytes to correctly handle multi-byte UTF-8 content. Format: <hex-size>rn<data>rn ... 0rnrn
find-crlf-bytesprocedureFind position of rn in a bytevector starting at pos
find-crlfprocedureFind position of rn in a string starting at pos
hex-string->numberprocedureConvert hex string to number
find-header-endprocedureFind the end of HTTP headers (position of rnrn)
skip-crlfprocedureSkip CRLF sequence(s) at position
http-requestvariableMake an HTTP request.
Low-level function for making HTTP requests. Prefer the convenience functions (http-get, http-post, etc.) for common cases.
The optional timeout: keyword bounds the response read. When given a positive number of seconds, the read is made non-blocking and a timeout error is raised if no complete response arrives before the deadline (so a half-open/blackholed connection fails cleanly instead of hanging). When omitted (the default), reads block exactly as before.
The optional connect-timeout: keyword (seconds) bounds the HTTPS connect phase, so a blackholed address can't hang on the OS SYN timeout. Omitted/#f keeps the blocking connect.
(http-request 'GET "https://api.example.com/users"
headers: #{ authorization: "Bearer token" })
(http-request 'POST "https://api.example.com/users"
headers: #{ content-type: "application/json" }
body: "{\"name\": \"Alice\"}"
timeout: 25)http-getvariableHTTP GET request.
(http-get "https://example.com/")
(http-get "https://api.example.com/users"
headers: #{ authorization: "Bearer token" })http-postvariableHTTP POST request.
If no Content-Type header is provided, defaults to application/x-www-form-urlencoded.
(http-post "https://api.example.com/data" "key=value")
(http-post "https://api.example.com/data"
"{\"key\": \"value\"}"
headers: #{ content-type: "application/json" })http-putvariableHTTP PUT request.
(http-put "https://api.example.com/users/123"
"{\"name\": \"Alice\"}"
headers: #{ content-type: "application/json" })http-deletevariableHTTP DELETE request.
(http-delete "https://api.example.com/users/123")
(http-delete "https://api.example.com/users/123"
headers: #{ authorization: "Bearer token" })http-headvariableHTTP HEAD request.
Like GET but only retrieves headers, not body. Useful for checking if a resource exists or getting metadata.
(let ((res (http-head "https://example.com/file.pdf")))
(http-response-header res "Content-Length"))http-optionsvariableHTTP OPTIONS request.
Query server for allowed methods on a resource.
(let ((res (http-options "https://api.example.com/users")))
(http-response-header res "Allow"))
; => "GET, POST, OPTIONS"http-patchvariableHTTP PATCH request.
Partially update a resource. Unlike PUT which replaces the entire resource, PATCH applies partial modifications.
(http-patch "https://api.example.com/users/123"
"{\"email\": \"new@example.com\"}"
headers: #{ content-type: "application/json" })http-response-jsonprocedureParse HTTP response body as JSON.
Returns the parsed JSON value, or #f if the response is #f or parsing fails. Requires sigil-json package.
(let ((res (http-get "https://api.example.com/data")))
(http-response-json res))
; => #{ users: #[...] count: 42 }http-get/jsonvariableHTTP GET request expecting JSON response.
Makes a GET request and parses the response body as JSON. Returns the parsed JSON value, or #f if request fails or status is not 2xx.
(http-get/json "https://api.example.com/users")
; => #{ users: #[...] }
(http-get/json "https://api.example.com/users"
headers: #{ authorization: "Bearer token" })http-post/jsonvariableHTTP POST request with JSON body, expecting JSON response.
Encodes the body as JSON, sets Content-Type to application/json, and parses the response as JSON. Returns parsed JSON on any status code, or #f if the request failed entirely.
(http-post/json "https://api.example.com/users"
#{ name: "Alice" email: "alice@example.com" })
; => #{ id: 123 name: "Alice" }
(http-post/json "https://api.example.com/users"
#{ name: "Alice" }
headers: #{ authorization: "Bearer token" })read-response-headersprocedureRead HTTP response headers only, without consuming the body.
Returns a list: (status-code headers leftover-string) where leftover-string is any body data already read past the header boundary. Returns #f on failure.
parse-header-resultprocedureParse headers from data at the given header-end position. Returns (status-code headers leftover-string).
stream-body-to-portprocedureStream response body from connection to an output port.
Writes leftover bytes (from header read) first, then reads remaining data as bytevectors and writes them to the port.
http-downloadprocedureDownload a URL to a file, streaming data directly to disk.
Unlike http-get which loads the entire response into memory, http-download streams the response body to a file, making it suitable for large downloads.
The on-progress callback receives (bytes-received total-bytes) where total-bytes may be #f if the server didn't send Content-Length.
Returns a dict with download info on success, or #f on failure.
(http-download "https://example.com/large-file.bin"
"/tmp/file.bin")
; => #{ status: 200 size: 12345 path: "/tmp/file.bin" }
(http-download "https://example.com/file.bin"
"/tmp/file.bin"
on-progress: (lambda (received total)
(display (str received "/" total "\r"))))build-api-urlprocedureBuild an API URL from a base URL and path segments.
Common pattern across API clients: concatenate a base URL with slash-separated path parts.
(build-api-url "https://api.example.com" "v1" "users" "123")
; => "https://api.example.com/v1/users/123"make-response-checkerprocedureCreate a response checker function for an API client.
Takes a name for error messages and an optional list of (status-code . message) pairs for specific error handling. Returns a function that checks an HTTP response and either returns parsed JSON on success or raises an error.
The optional parse-error keyword accepts a function (lambda (status body) ...) for custom error body parsing (e.g., JSON:API error extraction). When provided, it is called instead of the default handler for status >= 400 that don't match a specific handler entry.
(define check-response
(make-response-checker
name: "YouTube API"
handlers: (list
(cons 401 "Access token may be expired.")
(cons 403 "Possible quota exceeded."))))
(check-response (http-get url headers: auth))make-json-apiprocedureCreate authenticated JSON API method wrappers.
Takes a function that returns auth headers and a response checker, and returns a dict with get, post, put, patch, and delete functions that handle JSON encoding/decoding and authentication.
(define api (make-json-api
auth-headers: (lambda () #{ authorization: "Bearer tok" })
check-response: my-checker))
((dict-ref api get:) "https://api.example.com/users")
((dict-ref api post:) "https://api.example.com/users" #{ name: "Alice" })http-response?variable(No description)
http-response-statusvariable(No description)
http-response-headersvariable(No description)
http-response-bodyvariable(No description)