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