Package
courier — HTTP client for Pony.
Courier is an HTTP/1.1 client library built on
lori. It follows the same architectural
pattern as lori and stallion: a
protocol handler class (HTTPClientConnection) owned by the user's actor,
with synchronous fun ref callbacks. No hidden actors.
Getting Started¶
Implement HTTPClientConnectionActor on your actor, store an
HTTPClientConnection as a field, and override the lifecycle callbacks
you need:
use "courier"
use lori = "lori"
actor MyClient is HTTPClientConnectionActor
var _http: HTTPClientConnection = HTTPClientConnection.none()
let _out: OutStream
new create(auth: lori.TCPConnectAuth, host: String, port: String,
out: OutStream)
=>
_out = out
_http = HTTPClientConnection(auth, host, port, this,
ClientConnectionConfig)
fun ref _http_client_connection(): HTTPClientConnection => _http
fun ref on_connected() =>
_http.send_request(HTTPRequest(GET, "/"))
fun ref on_response(response: Response val) =>
_out.print(response.status.string() + " " + response.reason)
fun ref on_body_chunk(data: Array[U8] val) =>
_out.write(data)
fun ref on_response_complete() =>
_out.print("")
_http.close()
For HTTPS, use HTTPClientConnection.ssl() instead of
HTTPClientConnection().
One-Shot Timers¶
For response deadlines or application-level timeouts, use
HTTPClientConnection.set_timer(). Unlike idle timeout, this timer fires
unconditionally — I/O activity does not reset it. Only one timer can be active
per connection at a time. The typical pattern is a response deadline: set a
timer
after sending a request, cancel it when the response completes, close the
connection if the timer fires:
actor MyClient is HTTPClientConnectionActor
var _http: HTTPClientConnection = HTTPClientConnection.none()
var _timer: (lori.TimerToken | None) = None
let _out: OutStream
// ... constructor ...
fun ref _http_client_connection(): HTTPClientConnection => _http
fun ref on_connected() =>
_http.send_request(Request.get("/slow-endpoint").build())
match lori.MakeTimerDuration(5_000)
| let d: lori.TimerDuration =>
match _http.set_timer(d)
| let t: lori.TimerToken => _timer = t
| let err: lori.SetTimerError => None
end
end
fun ref on_response_complete() =>
match _timer
| let t: lori.TimerToken =>
_http.cancel_timer(t)
_timer = None
end
// process response...
_http.close()
fun ref on_timer(token: lori.TimerToken) =>
match _timer
| let t: lori.TimerToken if t == token =>
_timer = None
_out.print("Response timed out")
_http.close()
end
Following Redirects¶
Redirect following is opt-in via RedirectFollower, which wraps an
HTTPClientConnection and intercepts redirect responses transparently. The
actor's callback code has no redirect awareness — the follower handles hops
internally and forwards only the final response.
Same-origin redirects reuse the existing TCP connection. Cross-origin redirects
use a RedirectConnectionFactory to create a new one.
Implement RedirectFollowerNotify (which extends
HTTPClientLifecycleEventReceiver with on_redirect_error), store a
RedirectFollower instead of an HTTPClientConnection, and delegate
_http_client_connection() to _http.connection().
Key Types¶
HTTPClientConnectionActor— trait for your actorHTTPClientConnection— protocol handler class (stored as actor field)HTTPClientLifecycleEventReceiver— callback trait (default no-ops)RedirectFollower— wraps a connection and follows redirects transparentlyRedirectFollowerNotify— callback trait for actors using RedirectFollowerRedirectConnectionFactory— creates connections for cross-origin redirectsRedirect— a validated redirect hop (internal to RedirectFollower)RedirectError— why a redirect was not followed (TooManyRedirects,MissingLocation,InvalidLocation,InsecureRedirect)Origin— a URL's scheme, host, and port (the cross-origin boundary)HTTPRequest— request data (method, path, headers, body)Response— parsed response metadata (version, status, reason, headers)ClientConnectionConfig— parser limits, idle timeout, connection timeout, bind addressSendRequestResult— result ofsend_request()(success or error)ConnectionFailureReason— reason a connection attempt failed (ConnectionFailedDNS,ConnectionFailedTCP,ConnectionFailedSSL,ConnectionFailedTimeout,ConnectionFailedTimerError)lori.TimerToken— opaque token for timer cancellation and matchinglori.TimerDuration— validated timer duration (uselori.MakeTimerDuration(milliseconds)to create)lori.SetTimerError— timer setup failure (lori.SetTimerAlreadyActive,lori.SetTimerNotOpen)HTTPResponse— buffered response with complete body (fromResponseCollector)ResponseCollector— accumulates streaming callbacks intoHTTPResponseQueryParams— RFC 3986 query string encodingFormEncoder—application/x-www-form-urlencodedbody encodingBasicAuth— HTTP Basic authentication headerBearerAuth— HTTP Bearer token authentication headerRequest— factory for typed step-builder request constructionRequestOptions— builder interface for all methods (headers, query, auth)RequestOptionsWithBody— builder interface with body methods (POST, etc.)MultipartFormData—multipart/form-databuilder for file uploads and mixed form submissions; use withmultipart_body()on the request builder. For simple key-value form data without files, useFormEncoder/form_body()instead.ResponseJSON— parseHTTPResponsebody as JSONJSONDecoder— interface for typed JSON decodersJSONDecodeError— decode failure with descriptive messageDecodeJSON— parse and decode an HTTP response body in one step
Design¶
See Discussion #2 for the full design rationale.
Public Types¶
- primitive BasicAuth
- primitive BearerAuth
- primitive BodyTooLarge
- primitive CONNECT
- class ClientConnectionConfig
- primitive ConnectionClosed
- primitive ConnectionFailedDNS
- primitive ConnectionFailedSSL
- primitive ConnectionFailedTCP
- primitive ConnectionFailedTimeout
- primitive ConnectionFailedTimerError
- type ConnectionFailureReason
- primitive DELETE
- primitive DecodeJSON
- primitive FormEncoder
- primitive GET
- primitive HEAD
- primitive HTTP10
- primitive HTTP11
- class HTTPClientConnection
- trait HTTPClientConnectionActor
- trait HTTPClientLifecycleEventReceiver
- class HTTPRequest
- class HTTPResponse
- class Headers
- primitive InsecureRedirect
- primitive InvalidChunk
- primitive InvalidContentLength
- primitive InvalidLocation
- primitive InvalidStatusLine
- primitive InvalidVersion
- class JSONDecodeError
- interface JSONDecoder
- primitive MalformedHeaders
- interface Method
- primitive Methods
- primitive MissingLocation
- class MultipartFormData
- primitive OPTIONS
- class Origin
- primitive PATCH
- primitive POST
- primitive PUT
- type ParseError
- primitive QueryParams
- class Redirect
- interface RedirectConnectionFactory
- type RedirectError
- class RedirectFollower
- trait RedirectFollowerNotify
- primitive Request
- interface RequestOptions
- interface RequestOptionsWithBody
- class Response
- class ResponseCollector
- primitive ResponseJSON
- primitive ResponsePending
- type SendRequestError
- primitive SendRequestOK
- type SendRequestResult
- primitive TRACE
- primitive TooLarge
- primitive TooManyRedirects
- type Version