Lcl · Core · Packages · Libraries
A declarative HTTP request DSL over the Curl:: bindings:
one proc per verb, configured by a braced body.
Requires the lcl-curl package
(-DLCL_BUILD_CURL=ON) and lcl-json (JSON
responses are decoded through Json::). Build the library
into the CLI with -DLCL_BUILD_CURL_DSL_LIB=ON and bring the
verbs into scope with import Curl::Dsl. Every request goes
over the network, so nothing on this page is doctested.
import Curl::Dsl
let resp [POST https://api.example.com/items {
header "Content-Type: application/json"
timeout_ms 5000
body {{"name": "widget"}}
on_response {
puts "HTTP $status_code in $total_time s"
}
}]
get $resp status_code
get $resp jsonThe braced body is evaluated with these commands in scope; each sets one part of the request. All are optional, and the body itself may be omitted.
| Command | Effect |
|---|---|
header "Name: value" |
add one request header |
headers { ... } |
add one header per non-blank line of the block |
body text |
request body (sent via Curl::set_body) |
timeout_ms n |
Curl::set_timeout_ms |
connection_timeout_ms n |
Curl::set_connection_timeout_ms |
accept_timeout_ms n |
Curl::set_accept_timeout_ms |
verbose 0/1 |
Curl::set_verbose |
follow_redirects 0/1 |
Curl::set_follow_location |
ssl_verify peer host |
Curl::set_ssl_verify_peer
and Curl::set_ssl_verify_host |
ssl_verify_peer 0/1 |
Curl::set_ssl_verify_peer
only |
user_agent ua |
Curl::set_user_agent |
on_response { ... } |
block run after the transfer (see below) |
on_event { ... } |
block run per Server-Sent Event; switches the request to SSE mode |
http_signature { ... } |
collect HTTP-signature settings (see below) |
Each verb returns a response dict:
| Key | Value |
|---|---|
status_code |
HTTP status (Curl::get_response_code) |
body |
response body (empty in SSE mode) |
headers |
dict; currently only content-type, as a one-element
list |
json |
decoded body when the content type contains
application/json, else {} |
content_type |
Curl::get_content_type |
effective_url |
Curl::get_effective_url |
total_time |
Curl::get_total_time |
error_code |
Curl::get_last_error |
is_timeout |
Curl::is_timeout |
error |
Curl::error_string,
present only when error_code is non-zero |
A failed transfer raises
curl request failed: <reason> – except a timeout, and
any failure in SSE mode, which return the dict with
error_code/error set instead.
An on_response block runs after the transfer with
$status_code, $body, $headers,
$json, $content_type,
$effective_url, $total_time,
$is_timeout, $error_code and
$response_error (the error text, or empty)
bound as variables.
With on_event, the response is parsed as SSE through Curl::set_sse_callback
and the block runs once per event with $data,
$event_type, $id and $retry bound
(empty when the event lacks the field) and $event holding
the whole event dict. Give the request a timeout_ms: an SSE
stream ends by timing out.
GET http://localhost:8080/.sse {
timeout_ms 3000
on_event {
puts "$event_type: $data"
}
}An http_signature { ... } block signs the request per
RFC 9421 (HTTP Message Signatures), adding Signature-Input
and Signature headers. Inside the block:
key_id id, key_pem pem or
key_file path, algorithm alg
(hmac-sha256, rsa-pss-sha256,
rsa-pss-sha512, ecdsa-p256-sha256,
ecdsa-p384-sha384), name label (default
sig), cover (components) (default
()) and created ts (default: now). Components
are the derived
@method "@authority" "@scheme" "@path" "@query" "@target-uri" @request-target,
content-digest (sha-256 of the body, computed for you), or
any header set in the request. Quote the derived components in a list
literal – ("@method" "@path") – because a bare
@word inside (...) is the splice operator.
Signing needs the lcl-crypto package, and
key_file the lcl-io package.
POST https://api.example.com/orders {
header Content-Type: application/json
body {{"qty": 1}}
http_signature {
key_id "orders-key"
key_pem $secret
algorithm hmac-sha256
cover ("@method" "@authority" "@path" content-digest)
}
}Curl::DslCurl::Dsl::GET url (syntax_body {})Send a GET request to url, configured by
syntax_body; return the response dict.
let resp [GET https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::POST url (syntax_body {})Send a POST request to url (give it a
body in syntax_body); return the response
dict.
let resp [POST https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::PUT url (syntax_body {})Send a PUT request to url; return the
response dict.
let resp [PUT https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::DELETE url (syntax_body {})Send a DELETE request to url; return the
response dict.
let resp [DELETE https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::PATCH url (syntax_body {})Send a PATCH request to url; return the
response dict.
let resp [PATCH https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::OPTIONS url (syntax_body {})Send an OPTIONS request to url; return the
response dict.
let resp [OPTIONS https://api.example.com/resource {
header "Accept: application/json"
}]Curl::Dsl::HEAD url (syntax_body {})Send a HEAD request to url; return the
response dict.
Only the verb is changed (Curl::set_verb);
Curl::set_nobody
is not set.
let resp [HEAD https://api.example.com/resource {
header "Accept: application/json"
}]