REST and HTTP Operations
The iXML language provides facilities for building RESTful APIs and consuming HTTP services. REST operations cover two use cases: REST server for building APIs, and REST client for consuming external APIs. HTTP operations provide low-level request capabilities.
REST Server (Building APIs)
rest:server / rest:resource
rest:server defines a REST server with route-based dispatch. rest:resource defines individual endpoints.
Key concepts:
- Route parameters use
:namesyntax (/users/:id) - Variadic routes use
*name(/files/*path) - Query parameters are automatically extracted as local variables
- Set HTTP status with
<set var="return">404</set> - Each handler executes in its own local context — use
<use>to import parent variables
<rest:server>
<!-- GET /person/:id -->
<rest:resource route="/person/:id" method="GET">
<array:keyexists var="people" var_result="exists">$id</array:keyexists>
<is var="exists" type="false">
<set var="return">404</set>
<return/>
</is>
<header>Content-Type: application/json</header>
<output>
<encode:json var="people[$id]"/>
</output>
</rest:resource>
<!-- POST /person (JSON body) -->
<rest:resource route="/person" method="POST" var_body="body">
<try>
<decode:json var="data">$body</decode:json>
<if value1="$data.email" func="=" value2="">
<set var="return">400</set>
<header>Content-Type: application/json</header>
<output>{"error": "Email is required"}</output>
<return/>
</if>
<db:set entity="contacts" var="new_id" var_data="data"/>
<set var="return">201</set>
<header>Content-Type: application/json</header>
<output>{"result": 1, "id": $new_id}</output>
<catch var="error">
<set var="return">400</set>
<header>Content-Type: application/json</header>
<output>{"error": "Invalid JSON"}</output>
</catch>
</try>
</rest:resource>
<!-- DELETE /person/:id -->
<rest:resource route="/person/:id" method="DELETE">
<unset var="people[$id]"/>
<header>Content-Type: application/json</header>
<output>{"result": 1}</output>
</rest:resource>
</rest:server>
Accessing request data
<rest:resource route="/protected" method="GET" var_header="headers" var_body="body">
<use var="database_connection"/>
<!-- Path parameters become local variables -->
<output>ID: $id</output>
<!-- Query parameters become local variables -->
<output>Search: $q</output>
<!-- Request headers via var_header -->
<output>Auth: $headers.Authorization</output>
<!-- Request body via var_body -->
<decode:json var="payload">$body</decode:json>
</rest:resource>
Output Buffering: All output within rest:server is buffered. Do not send output before the rest:server block, as this will commit HTTP headers prematurely.
REST Client (Consuming APIs)
rest:client / rest:bind
rest:client establishes a connection context for an external API. rest:bind creates callable functions bound to endpoints.
<rest:client url="https://api.predic8.de/shop/v2" timeout="30">
<rest:header>
Content-Type: application/json
Accept: application/json
</rest:header>
<rest:bind var="createProduct" method="POST">/products/</rest:bind>
<rest:bind var="updateProduct" method="PATCH">/products/:id</rest:bind>
<rest:bind var="getProduct" method="GET">/products/:id</rest:bind>
<rest:bind var="deleteProduct" method="DELETE">/products/:id</rest:bind>
</rest:client>
<!-- Create a product -->
<call func="createProduct" var="response">
<param>{"name": "Wildberries", "price": 4.99}</param>
</call>
<decode:json var="product">$response</decode:json>
<output>Created product ID: $product.id</output>
<!-- Update the product -->
<call func="updateProduct">
<param>$product.id</param>
<param>{"price": 5.99}</param>
</call>
Parameter passing:
- Path parameters: first N params fill
:paramplaceholders in order - Request body: remaining params become the body (POST, PUT, PATCH)
- Use
type="raw"onrest:bindto get full response withstatus,type,header,body
HTTP Operations
http:request
http:request sends a low-level HTTP request with full control over headers, body, and response handling.
<!-- GET request -->
<http:request url="https://api.example.com/users/123" var="response"/>
<!-- POST with JSON -->
<http:request url="https://api.example.com/users" method="POST" var="response" var_info="info">
<http:header>
Content-Type: application/json
Authorization: Bearer $api_token
</http:header>
<http:body>
{"name": "Jane Doe", "email": "[email protected]"}
</http:body>
</http:request>
<!-- Check response status -->
<if value1="$info.status" func="=" value2="200">
<decode:json var="user">$response</decode:json>
<output>User: $user.name</output>
<else/>
<output>Error: $info.status</output>
</else></if>
The var_info array contains: status (HTTP code), type (Content-Type), header (raw headers), body (response body).
http:urlinfo
http:urlinfo parses a URL into its components (scheme, host, port, path, query, fragment, args):
<http:urlinfo var="info" url="https://example.com:8080/api/users?role=admin"/>
<output>Host: $info.host, Path: $info.path, Role: $info.args.role</output>
http:query
http:query builds a URL-encoded query string from an associative array:
<array var="params">
<set var="search">iXML programming</set>
<set var="limit">10</set>
</array>
<http:query var="params" var_result="qs"/>
<output>$qs</output>
<!-- search=iXML+programming&limit=10 -->
Response patterns
Consistent JSON envelope
<header>Content-Type: application/json</header>
<!-- Success -->
<output>{"result": 1, "data": <encode:json var="payload"/>}</output>
<!-- Error -->
<set var="return">400</set>
<output>{"error": "Validation failed", "details": "$message"}</output>
HTTP status codes
| Code | When to use |
|---|---|
| 200 | Successful GET, PUT, PATCH, DELETE |
| 201 | Successful POST creating a resource |
| 400 | Invalid input, malformed JSON |
| 401 | Missing or invalid authentication |
| 404 | Resource not found |
| 500 | Unexpected server error |