HTTP Client
Luma provides a built-in HTTP client through the http constructor.
Create a request, configure it step by step, and execute with .get(), .post(), or any HTTP method.
No imports, no dependencies — works out of the box.
Creating a request
h: http = http("https://api.example.com/users")Pass the full URL including any query parameters:
h: http = http("https://api.example.com/users?page=1&limit=10")Configuration
Headers
h.header("Authorization", "Bearer token123")
h.header("Accept", "application/json")Set any HTTP header. Call multiple times to add multiple headers.
Form fields
h.field("username", "alice")
h.field("password", "secret")Adds form data sent as application/x-www-form-urlencoded. The Content-Type header is set automatically when using .field(). Call multiple times to add multiple fields.
Body
h.body({"name": "Alice", "age": 30})When the body is a map, it is automatically serialized to JSON and Content-Type: application/json is set.
h.body("<xml><name>Alice</name></xml>")When the body is a string, it is sent as-is. Set the Content-Type yourself via .header() if needed.
.field() and .body() are mutually exclusive — use one or the other, not both.
Timeout
h.timeout(10)Set the request timeout in seconds. Default is 30 seconds. If the request takes longer, it panics (handle with .or()).
Executing
res = h.get()
res = h.post()
res = h.put()
res = h.delete()
res = h.patch()Each method sends the configured request and returns an HttpResponse.
Panics on network errors (DNS failure, connection refused, timeout). Use .or() for error handling.
Response
res.status // int — HTTP status code (200, 404, 500, etc.)
res.body // str — response body as string
res.headers // {str: str} — response headers| Field | Type | Description |
|---|---|---|
status |
int |
HTTP status code |
body |
str |
Response body as string |
headers |
{str: str} |
Response headers |
Examples
Simple GET
res = http("https://api.example.com/health").get()
print(res.status)
print(res.body)One line to create and execute. For simple requests, no configuration needed.
GET with authentication
h: http = http("https://api.example.com/users")
h.header("Authorization", "Bearer token123")
res = h.get()
users = json.decode(res.body)
print(users)POST JSON
h: http = http("https://api.example.com/users")
h.header("Authorization", "Bearer token123")
h.body({"name": "Alice", "age": 30})
res = h.post()
print("Created: ${res.status}")The map body is automatically serialized to JSON — no manual json.encode(), no escaped quotes.
POST form data
h: http = http("https://api.example.com/login")
h.field("username", "alice")
h.field("password", "secret")
res = h.post()
if res.status == 200 {
print("Logged in")
} else {
print("Login failed: ${res.status}")
}Form fields are sent as application/x-www-form-urlencoded, the standard format for HTML form submissions.
PUT update
h: http = http("https://api.example.com/users/1")
h.header("Authorization", "Bearer token123")
h.body({"name": "Bob", "age": 31})
h.timeout(5)
res = h.put()
print("Updated: ${res.status}")DELETE
h: http = http("https://api.example.com/users/1")
h.header("Authorization", "Bearer token123")
res = h.delete()
print("Deleted: ${res.status}")Error handling
res = http("https://unreachable.example.com").get().or(err -> {
print("Request failed: ${err}")
})Network errors, DNS failures, and timeouts are caught with .or(). Note that HTTP error status codes (4xx, 5xx) are not errors — they return a valid response with the status code.
Check status codes
res = http("https://api.example.com/users/1").get()
match res.status {
200 -> print("Found: ${res.body}")
404 -> print("User not found")
401 -> print("Not authorized")
_ -> print("Unexpected: ${res.status}")
}Read response headers
res = http("https://api.example.com/data").get()
content_type: str = res.headers["Content-Type"]
print("Content-Type: ${content_type}")Fetch and parse JSON
res = http("https://api.example.com/users").get()
if res.status == 200 {
users = json.decode(res.body)
print(json.pretty(users))
}Combine with the json namespace for API work. json.decode() parses the response body, json.pretty() formats it.
Call an API in a loop
ids: [int] = [1, 2, 3, 4, 5]
ids.walk(id) -> {
res = http("https://api.example.com/users/${id}").get()
if res.status == 200 {
user = json.decode(res.body)
print("User ${id}: ${user["name"]}")
}
}POST with timeout and error handling
h: http = http("https://slow-api.example.com/process")
h.body({"task": "analyze", "data": "large-dataset"})
h.timeout(60)
res = h.post().or(err -> {
print("Timed out or failed: ${err}")
})Method summary
| Method | Arguments | Description |
|---|---|---|
http() |
url: str |
Create an HTTP request |
.header() |
key: str, value: str |
Set a request header |
.field() |
key: str, value: str |
Add a form field |
.body() |
data: {str: any} |
Set JSON body (auto-serialized) |
.body() |
data: str |
Set raw string body |
.timeout() |
seconds: int |
Set request timeout (default: 30) |
.get() |
— | Execute GET request |
.post() |
— | Execute POST request |
.put() |
— | Execute PUT request |
.delete() |
— | Execute DELETE request |
.patch() |
— | Execute PATCH request |
Error handling
| Situation | Behavior |
|---|---|
| Network error (DNS, connection refused) | Panics — catch with .or() |
| Timeout exceeded | Panics — catch with .or() |
| HTTP 4xx or 5xx response | Returns normally with res.status set |
| Invalid URL | Panics — catch with .or() |
HTTP error codes are not panics. A 404 or 500 is a valid server response — your code decides what to do with it via res.status.
Design notes
httpis a constructor, not a namespace —http("url")creates a request object, similar tobuffer()andtcp.connect()- Builder pattern — configure step by step with
.header(),.field(),.body(),.timeout(), then execute - Auto-JSON — passing a map to
.body()serializes to JSON automatically. No manual encoding, no backslash escaping - Form fields are separate from body —
.field()builds URL-encoded form data,.body()sends raw or JSON. They don’t mix - Seconds for timeout —
h.timeout(10)is immediately clear. Milliseconds would require counting zeros - Panics on network errors only — HTTP status codes like 404, 500 are valid responses, not errors. The programmer decides how to handle them
- No connection pooling — each
http()call is independent. Connection reuse can be added later if needed - HTTPS by default — just use
https://in the URL. TLS is handled automatically