The HTTP Functions module enables you to register and invoke HTTP endpoints as native functions in your iii application. Perfect for integrating with external APIs, webhooks, and microservices.
Configuration
Configure the HTTP Functions module in config.yaml:
modules:
- class: modules::http_functions::HttpFunctionsModule
config:
security:
url_allowlist:
- "https://api.example.com/*"
- "https://*.trusted-domain.com/*"
block_private_ips: true
require_https: true
Security Configuration
The module includes built-in security controls to protect against SSRF and other vulnerabilities.
security.url_allowlist
array<string>
default:"['*']"
List of URL patterns allowed for HTTP function invocations. Supports wildcards.Examples:
https://api.example.com/* - Allow all endpoints under api.example.com
https://*.example.com/* - Allow all subdomains of example.com
* - Allow all URLs (use with caution)
security.block_private_ips
Block requests to private IP ranges (RFC 1918) to prevent SSRF attacks.Blocked ranges:
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
127.0.0.0/8
169.254.0.0/16
Require HTTPS for all HTTP function invocations. Rejects HTTP URLs.
The default security settings (block_private_ips: true, require_https: true) are recommended for production deployments to prevent SSRF vulnerabilities.
Registering HTTP Functions
HTTP functions are registered dynamically at runtime using the register_http_function method:
import { HttpFunctionConfig } from 'iii';
// Register an HTTP endpoint as a function
const config: HttpFunctionConfig = {
function_path: 'external.getUser',
url: 'https://api.example.com/users/{userId}',
method: 'GET',
description: 'Fetch user details from external API',
timeout_ms: 5000,
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.API_KEY,
},
};
await httpFunctionsModule.register_http_function(config);
HttpFunctionConfig
The function ID to register. Used when calling the function via client.call().
The HTTP endpoint URL. Supports path parameters using {param} syntax.Must pass URL validation against the allowlist.
HTTP method: GET, POST, PUT, PATCH, DELETE
Optional description of the function’s purpose
Request timeout in milliseconds
HTTP headers to include in the request. Common use cases:
- Authentication:
Authorization: Bearer {token}
- Content type:
Content-Type: application/json
- API keys:
X-API-Key: {key}
JSON schema for request validation
JSON schema for response validation
Arbitrary metadata attached to the function
Invoking HTTP Functions
Once registered, HTTP functions are called like any other function:
// Call the registered HTTP function
const result = await client.call('external.getUser', {
userId: 123,
expand: 'profile',
});
console.log(result);
// { id: 123, name: 'Alice', email: '[email protected]', ... }
Path Parameters
URL path parameters are extracted from the input data:
// URL: https://api.example.com/users/{userId}/posts/{postId}
const result = await client.call('external.getUserPost', {
userId: 123,
postId: 456,
});
// Makes request to: https://api.example.com/users/123/posts/456
Query Parameters
Non-path parameters are added as query strings:
// URL: https://api.example.com/users/{userId}
const result = await client.call('external.getUser', {
userId: 123,
expand: 'profile',
fields: 'name,email',
});
// Makes request to: https://api.example.com/users/123?expand=profile&fields=name,email
Request Body
For POST/PUT/PATCH requests, the input is sent as the request body:
// URL: https://api.example.com/users
// Method: POST
const result = await client.call('external.createUser', {
name: 'Bob',
email: '[email protected]',
});
// Sends POST request with JSON body: { name: 'Bob', email: '[email protected]' }
Authentication
The module supports various authentication methods:
const config = {
function_path: 'external.api',
url: 'https://api.example.com/data',
method: 'GET',
headers: {
'X-API-Key': process.env.API_KEY,
},
};
Bearer Token
const config = {
function_path: 'external.api',
url: 'https://api.example.com/data',
method: 'GET',
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
},
};
Basic Auth
const config = {
function_path: 'external.api',
url: 'https://api.example.com/data',
method: 'GET',
auth: {
type: 'basic',
username: 'user',
password: 'pass',
},
};
OAuth 2.0
const config = {
function_path: 'external.api',
url: 'https://api.example.com/data',
method: 'GET',
auth: {
type: 'oauth2',
token_url: 'https://auth.example.com/token',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
},
};
Unregistering Functions
Remove HTTP functions when they’re no longer needed:
await httpFunctionsModule.unregister_http_function('external.getUser');
Use Cases
External API Integration
// Register multiple endpoints from the same API
await httpFunctionsModule.register_http_function({
function_path: 'github.getUser',
url: 'https://api.github.com/users/{username}',
method: 'GET',
headers: {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
},
});
await httpFunctionsModule.register_http_function({
function_path: 'github.listRepos',
url: 'https://api.github.com/users/{username}/repos',
method: 'GET',
headers: {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
},
});
// Use in your functions
export async function analyzeUser(input: { username: string }) {
const user = await client.call('github.getUser', input);
const repos = await client.call('github.listRepos', input);
return {
user,
repoCount: repos.length,
languages: repos.map(r => r.language),
};
}
Microservice Communication
// Register internal microservices as functions
await httpFunctionsModule.register_http_function({
function_path: 'auth.validateToken',
url: 'http://auth-service:3000/validate',
method: 'POST',
timeout_ms: 2000,
});
await httpFunctionsModule.register_http_function({
function_path: 'billing.createInvoice',
url: 'http://billing-service:3000/invoices',
method: 'POST',
});
Webhook Forwarding
// Forward webhook data to external service
await httpFunctionsModule.register_http_function({
function_path: 'slack.sendMessage',
url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
method: 'POST',
});
export async function onOrderCreated(data: any) {
await client.call('slack.sendMessage', {
text: `New order #${data.orderId} for $${data.amount}`,
});
}
Error Handling
URL Validation Errors
// Fails if URL not in allowlist
await httpFunctionsModule.register_http_function({
function_path: 'bad.function',
url: 'https://blocked-domain.com/api',
method: 'GET',
});
// Returns: { code: 'url_validation_failed', message: '...' }
Request Failures
const result = await client.call('external.api', { ... });
if (result.error) {
console.error('HTTP request failed:', result.error);
// Handle timeout, network errors, etc.
}
Security Best Practices
- Use allowlists - Restrict HTTP functions to trusted domains
- Enable HTTPS - Set
require_https: true in production
- Block private IPs - Keep
block_private_ips: true to prevent SSRF
- Rotate credentials - Store API keys in environment variables, not hardcoded
- Validate responses - Use
response_format to validate external API responses
- Set timeouts - Prevent hanging requests with appropriate
timeout_ms values
- Rate limiting - Implement rate limiting for external API calls
- Monitor usage - Track HTTP function invocations for unusual patterns
Production Configuration
modules:
- class: modules::http_functions::HttpFunctionsModule
config:
security:
# Strict allowlist for production
url_allowlist:
- "https://api.stripe.com/*"
- "https://api.github.com/*"
- "https://*.googleapis.com/*"
# Prevent SSRF attacks
block_private_ips: true
# Enforce HTTPS
require_https: true
Development Configuration
modules:
- class: modules::http_functions::HttpFunctionsModule
config:
security:
# Allow all URLs in development
url_allowlist:
- "*"
# Allow localhost/private IPs for local services
block_private_ips: false
# Allow HTTP for local testing
require_https: false
Never use the development configuration in production. Always enforce strict security controls.
Limitations
- Synchronous only - HTTP functions are invoked synchronously
- No streaming - Response must fit in memory
- No retries - Failed requests do not retry automatically
- No caching - Responses are not cached (implement caching in your code)
API Reference
HttpInvoker
The underlying HTTP invoker is accessible via:
const invoker = httpFunctionsModule.http_invoker();
Registered Functions
Access the registry of HTTP functions:
const functions = httpFunctionsModule.http_functions();
for (const [path, config] of functions.entries()) {
console.log(`${path}: ${config.url}`);
}