HTTP Services
ARO provides built-in HTTP server and client capabilities. This chapter covers how to build web APIs and make HTTP requests.
HTTP Server
Starting the Server
Start an HTTP server in Application-Start:
(Application-Start: Web API) {
Start the <http-server> on port 8080.
Keepalive the <application> for the <events>.
Return an <OK: status> for the <startup>.
}
Route Handlers (Contract-First)
Define routes in openapi.yaml and implement feature sets named after operationIds:
(listUsers: User API) {
Retrieve the <users> from the <user-repository>.
Return an <OK: status> with <users>.
}
(createUser: User API) {
Extract the <user-data> from the <request: body>.
Create the <user> with <user-data>.
Store the <user> into the <user-repository>.
Return a <Created: status> with <user>.
}
(updateUser: User API) {
Extract the <user-id> from the <pathParameters: id>.
Extract the <updates> from the <request: body>.
Retrieve the <user> from the <user-repository> where id = <user-id>.
Transform the <updated-user> from the <user> with <updates>.
Store the <updated-user> into the <user-repository>.
Return an <OK: status> with <updated-user>.
}
(deleteUser: User API) {
Extract the <user-id> from the <pathParameters: id>.
Delete the <user> from the <user-repository> where id = <user-id>.
Return a <NoContent: status> for the <deletion>.
}
Request Data
Access request data using qualified variables:
(createUser: User API) {
(* Path parameters *)
Extract the <id> from the <pathParameters: id>.
(* Query string *)
Extract the <page> from the <queryParameters: page>.
Extract the <limit> from the <queryParameters: limit>.
(* Request body *)
Extract the <data> from the <request: body>.
(* Headers *)
Extract the <auth-token> from the <request: headers Authorization>.
Extract the <content-type> from the <request: headers Content-Type>.
...
}
Response Status Codes
Return appropriate status codes:
(* 2xx Success *)
Return an <OK: status> with <data>. (* 200 *)
Return a <Created: status> with <resource>. (* 201 *)
Return an <Accepted: status> for <async>. (* 202 *)
Return a <NoContent: status> for <deletion>. (* 204 *)
(* 4xx Client Errors *)
Return a <BadRequest: status> with <errors>. (* 400 *)
Return an <Unauthorized: status> for <auth>. (* 401 *)
Return a <Forbidden: status> for <access>. (* 403 *)
Return a <NotFound: status> for <missing>. (* 404 *)
(* 5xx Server Errors *)
Return an <InternalError: status> for <error>. (* 500 *)
RESTful Patterns
Collection Resource
(* List all *)
(listProducts: Product API) {
Extract the <page> from the <queryParameters: page>.
Extract the <limit> from the <queryParameters: limit>.
Retrieve the <products> from the <product-repository>
with pagination <page> <limit>.
Return an <OK: status> with <products>.
}
(* Create new *)
(createProduct: Product API) {
Extract the <product-data> from the <request: body>.
Validate the <product-data> for the <product-schema>.
Create the <product> with <product-data>.
Store the <product> into the <product-repository>.
Return a <Created: status> with <product>.
}
Single Resource
(* Get one *)
(getProduct: Product API) {
Extract the <product-id> from the <pathParameters: id>.
Retrieve the <product> from the <product-repository> where id = <product-id>.
if <product> is empty then {
Return a <NotFound: status> for the <missing: product>.
}
Return an <OK: status> with <product>.
}
(* Delete one *)
(deleteProduct: Product API) {
Extract the <product-id> from the <pathParameters: id>.
Retrieve the <product> from the <product-repository> where id = <product-id>.
if <product> is empty then {
Return a <NotFound: status> for the <missing: product>.
}
Delete the <product> from the <product-repository> where id = <product-id>.
Return a <NoContent: status> for the <deletion>.
}
Nested Resources
(* User's orders *)
(listUserOrders: Order API) {
Extract the <user-id> from the <pathParameters: userId>.
Retrieve the <orders> from the <order-repository> where userId = <user-id>.
Return an <OK: status> with <orders>.
}
(* Specific order for user *)
(getUserOrder: Order API) {
Extract the <user-id> from the <pathParameters: userId>.
Extract the <order-id> from the <pathParameters: orderId>.
Retrieve the <order> from the <order-repository>
where id = <order-id> and userId = <user-id>.
if <order> is empty then {
Return a <NotFound: status> for the <missing: order>.
}
Return an <OK: status> with <order>.
}
HTTP Client
Making Requests
Make outgoing HTTP requests:
(* Simple GET *)
Request the <data> from "https://api.example.com/resource".
(* GET with response handling *)
Request the <response> from "https://api.example.com/users".
Extract the <users> from the <response: body>.
Request Methods
(* GET *)
Request the <data> from <UserAPI: GET /users>.
(* POST *)
Call the <result> via <UserAPI: POST /users> with <user-data>.
(* PUT *)
Call the <updated> via <UserAPI: PUT /users/{id}> with <updates>.
(* DELETE *)
Call the <result> via <UserAPI: DELETE /users/{id}>.
Response Handling
(Fetch External Data: External Service) {
Request the <response> from "https://api.example.com/data".
Extract the <status-code> from the <response: statusCode>.
Extract the <body> from the <response: body>.
if <status-code> is not 200 then {
Log "API error: ${status-code}" to the <console>.
Return a <BadRequest: status> for the <api: error>.
}
Return an <OK: status> with <body>.
}
Unified URL I/O
ARO provides unified I/O for both local files and remote URLs. The same Read and
Write actions work with both file paths and HTTP URLs.
Reading from URLs (GET)
Use the url: system object to fetch data via HTTP GET:
(* Simple GET request *)
Read the <data> from the <url: "https://api.example.com/users">.
(* Response is auto-parsed based on Content-Type *)
Extract the <username> from the <data: name>.
Log "User: ${username}" to the <console>.
Writing to URLs (POST)
Use Write with a URL to send data via HTTP POST:
(* Create payload *)
Create the <payload> with {
title: "Hello",
body: "World",
userId: 1
}.
(* POST to API *)
Write the <payload> to the <url: "https://api.example.com/posts">.
Custom Headers and Options
Use the with clause for headers, timeout, and other options:
(* GET with authentication *)
Read the <data> from the <url: "https://api.example.com/protected"> with {
headers: {
Authorization: "Bearer ${token}",
Accept: "application/json"
},
timeout: 30
}.
(* POST with custom headers *)
Write the <payload> to the <url: "https://api.example.com/data"> with {
headers: {
Authorization: "Bearer ${token}",
X-Request-ID: "${request-id}"
}
}.
Data Pipeline Pattern
Seamlessly sync data between remote APIs and local files:
(Sync Data: Data Pipeline) {
(* Fetch from remote API *)
Read the <remotedata> from the <url: "https://api.example.com/export">.
(* Cache locally *)
Write the <remotedata> to the <file: "./data/snapshot.json">.
(* Read local report *)
Read the <report> from the <file: "./reports/daily.json">.
(* Upload to remote *)
Write the <report> to the <url: "https://api.example.com/upload">.
Return an <OK: status> for the <sync>.
}
Authentication
API Key
(getSecureData: Secure API) {
Extract the <api-key> from the <request: headers X-API-Key>.
when <api-key> is empty {
Return an <Unauthorized: status> for the <missing: api-key>.
}
Validate the <api-key> for the <api-key-registry>.
if <validation> is failed then {
Return an <Unauthorized: status> for the <invalid: api-key>.
}
Retrieve the <data> from the <repository>.
Return an <OK: status> with <data>.
}
Bearer Token
(getProtectedResource: Protected API) {
Extract the <auth-header> from the <request: headers Authorization>.
when <auth-header> is empty {
Return an <Unauthorized: status> for the <missing: token>.
}
Parse the <token> from the <auth-header> as "Bearer".
Validate the <token> for the <jwt-validator>.
if <token> is invalid then {
Return an <Unauthorized: status> for the <invalid: token>.
}
Extract the <user-id> from the <token: claims userId>.
Return an <OK: status> with <data>.
}
Best Practices
Validate Input
(createUser: User API) {
Extract the <user-data> from the <request: body>.
(* Always validate input *)
Validate the <user-data> for the <user-schema>.
if <validation> is failed then {
Return a <BadRequest: status> with <validation: errors>.
}
Create the <user> with <user-data>.
Return a <Created: status> with <user>.
}
Use Appropriate Status Codes
(* Be specific with status codes *)
Return a <Created: status> with <new-resource>. (* Not just OK *)
Return a <NoContent: status> for the <deletion>. (* Not OK with empty body *)
Return a <NotFound: status> for the <missing>. (* Not BadRequest *)
Next Steps
Contract-First Development - OpenAPI integration
Sockets - TCP communication