{"id":30583968,"url":"https://github.com/RustNSparks/sockudo","last_synced_at":"2025-08-29T09:02:49.456Z","repository":{"id":292414415,"uuid":"885984101","full_name":"RustNSparks/sockudo","owner":"RustNSparks","description":"Blazingly fast pusher drop-in replacement written in rust","archived":false,"fork":false,"pushed_at":"2025-08-26T00:37:54.000Z","size":2221,"stargazers_count":174,"open_issues_count":4,"forks_count":12,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-08-26T01:25:33.492Z","etag":null,"topics":["abbly","broadcasting","pusher","realtime","rust","rust-lang","websocket","websockets","ws"],"latest_commit_sha":null,"homepage":"https://sockudo.app/","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/RustNSparks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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},"funding":{"github":"radudiaconu0","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"radooku","thanks_dev":null,"custom":null}},"created_at":"2024-11-09T22:01:43.000Z","updated_at":"2025-08-25T16:09:46.000Z","dependencies_parsed_at":"2025-06-02T19:18:44.769Z","dependency_job_id":"d5524af4-cf1c-4af9-a071-74bd33eaa2d0","html_url":"https://github.com/RustNSparks/sockudo","commit_stats":null,"previous_names":["rustnsparks/sockudo"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/RustNSparks/sockudo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RustNSparks%2Fsockudo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RustNSparks%2Fsockudo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RustNSparks%2Fsockudo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RustNSparks%2Fsockudo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RustNSparks","download_url":"https://codeload.github.com/RustNSparks/sockudo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RustNSparks%2Fsockudo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272658762,"owners_count":24971604,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-29T02:00:10.610Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["abbly","broadcasting","pusher","realtime","rust","rust-lang","websocket","websockets","ws"],"created_at":"2025-08-29T09:02:48.317Z","updated_at":"2025-08-29T09:02:49.442Z","avatar_url":"https://github.com/RustNSparks.png","language":"Rust","funding_links":["https://github.com/sponsors/radudiaconu0","https://buymeacoffee.com/radooku"],"categories":["Rust"],"sub_categories":[],"readme":"# Sockudo 🚀\n\n[![Crates.io](https://img.shields.io/crates/v/sockudo)](https://crates.io/crates/sockudo)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Rust Version](https://img.shields.io/badge/rust-1.85+-blue.svg)](https://www.rust-lang.org)\n[![WebSocket](https://img.shields.io/badge/websocket-RFC%206455-green.svg)](https://tools.ietf.org/html/rfc6455)\n\n**High-performance, scalable WebSocket server built in Rust** 🦀\n\nSockudo is a production-ready WebSocket server that provides real-time communication capabilities with built-in support for channels, presence, authentication, rate limiting, and horizontal scaling. Designed for modern applications that need reliable, fast, and scalable real-time features. It aims for Pusher protocol compatibility, making it a potential backend for clients like Laravel Echo.\n\n📚 **[Full Documentation](https://sockudo.app/)** - Visit our comprehensive documentation site for detailed guides, API references, and examples.\n\n---\n\n## ✨ Features\n\n### 🚀 **Core Capabilities**\n- **High Performance**: Built in Rust for maximum speed and efficiency.\n- **WebSocket Protocol**: Full RFC 6455 compliance.\n- **Channel System**: Organize connections with public, private, and presence channels. Support for private-encrypted channels is also included.\n- **Real-time Messaging**: Instant bidirectional communication.\n- **Horizontal Scaling**: Multi-node clustering with adapters for Redis, Redis Cluster, and NATS.\n\n### 🔐 **Security \u0026 Authentication**\n- **Channel Authentication**: Secure private and presence channels using token-based authentication.\n- **User Authentication**: User sign-in support with custom data, typically validated with signatures.\n- **Rate Limiting**: Configurable limits for API and WebSocket connections, with backends like Memory or Redis.\n- **SSL/TLS Support**: Production-ready HTTPS and WSS.\n- **CORS Configuration**: Flexible cross-origin request handling.\n\n### 📊 **Monitoring \u0026 Observability**\n- **Prometheus Metrics**: Comprehensive performance monitoring for connections, messages, API calls, and adapter performance.\n- **Health Checks**: Built-in health endpoints (`/up/{appId}`) for load balancers.\n- **Structured Logging**: Configurable logging with multiple levels (DEBUG/INFO).\n- **Debug Mode**: Enhanced debugging for development.\n\n### 🎛️ **Flexible Architecture**\n- **Multiple Adapters**: Choose between Local (single-node), Redis, Redis Cluster, or NATS for message broadcasting and horizontal scaling.\n- **Database Support for App Management**: Options include Memory, MySQL, PostgreSQL, and DynamoDB for storing application configurations.\n- **Queue Systems**: Supports Memory, Redis, Redis Cluster, and SQS for background job processing (e.g., webhooks).\n- **Cache Backends**: Options for Memory, Redis, or Redis Cluster for caching channel data and other information.\n\n### 🔗 **Integration Features**\n- **Webhooks**: Real-time event notifications (e.g., `channel_occupied`, `channel_vacated`, `member_added`, `member_removed`, `client_event`) to your services.\n- **AWS Lambda**: Direct Lambda function invocation for webhooks.\n- **REST API**: Full HTTP API for triggering events, batch events, and managing channels/users.\n- **Client Libraries**: Compatible with Pusher client libraries due to protocol adherence.\n\n---\n\n## 🚀 Quick Start\n\n### Using Docker (Recommended)\n\n```bash\n# Clone the repository\ngit clone https://github.com/RustNSparks/sockudo.git\ncd sockudo\n\n# Ensure you have an .env file (e.g., from .env.example)\n# cp .env.example .env\n# nano .env # Customize if needed\n\n# Quick setup and start (assumes make command is configured)\nmake quick-start\n# Or, using docker-compose directly:\n# docker-compose up -d\n\n# you can also use \ndocker pull sockudo/sockudo:latest\n````\n\nSockudo should now be running. Default endpoints (configurable):\n\n- **WebSocket Server**: `ws://localhost:6001/app/your-app-key` (e.g., `demo-key` if using default app)\n- **REST API**: `http://localhost:6001` (e.g., `http://localhost:6001/apps/demo-app/events`)\n- **Metrics**: `http://localhost:9601/metrics`\n- **Health Check**: `http://localhost:6001/up/your-app-id`\n\n### Manual Installation\n\n#### Prerequisites\n\n- Rust 1.85+ (`rustup install stable`)\n- Redis (optional, for scaling and certain drivers)\n- MySQL/PostgreSQL (optional, for persistent app storage if using those drivers)\n\n#### Install from Crates.io\n\n```bash\n# Install using cargo\ncargo install sockudo\n\n# Or install using cargo-binstall (faster)\ncargo binstall sockudo\n\n# Run Sockudo (ensure config/config.json exists or use ENV variables)\nsockudo --config config/config.json\n```\n\n#### Build from Source\n\n```bash\n# Clone the repository\ngit clone [https://github.com/RustNSparks/sockudo.git](https://github.com/RustNSparks/sockudo.git)\ncd sockudo\n\n# Build the project\ncargo build --release\n\n# Run with a configuration file (recommended)\n./target/release/sockudo --config config/config.json\n# Or rely on environment variables and default app settings\n# ./target/release/sockudo\n```\n\n-----\n\n## 📖 Usage Examples\n\n### WebSocket Client Connection (JavaScript)\n\n```javascript\n// Connect to a public channel\nconst ws = new WebSocket('ws://localhost:6001/app/your-app-key'); // Replace your-app-key\n\nws.onopen = () =\u003e {\n    console.log('Connected to Sockudo!');\n    // Subscribe to a channel\n    ws.send(JSON.stringify({\n        event: 'pusher:subscribe',\n        data: { channel: 'my-channel' }\n    }));\n};\n\nws.onmessage = (event) =\u003e {\n    const data = JSON.parse(event.data);\n    console.log('Received:', data);\n\n    // Example: Handle connection established\n    if (data.event === 'pusher:connection_established') {\n        const socketId = JSON.parse(data.data).socket_id;\n        console.log('My Socket ID is:', socketId);\n    }\n    // Example: Handle subscription success\n    if (data.event === 'pusher_internal:subscription_succeeded' \u0026\u0026 data.channel === 'my-channel') {\n        console.log('Successfully subscribed to my-channel!');\n    }\n};\n\nws.onerror = (error) =\u003e {\n    console.error('WebSocket Error:', error);\n};\n\nws.onclose = () =\u003e {\n    console.log('Disconnected from Sockudo.');\n};\n```\n\n### Private Channel with Authentication (Conceptual Client-Side)\n\n*Actual signature generation must happen on your backend.*\n\n```javascript\nconst ws = new WebSocket('ws://localhost:6001/app/your-app-key');\n\nws.onopen = () =\u003e {\n    // Assume 'generatedAuthSignature' is obtained from your backend\n    // after authenticating the user for this channel and socket_id.\n    // See Security section for more details on signature generation.\n    const generatedAuthSignature = \"your-app-key:backend_generated_signature_for_private_channel\";\n\n    ws.send(JSON.stringify({\n        event: 'pusher:subscribe',\n        data: {\n            channel: 'private-orders',\n            auth: generatedAuthSignature\n        }\n    }));\n};\n```\n\n### Presence Channel (Conceptual Client-Side)\n\n*Actual signature generation must happen on your backend.*\n\n```javascript\nconst ws = new WebSocket('ws://localhost:6001/app/your-app-key');\n\nws.onopen = () =\u003e {\n    // Assume 'generatedAuthSignatureForPresence' is obtained from your backend.\n    // Channel data includes user_id and user_info for presence events.\n    const channelData = JSON.stringify({\n        user_id: 'user123',\n        user_info: { name: 'John Doe', avatar: 'avatar.jpg' }\n    });\n    const generatedAuthSignatureForPresence = \"your-app-key:backend_generated_signature_for_presence_channel\";\n\n\n    ws.send(JSON.stringify({\n        event: 'pusher:subscribe',\n        data: {\n            channel: 'presence-chat',\n            auth: generatedAuthSignatureForPresence,\n            channel_data: channelData\n        }\n    }));\n};\n\n// Listen for presence events\nws.onmessage = (event) =\u003e {\n    const message = JSON.parse(event.data);\n    if (message.event === 'pusher_internal:member_added' \u0026\u0026 message.channel === 'presence-chat') {\n        console.log('Member added:', message.data);\n    }\n    if (message.event === 'pusher_internal:member_removed' \u0026\u0026 message.channel === 'presence-chat') {\n        console.log('Member removed:', message.data);\n    }\n};\n```\n\n### REST API - Trigger Events (from your Backend)\n\nRequires authentication (see Security section).\n\n```bash\n# Send event to a channel\n# Replace your-app-id, your-app-key, and generate a valid auth_signature\ncurl -X POST \"http://localhost:6001/apps/your-app-id/events?auth_key=your-app-key\u0026auth_timestamp=$(date +%s)\u0026auth_version=1.0\u0026auth_signature=your_generated_signature\u0026body_md5=$(echo -n '{\"name\":\"order-update\",\"channels\":[\"private-orders\"],\"data\":{\"order_id\":123,\"status\":\"shipped\"}}' | md5sum | awk '{print $1}')\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"order-update\",\n    \"channels\": [\"private-orders\"],\n    \"data\": {\"order_id\": 123, \"status\": \"shipped\"}\n  }'\n```\n\n### Client Events (from an Authenticated WebSocket Client)\n\nClient events can only be sent on private or presence channels by authenticated clients for whom the app has `enable_client_messages` set to `true`.\n\n```javascript\n// Ensure the WebSocket 'ws' is connected and subscribed to 'presence-chat' (a private or presence channel)\nws.send(JSON.stringify({\n    event: 'client-message', // Must start with 'client-'\n    channel: 'presence-chat',\n    data: { message: 'Hello everyone!' }\n}));\n```\n\n-----\n\n## 🔧 Configuration\n\nSockudo can be configured via a JSON file (default: `config/config.json`) or environment variables. Environment variables generally override file settings for basic options, but the file provides more detailed structure.\n\n### Environment Variables (Key Examples)\n\nCreate a `.env` file (or set them in your environment):\n\n```bash\n# Basic Settings\nHOST=0.0.0.0\nPORT=6001\nDEBUG=false # Enables more verbose logging\n\n# Database (Example for Redis, adapt for MySQL/Postgres/DynamoDB if used as primary for AppManager)\nREDIS_URL=redis://redis:6379/0 # Used by multiple components if not overridden\n# For MySQL AppManager:\n# DATABASE_MYSQL_HOST=mysql\n# DATABASE_MYSQL_USER=sockudo\n# DATABASE_MYSQL_PASSWORD=your_mysql_password\n# DATABASE_MYSQL_DATABASE=sockudo\n\n# Drivers (Lowercase: \"local\", \"redis\", \"redis-cluster\", \"nats\", \"memory\", \"mysql\", \"pgsql\", \"dynamodb\", \"sqs\")\nADAPTER_DRIVER=redis        # For horizontal scaling\nAPP_MANAGER_DRIVER=memory   # For app definitions\nCACHE_DRIVER=redis          # For caching\nQUEUE_DRIVER=redis          # For webhooks \u0026 background tasks\n\n# Security\nSSL_ENABLED=false # Set to true for production and provide paths\n# SSL_CERT_PATH=/path/to/cert.pem\n# SSL_KEY_PATH=/path/to/key.pem\n# REDIS_PASSWORD=your-redis-password # If Redis requires auth\n\n# Application Defaults (if not using a persistent AppManager or for fallback)\nSOCKUDO_DEFAULT_APP_ID=demo-app\nSOCKUDO_DEFAULT_APP_KEY=demo-key\nSOCKUDO_DEFAULT_APP_SECRET=demo-secret\nSOCKUDO_ENABLE_CLIENT_MESSAGES=true # For the default app\n```\n\n*Refer to `src/options.rs` and `src/main.rs` for a comprehensive list of all ENV var overrides and their default behaviors.*\n\n### Configuration File (`config/config.json`)\n\nProvides detailed control over all aspects. Below is a snippet; refer to your uploaded `config/config.json` for the full structure.\n\n```json\n{\n  \"debug\": false,\n  \"host\": \"0.0.0.0\",\n  \"port\": 6001,\n  \"adapter\": {\n    \"driver\": \"redis\", // \"local\", \"redis\", \"redis-cluster\", \"nats\"\n    \"redis\": { // Settings for 'redis' or 'redis-cluster' if chosen\n      \"prefix\": \"sockudo_adapter:\",\n      \"cluster_mode\": false // Set true if using redis-cluster via the 'redis' driver config\n      // \"requests_timeout\": 5000\n    },\n    \"cluster\": { // Specific settings if driver is 'redis-cluster'\n      // \"nodes\": [\"redis://node1:7000\", \"redis://node2:7001\"],\n      // \"prefix\": \"sockudo_cluster_adapter:\"\n    },\n    \"nats\": {\n      // \"servers\": [\"nats://nats-server:4222\"],\n      // \"prefix\": \"sockudo_nats_adapter:\"\n    }\n  },\n  \"app_manager\": {\n    \"driver\": \"memory\", // \"memory\", \"mysql\", \"pgsql\", \"dynamodb\"\n    \"array\": { // Used if driver is \"memory\" and you want to define apps in config\n      \"apps\": [\n        {\n          \"id\": \"my-app-id\",\n          \"key\": \"my-app-key\",\n          \"secret\": \"my-app-secret\",\n          \"max_connections\": 1000,\n          \"enable_client_messages\": true,\n          // ... other app-specific limits from src/app/config.rs\n        }\n      ]\n    }\n    // database-specific app_manager configs would go under \"database\"\n  },\n  \"cache\": {\n    \"driver\": \"redis\", // \"memory\", \"redis\", \"redis-cluster\", \"none\"\n    \"redis\": {\n      // \"prefix\": \"sockudo_cache:\",\n      // \"url_override\": \"redis://another-redis:6379\"\n    },\n    \"memory\": {\n      // \"ttl\": 300, \"max_capacity\": 10000\n    }\n  },\n  \"queue\": {\n    \"driver\": \"redis\", // \"memory\", \"redis\", \"redis-cluster\", \"sqs\", \"none\"\n    \"redis\": {\n      // \"concurrency\": 5, \"prefix\": \"sockudo_queue:\"\n    },\n    \"sqs\": {\n      // \"region\": \"us-east-1\", \"concurrency\": 5\n    }\n  },\n  \"rate_limiter\": {\n    \"enabled\": true,\n    \"driver\": \"redis\", // Usually \"memory\" or \"redis\"\n    \"api_rate_limit\": {\n      \"max_requests\": 100,\n      \"window_seconds\": 60\n    },\n    \"websocket_rate_limit\": { // Applied on connection\n      \"max_requests\": 20,\n      \"window_seconds\": 60\n    }\n  },\n  \"metrics\": {\n    \"enabled\": true,\n    \"driver\": \"prometheus\", // Currently only prometheus supported\n    \"port\": 9601\n  },\n  \"ssl\": {\n    \"enabled\": false,\n    // \"cert_path\": \"/path/to/fullchain.pem\",\n    // \"key_path\": \"/path/to/privkey.pem\"\n  }\n  // ... many other options available, see src/options.rs\n}\n```\n\n-----\n\n## 🏗️ Architecture\n\n### System Overview\n\n```\n┌─────────────────┐    ┌───────────────────┐    ┌─────────────────┐\n│   Client Apps   │    │  Load Balancer    │    │   Sockudo Node  │\n│ (Web/Mobile/IoT)│◄──►│ (e.g., Nginx,    │◄──►│     (Rust)      │\n│ using PusherJS  │    │      AWS ALB)     │    │                 │\n└─────────────────┘    └───────────────────┘    └─────────────────┘\n                                                        │ ▲\n                                                        │ │ Pub/Sub\n                        ┌─────────────────┐             │ ▼\n                        │ Pub/Sub Backend │◄────────────┘\n                        │ (Redis/NATS for │\n                        │ HORIZONTAL_ADAPTER)│\n                        └─────────────────┘\n                                │\n      ┌─────────────────────┐   │   ┌────────────────────┐\n      │ App Config Storage  │◄──┘──►│  Cache Storage     │\n      │ (MySQL/PG/DynamoDB/ │       │  (Redis/Memory)    │\n      │      Memory)        │       └────────────────────┘\n      └─────────────────────┘\n```\n\n### Core Components\n\n- **Connection Handler (`adapter::handler`)**: Manages individual WebSocket connections, message parsing, authentication, and routing to appropriate handlers.\n- **Channel Manager (`channel::manager`)**: Handles channel subscriptions, presence state, and signature validation for channel access.\n- **Adapter (`adapter`)**: Abstract interface for connection management and message broadcasting.\n    - **Local Adapter (`adapter::local_adapter`)**: Single-node operation.\n    - **Horizontal Adapters (`adapter::redis_adapter`, `adapter::redis_cluster_adapter`, `adapter::nats_adapter`)**: Enable multi-node setups by using Redis, Redis Cluster, or NATS for pub/sub to synchronize state and broadcast messages across instances.\n- **App Manager (`app::manager`)**: Manages application configurations (keys, secrets, limits, features), with backends like Memory, MySQL, PostgreSQL, or DynamoDB.\n- **Cache Manager (`cache::manager`)**: Provides caching for frequently accessed data (e.g., channel information, app settings) with Memory or Redis backends.\n- **Queue Manager (`queue::manager`)**: Handles background tasks, primarily for delivering webhooks, using Memory, Redis, Redis Cluster, or SQS.\n- **Rate Limiter (`rate_limiter`)**: Protects against abuse with configurable limits, using Memory or Redis.\n- **Webhook System (`webhook`)**: Delivers real-time server events (e.g., channel occupied/vacated, member added/removed) to configured HTTP endpoints or AWS Lambda functions.\n- **Metrics Collector (`metrics`)**: Exposes Prometheus metrics for monitoring performance and health.\n- **HTTP Handler (`http_handler`)**: Provides the Pusher-compatible REST API for triggering events and querying server state.\n- **WebSocket Handler (`ws_handler`)**: Manages the WebSocket upgrade process and initial connection setup.\n\n### Supported Architectures\n\n- **Single Node**: Ideal for development, testing, and smaller applications. Uses `local` adapter and `memory` for other components if not otherwise configured.\n- **Multi-Node (Clustered)**: Achieves horizontal scalability using Redis, Redis Cluster, or NATS as the `adapter.driver` for message broadcasting and state synchronization between Sockudo instances.\n- **Microservices Integration**: Sockudo can act as the real-time layer in a microservices architecture, receiving events to broadcast via its HTTP API.\n- **Serverless Integration**: Webhooks can trigger AWS Lambda functions, allowing for serverless processing of Sockudo events.\n\n-----\n\n## 🔗 Client Integration\n\nSockudo implements the **Pusher WebSocket protocol**. This means you can use any Pusher-compatible **client-side library** to connect to Sockudo for real-time messaging.\n\nFor triggering events from **your application backend** (server-to-server), you would use Sockudo's HTTP API (see API Reference section), potentially with an HTTP client or a Pusher *server-side* library configured to point to Sockudo's HTTP API endpoints.\n\n### Recommended Client-Side Libraries (for WebSocket Connection)\n\n#### JavaScript/TypeScript (PusherJS)\n\nThis is the most common library for frontend integration.\n\n```bash\nnpm install pusher-js\n```\n\n```javascript\nimport Pusher from 'pusher-js';\n\nconst pusher = new Pusher('your-app-key', { // Use the 'key' from your app config\n  wsHost: 'localhost',      // Your Sockudo host\n  wsPort: 6001,             // Your Sockudo WebSocket port\n  wssPort: 6001,            // Your Sockudo WSS port (if SSL_ENABLED=true)\n  forceTLS: false,          // Set true if using WSS\n  enabledTransports: ['ws', 'wss'],\n  cluster: 'mt1', // Required by pusher-js, value doesn't impact Sockudo directly\n  disableStats: true, // Recommended\n  authEndpoint: '/your-backend-auth-endpoint-for-private-channels', // For private/presence channels\n  // authTransport: 'ajax' // or 'jsonp'\n});\n\nconst channel = pusher.subscribe('public-channel');\nchannel.bind('some-event', (data) =\u003e {\n  console.log('Received data:', data);\n});\n```\n\n#### Other Pusher-Compatible Client Libraries\n\nMost Pusher client libraries for other languages (Python, Ruby, Java, .NET/C\\#, Swift, Android, etc.) can be configured to connect to a custom host and port, allowing them to work with Sockudo. You'll typically need to set:\n\n- App Key\n- Host (your Sockudo server address)\n- Port (your Sockudo WebSocket port)\n- `forceTLS` (or equivalent for secure connections)\n- An `authEndpoint` if using private or presence channels.\n\n#### Native WebSocket\n\nYou can also connect directly using any standard WebSocket client, but you'll need to implement the Pusher message format manually (e.g., for `pusher:subscribe`, `pusher:ping`).\n\n```javascript\nconst ws = new WebSocket('ws://localhost:6001/app/your-app-key');\n\nws.onopen = () =\u003e {\n  ws.send(JSON.stringify({\n    event: 'pusher:subscribe',\n    data: { channel: 'my-channel' }\n  }));\n};\n// ... handle other WebSocket events\n```\n\n### Triggering Events from Your Backend (Server-Side)\n\nUse Sockudo's HTTP API. Examples using `curl` are in the \"Usage Examples\" and \"API Reference\" sections.\nLibraries like Pusher's official PHP or Go server libraries can also be used if you configure their host/port to point to your Sockudo HTTP API endpoint (`http://localhost:6001` by default).\n\n**PHP (Pusher Server SDK - configured for Sockudo)**\n\n```php\n\u003c?php\nrequire __DIR__ . '/vendor/autoload.php';\n\n$options = [\n  'host' =\u003e 'localhost', // Sockudo HTTP API host\n  'port' =\u003e 6001,        // Sockudo HTTP API port\n  'scheme' =\u003e 'http',    // 'https' if SSL_ENABLED=true on Sockudo\n  'encrypted' =\u003e false,  // Relevant for Pusher Cloud, less so here\n  'useTLS' =\u003e false,     // Set true if Sockudo is using HTTPS\n  // cluster parameter is often not needed when directly targeting Sockudo\n];\n\n// Ensure 'your-app-id' matches an 'id' field in one of your configured apps in Sockudo\n$pusher = new Pusher\\Pusher('your-app-key', 'your-app-secret', 'your-app-id', $options);\n\n$data = ['message' =\u003e 'hello world from PHP backend via Sockudo'];\n$pusher-\u003etrigger('my-channel', 'my-event', $data);\n?\u003e\n```\n\n**Go (Pusher HTTP Go - configured for Sockudo)**\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"[github.com/pusher/pusher-http-go/v5](https://github.com/pusher/pusher-http-go/v5)\"\n)\n\nfunc main() {\n\tclient := pusher.Client{\n\t\tAppID:   \"your-app-id\",     // Matches 'id' in Sockudo app config\n\t\tKey:     \"your-app-key\",    // Matches 'key'\n\t\tSecret:  \"your-app-secret\", // Matches 'secret'\n\t\tHost:    \"localhost:6001\",  // Sockudo HTTP API host and port\n\t\tSecure:  false,             // Set to true if Sockudo is using HTTPS\n\t}\n\n\tdata := map[string]string{\"message\": \"hello from Go backend via Sockudo\"}\n\tevents, err := client.Trigger(\"my-channel\", \"my-event\", data)\n\tif err != nil {\n\t\tfmt.Println(\"Error triggering event:\", err)\n\t\treturn\n\t}\n\tfmt.Printf(\"Events triggered: %+v\\n\", events)\n}\n```\n\n-----\n\n## 🚢 Deployment\n\nRefer to `README-Docker.md` for comprehensive Docker deployment instructions.\n\n### Docker Deployment (Recommended)\n\n#### Development\n\n```bash\n# Start development environment (uses docker-compose.yml and docker-compose.dev.yml)\nmake dev # (If Makefile provides this shortcut)\n# OR\ndocker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d\n\n# View logs\ndocker-compose logs -f sockudo\n\n# Run tests (if configured in a Makefile or script)\n# make test\n```\n\n#### Development Setup\n\n**For contributors and developers:**\n\n1. **Install Git hooks** (recommended for all developers):\n   ```bash\n   ./scripts/install-hooks.sh\n   ```\n   This installs a pre-commit hook that automatically formats Rust code before commits.\n\n2. **Manual hook installation** (alternative):\n   ```bash\n   # Copy the hook manually\n   cp scripts/git-hooks/pre-commit .git/hooks/pre-commit\n   chmod +x .git/hooks/pre-commit\n   ```\n\n3. **Disable hooks temporarily** (if needed):\n   ```bash\n   git commit --no-verify\n   ```\n\nThe pre-commit hook ensures your code is properly formatted and matches CI requirements.\n\n#### Production\n\n```bash\n# Production deployment (uses docker-compose.yml and docker-compose.prod.yml)\nmake prod # (If Makefile provides this shortcut)\n# OR\ndocker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d\n\n# Scale to multiple instances (example)\ndocker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --scale sockudo=3\n\n# Monitor performance (example for Docker stats)\ndocker stats\n```\n\n### Kubernetes\n\nA basic Kubernetes deployment might look like this (adapt as needed):\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: sockudo\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: sockudo\n  template:\n    metadata:\n      labels:\n        app: sockudo\n    spec:\n      containers:\n      - name: sockudo\n        image: sockudo/sockudo:latest\n        ports:\n        - name: websocket\n          containerPort: 6001 # Sockudo's application port\n        - name: metrics\n          containerPort: 9601 # Sockudo's metrics port\n        env:\n        - name: ADAPTER_DRIVER \n          value: \"redis\" # or \"nats\", \"redis-cluster\"\n        - name: REDIS_URL\n          value: \"redis://your-redis-service:6379\"\n        # Add other necessary environment variables or configmap mounts\n        # - name: CONFIG_FILE_PATH\n        #   value: \"/app/config/config.json\"\n        # volumeMounts:\n        # - name: sockudo-config\n        #   mountPath: /app/config\n      # volumes:\n      # - name: sockudo-config\n      #   configMap:\n      #     name: sockudo-configmap\n```\n\n*Ensure your Kubernetes setup includes appropriate services, ingress, and configuration for Redis/NATS if using horizontal scaling.*\n\n### Cloud Platforms\n\nSockudo can be deployed to various cloud platforms:\n\n#### AWS ECS / EKS\n\n- Use the production Docker image.\n- Configure Application Load Balancer (ALB) or Network Load Balancer (NLB) with WebSocket support (sticky sessions might be needed depending on adapter and client behavior, though ideally not with a proper horizontal adapter).\n- Use ElastiCache for Redis (standalone or cluster) if using Redis adapter/cache/queue.\n- Use RDS for MySQL/PostgreSQL if using those for AppManager.\n- Use Amazon SQS if using the SQS queue driver.\n\n#### Google Cloud Run / GKE\n\n- Enable WebSocket support in Cloud Run or use GKE.\n- Use Cloud Memorystore for Redis.\n- Configure Cloud SQL for persistent app storage.\n\n#### Azure Container Instances / AKS\n\n- Use Azure Cache for Redis.\n- Configure Azure Database for MySQL/PostgreSQL.\n\n-----\n\n## 📊 Monitoring\n\n### Metrics\n\nSockudo exposes Prometheus metrics at the `/metrics` endpoint (default port 9601).\nKey metrics include:\n\n- **Connection Metrics**:\n    * `sockudo_connected`: Current number of active WebSocket connections.\n    * `sockudo_new_connections_total`: Total number of new connections established.\n    * `sockudo_new_disconnections_total`: Total number of disconnections.\n- **Message Metrics**:\n    * `sockudo_ws_messages_sent_total`: Total WebSocket messages sent from server to clients.\n    * `sockudo_ws_messages_received_total`: Total WebSocket messages received by server from clients.\n    * `sockudo_socket_transmitted_bytes`: Total bytes transmitted over WebSockets.\n    * `sockudo_socket_received_bytes`: Total bytes received over WebSockets.\n- **HTTP API Metrics**:\n    * `sockudo_http_calls_received_total`: Total HTTP API calls received.\n    * `sockudo_http_transmitted_bytes`: Total bytes sent in HTTP API responses.\n    * `sockudo_http_received_bytes`: Total bytes received in HTTP API requests.\n- **Horizontal Adapter Metrics** (if using Redis/NATS adapter):\n    * `sockudo_horizontal_adapter_resolve_time`: Histogram of request resolution time between nodes.\n    * `sockudo_horizontal_adapter_resolved_promises`: Total promises fulfilled by other nodes.\n    * `sockudo_horizontal_adapter_sent_requests`: Total requests sent to other nodes.\n    * `sockudo_horizontal_adapter_received_requests`: Total requests received from other nodes.\n    * `sockudo_horizontal_adapter_received_responses`: Total responses received from other nodes.\n- **Channel Statistics** (can be inferred or might require specific metrics not listed but common):\n    * `sockudo_channel_count` (conceptual, often derived from `get_channels_with_socket_count`)\n    * `sockudo_active_subscriptions` (conceptual)\n\n### Grafana Dashboard\n\nIf using the Docker setup with Grafana (`docker-compose.prod.yml`), a pre-configured dashboard might be available. Otherwise, you can import/create dashboards in Grafana using Prometheus as a data source.\nAccess Grafana at `http://localhost:3000` (default credentials may vary, check `docker-compose.prod.yml` or `README-Docker.md`).\n\n### Health Checks\n\n- **Application Health**: `GET /up/{app_id}` - Checks if a specific application is responsive.\n- **Metrics Endpoint**: `GET /metrics` (default on port 9601) - Exposes Prometheus metrics.\n- **General Usage/Memory**: `GET /usage` - Provides memory usage statistics of the Sockudo server.\n\n-----\n\n## 🔒 Security\n\n### Authentication\n\n#### Channel Authentication (Private \u0026 Presence Channels)\n\nSockudo validates `auth` tokens for private and presence channel subscriptions. The signature is typically generated on your backend.\nThe string to sign depends on the channel type:\n\n- **Private channels**: `socket_id:channel_name`\n- **Presence channels**: `socket_id:channel_name:channel_data_json_string` (where `channel_data_json_string` is the JSON string provided in the `channel_data` field of the subscription request).\n\n**Example Server-Side Signature Generation (Node.js conceptual):**\n\n```javascript\nconst crypto = require('crypto');\n\nfunction generateChannelAuth(socketId, channelName, appSecret, channelDataString = null) {\n    let stringToSign = `${socketId}:${channelName}`;\n    if (channelDataString) { // For presence channels\n        stringToSign += `:${channelDataString}`;\n    }\n    const signature = crypto.createHmac('sha256', appSecret)\n                              .update(stringToSign)\n                              .digest('hex');\n    return signature; // The client would send appKey:signature\n}\n\n// For a private channel:\n// const appKey = \"your-app-key\";\n// const signature = generateChannelAuth(clientSocketId, \"private-mychannel\", \"your-app-secret\");\n// const authString = `${appKey}:${signature}`;\n\n// For a presence channel:\n// const channelData = JSON.stringify({ user_id: \"123\", user_info: { name: \"Alice\" } });\n// const presenceSignature = generateChannelAuth(clientSocketId, \"presence-mychannel\", \"your-app-secret\", channelData);\n// const presenceAuthString = `${appKey}:${presenceSignature}`;\n```\n\n*Refer to `src/channel/manager.rs` (method `get_data_to_sign_for_signature`) for the exact server-side string construction.*\n\n#### User Authentication (pusher:signin)\n\nIf `enable_user_authentication` is true for an app, clients can send a `pusher:signin` event.\nThe string to sign for user authentication is `socket_id::user::user_data_json_string`.\n\n**Example Server-Side User Auth Token Generation (Node.js conceptual):**\n\n```javascript\nconst crypto = require('crypto');\n\nfunction generateUserAuth(socketId, userDataJsonString, appSecret) {\n    const stringToSign = `${socketId}::user::${userDataJsonString}`;\n    const signature = crypto.createHmac('sha256', appSecret)\n                              .update(stringToSign)\n                              .digest('hex');\n    return signature; // The client would send this as part of the pusher:signin 'auth' field\n}\n\n// const appKey = \"your-app-key\";\n// const userData = JSON.stringify({ id: \"user456\", name: \"Bob\" });\n// const userAuthSignature = generateUserAuth(clientSocketId, userData, \"your-app-secret\");\n// Client sends: { event: \"pusher:signin\", data: { auth: `${appKey}:${userAuthSignature}`, user_data: userData }}\n// Note: The `src/app/auth.rs` `validate_channel_auth` and `sing_in_token_for_user_data` suggest the client sends only the signature part in `auth`, and `user_data` separately. Your client example shows this correctly.\n```\n\n*Refer to `src/app/auth.rs` (method `sing_in_token_for_user_data`) for server-side logic.*\n\n### Rate Limiting\n\nConfigure rate limits in `config/config.json` or via environment variables to protect against abuse. Sockudo supports separate limits for HTTP API requests and WebSocket connections.\nBackends like Memory or Redis can be used for rate limiting.\n\n```json\n{\n  \"rate_limiter\": {\n    \"enabled\": true,\n    \"driver\": \"redis\", // \"memory\" or \"redis\"\n    \"api_rate_limit\": {\n      \"max_requests\": 1000, // e.g., per minute for all API calls from an IP\n      \"window_seconds\": 60\n    },\n    \"websocket_rate_limit\": { // Applied per IP at connection time\n      \"max_requests\": 60,   // e.g., max 60 connection attempts per minute from an IP\n      \"window_seconds\": 60\n    },\n    \"redis\": { // If Redis driver is used for rate limiting\n        \"prefix\": \"sockudo_rl:\"\n        // url_override can also be set here if different from main Redis\n    }\n  }\n}\n```\n\n*Client-specific event rate limits are configured per-application (e.g., `max_client_events_per_second`).*\n\n### SSL/TLS\n\nEnable HTTPS and WSS in production by setting `ssl.enabled = true` in `config/config.json` or `SSL_ENABLED=true` (env var) and providing paths to your SSL certificate and key.\nRefer to `README-Docker.md` for Docker-specific SSL setup with Nginx or Let's Encrypt.\n\n```json\n  \"ssl\": {\n    \"enabled\": true,\n    \"cert_path\": \"/etc/letsencrypt/live/[your-domain.com/fullchain.pem](https://your-domain.com/fullchain.pem)\",\n    \"key_path\": \"/etc/letsencrypt/live/[your-domain.com/privkey.pem](https://your-domain.com/privkey.pem)\",\n    \"redirect_http\": true, // Redirect HTTP traffic to HTTPS\n    \"http_port\": 8080 // Port for HTTP redirect server if Sockudo handles SSL\n  }\n```\n\n-----\n\n## 🔌 API Reference\n\n### WebSocket Events (Pusher Protocol)\n\nSockudo aims for compatibility with the Pusher WebSocket protocol. Key events include:\n\n#### Connection Events\n\n- `pusher:connection_established`: Sent to the client upon successful connection. Contains `socket_id` and `activity_timeout`.\n- `pusher:error`: Sent to the client when an error occurs (e.g., auth failure, invalid message). Contains `code` and `message`.\n- `pusher:ping`: Sent by the server to check if the client is alive. Client should respond with `pusher:pong`.\n- `pusher:pong`: Sent by the client in response to a `pusher:ping`.\n\n#### Subscription Events\n\n- `pusher:subscribe`: Sent by the client to subscribe to a channel. `data` includes `channel` name and optionally `auth` string (for private/presence) and `channel_data` (for presence).\n- `pusher:unsubscribe`: Sent by the client to unsubscribe from a channel. `data` includes `channel` name.\n- `pusher_internal:subscription_succeeded`: Sent to the client by the server confirming successful subscription to a channel. For presence channels, `data` includes current member list (`presence.hash`, `presence.ids`, `presence.count`).\n- `pusher:cache_miss`: Sent to the client if they subscribe to a cache channel and no data is initially cached.\n\n#### Presence Events (Sent by Server to Subscribed Clients)\n\n- `pusher_internal:member_added`: Sent to clients in a presence channel when a new member joins. `data` includes `user_id` and `user_info`.\n- `pusher_internal:member_removed`: Sent to clients in a presence channel when a member leaves. `data` includes `user_id`.\n\n#### User Authentication Events\n\n- `pusher:signin`: Sent by the client to authenticate themselves as a user. `data` includes `auth` (app\\_key:signature) and `user_data` (JSON string with user details like `id`).\n- `pusher:signin_success`: Sent to the client by the server upon successful user sign-in.\n\n#### Client Events (Sent by Client, Broadcast by Server)\n\n- `client-*` (e.g., `client-message`): Custom events sent by one client, broadcast by the server to other clients on the same private or presence channel (excluding the sender by default, if `socket_id` is provided when triggering via HTTP API). The app must have `enable_client_messages: true`.\n\n### REST API Endpoints\n\nAll endpoints are prefixed with `/apps/{app_id}/`. Authentication is required for these endpoints.\n\n#### Trigger Event\n\n- **Endpoint**: `POST /apps/{app_id}/events`\n- **Description**: Triggers an event on one or more channels.\n- **Body**:\n  ```json\n  {\n    \"name\": \"your-event-name\",\n    \"channels\": [\"channel-1\", \"channel-2\"], // Or \"channel\": \"single-channel\"\n    \"data\": {\"key\": \"value\"}, // JSON string or object for event data\n    \"socket_id\": \"optional_socket_id_to_exclude\" // Optional\n    // \"info\": \"user_count,subscription_count\" // Optional: to request info about channels in response\n  }\n  ```\n- **Response**: `200 OK` with `{\"ok\":true}` or channel info if requested.\n\n#### Batch Events\n\n- **Endpoint**: `POST /apps/{app_id}/batch_events`\n- **Description**: Triggers multiple events in a single request.\n- **Body**:\n  ```json\n  {\n    \"batch\": [\n      {\"name\": \"event1\", \"channel\": \"channel1\", \"data\": \"data1\"},\n      {\"name\": \"event2\", \"channels\": [\"channel2\", \"channel3\"], \"data\": {\"complex\":\"data2\"}}\n      // Each item can also have \"socket_id\" and \"info\"\n    ]\n  }\n  ```\n- **Response**: `200 OK` with `{}` or `{\"batch\": [...]}` containing info for each event if requested.\n\n#### Get Channel Information\n\n- **Endpoint**: `GET /apps/{app_id}/channels/{channel_name}`\n- **Description**: Retrieves information about a specific channel.\n- **Query Parameters**:\n    * `info`: Comma-separated list of attributes to retrieve (e.g., `user_count`, `subscription_count`, `cache`).\n- **Response**: `200 OK` with JSON containing channel status (e.g., `{\"occupied\": true, \"user_count\": 5, \"subscription_count\": 10}`).\n\n#### List Channels\n\n- **Endpoint**: `GET /apps/{app_id}/channels`\n- **Description**: Retrieves a list of active channels in an application.\n- **Query Parameters**:\n    * `filter_by_prefix`: Filters channels by the given prefix.\n    * `info`: Comma-separated list of attributes to retrieve for each channel (e.g., `user_count`).\n- **Response**: `200 OK` with JSON `{\"channels\": {\"channel_name\": {\"user_count\": 2}, ...}}`.\n\n#### Get Users in a Presence Channel\n\n- **Endpoint**: `GET /apps/{app_id}/channels/{channel_name}/users`\n- **Description**: Retrieves the list of users subscribed to a specific presence channel.\n- **Response**: `200 OK` with JSON `{\"users\": [{\"id\": \"user_id_1\"}, {\"id\": \"user_id_2\"}]}`.\n\n#### Terminate User Connections\n\n- **Endpoint**: `POST /apps/{app_id}/users/{user_id}/terminate_connections`\n- **Description**: Forcibly disconnects all WebSocket connections associated with a given `user_id`.\n- **Response**: `200 OK` with `{\"ok\":true}`.\n\n### Other Endpoints\n\n- **Health Check**: `GET /up/{app_id}` - Returns `200 OK` if the app is known (even if no connections).\n- **Server Usage**: `GET /usage` - Returns server memory statistics.\n- **Prometheus Metrics**: `GET /metrics` (default port 9601) - Exposes metrics in Prometheus format.\n\n-----\n\n## 🧪 Testing\n\nSockudo includes a suite of tests to ensure reliability.\n\n### Running Tests\n\n```bash\n# Run all unit and integration tests (ensure dependencies like Redis might be needed for some)\ncargo test\n\n# For Docker-based testing environment, refer to README-Docker.md\n# Example: make ci-test (if Makefile provides this)\n```\n\n### Interactive Tester\n\nThe project includes an interactive tester in `test/interactive/` which can be used to manually test WebSocket connections, subscriptions, client events, and webhooks against a running Sockudo instance.\n\n### Automated Tests\n\nAutomated tests can be found in `test/automated/` which might include scripts for load testing or specific feature validation.\n\n-----\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get started, coding standards, and the PR review process.\n\n-----\n\n## 📚 Documentation\n\nFor more in-depth information:\n\n- Explore the `src/` directory for detailed module implementations.\n- Configuration options are detailed in `src/options.rs`.\n- Docker setup is described in `README-Docker.md`.\n- (If a dedicated documentation site exists, link it here, e.g., `https://sockudo.app` as mentioned in the original README).\n\n-----\n\n## 📄 License\n\nThis project is licensed under the **MIT License** - see the [LICENSE](https://github.com/RustNSparks/sockudo/blob/master/LICENSE) file for details.\n\n-----\n\n## 🙏 Acknowledgments\n\n- Built with [Tokio](https://tokio.rs/) for async runtime.\n- Uses [fastwebsockets](https://github.com/denoland/fastwebsockets) for WebSocket implementation.\n- Inspired by [Pusher](https://pusher.com/) and similar real-time messaging platforms like [Laravel Reverb](https://reverb.laravel.com/).\n- Thanks to the Rust community for its robust ecosystem and excellent crates.\n\n-----\n\n## 📞 Support\n\n### Community Support\n- **GitHub Issues**: [Report bugs and request features](https://github.com/RustNSparks/sockudo/issues)\n- **Crates.io**: [View package details](https://crates.io/crates/sockudo)\n- **Discord**: [Join the RustNSparks server](https://discord.gg/GUuxFbD8pU)\n- **X**: [Follow us on X](https://x.com/sockudorealtime)\n\n**⭐ Star us on GitHub if Sockudo helps your project\\! ⭐**\n\n[GitHub Repository](https://github.com/RustNSparks/sockudo) • [Crates.io](https://crates.io/crates/sockudo))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRustNSparks%2Fsockudo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRustNSparks%2Fsockudo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRustNSparks%2Fsockudo/lists"}