HTTP Client
mik-sdk provides an HTTP client for making outbound requests to external services. Enable it with the http-client feature.
Required Imports
Section titled “Required Imports”#[allow(warnings)]mod bindings;
use bindings::exports::mik::core::handler::{self, Guest, Response};use mik_sdk::prelude::*;The fetch! macro is included in mik_sdk::prelude::* by default.
Basic Requests
Section titled “Basic Requests”GET Request
Section titled “GET Request”fn fetch_data(_req: &Request) -> Response { let response = fetch!(GET "https://api.example.com/data") .send()?;
if response.is_success() { ok!({ "data": response.json() }) } else { error! { status: 502, title: "Upstream Error", detail: format!("API returned {}", response.status()) } }}POST with JSON Body
Section titled “POST with JSON Body”fn create_item(_req: &Request) -> Response { let response = fetch!(POST "https://api.example.com/items", json: { "name": "Widget", "price": 19.99 }).send()?;
if response.is_success() { ok!({ "created": true }) } else { error! { status: 500, title: "Failed", detail: "Could not create item" } }}Other HTTP Methods
Section titled “Other HTTP Methods”// PUTfetch!(PUT "https://api.example.com/items/123", json: { "name": "Updated Widget"}).send()?;
// PATCHfetch!(PATCH "https://api.example.com/items/123", json: { "price": 24.99}).send()?;
// DELETEfetch!(DELETE "https://api.example.com/items/123").send()?;Request Options
Section titled “Request Options”Headers
Section titled “Headers”let response = fetch!(GET "https://api.example.com/protected", headers: { "Authorization": format!("Bearer {}", token), "X-Custom-Header": "value" }).send()?;Timeout
Section titled “Timeout”Set a timeout in milliseconds:
let response = fetch!(GET "https://slow-api.example.com/data", timeout: 5000 // 5 seconds).send()?;Raw Body
Section titled “Raw Body”For non-JSON payloads:
let response = fetch!(POST "https://api.example.com/upload", headers: { "Content-Type": "text/plain" }, body: b"Raw text content").send()?;Combined Options
Section titled “Combined Options”let response = fetch!(POST "https://api.example.com/data", headers: { "Authorization": format!("Bearer {}", token), "X-Request-ID": random::uuid() }, json: { "name": "Example", "value": 42 }, timeout: 10000).send()?;Response Handling
Section titled “Response Handling”Response Object
Section titled “Response Object”let response = fetch!(GET "https://api.example.com/data").send()?;
// Status codelet status = response.status(); // u16
// Check success (2xx)if response.is_success() { // ...}
// Body as byteslet body = response.bytes(); // &[u8]
// Parse JSONif let Some(json) = response.json() { let value = json.path_str(&["field"]);}
// Headerslet content_type = response.header("Content-Type");Error Handling
Section titled “Error Handling”fn call_api(_req: &Request) -> Response { match fetch!(GET "https://api.example.com/data").send() { Ok(response) => { if response.is_success() { ok!({ "data": response.json() }) } else { error! { status: response.status(), title: "API Error", detail: format!("Upstream returned {}", response.status()) } } } Err(_) => { error! { status: 503, title: "Service Unavailable", detail: "Could not reach upstream service" } } }}Security Features
Section titled “Security Features”SSRF Protection
Section titled “SSRF Protection”Block requests to private IP addresses when using user-provided URLs:
fn fetch_url(body: UrlInput, _req: &Request) -> Response { // User-provided URL - enable SSRF protection let response = fetch!(GET &body.url) .deny_private_ips() // Blocks localhost, 10.x, 192.168.x, etc. .send()?;
ok!({ "fetched": true })}Blocked addresses include:
127.0.0.0/8(loopback)10.0.0.0/8(private)172.16.0.0/12(private)192.168.0.0/16(private)169.254.0.0/16(link-local)::1(IPv6 loopback)- Private IPv6 ranges
Trace ID Propagation
Section titled “Trace ID Propagation”Forward trace IDs for distributed tracing:
fn call_downstream(req: &Request) -> Response { let trace_id = req.trace_id_or(""); let response = fetch!(GET "https://api.example.com/data") .with_trace_id(if trace_id.is_empty() { None } else { Some(trace_id) }) .send()?;
ok!({ "traced": true })}This adds the traceparent header (W3C Trace Context) to outbound requests if present in the incoming request.
Dynamic URLs
Section titled “Dynamic URLs”Use string interpolation for dynamic URLs:
fn get_user(path: Id, _req: &Request) -> Response { let url = format!("https://api.example.com/users/{}", path.as_str());
let response = fetch!(GET &url).send()?;
ok!({ "data": response.json() })}Or inline:
let response = fetch!(GET format!("https://api.example.com/users/{}", user_id)) .send()?;Complete Example
Section titled “Complete Example”fn create_order(body: OrderInput, req: &Request) -> Response { // Validate order guard!(body.items.len() > 0, 400, "Order must have items");
let trace_id = req.trace_id_or(""); let trace_opt = if trace_id.is_empty() { None } else { Some(trace_id) };
// Call inventory service let inventory = fetch!(POST "http://inventory:8080/reserve", headers: { "X-Request-ID": random::uuid() }, json: { "items": body.items }, timeout: 5000 ) .with_trace_id(trace_opt) .send();
let inv_response = ensure!(inventory.ok(), 503, "Inventory service unavailable"); guard!(inv_response.is_success(), 409, "Items not available");
// Call payment service let payment = fetch!(POST "http://payments:8080/charge", json: { "amount": body.total, "customer_id": body.customer_id }, timeout: 10000 ) .with_trace_id(trace_opt) .send();
let pay_response = ensure!(payment.ok(), 503, "Payment service unavailable"); guard!(pay_response.is_success(), 402, "Payment failed");
// Create order let order_id = random::uuid(); log!(info, "order created", order_id: &order_id, customer: &body.customer_id);
created!(format!("/orders/{}", order_id), { "id": order_id, "status": "confirmed" })}API Summary
Section titled “API Summary”fetch! Macro Options
Section titled “fetch! Macro Options”| Option | Description |
|---|---|
headers: { ... } | HTTP headers |
json: { ... } | JSON body (sets Content-Type) |
body: bytes | Raw body bytes |
timeout: ms | Timeout in milliseconds |
Builder Methods
Section titled “Builder Methods”| Method | Description |
|---|---|
.send() | Execute the request |
.deny_private_ips() | Enable SSRF protection |
.with_trace_id(opt) | Add traceparent header |
Response Methods
Section titled “Response Methods”| Method | Returns | Description |
|---|---|---|
status() | u16 | HTTP status code |
is_success() | bool | True if 2xx |
body() | Vec<u8> | Response body |
header(name) | Option<&str> | Response header |