{"id":49659347,"url":"https://github.com/sockudo/sockudo-http-rust","last_synced_at":"2026-05-06T11:04:44.896Z","repository":{"id":348024141,"uuid":"1195300610","full_name":"sockudo/sockudo-http-rust","owner":"sockudo","description":"Sockudo Rust HTTP server SDK","archived":false,"fork":false,"pushed_at":"2026-04-20T18:25:29.000Z","size":219,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T20:30:21.464Z","etag":null,"topics":["http","realtime","rust","sdk","sockudo"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sockudo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T13:57:55.000Z","updated_at":"2026-04-20T18:25:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sockudo/sockudo-http-rust","commit_stats":null,"previous_names":["sockudo/sockudo-http-rust"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sockudo/sockudo-http-rust","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo-http-rust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo-http-rust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo-http-rust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo-http-rust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sockudo","download_url":"https://codeload.github.com/sockudo/sockudo-http-rust/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo-http-rust/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32690542,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T08:33:17.875Z","status":"ssl_error","status_checked_at":"2026-05-06T08:33:17.221Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","realtime","rust","sdk","sockudo"],"created_at":"2026-05-06T11:04:31.412Z","updated_at":"2026-05-06T11:04:44.891Z","avatar_url":"https://github.com/sockudo.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pusher HTTP Rust Client\n\nA fast, safe, and idiomatic Rust client for interacting with the Pusher HTTP API, allowing you to publish events, authorize channels, authenticate users, and handle webhooks from your Rust applications.\n\n## Features\n\n- Trigger events on public, private, and presence channels\n- Trigger events to specific users (User Authentication)\n- Trigger batch events for efficiency\n- Support for end-to-end encrypted channels\n- Authorize client subscriptions to private, presence, and encrypted channels\n- Authenticate users for user-specific Pusher features\n- Terminate user connections\n- Validate and process incoming Pusher webhooks\n- Configurable host, port, scheme (HTTP/HTTPS), and timeout\n- Asynchronous API using `async/await`\n- Typed responses and errors\n- **Fast JSON** with SIMD-accelerated `sonic-rs` library\n\n## Installation\n\nAdd the following to your `Cargo.toml`:\n\n```toml\n[dependencies]\npushers = \"1.4.0\"\nsonic-rs = \"0.5\"\ntokio = { version = \"1\", features = [\"full\"] }\n```\n\nThen run:\n\n```bash\ncargo build\n```\n\n## Usage\n\n### 1. Initialization\n\nConfigure and create a `Pusher` client:\n\n```rust\nuse pushers::{Config, Pusher, PusherError};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), PusherError\u003e {\n    let config = Config::builder()\n        .app_id(\"YOUR_APP_ID\")\n        .key(\"YOUR_APP_KEY\")\n        .secret(\"YOUR_APP_SECRET\")\n        .host(\"127.0.0.1\")\n        .port(6001)\n        .use_tls(false)\n        .timeout(std::time::Duration::from_secs(5)) // Optional\n        .build()?;\n\n    let pusher = Pusher::new(config)?;\n\n    // Your application logic here...\n    Ok(())\n}\n```\n\nFor encrypted channels, add the encryption master key:\n\n```rust\nlet config = Config::builder()\n    .app_id(\"YOUR_APP_ID\")\n    .key(\"YOUR_APP_KEY\")\n    .secret(\"YOUR_APP_SECRET\")\n    .host(\"127.0.0.1\")\n    .port(6001)\n    .use_tls(false)\n    .encryption_master_key_base64(\"YOUR_BASE64_ENCRYPTION_MASTER_KEY\")?\n    .build()?;\n```\n\nYou can also initialize from a URL:\n\n```rust\nuse pushers::{Pusher, PusherError};\n\nlet pusher = Pusher::from_url(\n    \"http://YOUR_APP_KEY:YOUR_APP_SECRET@127.0.0.1:6001/apps/YOUR_APP_ID\",\n    None,\n)?;\n```\n\n### 2. Triggering Events\n\n```rust\nuse pushers::{Pusher, Channel, PusherError};\nuse sonic_rs::json;\n\nasync fn trigger_event(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let channels = vec![Channel::from_string(\"my-channel\")?];\n    let event_name = \"new-message\";\n    let data = json!({ \"text\": \"Hello from Rust!\" });\n\n    match pusher.trigger(\u0026channels, event_name, data, None).await {\n        Ok(response) =\u003e {\n            println!(\"Event triggered! Status: {}\", response.status());\n        }\n        Err(e) =\u003e eprintln!(\"Error triggering event: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n**Encrypted channels:**\nIf `channels` contains a single encrypted channel (e.g., `\"private-encrypted-mychannel\"`) and you've set the `encryption_master_key` in the `Config`, the library will encrypt `data` automatically.\n\n**Excluding a recipient:**\n\n```rust\nuse pushers::{Pusher, Channel, PusherError, events::TriggerParams};\nuse sonic_rs::json;\n\nasync fn trigger_event_exclude(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let channels = vec![Channel::from_string(\"my-channel\")?];\n    let event_name = \"new-message\";\n    let data = json!({ \"text\": \"Hello from Rust!\" });\n    \n    let params = TriggerParams::builder()\n        .socket_id(\"socket_id_to_exclude\")\n        .build();\n\n    pusher.trigger(\u0026channels, event_name, data, Some(params)).await?;\n    Ok(())\n}\n```\n\n**Idempotency key:**\n\nAttach an idempotency key so the server deduplicates the event on retries, ensuring at-most-once delivery:\n\n```rust\nlet params = TriggerParams::builder()\n    .idempotency_key(\"unique-key-for-this-event\")\n    .build();\n\npusher.trigger(\u0026channels, event_name, data, Some(params)).await?;\n```\n\n### 3. Triggering Batch Events\n\n```rust\nuse pushers::{Pusher, PusherError, events::BatchEvent};\nuse sonic_rs::json;\n\nasync fn trigger_batch(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let batch = vec![\n        BatchEvent::new(\"event1\", \"channel-a\", json!({ \"value\": 1 })),\n        BatchEvent::new(\"event2\", \"channel-b\", json!({ \"value\": 2 })),\n    ];\n\n    match pusher.trigger_batch(batch).await {\n        Ok(response) =\u003e println!(\"Batch triggered! Status: {}\", response.status()),\n        Err(e) =\u003e eprintln!(\"Error triggering batch: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n### 4. Tag Filtering\n\nTag filtering allows you to add metadata tags to events, enabling clients to filter which events they receive based on tag values. This can significantly reduce bandwidth usage (60-90%) in high-volume scenarios.\n\n**Triggering a single event with tags:**\n\n```rust\nuse pushers::{Pusher, Channel, PusherError, events::TriggerParams};\nuse sonic_rs::json;\nuse std::collections::HashMap;\n\nasync fn trigger_with_tags(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let channels = vec![Channel::from_string(\"sports-updates\")?];\n    let event_name = \"match-event\";\n    let data = json!({\n        \"match_id\": \"123\",\n        \"team\": \"Home\",\n        \"player\": \"John Doe\",\n        \"minute\": 45\n    });\n\n    // Create tags for filtering\n    let mut tags = HashMap::new();\n    tags.insert(\"event_type\".to_string(), \"goal\".to_string());\n    tags.insert(\"priority\".to_string(), \"high\".to_string());\n\n    let params = TriggerParams::builder()\n        .tags(tags)\n        .build();\n\n    pusher.trigger(\u0026channels, event_name, data, Some(params)).await?;\n    Ok(())\n}\n```\n\n**Triggering batch events with tags:**\n\n```rust\nuse pushers::{Pusher, PusherError, events::BatchEvent};\nuse sonic_rs::json;\nuse std::collections::HashMap;\n\nasync fn trigger_batch_with_tags(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let mut goal_tags = HashMap::new();\n    goal_tags.insert(\"event_type\".to_string(), \"goal\".to_string());\n    goal_tags.insert(\"priority\".to_string(), \"high\".to_string());\n\n    let mut shot_tags = HashMap::new();\n    shot_tags.insert(\"event_type\".to_string(), \"shot\".to_string());\n    shot_tags.insert(\"xG\".to_string(), \"0.85\".to_string());\n\n    let batch = vec![\n        BatchEvent::new(\"match-event\", \"match:123\", json!({ \"type\": \"goal\", \"player\": \"Smith\" }))\n            .with_tags(goal_tags),\n        BatchEvent::new(\"match-event\", \"match:123\", json!({ \"type\": \"shot\", \"player\": \"Jones\" }))\n            .with_tags(shot_tags),\n    ];\n\n    pusher.trigger_batch(batch).await?;\n    Ok(())\n}\n```\n\n**Note:** Tag filtering must be enabled on the Sockudo server (`TAG_FILTERING_ENABLED=true`) for clients to filter events. Tags are key-value pairs where both keys and values are strings. Clients can subscribe with filter expressions to receive only events matching their criteria.\n\n### 5. Authorizing Channels\n\nTypically done in your HTTP handler when a client attempts to subscribe:\n\n```rust\nuse pushers::{Pusher, Channel, PusherError};\nuse sonic_rs::json;\n\nfn authorize_channel(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let socket_id = \"123.456\";\n    let channel_name = \"private-mychannel\";\n    let channel = Channel::from_string(channel_name)?;\n\n    // For presence channels, include user data:\n    let presence_data = Some(json!({\n        \"user_id\": \"unique_user_id\",\n        \"user_info\": { \"name\": \"Alice\" }\n    }));\n\n    match pusher.authorize_channel(socket_id, \u0026channel, presence_data.as_ref()) {\n        Ok(auth_signature) =\u003e {\n            println!(\"Auth success: {:?}\", auth_signature);\n            // Return `auth_signature` as JSON to client\n        }\n        Err(e) =\u003e eprintln!(\"Auth error: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n### 6. Authenticating Users\n\nFor server-to-user events:\n\n```rust\nuse pushers::{Pusher, PusherError};\nuse sonic_rs::json;\n\nfn authenticate_user(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let socket_id = \"789.012\";\n    let user_data = json!({\n        \"id\": \"user-bob\",      // required\n        \"name\": \"Bob The Builder\",\n        \"email\": \"bob@example.com\"\n    });\n\n    match pusher.authenticate_user(socket_id, \u0026user_data) {\n        Ok(user_auth) =\u003e {\n            println!(\"User auth success: {:?}\", user_auth);\n            // Return `user_auth` as JSON to client\n        }\n        Err(e) =\u003e eprintln!(\"User auth error: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n### 7. Sending an Event to a User\n\n```rust\nuse pushers::{Pusher, PusherError};\nuse sonic_rs::json;\n\nasync fn send_to_user(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let user_id = \"user-bob\";\n    let event_name = \"personal-notification\";\n    let data = json!({ \"alert\": \"Your report is ready!\" });\n\n    match pusher.send_to_user(user_id, event_name, data).await {\n        Ok(response) =\u003e println!(\"Sent to user! Status: {}\", response.status()),\n        Err(e) =\u003e eprintln!(\"Error sending to user: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n### 8. Terminating User Connections\n\n```rust\nuse pushers::{Pusher, PusherError};\n\nasync fn terminate_user(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let user_id = \"user-charlie\";\n\n    match pusher.terminate_user_connections(user_id).await {\n        Ok(response) =\u003e println!(\"Terminate successful! Status: {}\", response.status()),\n        Err(e) =\u003e eprintln!(\"Error terminating user: {:?}\", e),\n    }\n    Ok(())\n}\n```\n\n### 9. Handling Webhooks\n\n```rust\nuse pushers::{Pusher, PusherError, webhook::WebhookEvent};\nuse std::collections::BTreeMap;\n\nfn handle_webhook(pusher: \u0026Pusher) -\u003e Result\u003c(), PusherError\u003e {\n    let mut headers = BTreeMap::new();\n    headers.insert(\"X-Pusher-Key\".to_string(), \"YOUR_APP_KEY\".to_string());\n    headers.insert(\"X-Pusher-Signature\".to_string(), \"RECEIVED_SIGNATURE\".to_string());\n    headers.insert(\"Content-Type\".to_string(), \"application/json\".to_string());\n\n    let body = r#\"{\n        \"time_ms\": 1600000000000,\n        \"events\":[{\"name\":\"channel_occupied\",\"channel\":\"my-channel\"}]\n    }\"#;\n\n    let webhook = pusher.webhook(\u0026headers, body);\n\n    if webhook.is_valid(None) {\n        println!(\"Webhook is valid!\");\n        match webhook.get_events() {\n            Ok(events) =\u003e {\n                for event in events {\n                    match event {\n                        WebhookEvent::ChannelOccupied { channel } =\u003e {\n                            println!(\"Channel occupied: {}\", channel);\n                        }\n                        WebhookEvent::ChannelVacated { channel } =\u003e {\n                            println!(\"Channel vacated: {}\", channel);\n                        }\n                        WebhookEvent::MemberAdded { channel, user_id } =\u003e {\n                            println!(\"Member {} added to {}\", user_id, channel);\n                        }\n                        WebhookEvent::MemberRemoved { channel, user_id } =\u003e {\n                            println!(\"Member {} removed from {}\", user_id, channel);\n                        }\n                        _ =\u003e {}\n                    }\n                }\n            }\n            Err(e) =\u003e eprintln!(\"Error getting events: {:?}\", e),\n        }\n    } else {\n        eprintln!(\"Invalid webhook!\");\n    }\n    Ok(())\n}\n```\n\n### 10. Example: Integration with Axum\n\n```rust\nuse axum::{\n    extract::{Json, State},\n    http::{HeaderMap, StatusCode},\n    response::IntoResponse,\n    routing::post,\n    Router,\n};\nuse pushers::{Config, Pusher, Channel};\nuse serde::Deserialize;\nuse sonic_rs::{json, Value};\nuse std::{collections::BTreeMap, sync::Arc};\n\n#[derive(Clone)]\nstruct AppState {\n    pusher: Arc\u003cPusher\u003e,\n}\n\n#[tokio::main]\nasync fn main() {\n    let config = Config::builder()\n        .app_id(\"YOUR_APP_ID\")\n        .key(\"YOUR_APP_KEY\")\n        .secret(\"YOUR_APP_SECRET\")\n        .host(\"127.0.0.1\")\n        .port(6001)\n        .use_tls(false)\n        .build()\n        .expect(\"Failed to build config\");\n\n    let pusher = Arc::new(Pusher::new(config).expect(\"Failed to create client\"));\n    let app_state = AppState { pusher };\n\n    let app = Router::new()\n        .route(\"/pusher/auth\", post(pusher_auth_handler))\n        .route(\"/pusher/webhook\", post(pusher_webhook_handler))\n        .with_state(app_state);\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    println!(\"Listening on {}\", listener.local_addr().unwrap());\n    axum::serve(listener, app).await.unwrap();\n}\n\n#[derive(Deserialize)]\nstruct AuthRequest {\n    socket_id: String,\n    channel_name: String,\n    #[serde(alias = \"channel_data\")]\n    presence_data: Option\u003cValue\u003e,\n}\n\nasync fn pusher_auth_handler(\n    State(state): State\u003cAppState\u003e,\n    Json(payload): Json\u003cAuthRequest\u003e,\n) -\u003e impl IntoResponse {\n    let channel = match Channel::from_string(\u0026payload.channel_name) {\n        Ok(ch) =\u003e ch,\n        Err(_) =\u003e {\n            return (\n                StatusCode::BAD_REQUEST,\n                Json(json!({\"error\": \"Invalid channel_name\"})),\n            ).into_response();\n        }\n    };\n\n    match state.pusher.authorize_channel(\n        \u0026payload.socket_id,\n        \u0026channel,\n        payload.presence_data.as_ref(),\n    ) {\n        Ok(auth_response) =\u003e (StatusCode::OK, Json(auth_response)).into_response(),\n        Err(e) =\u003e {\n            eprintln!(\"Auth error: {:?}\", e);\n            (StatusCode::FORBIDDEN, Json(json!({ \"error\": \"Forbidden\" }))).into_response()\n        }\n    }\n}\n\nasync fn pusher_webhook_handler(\n    State(state): State\u003cAppState\u003e,\n    headers: HeaderMap,\n    body: String,\n) -\u003e impl IntoResponse {\n    let mut hdrs_btreemap = BTreeMap::new();\n    for (k, v) in headers.iter() {\n        if let Ok(s) = v.to_str() {\n            hdrs_btreemap.insert(k.as_str().to_string(), s.to_string());\n        }\n    }\n\n    let webhook = state.pusher.webhook(\u0026hdrs_btreemap, \u0026body);\n\n    if webhook.is_valid(None) {\n        (StatusCode::OK, Json(json!({ \"status\": \"ok\" }))).into_response()\n    } else {\n        (StatusCode::UNAUTHORIZED, Json(json!({ \"error\": \"Unauthorized\" }))).into_response()\n    }\n}\n```\n\n## Configuration Options\n\nThe `Config` struct is used to configure the Pusher client. Create it using `Config::builder()`:\n\n| Method | Description |\n|--------|-------------|\n| `app_id(id)` | Sets the Pusher app ID (required) |\n| `key(key)` | Sets the Pusher app key (required) |\n| `secret(secret)` | Sets the Pusher app secret (required) |\n| `cluster(name)` | Sets a named cluster (e.g., `\"eu\"`, `\"ap1\"`). For self-hosted Sockudo, use `host` and `port` directly instead. |\n| `host(host)` | Sets a custom host if not using a standard cluster |\n| `use_tls(bool)` | Enable HTTPS (default: `true`) |\n| `port(number)` | Custom port |\n| `timeout(duration)` | HTTP request timeout |\n| `encryption_master_key(key)` | Sets the 32-byte encryption master key from raw bytes |\n| `encryption_master_key_base64(key)` | Sets the encryption master key from a base64 encoded string |\n| `pool_max_idle_per_host(max)` | Maximum idle connections per host |\n| `enable_retry(enable)` | Enable/disable retry logic (default: `true`) |\n| `max_retries(max)` | Maximum retry attempts (default: `3`) |\n\nCall `.build()` on the `ConfigBuilder` to get a `Result\u003cConfig, PusherError\u003e`.\n\n## Channel History\n\n```rust\nuse sockudo_http::{HistoryParams, Sockudo};\n\nasync fn fetch_history(sockudo: \u0026Sockudo) -\u003e Result\u003c(), sockudo_http::SockudoError\u003e {\n    let page = sockudo\n        .channel_history_with_name(\n            \"my-channel\",\n            Some(\u0026HistoryParams {\n                limit: Some(50),\n                direction: Some(\"newest_first\".to_string()),\n                ..Default::default()\n            }),\n        )\n        .await?;\n\n    if let Some(cursor) = page.next_cursor.clone() {\n        let _next_page = sockudo\n            .channel_history_with_name(\n                \"my-channel\",\n                Some(\u0026HistoryParams {\n                    cursor: Some(cursor),\n                    ..Default::default()\n                }),\n            )\n            .await?;\n    }\n\n    Ok(())\n}\n```\n\n## Error Handling\n\nAll fallible methods return `Result\u003cT, PusherError\u003e`. The `PusherError` enum variants:\n\n| Variant | Description |\n|---------|-------------|\n| `Request(RequestError)` | HTTP request errors (network issues, non-success status codes) |\n| `Webhook(WebhookError)` | Webhook processing errors (signature validation, invalid body) |\n| `Config { message }` | Invalid configuration (missing app ID, invalid encryption key) |\n| `Validation { message }` | Input validation errors (invalid channel name, event name too long) |\n| `Encryption { message }` | Encryption/decryption errors for encrypted channels |\n| `Json(sonic_rs::Error)` | JSON serialization/deserialization errors |\n| `Http(reqwest::Error)` | Underlying HTTP client errors |\n\n## Contributing\n\nContributions are welcome! Please open issues for bugs or feature requests, or submit pull requests for improvements.\n\nWhen contributing code, please ensure:\n- Code is formatted with `cargo fmt`\n- Clippy lints are addressed (`cargo clippy --all-targets --all-features`)\n- New functionality is covered by tests\n- Documentation is updated accordingly\n\n## License\n\nThis project is licensed under the GNU Affero General Public License v3.0. See the `LICENSE.md` file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsockudo%2Fsockudo-http-rust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsockudo%2Fsockudo-http-rust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsockudo%2Fsockudo-http-rust/lists"}