Skip to main content
Writeback functions let your workflow send data to external systems. Slung provides two writeback mechanisms: WebSocket for connected clients and HTTP for external APIs.

WebSocket Writeback

Send data to WebSocket clients connected to the Slung runtime.

Function

writeback_ws(destination, "ALERT: threshold exceeded")?;
Function signature:
pub fn writeback_ws(destination: u64, data: &str) -> Result<()>
Parameters:
  • destination: u64 - Producer connection ID
  • data: &str - Text data to send
Returns:
  • Ok(()) - Message sent successfully
  • Err(_) - Failed to send message
Host function:
unsafe extern "C" {
    fn u_writeback_ws(producer_ptr: u64, data_ptr: usize) -> u32;
}
Returns 0 on success, non-zero on failure.

Getting Producer IDs

Producer IDs come from the Event structure:
pub struct Event {
    pub timestamp: i64,
    pub value: f64,
    pub tags: Vec<String>,
    pub producers: Vec<u64>,  // WebSocket connection IDs
}

Example: Alert System

use slung::prelude::*;

#[main]
fn main() -> Result<()> {
    let handle = query_live("AVG:temp:[sensor=1]")?;
    poll_handle(handle, on_event, 100.0)?;
    Ok(())
}

fn on_event(event: Event, alert_threshold: f64) -> Result<()> {
    if event.value > alert_threshold {
        println!("event timestamp={} value={}", event.timestamp, event.value);
        for producer in event.producers {
            writeback_ws(producer, "ALERT: threshold exceeded")?;
        }
    }
    Ok(())
}

Example: JSON Response

use slung::prelude::*;
use serde_json::json;

fn on_event(event: Event, _: ()) -> Result<()> {
    let response = json!({
        "timestamp": event.timestamp,
        "value": event.value,
        "status": if event.value > 100.0 { "high" } else { "normal" }
    });
    
    for producer in event.producers {
        writeback_ws(producer, &response.to_string())?;
    }
    
    Ok(())
}

Broadcasting to Multiple Clients

The producers field contains all connection IDs that contributed to the aggregate:
fn broadcast_alert(event: Event, message: &str) -> Result<()> {
    println!("Broadcasting to {} clients", event.producers.len());
    
    for producer in event.producers {
        match writeback_ws(producer, message) {
            Ok(_) => println!("Sent to producer {}", producer),
            Err(e) => println!("Failed to send to producer {}: {}", producer, e),
        }
    }
    
    Ok(())
}

HTTP Writeback

Send HTTP requests to external APIs and receive responses.

Function

let response = writeback_http(
    "https://api.example.com/alerts",
    r#"{"level":"critical","value":105.3}"#,
    WritebackMethod::POST
)?;
Function signature:
pub fn writeback_http(
    destination: &str,
    data: &str,
    method: WritebackMethod
) -> Result<Option<Vec<u8>>>
Parameters:
  • destination: &str - URL to send request to
  • data: &str - Request body
  • method: WritebackMethod - HTTP method
Returns:
  • Ok(Some(Vec<u8>)) - Response body as bytes
  • Ok(None) - No response body or request failed
Host function:
unsafe extern "C" {
    fn u_writeback_http(url_ptr: usize, data_ptr: usize, method_ptr: u32) -> usize;
}
Returns a pointer to a response Region, or 0 when there’s no response body or the request fails.

HTTP Methods

pub enum WritebackMethod {
    GET,     // 0
    POST,    // 1
    PUT,     // 2
    DELETE,  // 3
}

Example: POST Alert

use slung::prelude::*;

#[main]
fn main() -> Result<()> {
    let handle = query_live("AVG:cpu:[host=prod]:[5min]")?;
    poll_handle(handle, on_event, 80.0)?;
    Ok(())
}

fn on_event(event: Event, threshold: f64) -> Result<()> {
    if event.value > threshold {
        let payload = format!(
            r#"{{"timestamp":{},"value":{},"threshold":{}}}"#,
            event.timestamp, event.value, threshold
        );
        
        let response = writeback_http(
            "https://api.example.com/alerts",
            &payload,
            WritebackMethod::POST
        )?;
        
        if let Some(body) = response {
            println!("Alert sent, response: {:?}", String::from_utf8_lossy(&body));
        } else {
            println!("Alert sent, no response body");
        }
    }
    
    Ok(())
}

Example: GET External Data

use slung::prelude::*;

fn fetch_config() -> Result<Option<String>> {
    let response = writeback_http(
        "https://api.example.com/config",
        "",
        WritebackMethod::GET
    )?;
    
    match response {
        Some(body) => Ok(Some(String::from_utf8_lossy(&body).to_string())),
        None => Ok(None),
    }
}

Example: PUT Update

use slung::prelude::*;

fn update_metric(metric_id: &str, value: f64) -> Result<()> {
    let url = format!("https://api.example.com/metrics/{}", metric_id);
    let payload = format!(r#"{{"value":{}}}"#, value);
    
    writeback_http(&url, &payload, WritebackMethod::PUT)?;
    
    Ok(())
}

Example: DELETE Resource

use slung::prelude::*;

fn delete_stale_alert(alert_id: &str) -> Result<()> {
    let url = format!("https://api.example.com/alerts/{}", alert_id);
    
    writeback_http(&url, "", WritebackMethod::DELETE)?;
    
    Ok(())
}

Error Handling

WebSocket Errors

for producer in event.producers {
    match writeback_ws(producer, "message") {
        Ok(_) => {},
        Err(e) => {
            println!("Failed to send to producer {}: {}", producer, e);
            // Continue trying other producers
        }
    }
}

HTTP Errors

match writeback_http(url, data, WritebackMethod::POST)? {
    Some(body) => {
        // Got response
        println!("Response: {:?}", String::from_utf8_lossy(&body));
    }
    None => {
        // No response body or request failed
        // Cannot distinguish between these cases
        println!("Warning: no response (may indicate failure)");
    }
}

Advanced Patterns

Dual Writeback

Send to both WebSocket clients and HTTP endpoints:
fn on_event(event: Event, threshold: f64) -> Result<()> {
    if event.value > threshold {
        let message = format!("Alert: value {} exceeds threshold {}", event.value, threshold);
        
        // Notify connected clients
        for producer in &event.producers {
            writeback_ws(*producer, &message)?;
        }
        
        // Log to external system
        let payload = format!(
            r#"{{"timestamp":{},"value":{},"producers":{}}}"#,
            event.timestamp, event.value, event.producers.len()
        );
        writeback_http(
            "https://api.example.com/logs",
            &payload,
            WritebackMethod::POST
        )?;
    }
    
    Ok(())
}

Conditional Writeback

fn on_event(event: Event, _: ()) -> Result<()> {
    match event.tags.iter().find(|t| t.starts_with("severity=")) {
        Some(tag) if tag.contains("critical") => {
            // Critical: HTTP alert
            writeback_http(
                "https://pagerduty.example.com/alert",
                &format!(r#"{{"value":{}}}"#, event.value),
                WritebackMethod::POST
            )?;
        }
        Some(tag) if tag.contains("warning") => {
            // Warning: WebSocket notification
            for producer in event.producers {
                writeback_ws(producer, "Warning level reached")?;
            }
        }
        _ => {
            // Info: no action
        }
    }
    
    Ok(())
}

Rate Limiting

use std::time::{Duration, Instant};

static mut LAST_HTTP_CALL: Option<Instant> = None;
const MIN_INTERVAL: Duration = Duration::from_secs(60);

fn rate_limited_writeback(data: &str) -> Result<()> {
    unsafe {
        let now = Instant::now();
        
        if let Some(last) = LAST_HTTP_CALL {
            if now.duration_since(last) < MIN_INTERVAL {
                println!("Rate limit: skipping HTTP call");
                return Ok(());
            }
        }
        
        writeback_http(
            "https://api.example.com/metrics",
            data,
            WritebackMethod::POST
        )?;
        
        LAST_HTTP_CALL = Some(now);
        Ok(())
    }
}

Best Practices

  1. Handle failures gracefully: Writeback can fail if connections close or HTTP requests timeout
  2. Don’t block on HTTP: HTTP writeback is synchronous and can slow your workflow
  3. Batch when possible: Aggregate multiple events before writing back
  4. Use appropriate protocols: WebSocket for real-time client notifications, HTTP for external integrations
  5. Include context: Send enough information (timestamp, tags) for receivers to process events
  6. Log failures: Track which producers or endpoints fail to help debug connectivity issues

Next Steps

Live Queries

Learn about live query subscriptions

Historical Queries

Query aggregated historical data

Build docs developers (and LLMs) love